/* * Copyright (c) 2017-2023 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if SKYWALK #include #include #include #endif /* SKYWALK */ #include #include #define ESP_HDR_SIZE 4 #define PORT_ISAKMP 500 #define PORT_ISAKMP_NATT 4500 /* rfc3948 */ #define IF_XNAME(ifp) ((ifp) != NULL ? (ifp)->if_xname : "") extern bool IOPMCopySleepWakeUUIDKey(char *buffer, size_t buf_len); SYSCTL_DECL(_net_link_generic_system); SYSCTL_NODE(_net_link_generic_system, OID_AUTO, port_used, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "if port used"); struct if_ports_used_stats if_ports_used_stats = {}; static int sysctl_if_ports_used_stats SYSCTL_HANDLER_ARGS; SYSCTL_PROC(_net_link_generic_system_port_used, OID_AUTO, stats, CTLTYPE_STRUCT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, sysctl_if_ports_used_stats, "S,struct if_ports_used_stats", ""); static uuid_t current_wakeuuid; SYSCTL_OPAQUE(_net_link_generic_system_port_used, OID_AUTO, current_wakeuuid, CTLFLAG_RD | CTLFLAG_LOCKED, current_wakeuuid, sizeof(uuid_t), "S,uuid_t", ""); static int sysctl_net_port_info_list SYSCTL_HANDLER_ARGS; SYSCTL_PROC(_net_link_generic_system_port_used, OID_AUTO, list, CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, sysctl_net_port_info_list, "S,xnpigen", ""); static int use_test_wakeuuid = 0; static uuid_t test_wakeuuid; #if (DEVELOPMENT || DEBUG) SYSCTL_INT(_net_link_generic_system_port_used, OID_AUTO, use_test_wakeuuid, CTLFLAG_RW | CTLFLAG_LOCKED, &use_test_wakeuuid, 0, ""); int sysctl_new_test_wakeuuid SYSCTL_HANDLER_ARGS; SYSCTL_PROC(_net_link_generic_system_port_used, OID_AUTO, new_test_wakeuuid, CTLTYPE_STRUCT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, sysctl_new_test_wakeuuid, "S,uuid_t", ""); int sysctl_clear_test_wakeuuid SYSCTL_HANDLER_ARGS; SYSCTL_PROC(_net_link_generic_system_port_used, OID_AUTO, clear_test_wakeuuid, CTLTYPE_STRUCT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, sysctl_clear_test_wakeuuid, "S,uuid_t", ""); SYSCTL_OPAQUE(_net_link_generic_system_port_used, OID_AUTO, test_wakeuuid, CTLFLAG_RD | CTLFLAG_LOCKED, test_wakeuuid, sizeof(uuid_t), "S,uuid_t", ""); #endif /* (DEVELOPMENT || DEBUG) */ static int sysctl_get_ports_used SYSCTL_HANDLER_ARGS; SYSCTL_NODE(_net_link_generic_system, OID_AUTO, get_ports_used, CTLFLAG_RD | CTLFLAG_LOCKED, sysctl_get_ports_used, ""); int if_ports_used_verbose = 0; SYSCTL_INT(_net_link_generic_system_port_used, OID_AUTO, verbose, CTLFLAG_RW | CTLFLAG_LOCKED, &if_ports_used_verbose, 0, ""); struct timeval wakeuuid_not_set_last_time; int sysctl_wakeuuid_not_set_last_time SYSCTL_HANDLER_ARGS; static SYSCTL_PROC(_net_link_generic_system_port_used, OID_AUTO, wakeuuid_not_set_last_time, CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, sysctl_wakeuuid_not_set_last_time, "S,timeval", ""); char wakeuuid_not_set_last_if[IFXNAMSIZ]; int sysctl_wakeuuid_not_set_last_if SYSCTL_HANDLER_ARGS; static SYSCTL_PROC(_net_link_generic_system_port_used, OID_AUTO, wakeuuid_not_set_last_if, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, sysctl_wakeuuid_not_set_last_if, "A", ""); struct timeval wakeuuid_last_update_time; int sysctl_wakeuuid_last_update_time SYSCTL_HANDLER_ARGS; static SYSCTL_PROC(_net_link_generic_system_port_used, OID_AUTO, wakeuuid_last_update_time, CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, sysctl_wakeuuid_last_update_time, "S,timeval", ""); struct net_port_info_wake_event last_attributed_wake_event; int sysctl_last_attributed_wake_event SYSCTL_HANDLER_ARGS; static SYSCTL_PROC(_net_link_generic_system_port_used, OID_AUTO, last_attributed_wake_event, CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, sysctl_last_attributed_wake_event, "S,net_port_info_wake_event", ""); static bool last_wake_phy_if_set = false; static char last_wake_phy_if_name[IFNAMSIZ]; /* name + unit */ static uint32_t last_wake_phy_if_family; static uint32_t last_wake_phy_if_subfamily; static uint32_t last_wake_phy_if_functional_type; static bool has_notified_wake_pkt = false; static bool has_notified_unattributed_wake = false; static LCK_GRP_DECLARE(net_port_entry_head_lock_group, "net port entry lock"); static LCK_MTX_DECLARE(net_port_entry_head_lock, &net_port_entry_head_lock_group); struct net_port_entry { SLIST_ENTRY(net_port_entry) npe_list_next; TAILQ_ENTRY(net_port_entry) npe_hash_next; struct net_port_info npe_npi; }; static KALLOC_TYPE_DEFINE(net_port_entry_zone, struct net_port_entry, NET_KT_DEFAULT); static SLIST_HEAD(net_port_entry_list, net_port_entry) net_port_entry_list = SLIST_HEAD_INITIALIZER(&net_port_entry_list); struct timeval wakeuiid_last_check; #if (DEBUG | DEVELOPMENT) static int64_t npi_search_list_total = 0; SYSCTL_QUAD(_net_link_generic_system_port_used, OID_AUTO, npi_search_list_total, CTLFLAG_RD | CTLFLAG_LOCKED, &npi_search_list_total, ""); static int64_t npi_search_list_max = 0; SYSCTL_QUAD(_net_link_generic_system_port_used, OID_AUTO, npi_search_list_max, CTLFLAG_RD | CTLFLAG_LOCKED, &npi_search_list_max, ""); #endif /* (DEBUG | DEVELOPMENT) */ /* * Hashing of the net_port_entry list is based on the local port * * The hash masks uses the least significant bits so we have to use host byte order * when applying the mask because the LSB have more entropy that the MSB (most local ports * are in the high dynamic port range) */ #define NPE_HASH_BUCKET_COUNT 32 #define NPE_HASH_MASK (NPE_HASH_BUCKET_COUNT - 1) #define NPE_HASH_VAL(_lport) (ntohs(_lport) & NPE_HASH_MASK) #define NPE_HASH_HEAD(_lport) (&net_port_entry_hash_table[NPE_HASH_VAL(_lport)]) static TAILQ_HEAD(net_port_entry_hash_table, net_port_entry) * net_port_entry_hash_table = NULL; /* * Initialize IPv4 source address hash table. */ void if_ports_used_init(void) { if (net_port_entry_hash_table != NULL) { return; } net_port_entry_hash_table = zalloc_permanent( NPE_HASH_BUCKET_COUNT * sizeof(*net_port_entry_hash_table), ZALIGN_PTR); } static void net_port_entry_list_clear(void) { struct net_port_entry *npe; LCK_MTX_ASSERT(&net_port_entry_head_lock, LCK_MTX_ASSERT_OWNED); while ((npe = SLIST_FIRST(&net_port_entry_list)) != NULL) { SLIST_REMOVE_HEAD(&net_port_entry_list, npe_list_next); TAILQ_REMOVE(NPE_HASH_HEAD(npe->npe_npi.npi_local_port), npe, npe_hash_next); zfree(net_port_entry_zone, npe); } for (int i = 0; i < NPE_HASH_BUCKET_COUNT; i++) { VERIFY(TAILQ_EMPTY(&net_port_entry_hash_table[i])); } if_ports_used_stats.ifpu_npe_count = 0; if_ports_used_stats.ifpu_wakeuid_gen++; } static bool get_test_wake_uuid(uuid_string_t wakeuuid_str, size_t len) { if (__improbable(use_test_wakeuuid)) { if (!uuid_is_null(test_wakeuuid)) { if (wakeuuid_str != NULL && len != 0) { uuid_unparse(test_wakeuuid, wakeuuid_str); } return true; } else { return false; } } else { return false; } } static bool is_wakeuuid_set(void) { /* * IOPMCopySleepWakeUUIDKey() tells if SleepWakeUUID is currently set * That means we are currently in a sleep/wake cycle */ return get_test_wake_uuid(NULL, 0) || IOPMCopySleepWakeUUIDKey(NULL, 0); } void if_ports_used_update_wakeuuid(struct ifnet *ifp) { uuid_t wakeuuid; bool wakeuuid_is_set = false; bool updated = false; uuid_string_t wakeuuid_str; uuid_clear(wakeuuid); if (__improbable(use_test_wakeuuid)) { wakeuuid_is_set = get_test_wake_uuid(wakeuuid_str, sizeof(wakeuuid_str)); } else { wakeuuid_is_set = IOPMCopySleepWakeUUIDKey(wakeuuid_str, sizeof(wakeuuid_str)); } if (wakeuuid_is_set) { if (uuid_parse(wakeuuid_str, wakeuuid) != 0) { os_log(OS_LOG_DEFAULT, "%s: IOPMCopySleepWakeUUIDKey got bad value %s\n", __func__, wakeuuid_str); wakeuuid_is_set = false; } } if (!wakeuuid_is_set) { if (ifp != NULL) { if (if_ports_used_verbose > 0) { os_log_info(OS_LOG_DEFAULT, "%s: SleepWakeUUID not set, " "don't update the port list for %s\n", __func__, ifp != NULL ? if_name(ifp) : ""); } if_ports_used_stats.ifpu_wakeuuid_not_set_count += 1; microtime(&wakeuuid_not_set_last_time); strlcpy(wakeuuid_not_set_last_if, if_name(ifp), sizeof(wakeuuid_not_set_last_if)); } return; } lck_mtx_lock(&net_port_entry_head_lock); if (uuid_compare(wakeuuid, current_wakeuuid) != 0) { net_port_entry_list_clear(); uuid_copy(current_wakeuuid, wakeuuid); microtime(&wakeuuid_last_update_time); updated = true; has_notified_wake_pkt = false; has_notified_unattributed_wake = false; memset(&last_attributed_wake_event, 0, sizeof(last_attributed_wake_event)); last_wake_phy_if_set = false; memset(&last_wake_phy_if_name, 0, sizeof(last_wake_phy_if_name)); last_wake_phy_if_family = IFRTYPE_FAMILY_ANY; last_wake_phy_if_subfamily = IFRTYPE_SUBFAMILY_ANY; last_wake_phy_if_functional_type = IFRTYPE_FUNCTIONAL_UNKNOWN; } /* * Record the time last checked */ microuptime(&wakeuiid_last_check); lck_mtx_unlock(&net_port_entry_head_lock); if (updated && if_ports_used_verbose > 0) { uuid_string_t uuid_str; uuid_unparse(current_wakeuuid, uuid_str); os_log(OS_LOG_DEFAULT, "%s: current wakeuuid %s", __func__, uuid_str); } } static bool net_port_info_equal(const struct net_port_info *x, const struct net_port_info *y) { ASSERT(x != NULL && y != NULL); if (x->npi_if_index == y->npi_if_index && x->npi_local_port == y->npi_local_port && x->npi_foreign_port == y->npi_foreign_port && x->npi_owner_pid == y->npi_owner_pid && x->npi_effective_pid == y->npi_effective_pid && x->npi_flags == y->npi_flags && memcmp(&x->npi_local_addr_, &y->npi_local_addr_, sizeof(union in_addr_4_6)) == 0 && memcmp(&x->npi_foreign_addr_, &y->npi_foreign_addr_, sizeof(union in_addr_4_6)) == 0) { return true; } return false; } static bool net_port_info_has_entry(const struct net_port_info *npi) { struct net_port_entry *npe; bool found = false; int32_t count = 0; LCK_MTX_ASSERT(&net_port_entry_head_lock, LCK_MTX_ASSERT_OWNED); TAILQ_FOREACH(npe, NPE_HASH_HEAD(npi->npi_local_port), npe_hash_next) { count += 1; if (net_port_info_equal(&npe->npe_npi, npi)) { found = true; break; } } if_ports_used_stats.ifpu_npi_hash_search_total += count; if (count > if_ports_used_stats.ifpu_npi_hash_search_max) { if_ports_used_stats.ifpu_npi_hash_search_max = count; } return found; } static bool net_port_info_add_entry(const struct net_port_info *npi) { struct net_port_entry *npe = NULL; uint32_t num = 0; bool entry_added = false; ASSERT(npi != NULL); if (__improbable(is_wakeuuid_set() == false)) { if_ports_used_stats.ifpu_npi_not_added_no_wakeuuid++; if (if_ports_used_verbose > 0) { os_log(OS_LOG_DEFAULT, "%s: wakeuuid not set not adding " "port: %u flags: 0x%xif: %u pid: %u epid %u", __func__, ntohs(npi->npi_local_port), npi->npi_flags, npi->npi_if_index, npi->npi_owner_pid, npi->npi_effective_pid); } return false; } npe = zalloc_flags(net_port_entry_zone, Z_WAITOK | Z_ZERO); if (__improbable(npe == NULL)) { os_log(OS_LOG_DEFAULT, "%s: zalloc() failed for " "port: %u flags: 0x%x if: %u pid: %u epid %u", __func__, ntohs(npi->npi_local_port), npi->npi_flags, npi->npi_if_index, npi->npi_owner_pid, npi->npi_effective_pid); return false; } memcpy(&npe->npe_npi, npi, sizeof(npe->npe_npi)); lck_mtx_lock(&net_port_entry_head_lock); if (net_port_info_has_entry(npi) == false) { SLIST_INSERT_HEAD(&net_port_entry_list, npe, npe_list_next); TAILQ_INSERT_HEAD(NPE_HASH_HEAD(npi->npi_local_port), npe, npe_hash_next); num = (uint32_t)if_ports_used_stats.ifpu_npe_count++; /* rollover OK */ entry_added = true; if (if_ports_used_stats.ifpu_npe_count > if_ports_used_stats.ifpu_npe_max) { if_ports_used_stats.ifpu_npe_max = if_ports_used_stats.ifpu_npe_count; } if_ports_used_stats.ifpu_npe_total++; if (if_ports_used_verbose > 1) { os_log(OS_LOG_DEFAULT, "%s: num %u for " "port: %u flags: 0x%x if: %u pid: %u epid %u", __func__, num, ntohs(npi->npi_local_port), npi->npi_flags, npi->npi_if_index, npi->npi_owner_pid, npi->npi_effective_pid); } } else { if_ports_used_stats.ifpu_npe_dup++; if (if_ports_used_verbose > 2) { os_log(OS_LOG_DEFAULT, "%s: already added " "port: %u flags: 0x%x if: %u pid: %u epid %u", __func__, ntohs(npi->npi_local_port), npi->npi_flags, npi->npi_if_index, npi->npi_owner_pid, npi->npi_effective_pid); } } lck_mtx_unlock(&net_port_entry_head_lock); if (entry_added == false) { zfree(net_port_entry_zone, npe); } return entry_added; } #if (DEVELOPMENT || DEBUG) int sysctl_new_test_wakeuuid SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg1, arg2) int error = 0; if (kauth_cred_issuser(kauth_cred_get()) == 0) { return EPERM; } if (req->oldptr == USER_ADDR_NULL) { req->oldidx = sizeof(uuid_t); return 0; } if (req->newptr != USER_ADDR_NULL) { uuid_generate(test_wakeuuid); if_ports_used_update_wakeuuid(NULL); } error = SYSCTL_OUT(req, test_wakeuuid, MIN(sizeof(uuid_t), req->oldlen)); return error; } int sysctl_clear_test_wakeuuid SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg1, arg2) int error = 0; if (kauth_cred_issuser(kauth_cred_get()) == 0) { return EPERM; } if (req->oldptr == USER_ADDR_NULL) { req->oldidx = sizeof(uuid_t); return 0; } if (req->newptr != USER_ADDR_NULL) { uuid_clear(test_wakeuuid); if_ports_used_update_wakeuuid(NULL); } error = SYSCTL_OUT(req, test_wakeuuid, MIN(sizeof(uuid_t), req->oldlen)); return error; } #endif /* (DEVELOPMENT || DEBUG) */ static int sysctl_timeval(struct sysctl_req *req, const struct timeval *tv) { if (proc_is64bit(req->p)) { struct user64_timeval tv64 = {}; tv64.tv_sec = tv->tv_sec; tv64.tv_usec = tv->tv_usec; return SYSCTL_OUT(req, &tv64, sizeof(tv64)); } else { struct user32_timeval tv32 = {}; tv32.tv_sec = (user32_time_t)tv->tv_sec; tv32.tv_usec = tv->tv_usec; return SYSCTL_OUT(req, &tv32, sizeof(tv32)); } } int sysctl_wakeuuid_last_update_time SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg1, arg2) return sysctl_timeval(req, &wakeuuid_last_update_time); } int sysctl_wakeuuid_not_set_last_time SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg1, arg2) return sysctl_timeval(req, &wakeuuid_not_set_last_time); } int sysctl_wakeuuid_not_set_last_if SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg1, arg2) return SYSCTL_OUT(req, &wakeuuid_not_set_last_if, strlen(wakeuuid_not_set_last_if) + 1); } int sysctl_if_ports_used_stats SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg1, arg2) size_t len = sizeof(struct if_ports_used_stats); if (req->oldptr != 0) { len = MIN(req->oldlen, sizeof(struct if_ports_used_stats)); } return SYSCTL_OUT(req, &if_ports_used_stats, len); } static int sysctl_net_port_info_list SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg1, arg2) int error = 0; struct xnpigen xnpigen; struct net_port_entry *npe; if ((error = priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NETWORK_STATISTICS, 0)) != 0) { return EPERM; } lck_mtx_lock(&net_port_entry_head_lock); if (req->oldptr == USER_ADDR_NULL) { /* Add a 25% cushion */ size_t cnt = (size_t)if_ports_used_stats.ifpu_npe_count; cnt += cnt >> 4; req->oldidx = sizeof(struct xnpigen) + cnt * sizeof(struct net_port_info); goto done; } memset(&xnpigen, 0, sizeof(struct xnpigen)); xnpigen.xng_len = sizeof(struct xnpigen); xnpigen.xng_gen = (uint32_t)if_ports_used_stats.ifpu_wakeuid_gen; uuid_copy(xnpigen.xng_wakeuuid, current_wakeuuid); xnpigen.xng_npi_count = (uint32_t)if_ports_used_stats.ifpu_npe_count; xnpigen.xng_npi_size = sizeof(struct net_port_info); error = SYSCTL_OUT(req, &xnpigen, sizeof(xnpigen)); if (error != 0) { printf("%s: SYSCTL_OUT(xnpigen) error %d\n", __func__, error); goto done; } SLIST_FOREACH(npe, &net_port_entry_list, npe_list_next) { error = SYSCTL_OUT(req, &npe->npe_npi, sizeof(struct net_port_info)); if (error != 0) { printf("%s: SYSCTL_OUT(npi) error %d\n", __func__, error); goto done; } } done: lck_mtx_unlock(&net_port_entry_head_lock); return error; } /* * Mirror the arguments of ifnet_get_local_ports_extended() * ifindex * protocol * flags */ static int sysctl_get_ports_used SYSCTL_HANDLER_ARGS { #pragma unused(oidp) int *name = (int *)arg1; int namelen = arg2; int error = 0; int idx; protocol_family_t protocol; u_int32_t flags; ifnet_t ifp = NULL; u_int8_t *bitfield = NULL; if (req->newptr != USER_ADDR_NULL) { error = EPERM; goto done; } /* * 3 is the required number of parameters: ifindex, protocol and flags */ if (namelen != 3) { error = ENOENT; goto done; } if (req->oldptr == USER_ADDR_NULL) { req->oldidx = bitstr_size(IP_PORTRANGE_SIZE); goto done; } if (req->oldlen < bitstr_size(IP_PORTRANGE_SIZE)) { error = ENOMEM; goto done; } bitfield = (u_int8_t *) kalloc_data(bitstr_size(IP_PORTRANGE_SIZE), Z_WAITOK | Z_ZERO); if (bitfield == NULL) { error = ENOMEM; goto done; } idx = name[0]; protocol = name[1]; flags = name[2]; ifnet_head_lock_shared(); if (IF_INDEX_IN_RANGE(idx)) { ifp = ifindex2ifnet[idx]; } ifnet_head_done(); error = ifnet_get_local_ports_extended(ifp, protocol, flags, bitfield); if (error != 0) { printf("%s: ifnet_get_local_ports_extended() error %d\n", __func__, error); goto done; } error = SYSCTL_OUT(req, bitfield, bitstr_size(IP_PORTRANGE_SIZE)); done: if (bitfield != NULL) { kfree_data(bitfield, bitstr_size(IP_PORTRANGE_SIZE)); } return error; } __private_extern__ bool if_ports_used_add_inpcb(const uint32_t ifindex, const struct inpcb *inp) { struct net_port_info npi = {}; struct socket *so = inp->inp_socket; /* This is unlikely to happen but better be safe than sorry */ if (ifindex > UINT16_MAX) { os_log(OS_LOG_DEFAULT, "%s: ifindex %u too big", __func__, ifindex); return false; } if (ifindex != 0) { npi.npi_if_index = (uint16_t)ifindex; } else if (inp->inp_last_outifp != NULL) { npi.npi_if_index = (uint16_t)inp->inp_last_outifp->if_index; } if (IF_INDEX_IN_RANGE(npi.npi_if_index)) { struct ifnet *ifp = ifindex2ifnet[npi.npi_if_index]; if (ifp != NULL && IFNET_IS_COMPANION_LINK(ifp)) { npi.npi_flags |= NPIF_COMPLINK; } } npi.npi_flags |= NPIF_SOCKET; npi.npi_timestamp.tv_sec = (int32_t)wakeuiid_last_check.tv_sec; npi.npi_timestamp.tv_usec = wakeuiid_last_check.tv_usec; if (so->so_options & SO_NOWAKEFROMSLEEP) { npi.npi_flags |= NPIF_NOWAKE; } if (SOCK_PROTO(so) == IPPROTO_TCP) { struct tcpcb *tp = intotcpcb(inp); npi.npi_flags |= NPIF_TCP; if (tp != NULL && tp->t_state == TCPS_LISTEN) { npi.npi_flags |= NPIF_LISTEN; } } else if (SOCK_PROTO(so) == IPPROTO_UDP) { npi.npi_flags |= NPIF_UDP; } else { os_log(OS_LOG_DEFAULT, "%s: unexpected protocol %u for inp %p", __func__, SOCK_PROTO(inp->inp_socket), inp); return false; } uuid_copy(npi.npi_flow_uuid, inp->necp_client_uuid); npi.npi_local_port = inp->inp_lport; npi.npi_foreign_port = inp->inp_fport; /* * Take in account IPv4 addresses mapped on IPv6 */ if ((inp->inp_vflag & INP_IPV6) != 0 && (inp->inp_flags & IN6P_IPV6_V6ONLY) == 0 && (inp->inp_vflag & (INP_IPV6 | INP_IPV4)) == (INP_IPV6 | INP_IPV4)) { npi.npi_flags |= NPIF_IPV6 | NPIF_IPV4; memcpy(&npi.npi_local_addr_in6, &inp->in6p_laddr, sizeof(struct in6_addr)); } else if (inp->inp_vflag & INP_IPV4) { npi.npi_flags |= NPIF_IPV4; npi.npi_local_addr_in = inp->inp_laddr; npi.npi_foreign_addr_in = inp->inp_faddr; } else { npi.npi_flags |= NPIF_IPV6; memcpy(&npi.npi_local_addr_in6, &inp->in6p_laddr, sizeof(struct in6_addr)); memcpy(&npi.npi_foreign_addr_in6, &inp->in6p_faddr, sizeof(struct in6_addr)); /* Clear the embedded scope ID */ if (IN6_IS_ADDR_LINKLOCAL(&npi.npi_local_addr_in6)) { npi.npi_local_addr_in6.s6_addr16[1] = 0; } if (IN6_IS_ADDR_LINKLOCAL(&npi.npi_foreign_addr_in6)) { npi.npi_foreign_addr_in6.s6_addr16[1] = 0; } } npi.npi_owner_pid = so->last_pid; if (so->last_pid != 0) { proc_name(so->last_pid, npi.npi_owner_pname, sizeof(npi.npi_owner_pname)); uuid_copy(npi.npi_owner_uuid, so->last_uuid); } if (so->so_flags & SOF_DELEGATED) { npi.npi_flags |= NPIF_DELEGATED; npi.npi_effective_pid = so->e_pid; if (so->e_pid != 0) { proc_name(so->e_pid, npi.npi_effective_pname, sizeof(npi.npi_effective_pname)); } uuid_copy(npi.npi_effective_uuid, so->e_uuid); } else { npi.npi_effective_pid = so->last_pid; if (so->last_pid != 0) { strlcpy(npi.npi_effective_pname, npi.npi_owner_pname, sizeof(npi.npi_effective_pname)); } uuid_copy(npi.npi_effective_uuid, so->last_uuid); } return net_port_info_add_entry(&npi); } #if SKYWALK __private_extern__ bool if_ports_used_add_flow_entry(const struct flow_entry *fe, const uint32_t ifindex, const struct ns_flow_info *nfi, uint32_t ns_flags) { struct net_port_info npi = {}; /* This is unlikely to happen but better be safe than sorry */ if (ifindex > UINT16_MAX) { os_log(OS_LOG_DEFAULT, "%s: ifindex %u too big", __func__, ifindex); return false; } npi.npi_if_index = (uint16_t)ifindex; if (IF_INDEX_IN_RANGE(ifindex)) { struct ifnet *ifp = ifindex2ifnet[ifindex]; if (ifp != NULL && IFNET_IS_COMPANION_LINK(ifp)) { npi.npi_flags |= NPIF_COMPLINK; } } npi.npi_flags |= NPIF_CHANNEL; npi.npi_timestamp.tv_sec = (int32_t)wakeuiid_last_check.tv_sec; npi.npi_timestamp.tv_usec = wakeuiid_last_check.tv_usec; if (ns_flags & NETNS_NOWAKEFROMSLEEP) { npi.npi_flags |= NPIF_NOWAKE; } if ((ns_flags & NETNS_OWNER_MASK) == NETNS_LISTENER) { npi.npi_flags |= NPIF_LISTEN; } uuid_copy(npi.npi_flow_uuid, nfi->nfi_flow_uuid); if (nfi->nfi_protocol == IPPROTO_TCP) { npi.npi_flags |= NPIF_TCP; } else if (nfi->nfi_protocol == IPPROTO_UDP) { npi.npi_flags |= NPIF_UDP; } else { os_log(OS_LOG_DEFAULT, "%s: unexpected protocol %u for nfi %p", __func__, nfi->nfi_protocol, nfi); return false; } if (nfi->nfi_laddr.sa.sa_family == AF_INET) { npi.npi_flags |= NPIF_IPV4; npi.npi_local_port = nfi->nfi_laddr.sin.sin_port; npi.npi_foreign_port = nfi->nfi_faddr.sin.sin_port; npi.npi_local_addr_in = nfi->nfi_laddr.sin.sin_addr; npi.npi_foreign_addr_in = nfi->nfi_faddr.sin.sin_addr; } else { npi.npi_flags |= NPIF_IPV6; npi.npi_local_port = nfi->nfi_laddr.sin6.sin6_port; npi.npi_foreign_port = nfi->nfi_faddr.sin6.sin6_port; memcpy(&npi.npi_local_addr_in6, &nfi->nfi_laddr.sin6.sin6_addr, sizeof(struct in6_addr)); memcpy(&npi.npi_foreign_addr_in6, &nfi->nfi_faddr.sin6.sin6_addr, sizeof(struct in6_addr)); /* Clear the embedded scope ID */ if (IN6_IS_ADDR_LINKLOCAL(&npi.npi_local_addr_in6)) { npi.npi_local_addr_in6.s6_addr16[1] = 0; } if (IN6_IS_ADDR_LINKLOCAL(&npi.npi_foreign_addr_in6)) { npi.npi_foreign_addr_in6.s6_addr16[1] = 0; } } npi.npi_owner_pid = nfi->nfi_owner_pid; strlcpy(npi.npi_owner_pname, nfi->nfi_owner_name, sizeof(npi.npi_owner_pname)); /* * Get the proc UUID from the pid as the the proc UUID is not present * in the flow_entry */ proc_t proc = proc_find(npi.npi_owner_pid); if (proc != PROC_NULL) { proc_getexecutableuuid(proc, npi.npi_owner_uuid, sizeof(npi.npi_owner_uuid)); proc_rele(proc); } if (nfi->nfi_effective_pid != -1) { npi.npi_effective_pid = nfi->nfi_effective_pid; strlcpy(npi.npi_effective_pname, nfi->nfi_effective_name, sizeof(npi.npi_effective_pname)); uuid_copy(npi.npi_effective_uuid, fe->fe_eproc_uuid); } else { npi.npi_effective_pid = npi.npi_owner_pid; strlcpy(npi.npi_effective_pname, npi.npi_owner_pname, sizeof(npi.npi_effective_pname)); uuid_copy(npi.npi_effective_uuid, npi.npi_owner_uuid); } return net_port_info_add_entry(&npi); } #endif /* SKYWALK */ static void net_port_info_log_npi(const char *s, const struct net_port_info *npi) { char lbuf[MAX_IPv6_STR_LEN] = {}; char fbuf[MAX_IPv6_STR_LEN] = {}; if (npi->npi_flags & NPIF_IPV4) { inet_ntop(PF_INET, &npi->npi_local_addr_in.s_addr, lbuf, sizeof(lbuf)); inet_ntop(PF_INET, &npi->npi_foreign_addr_in.s_addr, fbuf, sizeof(fbuf)); } else if (npi->npi_flags & NPIF_IPV6) { inet_ntop(PF_INET6, &npi->npi_local_addr_in6, lbuf, sizeof(lbuf)); inet_ntop(PF_INET6, &npi->npi_foreign_addr_in6, fbuf, sizeof(fbuf)); } os_log(OS_LOG_DEFAULT, "%s net_port_info if_index %u arch %s family %s proto %s local %s:%u foreign %s:%u pid: %u epid %u", s != NULL ? s : "", npi->npi_if_index, (npi->npi_flags & NPIF_SOCKET) ? "so" : (npi->npi_flags & NPIF_CHANNEL) ? "ch" : "unknown", (npi->npi_flags & NPIF_IPV4) ? "ipv4" : (npi->npi_flags & NPIF_IPV6) ? "ipv6" : "unknown", npi->npi_flags & NPIF_TCP ? "tcp" : npi->npi_flags & NPIF_UDP ? "udp" : npi->npi_flags & NPIF_ESP ? "esp" : "unknown", lbuf, ntohs(npi->npi_local_port), fbuf, ntohs(npi->npi_foreign_port), npi->npi_owner_pid, npi->npi_effective_pid); } /* * net_port_info_match_npi() returns true for an exact match that does not have "no wake" set */ #define NPI_MATCH_IPV4 (NPIF_IPV4 | NPIF_TCP | NPIF_UDP) #define NPI_MATCH_IPV6 (NPIF_IPV6 | NPIF_TCP | NPIF_UDP) static bool net_port_info_match_npi(struct net_port_entry *npe, const struct net_port_info *in_npi, struct net_port_entry **best_match) { if (__improbable(net_wake_pkt_debug > 1)) { net_port_info_log_npi(" ", &npe->npe_npi); } /* * The interfaces must match or be both companion link */ if (npe->npe_npi.npi_if_index != in_npi->npi_if_index && !((npe->npe_npi.npi_flags & NPIF_COMPLINK) && (in_npi->npi_flags & NPIF_COMPLINK))) { return false; } /* * The local ports and protocols must match */ if (npe->npe_npi.npi_local_port != in_npi->npi_local_port || ((npe->npe_npi.npi_flags & NPI_MATCH_IPV4) != (in_npi->npi_flags & NPI_MATCH_IPV4) && (npe->npe_npi.npi_flags & NPI_MATCH_IPV6) != (in_npi->npi_flags & NPI_MATCH_IPV6))) { return false; } /* * Search stops on an exact match */ if (npe->npe_npi.npi_foreign_port == in_npi->npi_foreign_port) { if ((npe->npe_npi.npi_flags & NPIF_IPV4) && (npe->npe_npi.npi_flags & NPIF_IPV4)) { if (in_npi->npi_local_addr_in.s_addr == npe->npe_npi.npi_local_addr_in.s_addr && in_npi->npi_foreign_addr_in.s_addr == npe->npe_npi.npi_foreign_addr_in.s_addr) { if (npe->npe_npi.npi_flags & NPIF_NOWAKE) { /* * Do not overwrite an existing match when "no wake" is set */ if (*best_match == NULL) { *best_match = npe; } return false; } *best_match = npe; return true; } } if ((npe->npe_npi.npi_flags & NPIF_IPV6) && (npe->npe_npi.npi_flags & NPIF_IPV6)) { if (memcmp(&npe->npe_npi.npi_local_addr_, &in_npi->npi_local_addr_, sizeof(union in_addr_4_6)) == 0 && memcmp(&npe->npe_npi.npi_foreign_addr_, &in_npi->npi_foreign_addr_, sizeof(union in_addr_4_6)) == 0) { if (npe->npe_npi.npi_flags & NPIF_NOWAKE) { /* * Do not overwrite an existing match when "no wake" is set */ if (*best_match == NULL) { *best_match = npe; } return false; } *best_match = npe; return true; } } } /* * Skip connected entries as we are looking for a wildcard match * on the local address and port */ if (npe->npe_npi.npi_foreign_port != 0) { return false; } /* * Do not overwrite an existing match when "no wake" is set */ if (*best_match != NULL && (npe->npe_npi.npi_flags & NPIF_NOWAKE) != 0) { return false; } /* * The local address matches: this is our 2nd best match */ if (memcmp(&npe->npe_npi.npi_local_addr_, &in_npi->npi_local_addr_, sizeof(union in_addr_4_6)) == 0) { *best_match = npe; return false; } /* * Only the local port matches, do not override a match * on the local address */ if (*best_match == NULL) { *best_match = npe; } return false; } #undef NPI_MATCH_IPV4 #undef NPI_MATCH_IPV6 /* * */ static bool net_port_info_find_match(struct net_port_info *in_npi) { struct net_port_entry *npe; struct net_port_entry *best_match = NULL; lck_mtx_lock(&net_port_entry_head_lock); uint32_t count = 0; TAILQ_FOREACH(npe, NPE_HASH_HEAD(in_npi->npi_local_port), npe_hash_next) { count += 1; /* * Search stop on an exact match */ if (net_port_info_match_npi(npe, in_npi, &best_match)) { break; } } if (best_match != NULL) { best_match->npe_npi.npi_flags |= NPIF_WAKEPKT; if (best_match->npe_npi.npi_flags & NPIF_NOWAKE) { in_npi->npi_flags |= NPIF_NOWAKE; } in_npi->npi_owner_pid = best_match->npe_npi.npi_owner_pid; in_npi->npi_effective_pid = best_match->npe_npi.npi_effective_pid; strlcpy(in_npi->npi_owner_pname, best_match->npe_npi.npi_owner_pname, sizeof(in_npi->npi_owner_pname)); strlcpy(in_npi->npi_effective_pname, best_match->npe_npi.npi_effective_pname, sizeof(in_npi->npi_effective_pname)); uuid_copy(in_npi->npi_owner_uuid, best_match->npe_npi.npi_owner_uuid); uuid_copy(in_npi->npi_effective_uuid, best_match->npe_npi.npi_effective_uuid); } lck_mtx_unlock(&net_port_entry_head_lock); if (__improbable(net_wake_pkt_debug > 0)) { if (best_match != NULL) { net_port_info_log_npi("wake packet match", in_npi); } else { net_port_info_log_npi("wake packet no match", in_npi); } } return best_match != NULL ? true : false; } #if (DEBUG || DEVELOPMENT) static void net_port_info_log_una_wake_event(const char *s, struct net_port_info_una_wake_event *ev) { char lbuf[MAX_IPv6_STR_LEN] = {}; char fbuf[MAX_IPv6_STR_LEN] = {}; if (ev->una_wake_pkt_flags & NPIF_IPV4) { inet_ntop(PF_INET, &ev->una_wake_pkt_local_addr_._in_a_4.s_addr, lbuf, sizeof(lbuf)); inet_ntop(PF_INET, &ev->una_wake_pkt_foreign_addr_._in_a_4.s_addr, fbuf, sizeof(fbuf)); } else if (ev->una_wake_pkt_flags & NPIF_IPV6) { inet_ntop(PF_INET6, &ev->una_wake_pkt_local_addr_._in_a_6.s6_addr, lbuf, sizeof(lbuf)); inet_ntop(PF_INET6, &ev->una_wake_pkt_foreign_addr_._in_a_6.s6_addr, fbuf, sizeof(fbuf)); } os_log(OS_LOG_DEFAULT, "%s if %s (%u) phy_if %s proto %s local %s:%u foreign %s:%u len: %u datalen: %u cflags: 0x%x proto: %u", s != NULL ? s : "", ev->una_wake_pkt_ifname, ev->una_wake_pkt_if_index, ev->una_wake_pkt_phy_ifname, ev->una_wake_pkt_flags & NPIF_TCP ? "tcp" : ev->una_wake_pkt_flags ? "udp" : ev->una_wake_pkt_flags & NPIF_ESP ? "esp" : "unknown", lbuf, ntohs(ev->una_wake_pkt_local_port), fbuf, ntohs(ev->una_wake_pkt_foreign_port), ev->una_wake_pkt_total_len, ev->una_wake_pkt_data_len, ev->una_wake_pkt_control_flags, ev->una_wake_pkt_proto); } static void net_port_info_log_wake_event(const char *s, struct net_port_info_wake_event *ev) { char lbuf[MAX_IPv6_STR_LEN] = {}; char fbuf[MAX_IPv6_STR_LEN] = {}; if (ev->wake_pkt_flags & NPIF_IPV4) { inet_ntop(PF_INET, &ev->wake_pkt_local_addr_._in_a_4.s_addr, lbuf, sizeof(lbuf)); inet_ntop(PF_INET, &ev->wake_pkt_foreign_addr_._in_a_4.s_addr, fbuf, sizeof(fbuf)); } else if (ev->wake_pkt_flags & NPIF_IPV6) { inet_ntop(PF_INET6, &ev->wake_pkt_local_addr_._in_a_6.s6_addr, lbuf, sizeof(lbuf)); inet_ntop(PF_INET6, &ev->wake_pkt_foreign_addr_._in_a_6.s6_addr, fbuf, sizeof(fbuf)); } os_log(OS_LOG_DEFAULT, "%s if %s (%u) phy_if %s proto %s local %s:%u foreign %s:%u len: %u datalen: %u cflags: 0x%x proc %s eproc %s", s != NULL ? s : "", ev->wake_pkt_ifname, ev->wake_pkt_if_index, ev->wake_pkt_phy_ifname, ev->wake_pkt_flags & NPIF_TCP ? "tcp" : ev->wake_pkt_flags ? "udp" : ev->wake_pkt_flags & NPIF_ESP ? "esp" : "unknown", lbuf, ntohs(ev->wake_pkt_port), fbuf, ntohs(ev->wake_pkt_foreign_port), ev->wake_pkt_total_len, ev->wake_pkt_data_len, ev->wake_pkt_control_flags, ev->wake_pkt_owner_pname, ev->wake_pkt_effective_pname); } #endif /* (DEBUG || DEVELOPMENT) */ /* * The process attribution of a wake packet can take several steps: * * 1) After device wakes, the first interface that sees a wake packet is the * physical interface and we remember it via if_set_wake_physical_interface() * * 2) We try to attribute a packet to a flow or not based on the physical interface. * If we find a flow, then the physical interface is the same as the interface used * by the TCP/UDP flow. * * 3) If the packet is tunneled or redirected we are going to do the attribution again * and the physical will be different from the interface used the TCP/UDP flow. */ static void if_set_wake_physical_interface(struct ifnet *ifp) { if (last_wake_phy_if_set == true || ifp == NULL) { return; } last_wake_phy_if_set = true; strlcpy(last_wake_phy_if_name, IF_XNAME(ifp), sizeof(last_wake_phy_if_name)); last_wake_phy_if_family = ifp->if_family; last_wake_phy_if_subfamily = ifp->if_subfamily; last_wake_phy_if_functional_type = if_functional_type(ifp, true); } static void if_notify_unattributed_wake_mbuf(struct ifnet *ifp, struct mbuf *m, struct net_port_info *npi, uint32_t pkt_total_len, uint32_t pkt_data_len, uint16_t pkt_control_flags, uint16_t proto) { struct kev_msg ev_msg = {}; LCK_MTX_ASSERT(&net_port_entry_head_lock, LCK_MTX_ASSERT_NOTOWNED); lck_mtx_lock(&net_port_entry_head_lock); if (has_notified_unattributed_wake) { lck_mtx_unlock(&net_port_entry_head_lock); if_ports_used_stats.ifpu_dup_unattributed_wake_event += 1; if (__improbable(net_wake_pkt_debug > 0)) { net_port_info_log_npi("already notified unattributed wake packet", npi); } return; } has_notified_unattributed_wake = true; lck_mtx_unlock(&net_port_entry_head_lock); if_ports_used_stats.ifpu_unattributed_wake_event += 1; ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_POWER_SUBCLASS; ev_msg.event_code = KEV_POWER_UNATTRIBUTED_WAKE; struct net_port_info_una_wake_event event_data = {}; uuid_copy(event_data.una_wake_uuid, current_wakeuuid); event_data.una_wake_pkt_if_index = ifp != NULL ? ifp->if_index : 0; event_data.una_wake_pkt_flags = npi->npi_flags; event_data.una_wake_pkt_local_port = npi->npi_local_port; event_data.una_wake_pkt_foreign_port = npi->npi_foreign_port; event_data.una_wake_pkt_local_addr_ = npi->npi_local_addr_; event_data.una_wake_pkt_foreign_addr_ = npi->npi_foreign_addr_; event_data.una_wake_pkt_total_len = pkt_total_len; event_data.una_wake_pkt_data_len = pkt_data_len; event_data.una_wake_pkt_control_flags = pkt_control_flags; event_data.una_wake_pkt_proto = proto; if (ifp != NULL) { strlcpy(event_data.una_wake_pkt_ifname, IF_XNAME(ifp), sizeof(event_data.una_wake_pkt_ifname)); event_data.una_wake_pkt_if_info.npi_if_family = ifp->if_family; event_data.una_wake_pkt_if_info.npi_if_subfamily = ifp->if_subfamily; event_data.una_wake_pkt_if_info.npi_if_functional_type = if_functional_type(ifp, true); strlcpy(event_data.una_wake_pkt_phy_ifname, last_wake_phy_if_name, sizeof(event_data.una_wake_pkt_phy_ifname)); event_data.una_wake_pkt_phy_if_info.npi_if_family = last_wake_phy_if_family; event_data.una_wake_pkt_phy_if_info.npi_if_subfamily = last_wake_phy_if_subfamily; event_data.una_wake_pkt_phy_if_info.npi_if_functional_type = last_wake_phy_if_functional_type; } else { if_ports_used_stats.ifpu_unattributed_null_recvif += 1; } event_data.una_wake_ptk_len = m->m_pkthdr.len > NPI_MAX_UNA_WAKE_PKT_LEN ? NPI_MAX_UNA_WAKE_PKT_LEN : (u_int16_t)m->m_pkthdr.len; errno_t error = mbuf_copydata(m, 0, event_data.una_wake_ptk_len, (void *)event_data.una_wake_pkt); if (error != 0) { uuid_string_t wake_uuid_str; uuid_unparse(event_data.una_wake_uuid, wake_uuid_str); os_log_error(OS_LOG_DEFAULT, "%s: mbuf_copydata() failed with error %d for wake uuid %s", __func__, error, wake_uuid_str); if_ports_used_stats.ifpu_unattributed_wake_event_error += 1; return; } 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) { uuid_string_t wake_uuid_str; uuid_unparse(event_data.una_wake_uuid, wake_uuid_str); os_log_error(OS_LOG_DEFAULT, "%s: kev_post_msg() failed with error %d for wake uuid %s", __func__, result, wake_uuid_str); if_ports_used_stats.ifpu_unattributed_wake_event_error += 1; } #if (DEBUG || DEVELOPMENT) net_port_info_log_una_wake_event("unattributed wake packet event", &event_data); #endif /* (DEBUG || DEVELOPMENT) */ } static void if_notify_wake_packet(struct ifnet *ifp, struct net_port_info *npi, uint32_t pkt_total_len, uint32_t pkt_data_len, uint16_t pkt_control_flags) { struct kev_msg ev_msg = {}; ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_POWER_SUBCLASS; ev_msg.event_code = KEV_POWER_WAKE_PACKET; struct net_port_info_wake_event event_data = {}; uuid_copy(event_data.wake_uuid, current_wakeuuid); event_data.wake_pkt_if_index = ifp->if_index; event_data.wake_pkt_port = npi->npi_local_port; event_data.wake_pkt_flags = npi->npi_flags; event_data.wake_pkt_owner_pid = npi->npi_owner_pid; event_data.wake_pkt_effective_pid = npi->npi_effective_pid; strlcpy(event_data.wake_pkt_owner_pname, npi->npi_owner_pname, sizeof(event_data.wake_pkt_owner_pname)); strlcpy(event_data.wake_pkt_effective_pname, npi->npi_effective_pname, sizeof(event_data.wake_pkt_effective_pname)); uuid_copy(event_data.wake_pkt_owner_uuid, npi->npi_owner_uuid); uuid_copy(event_data.wake_pkt_effective_uuid, npi->npi_effective_uuid); event_data.wake_pkt_foreign_port = npi->npi_foreign_port; event_data.wake_pkt_local_addr_ = npi->npi_local_addr_; event_data.wake_pkt_foreign_addr_ = npi->npi_foreign_addr_; strlcpy(event_data.wake_pkt_ifname, IF_XNAME(ifp), sizeof(event_data.wake_pkt_ifname)); event_data.wake_pkt_if_info.npi_if_family = ifp->if_family; event_data.wake_pkt_if_info.npi_if_subfamily = ifp->if_subfamily; event_data.wake_pkt_if_info.npi_if_functional_type = if_functional_type(ifp, true); strlcpy(event_data.wake_pkt_phy_ifname, last_wake_phy_if_name, sizeof(event_data.wake_pkt_phy_ifname)); event_data.wake_pkt_phy_if_info.npi_if_family = last_wake_phy_if_family; event_data.wake_pkt_phy_if_info.npi_if_subfamily = last_wake_phy_if_subfamily; event_data.wake_pkt_phy_if_info.npi_if_functional_type = last_wake_phy_if_functional_type; event_data.wake_pkt_total_len = pkt_total_len; event_data.wake_pkt_data_len = pkt_data_len; event_data.wake_pkt_control_flags = pkt_control_flags; if (npi->npi_flags & NPIF_NOWAKE) { event_data.wake_pkt_control_flags |= NPICF_NOWAKE; } ev_msg.dv[0].data_ptr = &event_data; ev_msg.dv[0].data_length = sizeof(event_data); LCK_MTX_ASSERT(&net_port_entry_head_lock, LCK_MTX_ASSERT_NOTOWNED); lck_mtx_lock(&net_port_entry_head_lock); if (has_notified_wake_pkt) { lck_mtx_unlock(&net_port_entry_head_lock); if_ports_used_stats.ifpu_dup_wake_pkt_event += 1; if (__improbable(net_wake_pkt_debug > 0)) { net_port_info_log_npi("already notified wake packet", npi); } return; } has_notified_wake_pkt = true; memcpy(&last_attributed_wake_event, &event_data, sizeof(last_attributed_wake_event)); lck_mtx_unlock(&net_port_entry_head_lock); if (npi->npi_flags & NPIF_NOWAKE) { if_ports_used_stats.ifpu_spurious_wake_event += 1; } else { if_ports_used_stats.ifpu_wake_pkt_event += 1; } int result = kev_post_msg(&ev_msg); if (result != 0) { uuid_string_t wake_uuid_str; uuid_unparse(event_data.wake_uuid, wake_uuid_str); os_log_error(OS_LOG_DEFAULT, "%s: kev_post_msg() failed with error %d for wake uuid %s", __func__, result, wake_uuid_str); if_ports_used_stats.ifpu_wake_pkt_event_error += 1; } #if (DEBUG || DEVELOPMENT) net_port_info_log_wake_event("attributed wake packet event", &event_data); #endif /* (DEBUG || DEVELOPMENT) */ } static bool is_encapsulated_esp(struct mbuf *m, size_t data_offset) { /* * They are three cases: * - Keep alive: 1 byte payload * - IKE: payload start with 4 bytes header set to zero before ISAKMP header * - otherwise it's ESP */ ASSERT(m->m_pkthdr.len >= data_offset); size_t data_len = m->m_pkthdr.len - data_offset; if (data_len == 1) { return false; } else if (data_len > ESP_HDR_SIZE) { uint8_t payload[ESP_HDR_SIZE]; errno_t error = mbuf_copydata(m, data_offset, ESP_HDR_SIZE, &payload); if (error != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata(ESP_HDR_SIZE) error %d", __func__, error); } else if (payload[0] == 0 && payload[1] == 0 && payload[2] == 0 && payload[3] == 0) { return false; } } return true; } void if_ports_used_match_mbuf(struct ifnet *ifp, protocol_family_t proto_family, struct mbuf *m) { errno_t error; struct net_port_info npi = {}; bool found = false; uint32_t pkt_total_len = 0; uint32_t pkt_data_len = 0; uint16_t pkt_control_flags = 0; uint16_t pkt_proto = 0; if ((m->m_pkthdr.pkt_flags & PKTF_WAKE_PKT) == 0) { if_ports_used_stats.ifpu_match_wake_pkt_no_flag += 1; os_log_error(OS_LOG_DEFAULT, "%s: called PKTF_WAKE_PKT not set from %s", __func__, ifp != NULL ? IF_XNAME(ifp) : ""); return; } if_ports_used_stats.ifpu_so_match_wake_pkt += 1; npi.npi_flags |= NPIF_SOCKET; /* For logging */ pkt_total_len = m->m_pkthdr.len; pkt_data_len = pkt_total_len; if (ifp != NULL) { npi.npi_if_index = ifp->if_index; if (IFNET_IS_COMPANION_LINK(ifp)) { npi.npi_flags |= NPIF_COMPLINK; } if_set_wake_physical_interface(ifp); } if (proto_family == PF_INET) { struct ip iphdr = {}; if_ports_used_stats.ifpu_ipv4_wake_pkt += 1; error = mbuf_copydata(m, 0, sizeof(struct ip), &iphdr); if (error != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata(ip) error %d", __func__, error); goto failed; } npi.npi_flags |= NPIF_IPV4; npi.npi_local_addr_in = iphdr.ip_dst; npi.npi_foreign_addr_in = iphdr.ip_src; /* * Check if this is a fragment that is not the first fragment */ if ((ntohs(iphdr.ip_off) & ~(IP_DF | IP_RF)) && (ntohs(iphdr.ip_off) & IP_OFFMASK) != 0) { npi.npi_flags |= NPIF_FRAG; if_ports_used_stats.ifpu_frag_wake_pkt += 1; } if ((iphdr.ip_hl << 2) < pkt_data_len) { pkt_data_len -= iphdr.ip_hl << 2; } else { pkt_data_len = 0; } pkt_proto = iphdr.ip_p; switch (iphdr.ip_p) { case IPPROTO_TCP: { if_ports_used_stats.ifpu_tcp_wake_pkt += 1; npi.npi_flags |= NPIF_TCP; if (npi.npi_flags & NPIF_FRAG) { goto failed; } struct tcphdr th = {}; error = mbuf_copydata(m, iphdr.ip_hl << 2, sizeof(struct tcphdr), &th); if (error != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata(tcphdr) error %d", __func__, error); goto failed; } npi.npi_local_port = th.th_dport; npi.npi_foreign_port = th.th_sport; if (pkt_data_len < sizeof(struct tcphdr) || pkt_data_len < (th.th_off << 2)) { pkt_data_len = 0; } else { pkt_data_len -= th.th_off << 2; } pkt_control_flags = th.th_flags; break; } case IPPROTO_UDP: { if_ports_used_stats.ifpu_udp_wake_pkt += 1; npi.npi_flags |= NPIF_UDP; if (npi.npi_flags & NPIF_FRAG) { goto failed; } struct udphdr uh = {}; size_t udp_offset = iphdr.ip_hl << 2; error = mbuf_copydata(m, udp_offset, sizeof(struct udphdr), &uh); if (error != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata(udphdr) error %d", __func__, error); goto failed; } npi.npi_local_port = uh.uh_dport; npi.npi_foreign_port = uh.uh_sport; /* * Let the ESP layer handle wake packets */ if (ntohs(uh.uh_dport) == PORT_ISAKMP_NATT || ntohs(uh.uh_sport) == PORT_ISAKMP_NATT) { if_ports_used_stats.ifpu_isakmp_natt_wake_pkt += 1; if (is_encapsulated_esp(m, udp_offset + sizeof(struct udphdr))) { if (net_wake_pkt_debug > 0) { net_port_info_log_npi("defer ISAKMP_NATT matching", &npi); } return; } } if (pkt_data_len < sizeof(struct udphdr)) { pkt_data_len = 0; } else { pkt_data_len -= sizeof(struct udphdr); } break; } case IPPROTO_ESP: { /* * Let the ESP layer handle wake packets */ if_ports_used_stats.ifpu_esp_wake_pkt += 1; npi.npi_flags |= NPIF_ESP; if (net_wake_pkt_debug > 0) { net_port_info_log_npi("defer ESP matching", &npi); } return; } default: if_ports_used_stats.ifpu_bad_proto_wake_pkt += 1; os_log(OS_LOG_DEFAULT, "%s: unexpected IPv4 protocol %u from %s", __func__, iphdr.ip_p, IF_XNAME(ifp)); goto failed; } } else if (proto_family == PF_INET6) { struct ip6_hdr ip6_hdr = {}; if_ports_used_stats.ifpu_ipv6_wake_pkt += 1; error = mbuf_copydata(m, 0, sizeof(struct ip6_hdr), &ip6_hdr); if (error != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata(ip6_hdr) error %d", __func__, error); goto failed; } npi.npi_flags |= NPIF_IPV6; memcpy(&npi.npi_local_addr_in6, &ip6_hdr.ip6_dst, sizeof(struct in6_addr)); memcpy(&npi.npi_foreign_addr_in6, &ip6_hdr.ip6_src, sizeof(struct in6_addr)); size_t l3_len = sizeof(struct ip6_hdr); uint8_t l4_proto = ip6_hdr.ip6_nxt; pkt_proto = l4_proto; if (pkt_data_len < l3_len) { pkt_data_len = 0; } else { pkt_data_len -= l3_len; } /* * Check if this is a fragment that is not the first fragment */ if (l4_proto == IPPROTO_FRAGMENT) { struct ip6_frag ip6_frag; error = mbuf_copydata(m, sizeof(struct ip6_hdr), sizeof(struct ip6_frag), &ip6_frag); if (error != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata(ip6_frag) error %d", __func__, error); goto failed; } l3_len += sizeof(struct ip6_frag); l4_proto = ip6_frag.ip6f_nxt; if ((ip6_frag.ip6f_offlg & IP6F_OFF_MASK) != 0) { npi.npi_flags |= NPIF_FRAG; if_ports_used_stats.ifpu_frag_wake_pkt += 1; } } switch (l4_proto) { case IPPROTO_TCP: { if_ports_used_stats.ifpu_tcp_wake_pkt += 1; npi.npi_flags |= NPIF_TCP; /* * Cannot attribute a fragment that is not the first fragment as it * not have the TCP header */ if (npi.npi_flags & NPIF_FRAG) { goto failed; } struct tcphdr th = {}; error = mbuf_copydata(m, l3_len, sizeof(struct tcphdr), &th); if (error != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata(tcphdr) error %d", __func__, error); if_ports_used_stats.ifpu_incomplete_tcp_hdr_pkt += 1; goto failed; } npi.npi_local_port = th.th_dport; npi.npi_foreign_port = th.th_sport; if (pkt_data_len < sizeof(struct tcphdr) || pkt_data_len < (th.th_off << 2)) { pkt_data_len = 0; } else { pkt_data_len -= th.th_off << 2; } pkt_control_flags = th.th_flags; break; } case IPPROTO_UDP: { if_ports_used_stats.ifpu_udp_wake_pkt += 1; npi.npi_flags |= NPIF_UDP; /* * Cannot attribute a fragment that is not the first fragment as it * not have the UDP header */ if (npi.npi_flags & NPIF_FRAG) { goto failed; } struct udphdr uh = {}; error = mbuf_copydata(m, l3_len, sizeof(struct udphdr), &uh); if (error != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata(udphdr) error %d", __func__, error); if_ports_used_stats.ifpu_incomplete_udp_hdr_pkt += 1; goto failed; } npi.npi_local_port = uh.uh_dport; npi.npi_foreign_port = uh.uh_sport; /* * Let the ESP layer handle wake packets */ if (ntohs(npi.npi_local_port) == PORT_ISAKMP_NATT || ntohs(npi.npi_foreign_port) == PORT_ISAKMP_NATT) { if_ports_used_stats.ifpu_isakmp_natt_wake_pkt += 1; if (is_encapsulated_esp(m, l3_len + sizeof(struct udphdr))) { if (net_wake_pkt_debug > 0) { net_port_info_log_npi("defer encapsulated ESP matching", &npi); } return; } } if (pkt_data_len < sizeof(struct udphdr)) { pkt_data_len = 0; } else { pkt_data_len -= sizeof(struct udphdr); } break; } case IPPROTO_ESP: { /* * Let the ESP layer handle the wake packet */ if_ports_used_stats.ifpu_esp_wake_pkt += 1; npi.npi_flags |= NPIF_ESP; if (net_wake_pkt_debug > 0) { net_port_info_log_npi("defer ESP matching", &npi); } return; } default: if_ports_used_stats.ifpu_bad_proto_wake_pkt += 1; os_log(OS_LOG_DEFAULT, "%s: unexpected IPv6 protocol %u from %s", __func__, ip6_hdr.ip6_nxt, IF_XNAME(ifp)); goto failed; } } else { if_ports_used_stats.ifpu_bad_family_wake_pkt += 1; os_log(OS_LOG_DEFAULT, "%s: unexpected protocol family %d from %s", __func__, proto_family, IF_XNAME(ifp)); goto failed; } if (ifp == NULL) { goto failed; } found = net_port_info_find_match(&npi); if (found) { if_notify_wake_packet(ifp, &npi, pkt_total_len, pkt_data_len, pkt_control_flags); } else { if_notify_unattributed_wake_mbuf(ifp, m, &npi, pkt_total_len, pkt_data_len, pkt_control_flags, pkt_proto); } return; failed: if_notify_unattributed_wake_mbuf(ifp, m, &npi, pkt_total_len, pkt_data_len, pkt_control_flags, pkt_proto); } #if SKYWALK static void if_notify_unattributed_wake_pkt(struct ifnet *ifp, struct __kern_packet *pkt, struct net_port_info *npi, uint32_t pkt_total_len, uint32_t pkt_data_len, uint16_t pkt_control_flags, uint16_t proto) { struct kev_msg ev_msg = {}; LCK_MTX_ASSERT(&net_port_entry_head_lock, LCK_MTX_ASSERT_NOTOWNED); lck_mtx_lock(&net_port_entry_head_lock); if (has_notified_unattributed_wake) { lck_mtx_unlock(&net_port_entry_head_lock); if_ports_used_stats.ifpu_dup_unattributed_wake_event += 1; if (__improbable(net_wake_pkt_debug > 0)) { net_port_info_log_npi("already notified unattributed wake packet", npi); } return; } has_notified_unattributed_wake = true; lck_mtx_unlock(&net_port_entry_head_lock); if_ports_used_stats.ifpu_unattributed_wake_event += 1; if (ifp == NULL) { os_log(OS_LOG_DEFAULT, "%s: receive interface is NULL", __func__); if_ports_used_stats.ifpu_unattributed_null_recvif += 1; } ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_POWER_SUBCLASS; ev_msg.event_code = KEV_POWER_UNATTRIBUTED_WAKE; struct net_port_info_una_wake_event event_data = {}; uuid_copy(event_data.una_wake_uuid, current_wakeuuid); event_data.una_wake_pkt_if_index = ifp != NULL ? ifp->if_index : 0; event_data.una_wake_pkt_flags = npi->npi_flags; uint16_t offset = kern_packet_get_network_header_offset(SK_PKT2PH(pkt)); event_data.una_wake_ptk_len = pkt->pkt_length - offset > NPI_MAX_UNA_WAKE_PKT_LEN ? NPI_MAX_UNA_WAKE_PKT_LEN : (u_int16_t) pkt->pkt_length - offset; kern_packet_copy_bytes(SK_PKT2PH(pkt), offset, event_data.una_wake_ptk_len, event_data.una_wake_pkt); event_data.una_wake_pkt_local_port = npi->npi_local_port; event_data.una_wake_pkt_foreign_port = npi->npi_foreign_port; event_data.una_wake_pkt_local_addr_ = npi->npi_local_addr_; event_data.una_wake_pkt_foreign_addr_ = npi->npi_foreign_addr_; if (ifp != NULL) { strlcpy(event_data.una_wake_pkt_ifname, IF_XNAME(ifp), sizeof(event_data.una_wake_pkt_ifname)); } event_data.una_wake_pkt_total_len = pkt_total_len; event_data.una_wake_pkt_data_len = pkt_data_len; event_data.una_wake_pkt_control_flags = pkt_control_flags; event_data.una_wake_pkt_proto = proto; 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) { uuid_string_t wake_uuid_str; uuid_unparse(event_data.una_wake_uuid, wake_uuid_str); os_log_error(OS_LOG_DEFAULT, "%s: kev_post_msg() failed with error %d for wake uuid %s", __func__, result, wake_uuid_str); if_ports_used_stats.ifpu_unattributed_wake_event_error += 1; } #if (DEBUG || DEVELOPMENT) net_port_info_log_una_wake_event("unattributed wake packet event", &event_data); #endif /* (DEBUG || DEVELOPMENT) */ } void if_ports_used_match_pkt(struct ifnet *ifp, struct __kern_packet *pkt) { struct net_port_info npi = {}; bool found = false; uint32_t pkt_total_len = 0; uint32_t pkt_data_len = 0; uint16_t pkt_control_flags = 0; uint16_t pkt_proto = 0; if ((pkt->pkt_pflags & PKT_F_WAKE_PKT) == 0) { if_ports_used_stats.ifpu_match_wake_pkt_no_flag += 1; os_log_error(OS_LOG_DEFAULT, "%s: called PKT_F_WAKE_PKT not set from %s", __func__, IF_XNAME(ifp)); return; } if_ports_used_stats.ifpu_ch_match_wake_pkt += 1; npi.npi_flags |= NPIF_CHANNEL; /* For logging */ pkt_total_len = pkt->pkt_flow_ip_hlen + pkt->pkt_flow_tcp_hlen + pkt->pkt_flow_ulen; pkt_data_len = pkt->pkt_flow_ulen; if (ifp != NULL) { npi.npi_if_index = ifp->if_index; if (IFNET_IS_COMPANION_LINK(ifp)) { npi.npi_flags |= NPIF_COMPLINK; } if_set_wake_physical_interface(ifp); } switch (pkt->pkt_flow_ip_ver) { case IPVERSION: if_ports_used_stats.ifpu_ipv4_wake_pkt += 1; npi.npi_flags |= NPIF_IPV4; npi.npi_local_addr_in = pkt->pkt_flow_ipv4_dst; npi.npi_foreign_addr_in = pkt->pkt_flow_ipv4_src; break; case IPV6_VERSION: if_ports_used_stats.ifpu_ipv6_wake_pkt += 1; npi.npi_flags |= NPIF_IPV6; memcpy(&npi.npi_local_addr_in6, &pkt->pkt_flow_ipv6_dst, sizeof(struct in6_addr)); memcpy(&npi.npi_foreign_addr_in6, &pkt->pkt_flow_ipv6_src, sizeof(struct in6_addr)); break; default: if_ports_used_stats.ifpu_bad_family_wake_pkt += 1; os_log(OS_LOG_DEFAULT, "%s: unexpected protocol family %u from %s", __func__, pkt->pkt_flow_ip_ver, IF_XNAME(ifp)); goto failed; } pkt_proto = pkt->pkt_flow_ip_ver; /* * Check if this is a fragment that is not the first fragment */ if (pkt->pkt_flow_ip_is_frag && !pkt->pkt_flow_ip_is_first_frag) { os_log(OS_LOG_DEFAULT, "%s: unexpected wake fragment from %s", __func__, IF_XNAME(ifp)); npi.npi_flags |= NPIF_FRAG; if_ports_used_stats.ifpu_frag_wake_pkt += 1; } switch (pkt->pkt_flow_ip_proto) { case IPPROTO_TCP: { if_ports_used_stats.ifpu_tcp_wake_pkt += 1; npi.npi_flags |= NPIF_TCP; /* * Cannot attribute a fragment that is not the first fragment as it * not have the TCP header */ if (npi.npi_flags & NPIF_FRAG) { goto failed; } struct tcphdr *tcp = (struct tcphdr *)pkt->pkt_flow_tcp_hdr; if (tcp == NULL) { os_log(OS_LOG_DEFAULT, "%s: pkt with unassigned TCP header from %s", __func__, IF_XNAME(ifp)); if_ports_used_stats.ifpu_incomplete_tcp_hdr_pkt += 1; goto failed; } npi.npi_local_port = tcp->th_dport; npi.npi_foreign_port = tcp->th_sport; pkt_control_flags = tcp->th_flags; break; } case IPPROTO_UDP: { if_ports_used_stats.ifpu_udp_wake_pkt += 1; npi.npi_flags |= NPIF_UDP; /* * Cannot attribute a fragment that is not the first fragment as it * not have the UDP header */ if (npi.npi_flags & NPIF_FRAG) { goto failed; } struct udphdr *uh = (struct udphdr *)pkt->pkt_flow_udp_hdr; if (uh == NULL) { os_log(OS_LOG_DEFAULT, "%s: pkt with unassigned UDP header from %s", __func__, IF_XNAME(ifp)); if_ports_used_stats.ifpu_incomplete_udp_hdr_pkt += 1; goto failed; } npi.npi_local_port = uh->uh_dport; npi.npi_foreign_port = uh->uh_sport; /* * Defer matching of UDP NAT traversal to ip_input * (assumes IKE uses sockets) */ if (ntohs(npi.npi_local_port) == PORT_ISAKMP_NATT || ntohs(npi.npi_foreign_port) == PORT_ISAKMP_NATT) { if_ports_used_stats.ifpu_deferred_isakmp_natt_wake_pkt += 1; if (net_wake_pkt_debug > 0) { net_port_info_log_npi("defer ISAKMP_NATT matching", &npi); } return; } break; } case IPPROTO_ESP: { /* * Let the ESP layer handle the wake packet */ if_ports_used_stats.ifpu_esp_wake_pkt += 1; npi.npi_flags |= NPIF_ESP; if (net_wake_pkt_debug > 0) { net_port_info_log_npi("defer ESP matching", &npi); } return; } default: if_ports_used_stats.ifpu_bad_proto_wake_pkt += 1; os_log(OS_LOG_DEFAULT, "%s: unexpected IP protocol %u from %s", __func__, pkt->pkt_flow_ip_proto, IF_XNAME(ifp)); goto failed; } if (ifp == NULL) { goto failed; } found = net_port_info_find_match(&npi); if (found) { if_notify_wake_packet(ifp, &npi, pkt_total_len, pkt_data_len, pkt_control_flags); } else { if_notify_unattributed_wake_pkt(ifp, pkt, &npi, pkt_total_len, pkt_data_len, pkt_control_flags, pkt_proto); } return; failed: if_notify_unattributed_wake_pkt(ifp, pkt, &npi, pkt_total_len, pkt_data_len, pkt_control_flags, pkt_proto); } #endif /* SKYWALK */ int sysctl_last_attributed_wake_event SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg1, arg2) size_t len = sizeof(struct net_port_info_wake_event); if (req->oldptr != 0) { len = MIN(req->oldlen, len); } lck_mtx_lock(&net_port_entry_head_lock); int error = SYSCTL_OUT(req, &last_attributed_wake_event, len); lck_mtx_unlock(&net_port_entry_head_lock); return error; }