+ frame->interval = (u_int16_t)natt_keepalive_interval;
+ }
+ return TRUE;
+}
+
+static int
+sysctl_ipsec_wake_packet SYSCTL_HANDLER_ARGS
+{
+ #pragma unused(oidp, arg1, arg2)
+ if (req->newptr != USER_ADDR_NULL) {
+ ipseclog((LOG_ERR, "ipsec: invalid parameters"));
+ return EINVAL;
+ }
+
+ struct proc *p = current_proc();
+ if (p != NULL) {
+ uid_t uid = kauth_cred_getuid(proc_ucred(p));
+ if (uid != 0 && priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_IPSEC_WAKE_PACKET, 0) != 0) {
+ ipseclog((LOG_ERR, "process does not hold necessary entitlement to get ipsec wake packet"));
+ return EPERM;
+ }
+
+ int result = sysctl_io_opaque(req, &ipsec_wake_pkt, sizeof(ipsec_wake_pkt), NULL);
+
+ ipseclog((LOG_NOTICE, "%s: uuid %s spi %u seq %u len %u result %d",
+ __func__,
+ ipsec_wake_pkt.wake_uuid,
+ ipsec_wake_pkt.wake_pkt_spi,
+ ipsec_wake_pkt.wake_pkt_seq,
+ ipsec_wake_pkt.wake_pkt_len,
+ result));
+
+ return result;
+ }
+
+ return EINVAL;
+}
+
+SYSCTL_PROC(_net_link_generic_system, OID_AUTO, ipsec_wake_pkt, CTLTYPE_STRUCT | CTLFLAG_RD |
+ CTLFLAG_LOCKED, 0, 0, &sysctl_ipsec_wake_packet, "S,ipsec wake packet", "");
+
+void
+ipsec_save_wake_packet(struct mbuf *wake_mbuf, u_int32_t spi, u_int32_t seq)
+{
+ if (wake_mbuf == NULL) {
+ ipseclog((LOG_ERR, "ipsec: bad wake packet"));
+ return;
+ }
+
+ lck_mtx_lock(sadb_mutex);
+ if (__probable(!ipsec_save_wake_pkt)) {
+ goto done;
+ }
+
+ u_int16_t max_len = (wake_mbuf->m_pkthdr.len > IPSEC_MAX_WAKE_PKT_LEN) ? IPSEC_MAX_WAKE_PKT_LEN : (u_int16_t)wake_mbuf->m_pkthdr.len;
+ m_copydata(wake_mbuf, 0, max_len, (void *)ipsec_wake_pkt.wake_pkt);
+ ipsec_wake_pkt.wake_pkt_len = max_len;
+
+ ipsec_wake_pkt.wake_pkt_spi = spi;
+ ipsec_wake_pkt.wake_pkt_seq = seq;
+
+ ipseclog((LOG_NOTICE, "%s: uuid %s spi %u seq %u len %u",
+ __func__,
+ ipsec_wake_pkt.wake_uuid,
+ ipsec_wake_pkt.wake_pkt_spi,
+ ipsec_wake_pkt.wake_pkt_seq,
+ ipsec_wake_pkt.wake_pkt_len));
+
+ struct kev_msg ev_msg;
+ bzero(&ev_msg, sizeof(ev_msg));
+
+ ev_msg.vendor_code = KEV_VENDOR_APPLE;
+ ev_msg.kev_class = KEV_NETWORK_CLASS;
+ ev_msg.kev_subclass = KEV_IPSEC_SUBCLASS;
+ ev_msg.event_code = KEV_IPSEC_WAKE_PACKET;
+
+ struct ipsec_wake_pkt_event_data event_data;
+ strlcpy(event_data.wake_uuid, ipsec_wake_pkt.wake_uuid, sizeof(event_data.wake_uuid));
+ ev_msg.dv[0].data_ptr = &event_data;
+ ev_msg.dv[0].data_length = sizeof(event_data);
+
+ int result = kev_post_msg(&ev_msg);
+ if (result != 0) {
+ os_log_error(OS_LOG_DEFAULT, "%s: kev_post_msg() failed with error %d for wake uuid %s",
+ __func__, result, ipsec_wake_pkt.wake_uuid);
+ }
+
+ ipsec_save_wake_pkt = false;
+done:
+ lck_mtx_unlock(sadb_mutex);
+ return;
+}
+
+static void
+ipsec_get_local_ports(void)
+{
+ errno_t error;
+ ifnet_t *ifp_list;
+ uint32_t count, i;
+ static uint8_t port_bitmap[bitstr_size(IP_PORTRANGE_SIZE)];
+
+ error = ifnet_list_get_all(IFNET_FAMILY_IPSEC, &ifp_list, &count);
+ if (error != 0) {
+ os_log_error(OS_LOG_DEFAULT, "%s: ifnet_list_get_all() failed %d",
+ __func__, error);
+ return;
+ }
+ for (i = 0; i < count; i++) {
+ ifnet_t ifp = ifp_list[i];
+
+ /*
+ * Get all the TCP and UDP ports for IPv4 and IPv6
+ */
+ error = ifnet_get_local_ports_extended(ifp, PF_UNSPEC,
+ IFNET_GET_LOCAL_PORTS_WILDCARDOK |
+ IFNET_GET_LOCAL_PORTS_NOWAKEUPOK |
+ IFNET_GET_LOCAL_PORTS_ANYTCPSTATEOK,
+ port_bitmap);
+ if (error != 0) {
+ os_log_error(OS_LOG_DEFAULT, "%s: ifnet_get_local_ports_extended(%s) failed %d",
+ __func__, if_name(ifp), error);
+ }
+ }
+ ifnet_list_free(ifp_list);
+}
+
+static IOReturn
+ipsec_sleep_wake_handler(void *target, void *refCon, UInt32 messageType,
+ void *provider, void *messageArgument, vm_size_t argSize)
+{
+#pragma unused(target, refCon, provider, messageArgument, argSize)
+ switch (messageType) {
+ case kIOMessageSystemWillSleep:
+ {
+ ipsec_get_local_ports();
+ ipsec_save_wake_pkt = false;
+ memset(&ipsec_wake_pkt, 0, sizeof(ipsec_wake_pkt));
+ IOPMCopySleepWakeUUIDKey(ipsec_wake_pkt.wake_uuid,
+ sizeof(ipsec_wake_pkt.wake_uuid));
+ ipseclog((LOG_NOTICE,
+ "ipsec: system will sleep, uuid: %s", ipsec_wake_pkt.wake_uuid));
+ break;
+ }
+ case kIOMessageSystemHasPoweredOn:
+ {
+ char wake_reason[128] = {0};
+ size_t size = sizeof(wake_reason);
+ if (kernel_sysctlbyname("kern.wakereason", wake_reason, &size, NULL, 0) == 0) {
+ if (strnstr(wake_reason, "wlan", size) == 0 ||
+ strnstr(wake_reason, "WL.OutboxNotEmpty", size) == 0 ||
+ strnstr(wake_reason, "baseband", size) == 0 ||
+ strnstr(wake_reason, "bluetooth", size) == 0 ||
+ strnstr(wake_reason, "BT.OutboxNotEmpty", size) == 0) {
+ ipsec_save_wake_pkt = true;
+ ipseclog((LOG_NOTICE,
+ "ipsec: system has powered on, uuid: %s reason %s", ipsec_wake_pkt.wake_uuid, wake_reason));
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return IOPMAckImplied;
+}
+
+void
+ipsec_monitor_sleep_wake(void)
+{
+ LCK_MTX_ASSERT(sadb_mutex, LCK_MTX_ASSERT_OWNED);
+
+ if (sleep_wake_handle == NULL) {
+ sleep_wake_handle = registerSleepWakeInterest(ipsec_sleep_wake_handler,
+ NULL, NULL);
+ if (sleep_wake_handle != NULL) {
+ ipseclog((LOG_INFO,
+ "ipsec: monitoring sleep wake"));
+ }