/* * Copyright (c) 2004-2022 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 "kpi_interface.h" #include #include /* for definition of NULL */ #include /* for panic */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #endif #include #include #include #include "net/net_str_id.h" #if CONFIG_MACF #include #include #endif #if SKYWALK #include #include #endif /* SKYWALK */ extern uint64_t if_creation_generation_count; #undef ifnet_allocate errno_t ifnet_allocate(const struct ifnet_init_params *init, ifnet_t *ifp); static errno_t ifnet_allocate_common(const struct ifnet_init_params *init, ifnet_t *ifp, bool is_internal); #define TOUCHLASTCHANGE(__if_lastchange) { \ (__if_lastchange)->tv_sec = (time_t)net_uptime(); \ (__if_lastchange)->tv_usec = 0; \ } static errno_t ifnet_defrouter_llreachinfo(ifnet_t, sa_family_t, struct ifnet_llreach_info *); static void ifnet_kpi_free(ifnet_t); static errno_t ifnet_list_get_common(ifnet_family_t, boolean_t, ifnet_t **, u_int32_t *); static errno_t ifnet_set_lladdr_internal(ifnet_t, const void *, size_t, u_char, int); static errno_t ifnet_awdl_check_eflags(ifnet_t, u_int32_t *, u_int32_t *); /* * Temporary work around until we have real reference counting * * We keep the bits about calling dlil_if_release (which should be * called recycle) transparent by calling it from our if_free function * pointer. We have to keep the client's original detach function * somewhere so we can call it. */ static void ifnet_kpi_free(ifnet_t ifp) { if ((ifp->if_refflags & IFRF_EMBRYONIC) == 0) { ifnet_detached_func detach_func; detach_func = ifp->if_detach; if (detach_func != NULL) { (*detach_func)(ifp); } } ifnet_dispose(ifp); } errno_t ifnet_allocate_common(const struct ifnet_init_params *init, ifnet_t *ifp, bool is_internal) { struct ifnet_init_eparams einit; bzero(&einit, sizeof(einit)); einit.ver = IFNET_INIT_CURRENT_VERSION; einit.len = sizeof(einit); einit.flags = IFNET_INIT_LEGACY | IFNET_INIT_NX_NOAUTO; if (!is_internal) { einit.flags |= IFNET_INIT_ALLOC_KPI; } einit.uniqueid = init->uniqueid; einit.uniqueid_len = init->uniqueid_len; einit.name = init->name; einit.unit = init->unit; einit.family = init->family; einit.type = init->type; einit.output = init->output; einit.demux = init->demux; einit.add_proto = init->add_proto; einit.del_proto = init->del_proto; einit.check_multi = init->check_multi; einit.framer = init->framer; einit.softc = init->softc; einit.ioctl = init->ioctl; einit.set_bpf_tap = init->set_bpf_tap; einit.detach = init->detach; einit.event = init->event; einit.broadcast_addr = init->broadcast_addr; einit.broadcast_len = init->broadcast_len; return ifnet_allocate_extended(&einit, ifp); } errno_t ifnet_allocate_internal(const struct ifnet_init_params *init, ifnet_t *ifp) { return ifnet_allocate_common(init, ifp, true); } errno_t ifnet_allocate(const struct ifnet_init_params *init, ifnet_t *ifp) { return ifnet_allocate_common(init, ifp, false); } static void ifnet_set_broadcast_addr(ifnet_t ifp, const void * broadcast_addr, u_int32_t broadcast_len) { if (broadcast_len == 0 || broadcast_addr == NULL) { /* no broadcast address */ bzero(&ifp->if_broadcast, sizeof(ifp->if_broadcast)); } else if (broadcast_len > sizeof(ifp->if_broadcast.u.buffer)) { ifp->if_broadcast.u.ptr = (u_char *)kalloc_data(broadcast_len, Z_WAITOK | Z_NOFAIL); bcopy(broadcast_addr, ifp->if_broadcast.u.ptr, broadcast_len); } else { bcopy(broadcast_addr, ifp->if_broadcast.u.buffer, broadcast_len); } ifp->if_broadcast.length = broadcast_len; } errno_t ifnet_allocate_extended(const struct ifnet_init_eparams *einit0, ifnet_t *interface) { #if SKYWALK ifnet_start_func ostart = NULL; #endif /* SKYWALK */ struct ifnet_init_eparams einit; struct ifnet *ifp = NULL; char if_xname[IFXNAMSIZ] = {0}; int error; einit = *einit0; if (einit.ver != IFNET_INIT_CURRENT_VERSION || einit.len < sizeof(einit)) { return EINVAL; } if (einit.family == 0 || einit.name == NULL || strlen(einit.name) >= IFNAMSIZ || (einit.type & 0xFFFFFF00) != 0 || einit.type == 0) { return EINVAL; } #if SKYWALK /* headroom must be a multiple of 8 bytes */ if ((einit.tx_headroom & 0x7) != 0) { return EINVAL; } if ((einit.flags & IFNET_INIT_SKYWALK_NATIVE) == 0) { /* * Currently Interface advisory reporting is supported only * for skywalk interface. */ if ((einit.flags & IFNET_INIT_IF_ADV) != 0) { return EINVAL; } } #endif /* SKYWALK */ if (einit.flags & IFNET_INIT_LEGACY) { #if SKYWALK if (einit.flags & IFNET_INIT_SKYWALK_NATIVE) { return EINVAL; } #endif /* SKYWALK */ if (einit.output == NULL || (einit.flags & IFNET_INIT_INPUT_POLL)) { return EINVAL; } einit.pre_enqueue = NULL; einit.start = NULL; einit.output_ctl = NULL; einit.output_sched_model = IFNET_SCHED_MODEL_NORMAL; einit.input_poll = NULL; einit.input_ctl = NULL; } else { #if SKYWALK /* * For native Skywalk drivers, steer all start requests * to ifp_if_start() until the netif device adapter is * fully activated, at which point we will point it to * nx_netif_doorbell(). */ if (einit.flags & IFNET_INIT_SKYWALK_NATIVE) { if (einit.start != NULL) { return EINVAL; } /* override output start callback */ ostart = einit.start = ifp_if_start; } else { ostart = einit.start; } #endif /* SKYWALK */ if (einit.start == NULL) { return EINVAL; } einit.output = NULL; if (einit.output_sched_model >= IFNET_SCHED_MODEL_MAX) { return EINVAL; } if (einit.flags & IFNET_INIT_INPUT_POLL) { if (einit.input_poll == NULL || einit.input_ctl == NULL) { return EINVAL; } } else { einit.input_poll = NULL; einit.input_ctl = NULL; } } if (einit.type > UCHAR_MAX) { return EINVAL; } if (einit.unit > SHRT_MAX) { return EINVAL; } /* Initialize external name (name + unit) */ (void) snprintf(if_xname, sizeof(if_xname), "%s%d", einit.name, einit.unit); if (einit.uniqueid == NULL) { einit.uniqueid = if_xname; einit.uniqueid_len = (uint32_t)strlen(if_xname); } error = dlil_if_acquire(einit.family, einit.uniqueid, einit.uniqueid_len, if_xname, &ifp); if (error == 0) { uint64_t br; /* * Cast ifp->if_name as non const. dlil_if_acquire sets it up * to point to storage of at least IFNAMSIZ bytes. It is safe * to write to this. */ strlcpy(__DECONST(char *, ifp->if_name), einit.name, IFNAMSIZ); ifp->if_type = (u_char)einit.type; ifp->if_family = einit.family; ifp->if_subfamily = einit.subfamily; ifp->if_unit = (short)einit.unit; ifp->if_output = einit.output; ifp->if_pre_enqueue = einit.pre_enqueue; ifp->if_start = einit.start; ifp->if_output_ctl = einit.output_ctl; ifp->if_output_sched_model = einit.output_sched_model; ifp->if_output_bw.eff_bw = einit.output_bw; ifp->if_output_bw.max_bw = einit.output_bw_max; ifp->if_output_lt.eff_lt = einit.output_lt; ifp->if_output_lt.max_lt = einit.output_lt_max; ifp->if_input_poll = einit.input_poll; ifp->if_input_ctl = einit.input_ctl; ifp->if_input_bw.eff_bw = einit.input_bw; ifp->if_input_bw.max_bw = einit.input_bw_max; ifp->if_input_lt.eff_lt = einit.input_lt; ifp->if_input_lt.max_lt = einit.input_lt_max; ifp->if_demux = einit.demux; ifp->if_add_proto = einit.add_proto; ifp->if_del_proto = einit.del_proto; ifp->if_check_multi = einit.check_multi; ifp->if_framer_legacy = einit.framer; ifp->if_framer = einit.framer_extended; ifp->if_softc = einit.softc; ifp->if_ioctl = einit.ioctl; ifp->if_set_bpf_tap = einit.set_bpf_tap; ifp->if_free = (einit.free != NULL) ? einit.free : ifnet_kpi_free; ifp->if_event = einit.event; ifp->if_detach = einit.detach; /* Initialize Network ID */ ifp->network_id_len = 0; bzero(&ifp->network_id, sizeof(ifp->network_id)); /* Initialize external name (name + unit) */ snprintf(__DECONST(char *, ifp->if_xname), IFXNAMSIZ, "%s", if_xname); /* * On embedded, framer() is already in the extended form; * we simply use it as is, unless the caller specifies * framer_extended() which will then override it. * * On non-embedded, framer() has long been exposed as part * of the public KPI, and therefore its signature must * remain the same (without the pre- and postpend length * parameters.) We special case ether_frameout, such that * it gets mapped to its extended variant. All other cases * utilize the stub routine which will simply return zeroes * for those new parameters. * * Internally, DLIL will only use the extended callback * variant which is represented by if_framer. */ #if !XNU_TARGET_OS_OSX if (ifp->if_framer == NULL && ifp->if_framer_legacy != NULL) { ifp->if_framer = ifp->if_framer_legacy; } #else /* XNU_TARGET_OS_OSX */ if (ifp->if_framer == NULL && ifp->if_framer_legacy != NULL) { if (ifp->if_framer_legacy == ether_frameout) { ifp->if_framer = ether_frameout_extended; } else { ifp->if_framer = ifnet_framer_stub; } } #endif /* XNU_TARGET_OS_OSX */ if (ifp->if_output_bw.eff_bw > ifp->if_output_bw.max_bw) { ifp->if_output_bw.max_bw = ifp->if_output_bw.eff_bw; } else if (ifp->if_output_bw.eff_bw == 0) { ifp->if_output_bw.eff_bw = ifp->if_output_bw.max_bw; } if (ifp->if_input_bw.eff_bw > ifp->if_input_bw.max_bw) { ifp->if_input_bw.max_bw = ifp->if_input_bw.eff_bw; } else if (ifp->if_input_bw.eff_bw == 0) { ifp->if_input_bw.eff_bw = ifp->if_input_bw.max_bw; } if (ifp->if_output_bw.max_bw == 0) { ifp->if_output_bw = ifp->if_input_bw; } else if (ifp->if_input_bw.max_bw == 0) { ifp->if_input_bw = ifp->if_output_bw; } /* Pin if_baudrate to 32 bits */ br = MAX(ifp->if_output_bw.max_bw, ifp->if_input_bw.max_bw); if (br != 0) { ifp->if_baudrate = (br > UINT32_MAX) ? UINT32_MAX : (uint32_t)br; } if (ifp->if_output_lt.eff_lt > ifp->if_output_lt.max_lt) { ifp->if_output_lt.max_lt = ifp->if_output_lt.eff_lt; } else if (ifp->if_output_lt.eff_lt == 0) { ifp->if_output_lt.eff_lt = ifp->if_output_lt.max_lt; } if (ifp->if_input_lt.eff_lt > ifp->if_input_lt.max_lt) { ifp->if_input_lt.max_lt = ifp->if_input_lt.eff_lt; } else if (ifp->if_input_lt.eff_lt == 0) { ifp->if_input_lt.eff_lt = ifp->if_input_lt.max_lt; } if (ifp->if_output_lt.max_lt == 0) { ifp->if_output_lt = ifp->if_input_lt; } else if (ifp->if_input_lt.max_lt == 0) { ifp->if_input_lt = ifp->if_output_lt; } if (ifp->if_ioctl == NULL) { ifp->if_ioctl = ifp_if_ioctl; } if_clear_eflags(ifp, -1); if (ifp->if_start != NULL) { if_set_eflags(ifp, IFEF_TXSTART); if (ifp->if_pre_enqueue == NULL) { ifp->if_pre_enqueue = ifnet_enqueue; } ifp->if_output = ifp->if_pre_enqueue; } if (ifp->if_input_poll != NULL) { if_set_eflags(ifp, IFEF_RXPOLL); } ifp->if_output_dlil = dlil_output_handler; ifp->if_input_dlil = dlil_input_handler; VERIFY(!(einit.flags & IFNET_INIT_LEGACY) || (ifp->if_pre_enqueue == NULL && ifp->if_start == NULL && ifp->if_output_ctl == NULL && ifp->if_input_poll == NULL && ifp->if_input_ctl == NULL)); VERIFY(!(einit.flags & IFNET_INIT_INPUT_POLL) || (ifp->if_input_poll != NULL && ifp->if_input_ctl != NULL)); ifnet_set_broadcast_addr(ifp, einit.broadcast_addr, einit.broadcast_len); if_clear_xflags(ifp, -1); #if SKYWALK ifp->if_tx_headroom = 0; ifp->if_tx_trailer = 0; ifp->if_rx_mit_ival = 0; ifp->if_save_start = ostart; if (einit.flags & IFNET_INIT_SKYWALK_NATIVE) { VERIFY(ifp->if_eflags & IFEF_TXSTART); VERIFY(!(einit.flags & IFNET_INIT_LEGACY)); if_set_eflags(ifp, IFEF_SKYWALK_NATIVE); ifp->if_tx_headroom = einit.tx_headroom; ifp->if_tx_trailer = einit.tx_trailer; ifp->if_rx_mit_ival = einit.rx_mit_ival; /* * For native Skywalk drivers, make sure packets * emitted by the BSD stack get dropped until the * interface is in service. When the netif host * adapter is fully activated, we'll point it to * nx_netif_output(). */ ifp->if_output = ifp_if_output; /* * Override driver-supplied parameters * and force IFEF_ENQUEUE_MULTI? */ if (sk_netif_native_txmodel == NETIF_NATIVE_TXMODEL_ENQUEUE_MULTI) { einit.start_delay_qlen = sk_tx_delay_qlen; einit.start_delay_timeout = sk_tx_delay_timeout; } /* netif comes with native interfaces */ VERIFY((ifp->if_xflags & IFXF_LEGACY) == 0); } else if (!ifnet_needs_compat(ifp)) { /* * If we're told not to plumb in netif compat * for this interface, set IFXF_NX_NOAUTO to * prevent DLIL from auto-attaching the nexus. */ einit.flags |= IFNET_INIT_NX_NOAUTO; /* legacy (non-netif) interface */ if_set_xflags(ifp, IFXF_LEGACY); } ifp->if_save_output = ifp->if_output; if ((einit.flags & IFNET_INIT_NX_NOAUTO) != 0) { if_set_xflags(ifp, IFXF_NX_NOAUTO); } if ((einit.flags & IFNET_INIT_IF_ADV) != 0) { if_set_eflags(ifp, IFEF_ADV_REPORT); } #else /* !SKYWALK */ /* legacy interface */ if_set_xflags(ifp, IFXF_LEGACY); #endif /* !SKYWALK */ if ((ifp->if_snd = ifclassq_alloc()) == NULL) { panic_plain("%s: ifp=%p couldn't allocate class queues", __func__, ifp); /* NOTREACHED */ } /* * output target queue delay is specified in millisecond * convert it to nanoseconds */ IFCQ_TARGET_QDELAY(ifp->if_snd) = einit.output_target_qdelay * 1000 * 1000; IFCQ_MAXLEN(ifp->if_snd) = einit.sndq_maxlen; ifnet_enqueue_multi_setup(ifp, einit.start_delay_qlen, einit.start_delay_timeout); IFCQ_PKT_DROP_LIMIT(ifp->if_snd) = IFCQ_DEFAULT_PKT_DROP_LIMIT; /* * Set embryonic flag; this will be cleared * later when it is fully attached. */ ifp->if_refflags = IFRF_EMBRYONIC; /* * Count the newly allocated ifnet */ OSIncrementAtomic64(&net_api_stats.nas_ifnet_alloc_count); INC_ATOMIC_INT64_LIM(net_api_stats.nas_ifnet_alloc_total); if ((einit.flags & IFNET_INIT_ALLOC_KPI) != 0) { if_set_xflags(ifp, IFXF_ALLOC_KPI); } else { OSIncrementAtomic64( &net_api_stats.nas_ifnet_alloc_os_count); INC_ATOMIC_INT64_LIM( net_api_stats.nas_ifnet_alloc_os_total); } if (ifp->if_subfamily == IFNET_SUBFAMILY_MANAGEMENT) { if_set_xflags(ifp, IFXF_MANAGEMENT); if_management_interface_check_needed = true; } /* * Increment the generation count on interface creation */ ifp->if_creation_generation_id = os_atomic_inc(&if_creation_generation_count, relaxed); *interface = ifp; } return error; } errno_t ifnet_reference(ifnet_t ifp) { return dlil_if_ref(ifp); } void ifnet_dispose(ifnet_t ifp) { dlil_if_release(ifp); } errno_t ifnet_release(ifnet_t ifp) { return dlil_if_free(ifp); } errno_t ifnet_interface_family_find(const char *module_string, ifnet_family_t *family_id) { if (module_string == NULL || family_id == NULL) { return EINVAL; } return net_str_id_find_internal(module_string, family_id, NSI_IF_FAM_ID, 1); } void * ifnet_softc(ifnet_t interface) { return (interface == NULL) ? NULL : interface->if_softc; } const char * ifnet_name(ifnet_t interface) { return (interface == NULL) ? NULL : interface->if_name; } ifnet_family_t ifnet_family(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_family; } ifnet_subfamily_t ifnet_subfamily(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_subfamily; } u_int32_t ifnet_unit(ifnet_t interface) { return (interface == NULL) ? (u_int32_t)0xffffffff : (u_int32_t)interface->if_unit; } u_int32_t ifnet_index(ifnet_t interface) { return (interface == NULL) ? (u_int32_t)0xffffffff : interface->if_index; } errno_t ifnet_set_flags(ifnet_t interface, u_int16_t new_flags, u_int16_t mask) { bool set_IFF_UP; bool change_IFF_UP; uint16_t old_flags; if (interface == NULL) { return EINVAL; } set_IFF_UP = (new_flags & IFF_UP) != 0; change_IFF_UP = (mask & IFF_UP) != 0; #if SKYWALK if (set_IFF_UP && change_IFF_UP) { /* * When a native skywalk interface is marked IFF_UP, ensure * the flowswitch is attached. */ ifnet_attach_native_flowswitch(interface); } #endif /* SKYWALK */ ifnet_lock_exclusive(interface); /* If we are modifying the up/down state, call if_updown */ if (change_IFF_UP) { if_updown(interface, set_IFF_UP); } old_flags = interface->if_flags; interface->if_flags = (new_flags & mask) | (interface->if_flags & ~mask); /* If we are modifying the multicast flag, set/unset the silent flag */ if ((old_flags & IFF_MULTICAST) != (interface->if_flags & IFF_MULTICAST)) { #if INET if (IGMP_IFINFO(interface) != NULL) { igmp_initsilent(interface, IGMP_IFINFO(interface)); } #endif /* INET */ if (MLD_IFINFO(interface) != NULL) { mld6_initsilent(interface, MLD_IFINFO(interface)); } } ifnet_lock_done(interface); return 0; } u_int16_t ifnet_flags(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_flags; } /* * This routine ensures the following: * * If IFEF_AWDL is set by the caller, also set the rest of flags as * defined in IFEF_AWDL_MASK. * * If IFEF_AWDL has been set on the interface and the caller attempts * to clear one or more of the associated flags in IFEF_AWDL_MASK, * return failure. * * If IFEF_AWDL_RESTRICTED is set by the caller, make sure IFEF_AWDL is set * on the interface. * * All other flags not associated with AWDL are not affected. * * See for current definition of IFEF_AWDL_MASK. */ static errno_t ifnet_awdl_check_eflags(ifnet_t ifp, u_int32_t *new_eflags, u_int32_t *mask) { u_int32_t eflags; ifnet_lock_assert(ifp, IFNET_LCK_ASSERT_EXCLUSIVE); eflags = (*new_eflags & *mask) | (ifp->if_eflags & ~(*mask)); if (ifp->if_eflags & IFEF_AWDL) { if (eflags & IFEF_AWDL) { if ((eflags & IFEF_AWDL_MASK) != IFEF_AWDL_MASK) { return EINVAL; } } else { *new_eflags &= ~IFEF_AWDL_MASK; *mask |= IFEF_AWDL_MASK; } } else if (eflags & IFEF_AWDL) { *new_eflags |= IFEF_AWDL_MASK; *mask |= IFEF_AWDL_MASK; } else if (eflags & IFEF_AWDL_RESTRICTED && !(ifp->if_eflags & IFEF_AWDL)) { return EINVAL; } return 0; } errno_t ifnet_set_eflags(ifnet_t interface, u_int32_t new_flags, u_int32_t mask) { uint32_t oeflags; struct kev_msg ev_msg; struct net_event_data ev_data; if (interface == NULL) { return EINVAL; } bzero(&ev_msg, sizeof(ev_msg)); ifnet_lock_exclusive(interface); /* * Sanity checks for IFEF_AWDL and its related flags. */ if (ifnet_awdl_check_eflags(interface, &new_flags, &mask) != 0) { ifnet_lock_done(interface); return EINVAL; } /* * Currently Interface advisory reporting is supported only for * skywalk interface. */ if ((((new_flags & mask) & IFEF_ADV_REPORT) != 0) && ((interface->if_eflags & IFEF_SKYWALK_NATIVE) == 0)) { ifnet_lock_done(interface); return EINVAL; } oeflags = interface->if_eflags; if_clear_eflags(interface, mask); if (new_flags != 0) { if_set_eflags(interface, (new_flags & mask)); } ifnet_lock_done(interface); if (interface->if_eflags & IFEF_AWDL_RESTRICTED && !(oeflags & IFEF_AWDL_RESTRICTED)) { ev_msg.event_code = KEV_DL_AWDL_RESTRICTED; /* * The interface is now restricted to applications that have * the entitlement. * The check for the entitlement will be done in the data * path, so we don't have to do anything here. */ } else if (oeflags & IFEF_AWDL_RESTRICTED && !(interface->if_eflags & IFEF_AWDL_RESTRICTED)) { ev_msg.event_code = KEV_DL_AWDL_UNRESTRICTED; } /* * Notify configd so that it has a chance to perform better * reachability detection. */ if (ev_msg.event_code) { bzero(&ev_data, sizeof(ev_data)); ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_DL_SUBCLASS; strlcpy(ev_data.if_name, interface->if_name, IFNAMSIZ); ev_data.if_family = interface->if_family; ev_data.if_unit = interface->if_unit; ev_msg.dv[0].data_length = sizeof(struct net_event_data); ev_msg.dv[0].data_ptr = &ev_data; ev_msg.dv[1].data_length = 0; dlil_post_complete_msg(interface, &ev_msg); } return 0; } u_int32_t ifnet_eflags(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_eflags; } errno_t ifnet_set_idle_flags_locked(ifnet_t ifp, u_int32_t new_flags, u_int32_t mask) { if (ifp == NULL) { return EINVAL; } ifnet_lock_assert(ifp, IFNET_LCK_ASSERT_EXCLUSIVE); /* * If this is called prior to ifnet attach, the actual work will * be done at attach time. Otherwise, if it is called after * ifnet detach, then it is a no-op. */ if (!ifnet_is_attached(ifp, 0)) { ifp->if_idle_new_flags = new_flags; ifp->if_idle_new_flags_mask = mask; return 0; } else { ifp->if_idle_new_flags = ifp->if_idle_new_flags_mask = 0; } ifp->if_idle_flags = (new_flags & mask) | (ifp->if_idle_flags & ~mask); return 0; } errno_t ifnet_set_idle_flags(ifnet_t ifp, u_int32_t new_flags, u_int32_t mask) { errno_t err; ifnet_lock_exclusive(ifp); err = ifnet_set_idle_flags_locked(ifp, new_flags, mask); ifnet_lock_done(ifp); return err; } u_int32_t ifnet_idle_flags(ifnet_t ifp) { return (ifp == NULL) ? 0 : ifp->if_idle_flags; } errno_t ifnet_set_link_quality(ifnet_t ifp, int quality) { errno_t err = 0; if (ifp == NULL || quality < IFNET_LQM_MIN || quality > IFNET_LQM_MAX) { err = EINVAL; goto done; } if (!ifnet_is_attached(ifp, 0)) { err = ENXIO; goto done; } if_lqm_update(ifp, quality, 0); done: return err; } int ifnet_link_quality(ifnet_t ifp) { int lqm; if (ifp == NULL) { return IFNET_LQM_THRESH_OFF; } ifnet_lock_shared(ifp); lqm = ifp->if_interface_state.lqm_state; ifnet_lock_done(ifp); return lqm; } errno_t ifnet_set_interface_state(ifnet_t ifp, struct if_interface_state *if_interface_state) { errno_t err = 0; if (ifp == NULL || if_interface_state == NULL) { err = EINVAL; goto done; } if (!ifnet_is_attached(ifp, 0)) { err = ENXIO; goto done; } if_state_update(ifp, if_interface_state); done: return err; } errno_t ifnet_get_interface_state(ifnet_t ifp, struct if_interface_state *if_interface_state) { errno_t err = 0; if (ifp == NULL || if_interface_state == NULL) { err = EINVAL; goto done; } if (!ifnet_is_attached(ifp, 0)) { err = ENXIO; goto done; } if_get_state(ifp, if_interface_state); done: return err; } static errno_t ifnet_defrouter_llreachinfo(ifnet_t ifp, sa_family_t af, struct ifnet_llreach_info *iflri) { if (ifp == NULL || iflri == NULL) { return EINVAL; } VERIFY(af == AF_INET || af == AF_INET6); return ifnet_llreach_get_defrouter(ifp, af, iflri); } errno_t ifnet_inet_defrouter_llreachinfo(ifnet_t ifp, struct ifnet_llreach_info *iflri) { return ifnet_defrouter_llreachinfo(ifp, AF_INET, iflri); } errno_t ifnet_inet6_defrouter_llreachinfo(ifnet_t ifp, struct ifnet_llreach_info *iflri) { return ifnet_defrouter_llreachinfo(ifp, AF_INET6, iflri); } errno_t ifnet_set_capabilities_supported(ifnet_t ifp, u_int32_t new_caps, u_int32_t mask) { errno_t error = 0; int tmp; if (ifp == NULL) { return EINVAL; } ifnet_lock_exclusive(ifp); tmp = (new_caps & mask) | (ifp->if_capabilities & ~mask); if ((tmp & ~IFCAP_VALID)) { error = EINVAL; } else { ifp->if_capabilities = tmp; } ifnet_lock_done(ifp); return error; } u_int32_t ifnet_capabilities_supported(ifnet_t ifp) { return (ifp == NULL) ? 0 : ifp->if_capabilities; } errno_t ifnet_set_capabilities_enabled(ifnet_t ifp, u_int32_t new_caps, u_int32_t mask) { errno_t error = 0; int tmp; struct kev_msg ev_msg; struct net_event_data ev_data; if (ifp == NULL) { return EINVAL; } ifnet_lock_exclusive(ifp); tmp = (new_caps & mask) | (ifp->if_capenable & ~mask); if ((tmp & ~IFCAP_VALID) || (tmp & ~ifp->if_capabilities)) { error = EINVAL; } else { ifp->if_capenable = tmp; } ifnet_lock_done(ifp); /* Notify application of the change */ bzero(&ev_data, sizeof(struct net_event_data)); bzero(&ev_msg, sizeof(struct kev_msg)); ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_DL_SUBCLASS; ev_msg.event_code = KEV_DL_IFCAP_CHANGED; strlcpy(&ev_data.if_name[0], ifp->if_name, IFNAMSIZ); ev_data.if_family = ifp->if_family; ev_data.if_unit = (u_int32_t)ifp->if_unit; ev_msg.dv[0].data_length = sizeof(struct net_event_data); ev_msg.dv[0].data_ptr = &ev_data; ev_msg.dv[1].data_length = 0; dlil_post_complete_msg(ifp, &ev_msg); return error; } u_int32_t ifnet_capabilities_enabled(ifnet_t ifp) { return (ifp == NULL) ? 0 : ifp->if_capenable; } static const ifnet_offload_t offload_mask = (IFNET_CSUM_IP | IFNET_CSUM_TCP | IFNET_CSUM_UDP | IFNET_CSUM_FRAGMENT | IFNET_IP_FRAGMENT | IFNET_CSUM_TCPIPV6 | IFNET_CSUM_UDPIPV6 | IFNET_IPV6_FRAGMENT | IFNET_CSUM_PARTIAL | IFNET_CSUM_ZERO_INVERT | IFNET_VLAN_TAGGING | IFNET_VLAN_MTU | IFNET_MULTIPAGES | IFNET_TSO_IPV4 | IFNET_TSO_IPV6 | IFNET_TX_STATUS | IFNET_HW_TIMESTAMP | IFNET_SW_TIMESTAMP); static const ifnet_offload_t any_offload_csum = IFNET_CHECKSUMF; static errno_t ifnet_set_offload_common(ifnet_t interface, ifnet_offload_t offload, boolean_t set_both) { u_int32_t ifcaps = 0; if (interface == NULL) { return EINVAL; } ifnet_lock_exclusive(interface); interface->if_hwassist = (offload & offload_mask); #if SKYWALK /* preserve skywalk capability */ if ((interface->if_capabilities & IFCAP_SKYWALK) != 0) { ifcaps |= IFCAP_SKYWALK; } #endif /* SKYWALK */ if (dlil_verbose) { log(LOG_DEBUG, "%s: set offload flags=0x%x\n", if_name(interface), interface->if_hwassist); } ifnet_lock_done(interface); if ((offload & any_offload_csum)) { ifcaps |= IFCAP_HWCSUM; } if ((offload & IFNET_TSO_IPV4)) { ifcaps |= IFCAP_TSO4; } if ((offload & IFNET_TSO_IPV6)) { ifcaps |= IFCAP_TSO6; } if ((offload & IFNET_LRO)) { ifcaps |= IFCAP_LRO; } if ((offload & IFNET_VLAN_MTU)) { ifcaps |= IFCAP_VLAN_MTU; } if ((offload & IFNET_VLAN_TAGGING)) { ifcaps |= IFCAP_VLAN_HWTAGGING; } if ((offload & IFNET_TX_STATUS)) { ifcaps |= IFCAP_TXSTATUS; } if ((offload & IFNET_HW_TIMESTAMP)) { ifcaps |= IFCAP_HW_TIMESTAMP; } if ((offload & IFNET_SW_TIMESTAMP)) { ifcaps |= IFCAP_SW_TIMESTAMP; } if ((offload & IFNET_CSUM_PARTIAL)) { ifcaps |= IFCAP_CSUM_PARTIAL; } if ((offload & IFNET_CSUM_ZERO_INVERT)) { ifcaps |= IFCAP_CSUM_ZERO_INVERT; } if (ifcaps != 0) { if (set_both) { (void) ifnet_set_capabilities_supported(interface, ifcaps, IFCAP_VALID); } (void) ifnet_set_capabilities_enabled(interface, ifcaps, IFCAP_VALID); } return 0; } errno_t ifnet_set_offload(ifnet_t interface, ifnet_offload_t offload) { return ifnet_set_offload_common(interface, offload, TRUE); } errno_t ifnet_set_offload_enabled(ifnet_t interface, ifnet_offload_t offload) { return ifnet_set_offload_common(interface, offload, FALSE); } ifnet_offload_t ifnet_offload(ifnet_t interface) { return (interface == NULL) ? 0 : (interface->if_hwassist & offload_mask); } errno_t ifnet_set_tso_mtu(ifnet_t interface, sa_family_t family, u_int32_t mtuLen) { errno_t error = 0; if (interface == NULL || mtuLen < interface->if_mtu) { return EINVAL; } if (mtuLen > IP_MAXPACKET) { return EINVAL; } switch (family) { case AF_INET: if (interface->if_hwassist & IFNET_TSO_IPV4) { interface->if_tso_v4_mtu = mtuLen; } else { error = EINVAL; } break; case AF_INET6: if (interface->if_hwassist & IFNET_TSO_IPV6) { interface->if_tso_v6_mtu = mtuLen; } else { error = EINVAL; } break; default: error = EPROTONOSUPPORT; break; } if (error == 0) { struct ifclassq *ifq = interface->if_snd; ASSERT(ifq != NULL); /* Inform all transmit queues about the new TSO MTU */ IFCQ_LOCK(ifq); ifnet_update_sndq(ifq, CLASSQ_EV_LINK_MTU); IFCQ_UNLOCK(ifq); } return error; } errno_t ifnet_get_tso_mtu(ifnet_t interface, sa_family_t family, u_int32_t *mtuLen) { errno_t error = 0; if (interface == NULL || mtuLen == NULL) { return EINVAL; } switch (family) { case AF_INET: if (interface->if_hwassist & IFNET_TSO_IPV4) { *mtuLen = interface->if_tso_v4_mtu; } else { error = EINVAL; } break; case AF_INET6: if (interface->if_hwassist & IFNET_TSO_IPV6) { *mtuLen = interface->if_tso_v6_mtu; } else { error = EINVAL; } break; default: error = EPROTONOSUPPORT; break; } return error; } errno_t ifnet_set_wake_flags(ifnet_t interface, u_int32_t properties, u_int32_t mask) { struct kev_msg ev_msg; struct net_event_data ev_data; bzero(&ev_data, sizeof(struct net_event_data)); bzero(&ev_msg, sizeof(struct kev_msg)); if (interface == NULL) { return EINVAL; } /* Do not accept wacky values */ if ((properties & mask) & ~IF_WAKE_VALID_FLAGS) { return EINVAL; } if ((mask & IF_WAKE_ON_MAGIC_PACKET) != 0) { if ((properties & IF_WAKE_ON_MAGIC_PACKET) != 0) { if_set_xflags(interface, IFXF_WAKE_ON_MAGIC_PACKET); } else { if_clear_xflags(interface, IFXF_WAKE_ON_MAGIC_PACKET); } } (void) ifnet_touch_lastchange(interface); /* Notify application of the change */ ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_DL_SUBCLASS; ev_msg.event_code = KEV_DL_WAKEFLAGS_CHANGED; strlcpy(&ev_data.if_name[0], interface->if_name, IFNAMSIZ); ev_data.if_family = interface->if_family; ev_data.if_unit = (u_int32_t)interface->if_unit; ev_msg.dv[0].data_length = sizeof(struct net_event_data); ev_msg.dv[0].data_ptr = &ev_data; ev_msg.dv[1].data_length = 0; dlil_post_complete_msg(interface, &ev_msg); return 0; } u_int32_t ifnet_get_wake_flags(ifnet_t interface) { u_int32_t flags = 0; if (interface == NULL) { return 0; } if ((interface->if_xflags & IFXF_WAKE_ON_MAGIC_PACKET) != 0) { flags |= IF_WAKE_ON_MAGIC_PACKET; } return flags; } /* * Should MIB data store a copy? */ errno_t ifnet_set_link_mib_data(ifnet_t interface, void *mibData, uint32_t mibLen) { if (interface == NULL) { return EINVAL; } ifnet_lock_exclusive(interface); interface->if_linkmib = (void*)mibData; interface->if_linkmiblen = mibLen; ifnet_lock_done(interface); return 0; } errno_t ifnet_get_link_mib_data(ifnet_t interface, void *mibData, uint32_t *mibLen) { errno_t result = 0; if (interface == NULL) { return EINVAL; } ifnet_lock_shared(interface); if (*mibLen < interface->if_linkmiblen) { result = EMSGSIZE; } if (result == 0 && interface->if_linkmib == NULL) { result = ENOTSUP; } if (result == 0) { *mibLen = interface->if_linkmiblen; bcopy(interface->if_linkmib, mibData, *mibLen); } ifnet_lock_done(interface); return result; } uint32_t ifnet_get_link_mib_data_length(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_linkmiblen; } errno_t ifnet_output(ifnet_t interface, protocol_family_t protocol_family, mbuf_t m, void *route, const struct sockaddr *dest) { if (interface == NULL || protocol_family == 0 || m == NULL) { if (m != NULL) { mbuf_freem_list(m); } return EINVAL; } return dlil_output(interface, protocol_family, m, route, dest, 0, NULL); } errno_t ifnet_output_raw(ifnet_t interface, protocol_family_t protocol_family, mbuf_t m) { if (interface == NULL || m == NULL) { if (m != NULL) { mbuf_freem_list(m); } return EINVAL; } return dlil_output(interface, protocol_family, m, NULL, NULL, 1, NULL); } errno_t ifnet_set_mtu(ifnet_t interface, u_int32_t mtu) { if (interface == NULL) { return EINVAL; } interface->if_mtu = mtu; return 0; } u_int32_t ifnet_mtu(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_mtu; } u_char ifnet_type(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_data.ifi_type; } errno_t ifnet_set_addrlen(ifnet_t interface, u_char addrlen) { if (interface == NULL) { return EINVAL; } interface->if_data.ifi_addrlen = addrlen; return 0; } u_char ifnet_addrlen(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_data.ifi_addrlen; } errno_t ifnet_set_hdrlen(ifnet_t interface, u_char hdrlen) { if (interface == NULL) { return EINVAL; } interface->if_data.ifi_hdrlen = hdrlen; return 0; } u_char ifnet_hdrlen(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_data.ifi_hdrlen; } errno_t ifnet_set_metric(ifnet_t interface, u_int32_t metric) { if (interface == NULL) { return EINVAL; } interface->if_data.ifi_metric = metric; return 0; } u_int32_t ifnet_metric(ifnet_t interface) { return (interface == NULL) ? 0 : interface->if_data.ifi_metric; } errno_t ifnet_set_baudrate(struct ifnet *ifp, uint64_t baudrate) { if (ifp == NULL) { return EINVAL; } ifp->if_output_bw.max_bw = ifp->if_input_bw.max_bw = ifp->if_output_bw.eff_bw = ifp->if_input_bw.eff_bw = baudrate; /* Pin if_baudrate to 32 bits until we can change the storage size */ ifp->if_baudrate = (baudrate > UINT32_MAX) ? UINT32_MAX : (uint32_t)baudrate; return 0; } u_int64_t ifnet_baudrate(struct ifnet *ifp) { return (ifp == NULL) ? 0 : ifp->if_baudrate; } errno_t ifnet_set_bandwidths(struct ifnet *ifp, struct if_bandwidths *output_bw, struct if_bandwidths *input_bw) { if (ifp == NULL) { return EINVAL; } /* set input values first (if any), as output values depend on them */ if (input_bw != NULL) { (void) ifnet_set_input_bandwidths(ifp, input_bw); } if (output_bw != NULL) { (void) ifnet_set_output_bandwidths(ifp, output_bw, FALSE); } return 0; } static void ifnet_set_link_status_outbw(struct ifnet *ifp) { struct if_wifi_status_v1 *sr; sr = &ifp->if_link_status->ifsr_u.ifsr_wifi.if_wifi_u.if_status_v1; if (ifp->if_output_bw.eff_bw != 0) { sr->valid_bitmask |= IF_WIFI_UL_EFFECTIVE_BANDWIDTH_VALID; sr->ul_effective_bandwidth = ifp->if_output_bw.eff_bw > UINT32_MAX ? UINT32_MAX : (uint32_t)ifp->if_output_bw.eff_bw; } if (ifp->if_output_bw.max_bw != 0) { sr->valid_bitmask |= IF_WIFI_UL_MAX_BANDWIDTH_VALID; sr->ul_max_bandwidth = ifp->if_output_bw.max_bw > UINT32_MAX ? UINT32_MAX : (uint32_t)ifp->if_output_bw.max_bw; } } errno_t ifnet_set_output_bandwidths(struct ifnet *ifp, struct if_bandwidths *bw, boolean_t locked) { struct if_bandwidths old_bw; struct ifclassq *ifq; u_int64_t br; VERIFY(ifp != NULL && bw != NULL); ifq = ifp->if_snd; if (!locked) { IFCQ_LOCK(ifq); } IFCQ_LOCK_ASSERT_HELD(ifq); old_bw = ifp->if_output_bw; if (bw->eff_bw != 0) { ifp->if_output_bw.eff_bw = bw->eff_bw; } if (bw->max_bw != 0) { ifp->if_output_bw.max_bw = bw->max_bw; } if (ifp->if_output_bw.eff_bw > ifp->if_output_bw.max_bw) { ifp->if_output_bw.max_bw = ifp->if_output_bw.eff_bw; } else if (ifp->if_output_bw.eff_bw == 0) { ifp->if_output_bw.eff_bw = ifp->if_output_bw.max_bw; } /* Pin if_baudrate to 32 bits */ br = MAX(ifp->if_output_bw.max_bw, ifp->if_input_bw.max_bw); if (br != 0) { ifp->if_baudrate = (br > UINT32_MAX) ? UINT32_MAX : (uint32_t)br; } /* Adjust queue parameters if needed */ if (old_bw.eff_bw != ifp->if_output_bw.eff_bw || old_bw.max_bw != ifp->if_output_bw.max_bw) { ifnet_update_sndq(ifq, CLASSQ_EV_LINK_BANDWIDTH); } if (!locked) { IFCQ_UNLOCK(ifq); } /* * If this is a Wifi interface, update the values in * if_link_status structure also. */ if (IFNET_IS_WIFI(ifp) && ifp->if_link_status != NULL) { lck_rw_lock_exclusive(&ifp->if_link_status_lock); ifnet_set_link_status_outbw(ifp); lck_rw_done(&ifp->if_link_status_lock); } return 0; } static void ifnet_set_link_status_inbw(struct ifnet *ifp) { struct if_wifi_status_v1 *sr; sr = &ifp->if_link_status->ifsr_u.ifsr_wifi.if_wifi_u.if_status_v1; if (ifp->if_input_bw.eff_bw != 0) { sr->valid_bitmask |= IF_WIFI_DL_EFFECTIVE_BANDWIDTH_VALID; sr->dl_effective_bandwidth = ifp->if_input_bw.eff_bw > UINT32_MAX ? UINT32_MAX : (uint32_t)ifp->if_input_bw.eff_bw; } if (ifp->if_input_bw.max_bw != 0) { sr->valid_bitmask |= IF_WIFI_DL_MAX_BANDWIDTH_VALID; sr->dl_max_bandwidth = ifp->if_input_bw.max_bw > UINT32_MAX ? UINT32_MAX : (uint32_t)ifp->if_input_bw.max_bw; } } errno_t ifnet_set_input_bandwidths(struct ifnet *ifp, struct if_bandwidths *bw) { struct if_bandwidths old_bw; VERIFY(ifp != NULL && bw != NULL); old_bw = ifp->if_input_bw; if (bw->eff_bw != 0) { ifp->if_input_bw.eff_bw = bw->eff_bw; } if (bw->max_bw != 0) { ifp->if_input_bw.max_bw = bw->max_bw; } if (ifp->if_input_bw.eff_bw > ifp->if_input_bw.max_bw) { ifp->if_input_bw.max_bw = ifp->if_input_bw.eff_bw; } else if (ifp->if_input_bw.eff_bw == 0) { ifp->if_input_bw.eff_bw = ifp->if_input_bw.max_bw; } if (IFNET_IS_WIFI(ifp) && ifp->if_link_status != NULL) { lck_rw_lock_exclusive(&ifp->if_link_status_lock); ifnet_set_link_status_inbw(ifp); lck_rw_done(&ifp->if_link_status_lock); } if (old_bw.eff_bw != ifp->if_input_bw.eff_bw || old_bw.max_bw != ifp->if_input_bw.max_bw) { ifnet_update_rcv(ifp, CLASSQ_EV_LINK_BANDWIDTH); } return 0; } u_int64_t ifnet_output_linkrate(struct ifnet *ifp) { struct ifclassq *ifq = ifp->if_snd; u_int64_t rate; IFCQ_LOCK_ASSERT_HELD(ifq); rate = ifp->if_output_bw.eff_bw; if (IFCQ_TBR_IS_ENABLED(ifq)) { u_int64_t tbr_rate = ifq->ifcq_tbr.tbr_rate_raw; VERIFY(tbr_rate > 0); rate = MIN(rate, ifq->ifcq_tbr.tbr_rate_raw); } return rate; } u_int64_t ifnet_input_linkrate(struct ifnet *ifp) { return ifp->if_input_bw.eff_bw; } errno_t ifnet_bandwidths(struct ifnet *ifp, struct if_bandwidths *output_bw, struct if_bandwidths *input_bw) { if (ifp == NULL) { return EINVAL; } if (output_bw != NULL) { *output_bw = ifp->if_output_bw; } if (input_bw != NULL) { *input_bw = ifp->if_input_bw; } return 0; } errno_t ifnet_set_latencies(struct ifnet *ifp, struct if_latencies *output_lt, struct if_latencies *input_lt) { if (ifp == NULL) { return EINVAL; } if (output_lt != NULL) { (void) ifnet_set_output_latencies(ifp, output_lt, FALSE); } if (input_lt != NULL) { (void) ifnet_set_input_latencies(ifp, input_lt); } return 0; } errno_t ifnet_set_output_latencies(struct ifnet *ifp, struct if_latencies *lt, boolean_t locked) { struct if_latencies old_lt; struct ifclassq *ifq; VERIFY(ifp != NULL && lt != NULL); ifq = ifp->if_snd; if (!locked) { IFCQ_LOCK(ifq); } IFCQ_LOCK_ASSERT_HELD(ifq); old_lt = ifp->if_output_lt; if (lt->eff_lt != 0) { ifp->if_output_lt.eff_lt = lt->eff_lt; } if (lt->max_lt != 0) { ifp->if_output_lt.max_lt = lt->max_lt; } if (ifp->if_output_lt.eff_lt > ifp->if_output_lt.max_lt) { ifp->if_output_lt.max_lt = ifp->if_output_lt.eff_lt; } else if (ifp->if_output_lt.eff_lt == 0) { ifp->if_output_lt.eff_lt = ifp->if_output_lt.max_lt; } /* Adjust queue parameters if needed */ if (old_lt.eff_lt != ifp->if_output_lt.eff_lt || old_lt.max_lt != ifp->if_output_lt.max_lt) { ifnet_update_sndq(ifq, CLASSQ_EV_LINK_LATENCY); } if (!locked) { IFCQ_UNLOCK(ifq); } return 0; } errno_t ifnet_set_input_latencies(struct ifnet *ifp, struct if_latencies *lt) { struct if_latencies old_lt; VERIFY(ifp != NULL && lt != NULL); old_lt = ifp->if_input_lt; if (lt->eff_lt != 0) { ifp->if_input_lt.eff_lt = lt->eff_lt; } if (lt->max_lt != 0) { ifp->if_input_lt.max_lt = lt->max_lt; } if (ifp->if_input_lt.eff_lt > ifp->if_input_lt.max_lt) { ifp->if_input_lt.max_lt = ifp->if_input_lt.eff_lt; } else if (ifp->if_input_lt.eff_lt == 0) { ifp->if_input_lt.eff_lt = ifp->if_input_lt.max_lt; } if (old_lt.eff_lt != ifp->if_input_lt.eff_lt || old_lt.max_lt != ifp->if_input_lt.max_lt) { ifnet_update_rcv(ifp, CLASSQ_EV_LINK_LATENCY); } return 0; } errno_t ifnet_latencies(struct ifnet *ifp, struct if_latencies *output_lt, struct if_latencies *input_lt) { if (ifp == NULL) { return EINVAL; } if (output_lt != NULL) { *output_lt = ifp->if_output_lt; } if (input_lt != NULL) { *input_lt = ifp->if_input_lt; } return 0; } errno_t ifnet_set_poll_params(struct ifnet *ifp, struct ifnet_poll_params *p) { errno_t err; if (ifp == NULL) { return EINVAL; } else if (!ifnet_is_attached(ifp, 1)) { return ENXIO; } #if SKYWALK if (SKYWALK_CAPABLE(ifp)) { err = netif_rxpoll_set_params(ifp, p, FALSE); ifnet_decr_iorefcnt(ifp); return err; } #endif /* SKYWALK */ err = dlil_rxpoll_set_params(ifp, p, FALSE); /* Release the io ref count */ ifnet_decr_iorefcnt(ifp); return err; } errno_t ifnet_poll_params(struct ifnet *ifp, struct ifnet_poll_params *p) { errno_t err; if (ifp == NULL || p == NULL) { return EINVAL; } else if (!ifnet_is_attached(ifp, 1)) { return ENXIO; } err = dlil_rxpoll_get_params(ifp, p); /* Release the io ref count */ ifnet_decr_iorefcnt(ifp); return err; } errno_t ifnet_stat_increment(struct ifnet *ifp, const struct ifnet_stat_increment_param *s) { if (ifp == NULL) { return EINVAL; } if (s->packets_in != 0) { os_atomic_add(&ifp->if_data.ifi_ipackets, s->packets_in, relaxed); } if (s->bytes_in != 0) { os_atomic_add(&ifp->if_data.ifi_ibytes, s->bytes_in, relaxed); } if (s->errors_in != 0) { os_atomic_add(&ifp->if_data.ifi_ierrors, s->errors_in, relaxed); } if (s->packets_out != 0) { os_atomic_add(&ifp->if_data.ifi_opackets, s->packets_out, relaxed); } if (s->bytes_out != 0) { os_atomic_add(&ifp->if_data.ifi_obytes, s->bytes_out, relaxed); } if (s->errors_out != 0) { os_atomic_add(&ifp->if_data.ifi_oerrors, s->errors_out, relaxed); } if (s->collisions != 0) { os_atomic_add(&ifp->if_data.ifi_collisions, s->collisions, relaxed); } if (s->dropped != 0) { os_atomic_add(&ifp->if_data.ifi_iqdrops, s->dropped, relaxed); } /* Touch the last change time. */ TOUCHLASTCHANGE(&ifp->if_lastchange); if (ifp->if_data_threshold != 0) { ifnet_notify_data_threshold(ifp); } return 0; } errno_t ifnet_stat_increment_in(struct ifnet *ifp, u_int32_t packets_in, u_int32_t bytes_in, u_int32_t errors_in) { if (ifp == NULL) { return EINVAL; } if (packets_in != 0) { os_atomic_add(&ifp->if_data.ifi_ipackets, packets_in, relaxed); } if (bytes_in != 0) { os_atomic_add(&ifp->if_data.ifi_ibytes, bytes_in, relaxed); } if (errors_in != 0) { os_atomic_add(&ifp->if_data.ifi_ierrors, errors_in, relaxed); } TOUCHLASTCHANGE(&ifp->if_lastchange); if (ifp->if_data_threshold != 0) { ifnet_notify_data_threshold(ifp); } return 0; } errno_t ifnet_stat_increment_out(struct ifnet *ifp, u_int32_t packets_out, u_int32_t bytes_out, u_int32_t errors_out) { if (ifp == NULL) { return EINVAL; } if (packets_out != 0) { os_atomic_add(&ifp->if_data.ifi_opackets, packets_out, relaxed); } if (bytes_out != 0) { os_atomic_add(&ifp->if_data.ifi_obytes, bytes_out, relaxed); } if (errors_out != 0) { os_atomic_add(&ifp->if_data.ifi_oerrors, errors_out, relaxed); } TOUCHLASTCHANGE(&ifp->if_lastchange); if (ifp->if_data_threshold != 0) { ifnet_notify_data_threshold(ifp); } return 0; } errno_t ifnet_set_stat(struct ifnet *ifp, const struct ifnet_stats_param *s) { if (ifp == NULL) { return EINVAL; } os_atomic_store(&ifp->if_data.ifi_ipackets, s->packets_in, release); os_atomic_store(&ifp->if_data.ifi_ibytes, s->bytes_in, release); os_atomic_store(&ifp->if_data.ifi_imcasts, s->multicasts_in, release); os_atomic_store(&ifp->if_data.ifi_ierrors, s->errors_in, release); os_atomic_store(&ifp->if_data.ifi_opackets, s->packets_out, release); os_atomic_store(&ifp->if_data.ifi_obytes, s->bytes_out, release); os_atomic_store(&ifp->if_data.ifi_omcasts, s->multicasts_out, release); os_atomic_store(&ifp->if_data.ifi_oerrors, s->errors_out, release); os_atomic_store(&ifp->if_data.ifi_collisions, s->collisions, release); os_atomic_store(&ifp->if_data.ifi_iqdrops, s->dropped, release); os_atomic_store(&ifp->if_data.ifi_noproto, s->no_protocol, release); /* Touch the last change time. */ TOUCHLASTCHANGE(&ifp->if_lastchange); if (ifp->if_data_threshold != 0) { ifnet_notify_data_threshold(ifp); } return 0; } errno_t ifnet_stat(struct ifnet *ifp, struct ifnet_stats_param *s) { if (ifp == NULL) { return EINVAL; } s->packets_in = os_atomic_load(&ifp->if_data.ifi_ipackets, relaxed); s->bytes_in = os_atomic_load(&ifp->if_data.ifi_ibytes, relaxed); s->multicasts_in = os_atomic_load(&ifp->if_data.ifi_imcasts, relaxed); s->errors_in = os_atomic_load(&ifp->if_data.ifi_ierrors, relaxed); s->packets_out = os_atomic_load(&ifp->if_data.ifi_opackets, relaxed); s->bytes_out = os_atomic_load(&ifp->if_data.ifi_obytes, relaxed); s->multicasts_out = os_atomic_load(&ifp->if_data.ifi_omcasts, relaxed); s->errors_out = os_atomic_load(&ifp->if_data.ifi_oerrors, relaxed); s->collisions = os_atomic_load(&ifp->if_data.ifi_collisions, relaxed); s->dropped = os_atomic_load(&ifp->if_data.ifi_iqdrops, relaxed); s->no_protocol = os_atomic_load(&ifp->if_data.ifi_noproto, relaxed); if (ifp->if_data_threshold != 0) { ifnet_notify_data_threshold(ifp); } return 0; } errno_t ifnet_touch_lastchange(ifnet_t interface) { if (interface == NULL) { return EINVAL; } TOUCHLASTCHANGE(&interface->if_lastchange); return 0; } errno_t ifnet_lastchange(ifnet_t interface, struct timeval *last_change) { if (interface == NULL) { return EINVAL; } *last_change = interface->if_data.ifi_lastchange; /* Crude conversion from uptime to calendar time */ last_change->tv_sec += boottime_sec(); return 0; } errno_t ifnet_touch_lastupdown(ifnet_t interface) { if (interface == NULL) { return EINVAL; } TOUCHLASTCHANGE(&interface->if_lastupdown); return 0; } errno_t ifnet_updown_delta(ifnet_t interface, struct timeval *updown_delta) { if (interface == NULL) { return EINVAL; } /* Calculate the delta */ updown_delta->tv_sec = (time_t)net_uptime(); if (updown_delta->tv_sec > interface->if_data.ifi_lastupdown.tv_sec) { updown_delta->tv_sec -= interface->if_data.ifi_lastupdown.tv_sec; } else { updown_delta->tv_sec = 0; } updown_delta->tv_usec = 0; return 0; } errno_t ifnet_get_address_list(ifnet_t interface, ifaddr_t **addresses) { return addresses == NULL ? EINVAL : ifnet_get_address_list_family(interface, addresses, 0); } struct ifnet_addr_list { SLIST_ENTRY(ifnet_addr_list) ifal_le; struct ifaddr *ifal_ifa; }; errno_t ifnet_get_address_list_family(ifnet_t interface, ifaddr_t **addresses, sa_family_t family) { return ifnet_get_address_list_family_internal(interface, addresses, family, 0, Z_WAITOK, 0); } errno_t ifnet_get_inuse_address_list(ifnet_t interface, ifaddr_t **addresses) { return addresses == NULL ? EINVAL : ifnet_get_address_list_family_internal(interface, addresses, 0, 0, Z_WAITOK, 1); } extern uint32_t tcp_find_anypcb_byaddr(struct ifaddr *ifa); extern uint32_t udp_find_anypcb_byaddr(struct ifaddr *ifa); __private_extern__ errno_t ifnet_get_address_list_family_internal(ifnet_t interface, ifaddr_t **addresses, sa_family_t family, int detached, int how, int return_inuse_addrs) { SLIST_HEAD(, ifnet_addr_list) ifal_head; struct ifnet_addr_list *ifal, *ifal_tmp; struct ifnet *ifp; int count = 0; errno_t err = 0; int usecount = 0; int index = 0; SLIST_INIT(&ifal_head); if (addresses == NULL) { err = EINVAL; goto done; } *addresses = NULL; if (detached) { /* * Interface has been detached, so skip the lookup * at ifnet_head and go directly to inner loop. */ ifp = interface; if (ifp == NULL) { err = EINVAL; goto done; } goto one; } ifnet_head_lock_shared(); TAILQ_FOREACH(ifp, &ifnet_head, if_link) { if (interface != NULL && ifp != interface) { continue; } one: ifnet_lock_shared(ifp); if (interface == NULL || interface == ifp) { struct ifaddr *ifa; TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { IFA_LOCK(ifa); if (family != 0 && ifa->ifa_addr->sa_family != family) { IFA_UNLOCK(ifa); continue; } ifal = kalloc_type(struct ifnet_addr_list, how); if (ifal == NULL) { IFA_UNLOCK(ifa); ifnet_lock_done(ifp); if (!detached) { ifnet_head_done(); } err = ENOMEM; goto done; } ifal->ifal_ifa = ifa; ifa_addref(ifa); SLIST_INSERT_HEAD(&ifal_head, ifal, ifal_le); ++count; IFA_UNLOCK(ifa); } } ifnet_lock_done(ifp); if (detached) { break; } } if (!detached) { ifnet_head_done(); } if (count == 0) { err = ENXIO; goto done; } *addresses = kalloc_type(ifaddr_t, count + 1, how | Z_ZERO); if (*addresses == NULL) { err = ENOMEM; goto done; } done: SLIST_FOREACH_SAFE(ifal, &ifal_head, ifal_le, ifal_tmp) { SLIST_REMOVE(&ifal_head, ifal, ifnet_addr_list, ifal_le); if (err == 0) { if (return_inuse_addrs) { usecount = tcp_find_anypcb_byaddr(ifal->ifal_ifa); usecount += udp_find_anypcb_byaddr(ifal->ifal_ifa); if (usecount) { (*addresses)[index] = ifal->ifal_ifa; index++; } else { ifa_remref(ifal->ifal_ifa); } } else { (*addresses)[--count] = ifal->ifal_ifa; } } else { ifa_remref(ifal->ifal_ifa); } kfree_type(struct ifnet_addr_list, ifal); } VERIFY(err == 0 || *addresses == NULL); if ((err == 0) && (count) && ((*addresses)[0] == NULL)) { VERIFY(return_inuse_addrs == 1); kfree_type(ifaddr_t, count + 1, *addresses); err = ENXIO; } return err; } void ifnet_free_address_list(ifaddr_t *addresses) { int i; if (addresses == NULL) { return; } for (i = 0; addresses[i] != NULL; i++) { ifa_remref(addresses[i]); } kfree_type(ifaddr_t, i + 1, addresses); } void * ifnet_lladdr(ifnet_t interface) { struct ifaddr *ifa; void *lladdr; if (interface == NULL) { return NULL; } /* * if_lladdr points to the permanent link address of * the interface and it never gets deallocated; internal * code should simply use IF_LLADDR() for performance. */ ifa = interface->if_lladdr; IFA_LOCK_SPIN(ifa); lladdr = LLADDR(SDL((void *)ifa->ifa_addr)); IFA_UNLOCK(ifa); return lladdr; } errno_t ifnet_llbroadcast_copy_bytes(ifnet_t interface, void *addr, size_t buffer_len, size_t *out_len) { if (interface == NULL || addr == NULL || out_len == NULL) { return EINVAL; } *out_len = interface->if_broadcast.length; if (buffer_len < interface->if_broadcast.length) { return EMSGSIZE; } if (interface->if_broadcast.length == 0) { return ENXIO; } if (interface->if_broadcast.length <= sizeof(interface->if_broadcast.u.buffer)) { bcopy(interface->if_broadcast.u.buffer, addr, interface->if_broadcast.length); } else { bcopy(interface->if_broadcast.u.ptr, addr, interface->if_broadcast.length); } return 0; } static errno_t ifnet_lladdr_copy_bytes_internal(ifnet_t interface, void *lladdr, size_t lladdr_len, kauth_cred_t *credp) { const u_int8_t *bytes; size_t bytes_len; struct ifaddr *ifa; uint8_t sdlbuf[SOCK_MAXADDRLEN + 1]; errno_t error = 0; /* * Make sure to accomodate the largest possible * size of SA(if_lladdr)->sa_len. */ _CASSERT(sizeof(sdlbuf) == (SOCK_MAXADDRLEN + 1)); if (interface == NULL || lladdr == NULL) { return EINVAL; } ifa = interface->if_lladdr; IFA_LOCK_SPIN(ifa); bcopy(ifa->ifa_addr, &sdlbuf, SDL(ifa->ifa_addr)->sdl_len); IFA_UNLOCK(ifa); bytes = dlil_ifaddr_bytes(SDL(&sdlbuf), &bytes_len, credp); if (bytes_len != lladdr_len) { bzero(lladdr, lladdr_len); error = EMSGSIZE; } else { bcopy(bytes, lladdr, bytes_len); } return error; } errno_t ifnet_lladdr_copy_bytes(ifnet_t interface, void *lladdr, size_t length) { return ifnet_lladdr_copy_bytes_internal(interface, lladdr, length, NULL); } errno_t ifnet_guarded_lladdr_copy_bytes(ifnet_t interface, void *lladdr, size_t length) { #if CONFIG_MACF kauth_cred_t cred; net_thread_marks_t marks; #endif kauth_cred_t *credp; errno_t error; #if CONFIG_MACF marks = net_thread_marks_push(NET_THREAD_CKREQ_LLADDR); cred = current_cached_proc_cred(PROC_NULL); credp = &cred; #else credp = NULL; #endif error = ifnet_lladdr_copy_bytes_internal(interface, lladdr, length, credp); #if CONFIG_MACF net_thread_marks_pop(marks); #endif return error; } static errno_t ifnet_set_lladdr_internal(ifnet_t interface, const void *lladdr, size_t lladdr_len, u_char new_type, int apply_type) { struct ifaddr *ifa; errno_t error = 0; if (interface == NULL) { return EINVAL; } ifnet_head_lock_shared(); ifnet_lock_exclusive(interface); if (lladdr_len != 0 && (lladdr_len != interface->if_addrlen || lladdr == 0)) { ifnet_lock_done(interface); ifnet_head_done(); return EINVAL; } ifa = ifnet_addrs[interface->if_index - 1]; if (ifa != NULL) { struct sockaddr_dl *sdl; IFA_LOCK_SPIN(ifa); sdl = (struct sockaddr_dl *)(void *)ifa->ifa_addr; if (lladdr_len != 0) { bcopy(lladdr, LLADDR(sdl), lladdr_len); } else { bzero(LLADDR(sdl), interface->if_addrlen); } /* lladdr_len-check with if_addrlen makes sure it fits in u_char */ sdl->sdl_alen = (u_char)lladdr_len; if (apply_type) { sdl->sdl_type = new_type; } IFA_UNLOCK(ifa); } else { error = ENXIO; } ifnet_lock_done(interface); ifnet_head_done(); /* Generate a kernel event */ if (error == 0) { intf_event_enqueue_nwk_wq_entry(interface, NULL, INTF_EVENT_CODE_LLADDR_UPDATE); dlil_post_msg(interface, KEV_DL_SUBCLASS, KEV_DL_LINK_ADDRESS_CHANGED, NULL, 0, FALSE); } return error; } errno_t ifnet_set_lladdr(ifnet_t interface, const void* lladdr, size_t lladdr_len) { return ifnet_set_lladdr_internal(interface, lladdr, lladdr_len, 0, 0); } errno_t ifnet_set_lladdr_and_type(ifnet_t interface, const void* lladdr, size_t lladdr_len, u_char type) { return ifnet_set_lladdr_internal(interface, lladdr, lladdr_len, type, 1); } errno_t ifnet_add_multicast(ifnet_t interface, const struct sockaddr *maddr, ifmultiaddr_t *ifmap) { if (interface == NULL || maddr == NULL) { return EINVAL; } /* Don't let users screw up protocols' entries. */ switch (maddr->sa_family) { case AF_LINK: { const struct sockaddr_dl *sdl = (const struct sockaddr_dl *)(uintptr_t)maddr; if (sdl->sdl_len < sizeof(struct sockaddr_dl) || (sdl->sdl_nlen + sdl->sdl_alen + sdl->sdl_slen + offsetof(struct sockaddr_dl, sdl_data) > sdl->sdl_len)) { return EINVAL; } break; } case AF_UNSPEC: if (maddr->sa_len < ETHER_ADDR_LEN + offsetof(struct sockaddr, sa_data)) { return EINVAL; } break; default: return EINVAL; } return if_addmulti_anon(interface, maddr, ifmap); } errno_t ifnet_remove_multicast(ifmultiaddr_t ifma) { struct sockaddr *maddr; if (ifma == NULL) { return EINVAL; } maddr = ifma->ifma_addr; /* Don't let users screw up protocols' entries. */ if (maddr->sa_family != AF_UNSPEC && maddr->sa_family != AF_LINK) { return EINVAL; } return if_delmulti_anon(ifma->ifma_ifp, maddr); } errno_t ifnet_get_multicast_list(ifnet_t ifp, ifmultiaddr_t **addresses) { int count = 0; int cmax = 0; struct ifmultiaddr *addr; if (ifp == NULL || addresses == NULL) { return EINVAL; } ifnet_lock_shared(ifp); LIST_FOREACH(addr, &ifp->if_multiaddrs, ifma_link) { cmax++; } *addresses = kalloc_type(ifmultiaddr_t, cmax + 1, Z_WAITOK); if (*addresses == NULL) { ifnet_lock_done(ifp); return ENOMEM; } LIST_FOREACH(addr, &ifp->if_multiaddrs, ifma_link) { if (count + 1 > cmax) { break; } (*addresses)[count] = (ifmultiaddr_t)addr; ifmaddr_reference((*addresses)[count]); count++; } (*addresses)[cmax] = NULL; ifnet_lock_done(ifp); return 0; } void ifnet_free_multicast_list(ifmultiaddr_t *addresses) { int i; if (addresses == NULL) { return; } for (i = 0; addresses[i] != NULL; i++) { ifmaddr_release(addresses[i]); } kfree_type(ifmultiaddr_t, i + 1, addresses); } errno_t ifnet_find_by_name(const char *ifname, ifnet_t *ifpp) { struct ifnet *ifp; size_t namelen; if (ifname == NULL) { return EINVAL; } namelen = strlen(ifname); *ifpp = NULL; ifnet_head_lock_shared(); TAILQ_FOREACH(ifp, &ifnet_head, if_link) { struct ifaddr *ifa; struct sockaddr_dl *ll_addr; ifa = ifnet_addrs[ifp->if_index - 1]; if (ifa == NULL) { continue; } IFA_LOCK(ifa); ll_addr = (struct sockaddr_dl *)(void *)ifa->ifa_addr; if (namelen == ll_addr->sdl_nlen && strncmp(ll_addr->sdl_data, ifname, ll_addr->sdl_nlen) == 0) { IFA_UNLOCK(ifa); *ifpp = ifp; ifnet_reference(*ifpp); break; } IFA_UNLOCK(ifa); } ifnet_head_done(); return (ifp == NULL) ? ENXIO : 0; } errno_t ifnet_list_get(ifnet_family_t family, ifnet_t **list, u_int32_t *count) { return ifnet_list_get_common(family, FALSE, list, count); } __private_extern__ errno_t ifnet_list_get_all(ifnet_family_t family, ifnet_t **list, u_int32_t *count) { return ifnet_list_get_common(family, TRUE, list, count); } struct ifnet_list { SLIST_ENTRY(ifnet_list) ifl_le; struct ifnet *ifl_ifp; }; static errno_t ifnet_list_get_common(ifnet_family_t family, boolean_t get_all, ifnet_t **list, u_int32_t *count) { #pragma unused(get_all) SLIST_HEAD(, ifnet_list) ifl_head; struct ifnet_list *ifl, *ifl_tmp; struct ifnet *ifp; int cnt = 0; errno_t err = 0; SLIST_INIT(&ifl_head); if (list == NULL || count == NULL) { err = EINVAL; goto done; } *count = 0; *list = NULL; ifnet_head_lock_shared(); TAILQ_FOREACH(ifp, &ifnet_head, if_link) { if (family == IFNET_FAMILY_ANY || ifp->if_family == family) { ifl = kalloc_type(struct ifnet_list, Z_NOWAIT); if (ifl == NULL) { ifnet_head_done(); err = ENOMEM; goto done; } ifl->ifl_ifp = ifp; ifnet_reference(ifp); SLIST_INSERT_HEAD(&ifl_head, ifl, ifl_le); ++cnt; } } ifnet_head_done(); if (cnt == 0) { err = ENXIO; goto done; } *list = kalloc_type(ifnet_t, cnt + 1, Z_WAITOK | Z_ZERO); if (*list == NULL) { err = ENOMEM; goto done; } *count = cnt; done: SLIST_FOREACH_SAFE(ifl, &ifl_head, ifl_le, ifl_tmp) { SLIST_REMOVE(&ifl_head, ifl, ifnet_list, ifl_le); if (err == 0) { (*list)[--cnt] = ifl->ifl_ifp; } else { ifnet_release(ifl->ifl_ifp); } kfree_type(struct ifnet_list, ifl); } return err; } void ifnet_list_free(ifnet_t *interfaces) { int i; if (interfaces == NULL) { return; } for (i = 0; interfaces[i]; i++) { ifnet_release(interfaces[i]); } kfree_type(ifnet_t, i + 1, interfaces); } /*************************************************************************/ /* ifaddr_t accessors */ /*************************************************************************/ errno_t ifaddr_reference(ifaddr_t ifa) { if (ifa == NULL) { return EINVAL; } ifa_addref(ifa); return 0; } errno_t ifaddr_release(ifaddr_t ifa) { if (ifa == NULL) { return EINVAL; } ifa_remref(ifa); return 0; } sa_family_t ifaddr_address_family(ifaddr_t ifa) { sa_family_t family = 0; if (ifa != NULL) { IFA_LOCK_SPIN(ifa); if (ifa->ifa_addr != NULL) { family = ifa->ifa_addr->sa_family; } IFA_UNLOCK(ifa); } return family; } errno_t ifaddr_address(ifaddr_t ifa, struct sockaddr *out_addr, u_int32_t addr_size) { u_int32_t copylen; if (ifa == NULL || out_addr == NULL) { return EINVAL; } IFA_LOCK_SPIN(ifa); if (ifa->ifa_addr == NULL) { IFA_UNLOCK(ifa); return ENOTSUP; } copylen = (addr_size >= ifa->ifa_addr->sa_len) ? ifa->ifa_addr->sa_len : addr_size; bcopy(ifa->ifa_addr, out_addr, copylen); if (ifa->ifa_addr->sa_len > addr_size) { IFA_UNLOCK(ifa); return EMSGSIZE; } IFA_UNLOCK(ifa); return 0; } errno_t ifaddr_dstaddress(ifaddr_t ifa, struct sockaddr *out_addr, u_int32_t addr_size) { u_int32_t copylen; if (ifa == NULL || out_addr == NULL) { return EINVAL; } IFA_LOCK_SPIN(ifa); if (ifa->ifa_dstaddr == NULL) { IFA_UNLOCK(ifa); return ENOTSUP; } copylen = (addr_size >= ifa->ifa_dstaddr->sa_len) ? ifa->ifa_dstaddr->sa_len : addr_size; bcopy(ifa->ifa_dstaddr, out_addr, copylen); if (ifa->ifa_dstaddr->sa_len > addr_size) { IFA_UNLOCK(ifa); return EMSGSIZE; } IFA_UNLOCK(ifa); return 0; } errno_t ifaddr_netmask(ifaddr_t ifa, struct sockaddr *out_addr, u_int32_t addr_size) { u_int32_t copylen; if (ifa == NULL || out_addr == NULL) { return EINVAL; } IFA_LOCK_SPIN(ifa); if (ifa->ifa_netmask == NULL) { IFA_UNLOCK(ifa); return ENOTSUP; } copylen = addr_size >= ifa->ifa_netmask->sa_len ? ifa->ifa_netmask->sa_len : addr_size; bcopy(ifa->ifa_netmask, out_addr, copylen); if (ifa->ifa_netmask->sa_len > addr_size) { IFA_UNLOCK(ifa); return EMSGSIZE; } IFA_UNLOCK(ifa); return 0; } ifnet_t ifaddr_ifnet(ifaddr_t ifa) { struct ifnet *ifp; if (ifa == NULL) { return NULL; } /* ifa_ifp is set once at creation time; it is never changed */ ifp = ifa->ifa_ifp; return ifp; } ifaddr_t ifaddr_withaddr(const struct sockaddr *address) { if (address == NULL) { return NULL; } return ifa_ifwithaddr(address); } ifaddr_t ifaddr_withdstaddr(const struct sockaddr *address) { if (address == NULL) { return NULL; } return ifa_ifwithdstaddr(address); } ifaddr_t ifaddr_withnet(const struct sockaddr *net) { if (net == NULL) { return NULL; } return ifa_ifwithnet(net); } ifaddr_t ifaddr_withroute(int flags, const struct sockaddr *destination, const struct sockaddr *gateway) { if (destination == NULL || gateway == NULL) { return NULL; } return ifa_ifwithroute(flags, destination, gateway); } ifaddr_t ifaddr_findbestforaddr(const struct sockaddr *addr, ifnet_t interface) { if (addr == NULL || interface == NULL) { return NULL; } return ifaof_ifpforaddr_select(addr, interface); } errno_t ifmaddr_reference(ifmultiaddr_t ifmaddr) { if (ifmaddr == NULL) { return EINVAL; } IFMA_ADDREF(ifmaddr); return 0; } errno_t ifmaddr_release(ifmultiaddr_t ifmaddr) { if (ifmaddr == NULL) { return EINVAL; } IFMA_REMREF(ifmaddr); return 0; } errno_t ifmaddr_address(ifmultiaddr_t ifma, struct sockaddr *out_addr, u_int32_t addr_size) { u_int32_t copylen; if (ifma == NULL || out_addr == NULL) { return EINVAL; } IFMA_LOCK(ifma); if (ifma->ifma_addr == NULL) { IFMA_UNLOCK(ifma); return ENOTSUP; } copylen = (addr_size >= ifma->ifma_addr->sa_len ? ifma->ifma_addr->sa_len : addr_size); bcopy(ifma->ifma_addr, out_addr, copylen); if (ifma->ifma_addr->sa_len > addr_size) { IFMA_UNLOCK(ifma); return EMSGSIZE; } IFMA_UNLOCK(ifma); return 0; } errno_t ifmaddr_lladdress(ifmultiaddr_t ifma, struct sockaddr *out_addr, u_int32_t addr_size) { struct ifmultiaddr *ifma_ll; if (ifma == NULL || out_addr == NULL) { return EINVAL; } if ((ifma_ll = ifma->ifma_ll) == NULL) { return ENOTSUP; } return ifmaddr_address(ifma_ll, out_addr, addr_size); } ifnet_t ifmaddr_ifnet(ifmultiaddr_t ifma) { return (ifma == NULL) ? NULL : ifma->ifma_ifp; } /**************************************************************************/ /* interface cloner */ /**************************************************************************/ errno_t ifnet_clone_attach(struct ifnet_clone_params *cloner_params, if_clone_t *ifcloner) { errno_t error = 0; struct if_clone *ifc = NULL; size_t namelen; if (cloner_params == NULL || ifcloner == NULL || cloner_params->ifc_name == NULL || cloner_params->ifc_create == NULL || cloner_params->ifc_destroy == NULL || (namelen = strlen(cloner_params->ifc_name)) >= IFNAMSIZ) { error = EINVAL; goto fail; } if (if_clone_lookup(cloner_params->ifc_name, NULL) != NULL) { printf("%s: already a cloner for %s\n", __func__, cloner_params->ifc_name); error = EEXIST; goto fail; } ifc = kalloc_type(struct if_clone, Z_WAITOK | Z_ZERO | Z_NOFAIL); strlcpy(ifc->ifc_name, cloner_params->ifc_name, IFNAMSIZ + 1); ifc->ifc_namelen = (uint8_t)namelen; ifc->ifc_maxunit = IF_MAXUNIT; ifc->ifc_create = cloner_params->ifc_create; ifc->ifc_destroy = cloner_params->ifc_destroy; error = if_clone_attach(ifc); if (error != 0) { printf("%s: if_clone_attach failed %d\n", __func__, error); goto fail; } *ifcloner = ifc; return 0; fail: if (ifc != NULL) { kfree_type(struct if_clone, ifc); } return error; } errno_t ifnet_clone_detach(if_clone_t ifcloner) { errno_t error = 0; struct if_clone *ifc = ifcloner; if (ifc == NULL) { return EINVAL; } if ((if_clone_lookup(ifc->ifc_name, NULL)) == NULL) { printf("%s: no cloner for %s\n", __func__, ifc->ifc_name); error = EINVAL; goto fail; } if_clone_detach(ifc); kfree_type(struct if_clone, ifc); fail: return error; } /**************************************************************************/ /* misc */ /**************************************************************************/ static errno_t ifnet_get_local_ports_extended_inner(ifnet_t ifp, protocol_family_t protocol, u_int32_t flags, u_int8_t *bitfield) { u_int32_t ifindex; /* no point in continuing if no address is assigned */ if (ifp != NULL && TAILQ_EMPTY(&ifp->if_addrhead)) { return 0; } if_ports_used_update_wakeuuid(ifp); #if SKYWALK if (netns_is_enabled()) { netns_get_local_ports(ifp, protocol, flags, bitfield); } #endif /* SKYWALK */ ifindex = (ifp != NULL) ? ifp->if_index : 0; if (!(flags & IFNET_GET_LOCAL_PORTS_TCPONLY)) { udp_get_ports_used(ifp, protocol, flags, bitfield); } if (!(flags & IFNET_GET_LOCAL_PORTS_UDPONLY)) { tcp_get_ports_used(ifp, protocol, flags, bitfield); } return 0; } errno_t ifnet_get_local_ports_extended(ifnet_t ifp, protocol_family_t protocol, u_int32_t flags, u_int8_t *bitfield) { ifnet_t parent_ifp = NULL; if (bitfield == NULL) { return EINVAL; } switch (protocol) { case PF_UNSPEC: case PF_INET: case PF_INET6: break; default: return EINVAL; } /* bit string is long enough to hold 16-bit port values */ bzero(bitfield, bitstr_size(IP_PORTRANGE_SIZE)); ifnet_get_local_ports_extended_inner(ifp, protocol, flags, bitfield); /* get local ports for parent interface */ if (ifp != NULL && ifnet_get_delegate_parent(ifp, &parent_ifp) == 0) { ifnet_get_local_ports_extended_inner(parent_ifp, protocol, flags, bitfield); ifnet_release_delegate_parent(ifp); } return 0; } errno_t ifnet_get_local_ports(ifnet_t ifp, u_int8_t *bitfield) { u_int32_t flags = IFNET_GET_LOCAL_PORTS_WILDCARDOK; return ifnet_get_local_ports_extended(ifp, PF_UNSPEC, flags, bitfield); } errno_t ifnet_notice_node_presence(ifnet_t ifp, struct sockaddr *sa, int32_t rssi, int lqm, int npm, u_int8_t srvinfo[48]) { if (ifp == NULL || sa == NULL || srvinfo == NULL) { return EINVAL; } if (sa->sa_len > sizeof(struct sockaddr_storage)) { return EINVAL; } if (sa->sa_family != AF_LINK && sa->sa_family != AF_INET6) { return EINVAL; } return dlil_node_present(ifp, sa, rssi, lqm, npm, srvinfo); } errno_t ifnet_notice_node_presence_v2(ifnet_t ifp, struct sockaddr *sa, struct sockaddr_dl *sdl, int32_t rssi, int lqm, int npm, u_int8_t srvinfo[48]) { /* Support older version if sdl is NULL */ if (sdl == NULL) { return ifnet_notice_node_presence(ifp, sa, rssi, lqm, npm, srvinfo); } if (ifp == NULL || sa == NULL || srvinfo == NULL) { return EINVAL; } if (sa->sa_len > sizeof(struct sockaddr_storage)) { return EINVAL; } if (sa->sa_family != AF_INET6) { return EINVAL; } if (sdl->sdl_family != AF_LINK) { return EINVAL; } return dlil_node_present_v2(ifp, sa, sdl, rssi, lqm, npm, srvinfo); } errno_t ifnet_notice_node_absence(ifnet_t ifp, struct sockaddr *sa) { if (ifp == NULL || sa == NULL) { return EINVAL; } if (sa->sa_len > sizeof(struct sockaddr_storage)) { return EINVAL; } if (sa->sa_family != AF_LINK && sa->sa_family != AF_INET6) { return EINVAL; } dlil_node_absent(ifp, sa); return 0; } errno_t ifnet_notice_primary_elected(ifnet_t ifp) { if (ifp == NULL) { return EINVAL; } dlil_post_msg(ifp, KEV_DL_SUBCLASS, KEV_DL_PRIMARY_ELECTED, NULL, 0, FALSE); return 0; } errno_t ifnet_tx_compl_status(ifnet_t ifp, mbuf_t m, tx_compl_val_t val) { #pragma unused(val) m_do_tx_compl_callback(m, ifp); return 0; } errno_t ifnet_tx_compl(ifnet_t ifp, mbuf_t m) { m_do_tx_compl_callback(m, ifp); return 0; } errno_t ifnet_report_issues(ifnet_t ifp, u_int8_t modid[IFNET_MODIDLEN], u_int8_t info[IFNET_MODARGLEN]) { if (ifp == NULL || modid == NULL) { return EINVAL; } dlil_report_issues(ifp, modid, info); return 0; } errno_t ifnet_set_delegate(ifnet_t ifp, ifnet_t delegated_ifp) { ifnet_t odifp = NULL; if (ifp == NULL) { return EINVAL; } else if (!ifnet_is_attached(ifp, 1)) { return ENXIO; } ifnet_lock_exclusive(ifp); odifp = ifp->if_delegated.ifp; if (odifp != NULL && odifp == delegated_ifp) { /* delegate info is unchanged; nothing more to do */ ifnet_lock_done(ifp); goto done; } // Test if this delegate interface would cause a loop ifnet_t delegate_check_ifp = delegated_ifp; while (delegate_check_ifp != NULL) { if (delegate_check_ifp == ifp) { printf("%s: delegating to %s would cause a loop\n", ifp->if_xname, delegated_ifp->if_xname); ifnet_lock_done(ifp); goto done; } delegate_check_ifp = delegate_check_ifp->if_delegated.ifp; } bzero(&ifp->if_delegated, sizeof(ifp->if_delegated)); if (delegated_ifp != NULL && ifp != delegated_ifp) { uint32_t set_eflags; ifp->if_delegated.ifp = delegated_ifp; ifnet_reference(delegated_ifp); ifp->if_delegated.type = delegated_ifp->if_type; ifp->if_delegated.family = delegated_ifp->if_family; ifp->if_delegated.subfamily = delegated_ifp->if_subfamily; ifp->if_delegated.expensive = delegated_ifp->if_eflags & IFEF_EXPENSIVE ? 1 : 0; ifp->if_delegated.constrained = delegated_ifp->if_xflags & IFXF_CONSTRAINED ? 1 : 0; /* * Propogate flags related to ECN from delegated interface */ if_clear_eflags(ifp, IFEF_ECN_ENABLE | IFEF_ECN_DISABLE); set_eflags = (delegated_ifp->if_eflags & (IFEF_ECN_ENABLE | IFEF_ECN_DISABLE)); if_set_eflags(ifp, set_eflags); printf("%s: is now delegating %s (type 0x%x, family %u, " "sub-family %u)\n", ifp->if_xname, delegated_ifp->if_xname, delegated_ifp->if_type, delegated_ifp->if_family, delegated_ifp->if_subfamily); } ifnet_lock_done(ifp); if (odifp != NULL) { if (odifp != delegated_ifp) { printf("%s: is no longer delegating %s\n", ifp->if_xname, odifp->if_xname); } ifnet_release(odifp); } /* Generate a kernel event */ dlil_post_msg(ifp, KEV_DL_SUBCLASS, KEV_DL_IFDELEGATE_CHANGED, NULL, 0, FALSE); done: /* Release the io ref count */ ifnet_decr_iorefcnt(ifp); return 0; } errno_t ifnet_get_delegate(ifnet_t ifp, ifnet_t *pdelegated_ifp) { if (ifp == NULL || pdelegated_ifp == NULL) { return EINVAL; } else if (!ifnet_is_attached(ifp, 1)) { return ENXIO; } ifnet_lock_shared(ifp); if (ifp->if_delegated.ifp != NULL) { ifnet_reference(ifp->if_delegated.ifp); } *pdelegated_ifp = ifp->if_delegated.ifp; ifnet_lock_done(ifp); /* Release the io ref count */ ifnet_decr_iorefcnt(ifp); return 0; } errno_t ifnet_get_keepalive_offload_frames(ifnet_t ifp, struct ifnet_keepalive_offload_frame *frames_array, u_int32_t frames_array_count, size_t frame_data_offset, u_int32_t *used_frames_count) { u_int32_t i; if (frames_array == NULL || used_frames_count == NULL || frame_data_offset >= IFNET_KEEPALIVE_OFFLOAD_FRAME_DATA_SIZE) { return EINVAL; } /* frame_data_offset should be 32-bit aligned */ if (P2ROUNDUP(frame_data_offset, sizeof(u_int32_t)) != frame_data_offset) { return EINVAL; } *used_frames_count = 0; if (frames_array_count == 0) { return 0; } /* Keep-alive offload not required for CLAT interface */ if (IS_INTF_CLAT46(ifp)) { return 0; } for (i = 0; i < frames_array_count; i++) { struct ifnet_keepalive_offload_frame *frame = frames_array + i; bzero(frame, sizeof(struct ifnet_keepalive_offload_frame)); } /* First collect IPsec related keep-alive frames */ *used_frames_count = key_fill_offload_frames_for_savs(ifp, frames_array, frames_array_count, frame_data_offset); /* If there is more room, collect other UDP keep-alive frames */ if (*used_frames_count < frames_array_count) { udp_fill_keepalive_offload_frames(ifp, frames_array, frames_array_count, frame_data_offset, used_frames_count); } /* If there is more room, collect other TCP keep-alive frames */ if (*used_frames_count < frames_array_count) { tcp_fill_keepalive_offload_frames(ifp, frames_array, frames_array_count, frame_data_offset, used_frames_count); } VERIFY(*used_frames_count <= frames_array_count); return 0; } errno_t ifnet_notify_tcp_keepalive_offload_timeout(ifnet_t ifp, struct ifnet_keepalive_offload_frame *frame) { errno_t error = 0; if (ifp == NULL || frame == NULL) { return EINVAL; } if (frame->type != IFNET_KEEPALIVE_OFFLOAD_FRAME_TCP) { return EINVAL; } if (frame->ether_type != IFNET_KEEPALIVE_OFFLOAD_FRAME_ETHERTYPE_IPV4 && frame->ether_type != IFNET_KEEPALIVE_OFFLOAD_FRAME_ETHERTYPE_IPV6) { return EINVAL; } if (frame->local_port == 0 || frame->remote_port == 0) { return EINVAL; } error = tcp_notify_kao_timeout(ifp, frame); return error; } errno_t ifnet_link_status_report(ifnet_t ifp, const void *buffer, size_t buffer_len) { struct if_link_status *ifsr; errno_t err = 0; if (ifp == NULL || buffer == NULL || buffer_len == 0) { return EINVAL; } ifnet_lock_shared(ifp); /* * Make sure that the interface is attached but there is no need * to take a reference because this call is coming from the driver. */ if (!ifnet_is_attached(ifp, 0)) { ifnet_lock_done(ifp); return ENXIO; } lck_rw_lock_exclusive(&ifp->if_link_status_lock); /* * If this is the first status report then allocate memory * to store it. */ if (ifp->if_link_status == NULL) { ifp->if_link_status = kalloc_type(struct if_link_status, Z_ZERO); if (ifp->if_link_status == NULL) { err = ENOMEM; goto done; } } ifsr = __DECONST(struct if_link_status *, buffer); if (ifp->if_type == IFT_CELLULAR) { struct if_cellular_status_v1 *if_cell_sr, *new_cell_sr; /* * Currently we have a single version -- if it does * not match, just return. */ if (ifsr->ifsr_version != IF_CELLULAR_STATUS_REPORT_CURRENT_VERSION) { err = ENOTSUP; goto done; } if (ifsr->ifsr_len != sizeof(*if_cell_sr)) { err = EINVAL; goto done; } if_cell_sr = &ifp->if_link_status->ifsr_u.ifsr_cell.if_cell_u.if_status_v1; new_cell_sr = &ifsr->ifsr_u.ifsr_cell.if_cell_u.if_status_v1; /* Check if we need to act on any new notifications */ if ((new_cell_sr->valid_bitmask & IF_CELL_UL_MSS_RECOMMENDED_VALID) && new_cell_sr->mss_recommended != if_cell_sr->mss_recommended) { os_atomic_or(&tcbinfo.ipi_flags, INPCBINFO_UPDATE_MSS, relaxed); inpcb_timer_sched(&tcbinfo, INPCB_TIMER_FAST); #if NECP necp_update_all_clients(); #endif } /* Finally copy the new information */ ifp->if_link_status->ifsr_version = ifsr->ifsr_version; ifp->if_link_status->ifsr_len = ifsr->ifsr_len; if_cell_sr->valid_bitmask = 0; bcopy(new_cell_sr, if_cell_sr, sizeof(*if_cell_sr)); } else if (IFNET_IS_WIFI(ifp)) { struct if_wifi_status_v1 *if_wifi_sr, *new_wifi_sr; /* Check version */ if (ifsr->ifsr_version != IF_WIFI_STATUS_REPORT_CURRENT_VERSION) { err = ENOTSUP; goto done; } if (ifsr->ifsr_len != sizeof(*if_wifi_sr)) { err = EINVAL; goto done; } if_wifi_sr = &ifp->if_link_status->ifsr_u.ifsr_wifi.if_wifi_u.if_status_v1; new_wifi_sr = &ifsr->ifsr_u.ifsr_wifi.if_wifi_u.if_status_v1; ifp->if_link_status->ifsr_version = ifsr->ifsr_version; ifp->if_link_status->ifsr_len = ifsr->ifsr_len; if_wifi_sr->valid_bitmask = 0; bcopy(new_wifi_sr, if_wifi_sr, sizeof(*if_wifi_sr)); /* * Update the bandwidth values if we got recent values * reported through the other KPI. */ if (!(new_wifi_sr->valid_bitmask & IF_WIFI_UL_MAX_BANDWIDTH_VALID) && ifp->if_output_bw.max_bw > 0) { if_wifi_sr->valid_bitmask |= IF_WIFI_UL_MAX_BANDWIDTH_VALID; if_wifi_sr->ul_max_bandwidth = ifp->if_output_bw.max_bw > UINT32_MAX ? UINT32_MAX : (uint32_t)ifp->if_output_bw.max_bw; } if (!(new_wifi_sr->valid_bitmask & IF_WIFI_UL_EFFECTIVE_BANDWIDTH_VALID) && ifp->if_output_bw.eff_bw > 0) { if_wifi_sr->valid_bitmask |= IF_WIFI_UL_EFFECTIVE_BANDWIDTH_VALID; if_wifi_sr->ul_effective_bandwidth = ifp->if_output_bw.eff_bw > UINT32_MAX ? UINT32_MAX : (uint32_t)ifp->if_output_bw.eff_bw; } if (!(new_wifi_sr->valid_bitmask & IF_WIFI_DL_MAX_BANDWIDTH_VALID) && ifp->if_input_bw.max_bw > 0) { if_wifi_sr->valid_bitmask |= IF_WIFI_DL_MAX_BANDWIDTH_VALID; if_wifi_sr->dl_max_bandwidth = ifp->if_input_bw.max_bw > UINT32_MAX ? UINT32_MAX : (uint32_t)ifp->if_input_bw.max_bw; } if (!(new_wifi_sr->valid_bitmask & IF_WIFI_DL_EFFECTIVE_BANDWIDTH_VALID) && ifp->if_input_bw.eff_bw > 0) { if_wifi_sr->valid_bitmask |= IF_WIFI_DL_EFFECTIVE_BANDWIDTH_VALID; if_wifi_sr->dl_effective_bandwidth = ifp->if_input_bw.eff_bw > UINT32_MAX ? UINT32_MAX : (uint32_t)ifp->if_input_bw.eff_bw; } } done: lck_rw_done(&ifp->if_link_status_lock); ifnet_lock_done(ifp); return err; } /*************************************************************************/ /* Fastlane QoS Ca */ /*************************************************************************/ errno_t ifnet_set_fastlane_capable(ifnet_t interface, boolean_t capable) { if (interface == NULL) { return EINVAL; } if_set_qosmarking_mode(interface, capable ? IFRTYPE_QOSMARKING_FASTLANE : IFRTYPE_QOSMARKING_MODE_NONE); return 0; } errno_t ifnet_get_fastlane_capable(ifnet_t interface, boolean_t *capable) { if (interface == NULL || capable == NULL) { return EINVAL; } if (interface->if_qosmarking_mode == IFRTYPE_QOSMARKING_FASTLANE) { *capable = true; } else { *capable = false; } return 0; } errno_t ifnet_get_unsent_bytes(ifnet_t interface, int64_t *unsent_bytes) { int64_t bytes; if (interface == NULL || unsent_bytes == NULL) { return EINVAL; } bytes = *unsent_bytes = 0; if (!IF_FULLY_ATTACHED(interface)) { return ENXIO; } bytes = interface->if_sndbyte_unsent; if (interface->if_eflags & IFEF_TXSTART) { bytes += IFCQ_BYTES(interface->if_snd); } *unsent_bytes = bytes; return 0; } errno_t ifnet_get_buffer_status(const ifnet_t ifp, ifnet_buffer_status_t *buf_status) { if (ifp == NULL || buf_status == NULL) { return EINVAL; } bzero(buf_status, sizeof(*buf_status)); if (!IF_FULLY_ATTACHED(ifp)) { return ENXIO; } if (ifp->if_eflags & IFEF_TXSTART) { buf_status->buf_interface = IFCQ_BYTES(ifp->if_snd); } buf_status->buf_sndbuf = ((buf_status->buf_interface != 0) || (ifp->if_sndbyte_unsent != 0)) ? 1 : 0; return 0; } void ifnet_normalise_unsent_data(void) { struct ifnet *ifp; ifnet_head_lock_shared(); TAILQ_FOREACH(ifp, &ifnet_head, if_link) { ifnet_lock_exclusive(ifp); if (!IF_FULLY_ATTACHED(ifp)) { ifnet_lock_done(ifp); continue; } if (!(ifp->if_eflags & IFEF_TXSTART)) { ifnet_lock_done(ifp); continue; } if (ifp->if_sndbyte_total > 0 || IFCQ_BYTES(ifp->if_snd) > 0) { ifp->if_unsent_data_cnt++; } ifnet_lock_done(ifp); } ifnet_head_done(); } errno_t ifnet_set_low_power_mode(ifnet_t ifp, boolean_t on) { errno_t error; error = if_set_low_power(ifp, on); return error; } errno_t ifnet_get_low_power_mode(ifnet_t ifp, boolean_t *on) { if (ifp == NULL || on == NULL) { return EINVAL; } *on = ((ifp->if_xflags & IFXF_LOW_POWER) != 0); return 0; }