/* * Copyright (c) 2010-2021 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 #define RVI_IF_NAME "rvi" #define RVI_DIR_IN IFF_LINK0 #define RVI_DIR_OUT IFF_LINK1 #define RVI_DIR_INOUT (RVI_DIR_IN | RVI_DIR_OUT) #define RVI_IF_FAMILY IFNET_FAMILY_LOOPBACK #define RVI_IF_TYPE IFT_OTHER #define RVI_IF_FLAGS (IFF_UP | IFF_DEBUG | RVI_DIR_INOUT) struct rvi_client_t { LIST_ENTRY(rvi_client_t) _cle; ifnet_t _ifp; uint32_t _unit; uint32_t _vif; uint32_t _raw_count; uint32_t _pktap_count; }; static LIST_HEAD(, rvi_client_t) _s_rvi_clients; static LCK_GRP_DECLARE(rvi_grp, "remote virtual interface lock"); static LCK_RW_DECLARE(rvi_mtx, &rvi_grp); static kern_ctl_ref rvi_kernctl = NULL; kern_return_t rvi_start(kmod_info_t *, void *); kern_return_t rvi_stop(kmod_info_t *, void *); static void rvi_insert_client(struct rvi_client_t *); static errno_t rvi_create_if(struct rvi_client_t *); static errno_t rvi_destroy_if(struct rvi_client_t *); static inline void rvi_lock_shared(lck_rw_t *); static inline void rvi_lock_exclusive(lck_rw_t *); static inline void rvi_lock_done_shared(lck_rw_t *); static inline void rvi_lock_done_exclusive(lck_rw_t *); static errno_t rvi_output(ifnet_t, mbuf_t); static errno_t rvi_demux(ifnet_t, mbuf_t, char *, protocol_family_t *); static errno_t rvi_ioctl(ifnet_t, unsigned long, void *); static errno_t rvi_add_proto(ifnet_t, protocol_family_t, const struct ifnet_demux_desc *, uint32_t); static errno_t rvi_del_proto(ifnet_t, protocol_family_t); static errno_t rvi_set_bpf_tap(ifnet_t, uint32_t, bpf_tap_mode); static void rvi_detach(ifnet_t); static errno_t rvi_bpf_tap(ifnet_t, mbuf_t, int, struct rvi_client_t *, struct pktap_header *); static errno_t rvi_register_control(void); static errno_t rvi_ctl_connect(kern_ctl_ref, struct sockaddr_ctl *, void **); static errno_t rvi_ctl_send(kern_ctl_ref, uint32_t, void *, mbuf_t, int); static errno_t rvi_ctl_disconnect(kern_ctl_ref, uint32_t, void *); static errno_t rvi_ctl_getopt(kern_ctl_ref, uint32_t, void *, int, void *, size_t *); int rvi_init() { int error = 0; if ((error = rvi_register_control()) != 0) { os_log(OS_LOG_DEFAULT, "rvi_start failed: rvi_register_control failure"); return error; } return 0; } static inline void rvi_lock_shared(lck_rw_t *mtx) { lck_rw_lock_shared(mtx); } static inline void rvi_lock_exclusive(lck_rw_t *mtx) { lck_rw_lock_exclusive(mtx); } static inline void rvi_lock_done_shared(lck_rw_t *mtx) { lck_rw_unlock_shared(mtx); } static inline void rvi_lock_done_exclusive(lck_rw_t *mtx) { lck_rw_unlock_exclusive(mtx); } static errno_t rvi_create_if(struct rvi_client_t *client) { errno_t err = 0; struct ifnet_init_params rvi_ifinit; memset(&rvi_ifinit, 0x0, sizeof(rvi_ifinit)); rvi_ifinit.name = RVI_IF_NAME; rvi_ifinit.unit = client->_vif; rvi_ifinit.type = RVI_IF_TYPE; rvi_ifinit.family = RVI_IF_FAMILY; rvi_ifinit.output = rvi_output; rvi_ifinit.demux = rvi_demux; rvi_ifinit.add_proto = rvi_add_proto; rvi_ifinit.del_proto = rvi_del_proto; rvi_ifinit.ioctl = rvi_ioctl; rvi_ifinit.detach = rvi_detach; rvi_ifinit.softc = client; err = ifnet_allocate(&rvi_ifinit, &client->_ifp); if (err != 0) { os_log(OS_LOG_DEFAULT, "%s: ifnet_allocate for %s%d failed - %d", __func__, RVI_IF_NAME, client->_vif, err); goto done; } ifnet_set_flags(client->_ifp, RVI_IF_FLAGS, RVI_IF_FLAGS); err = ifnet_attach(client->_ifp, NULL); if (err != 0) { os_log(OS_LOG_DEFAULT, "%s: ifnet_attach for %s%d failed - %d", __func__, RVI_IF_NAME, client->_vif, err); ifnet_release(client->_ifp); goto done; } bpf_attach(client->_ifp, DLT_PKTAP, sizeof(struct pktap_header), NULL, rvi_set_bpf_tap); bpf_attach(client->_ifp, DLT_RAW, 0, NULL, rvi_set_bpf_tap); done: return err; } static errno_t rvi_destroy_if(struct rvi_client_t *client) { errno_t err = 0; if (client == NULL) { goto done; } err = ifnet_detach(client->_ifp); if (err != 0) { os_log(OS_LOG_DEFAULT, "%s: ifnet_detach for %s%d failed - %d", __func__, RVI_IF_NAME, client->_vif, err); } done: return err; } static void rvi_detach(ifnet_t ifp) { struct rvi_client_t *client; rvi_lock_exclusive(&rvi_mtx); client = ifnet_softc(ifp); LIST_REMOVE(client, _cle); ifnet_release(ifp); rvi_lock_done_exclusive(&rvi_mtx); kfree_type(struct rvi_client_t, client); } static void rvi_insert_client(struct rvi_client_t *client) { struct rvi_client_t *itr = NULL; uint32_t ph = 0; rvi_lock_exclusive(&rvi_mtx); if (LIST_EMPTY(&_s_rvi_clients)) { LIST_INSERT_HEAD(&_s_rvi_clients, client, _cle); } else { LIST_FOREACH(itr, &_s_rvi_clients, _cle) { if (ph != itr->_vif) { LIST_INSERT_BEFORE(itr, client, _cle); break; } ph++; if (LIST_NEXT(itr, _cle) == NULL) { LIST_INSERT_AFTER(itr, client, _cle); break; } } } rvi_lock_done_exclusive(&rvi_mtx); client->_vif = ph; } static void rvi_remove_client(uint32_t unit) { struct rvi_client_t *client = NULL; rvi_lock_shared(&rvi_mtx); LIST_FOREACH(client, &_s_rvi_clients, _cle) { if (client->_unit == unit) { break; } } rvi_lock_done_shared(&rvi_mtx); if (client == NULL) { panic("rvi_ctl_disconnect: received a disconnect notification without a cache entry"); } (void)rvi_destroy_if(client); } static errno_t rvi_register_control(void) { errno_t err = 0; struct kern_ctl_reg kern_ctl; bzero(&kern_ctl, sizeof(kern_ctl)); strlcpy(kern_ctl.ctl_name, RVI_CONTROL_NAME, sizeof(kern_ctl.ctl_name)); kern_ctl.ctl_name[sizeof(kern_ctl.ctl_name) - 1] = 0; kern_ctl.ctl_flags = CTL_FLAG_PRIVILEGED; kern_ctl.ctl_sendsize = RVI_BUFFERSZ; kern_ctl.ctl_recvsize = RVI_BUFFERSZ; kern_ctl.ctl_connect = rvi_ctl_connect; kern_ctl.ctl_disconnect = rvi_ctl_disconnect; kern_ctl.ctl_send = rvi_ctl_send; kern_ctl.ctl_setopt = NULL; kern_ctl.ctl_getopt = rvi_ctl_getopt; err = ctl_register(&kern_ctl, &rvi_kernctl); return err; } static errno_t rvi_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac, void **unitinfo) { #pragma unused(kctlref) errno_t err = 0; struct rvi_client_t *client = NULL; client = kalloc_type(struct rvi_client_t, Z_WAITOK | Z_ZERO | Z_NOFAIL); client->_unit = sac->sc_unit; rvi_insert_client(client); err = rvi_create_if(client); if (err != 0) { os_log(OS_LOG_DEFAULT, "%s: failure to create virtual interface %d", __func__, err); } *unitinfo = client; return err; } static errno_t rvi_ctl_disconnect(kern_ctl_ref kctlref, uint32_t unit, void *unitinfo) { #pragma unused(kctlref) #pragma unused(unitinfo) errno_t err = 0; rvi_remove_client(unit); return err; } static errno_t rvi_ctl_getopt(kern_ctl_ref kctlref, uint32_t unit, void *unitinfo, int opt, void *data, size_t *len) { #pragma unused(kctlref) #pragma unused(unit) errno_t err = 0; int n; struct rvi_client_t *client = (struct rvi_client_t *)unitinfo; rvi_lock_shared(&rvi_mtx); switch (opt) { case RVI_COMMAND_GET_INTERFACE: if (data == NULL || len == NULL) { err = EINVAL; break; } n = scnprintf(data, *len, "%s%u", ifnet_name(client->_ifp), ifnet_unit(client->_ifp)); *len = n + 1; break; case RVI_COMMAND_VERSION: if (data == NULL || len == NULL || *len < sizeof(int)) { err = EINVAL; break; } *(int *)data = RVI_VERSION_CURRENT; *len = sizeof(int); break; default: err = ENOPROTOOPT; break; } rvi_lock_done_shared(&rvi_mtx); return err; } static errno_t rvi_ctl_send(kern_ctl_ref kctlref, uint32_t unit, void *unitinfo, mbuf_t m, int flags) { #pragma unused(kctlref) #pragma unused(unit) #pragma unused(flags) errno_t err = 0; struct rvi_client_t *client = (struct rvi_client_t *)unitinfo; struct pktap_header pktap_hdr; uint32_t hdr_length; err = mbuf_copydata(m, 0, sizeof(struct pktap_header), (void *)&pktap_hdr); if (err != 0) { os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata failed %d", __func__, err); goto done; } hdr_length = pktap_hdr.pth_length; mbuf_adj(m, hdr_length); rvi_lock_shared(&rvi_mtx); err = rvi_bpf_tap(client->_ifp, m, pktap_hdr.pth_flags & PTH_FLAG_DIR_OUT ? 1 : 0, client, &pktap_hdr); rvi_lock_done_shared(&rvi_mtx); done: mbuf_freem(m); return err; } static errno_t rvi_output(ifnet_t ifp, mbuf_t m) { #pragma unused(ifp) mbuf_freem(m); return 0; } static errno_t rvi_demux(ifnet_t ifp, mbuf_t m, char *header, protocol_family_t *ppf) { #pragma unused(ifp) #pragma unused(m) #pragma unused(header) #pragma unused(ppf) return ENOTSUP; } static errno_t rvi_add_proto( ifnet_t ifp, protocol_family_t pf, const struct ifnet_demux_desc *dmx, uint32_t cnt) { #pragma unused(ifp) #pragma unused(pf) #pragma unused(dmx) #pragma unused(cnt) return EINVAL; } static errno_t rvi_del_proto(ifnet_t ifp, protocol_family_t pf) { #pragma unused(ifp) #pragma unused(pf) return EINVAL; } static errno_t rvi_ioctl(ifnet_t ifp, unsigned long cmd, void *data) { #pragma unused(ifp) #pragma unused(cmd) #pragma unused(data) return ENOTSUP; } static errno_t rvi_set_bpf_tap(ifnet_t ifp, uint32_t dlt, bpf_tap_mode mode) { struct rvi_client_t *client; rvi_lock_shared(&rvi_mtx); client = ifnet_softc(ifp); if (client == NULL) { os_log(OS_LOG_DEFAULT, "%s: ifnet_softc is NULL for ifp %p", __func__, ifp); goto done; } switch (dlt) { case DLT_RAW: if (mode == 0) { if (client->_raw_count > 0) { client->_raw_count--; } } else { client->_raw_count++; } break; case DLT_PKTAP: if (mode == 0) { if (client->_pktap_count > 0) { client->_pktap_count--; } } else { client->_pktap_count++; } break; } done: rvi_lock_done_shared(&rvi_mtx); return 0; } /* * Note: called with the rvi lock taken as shared */ static errno_t rvi_bpf_tap(ifnet_t ifp, mbuf_t m, int outgoing, struct rvi_client_t *client, struct pktap_header *pktap_hdr) { #pragma unused(ifp) errno_t err = 0; void (*bpf_tap_fn)(ifnet_t, uint32_t, mbuf_t, void *, size_t ) = outgoing ? bpf_tap_out : bpf_tap_in; if (client->_pktap_count > 0) { bpf_tap_fn(client->_ifp, DLT_PKTAP, m, pktap_hdr, sizeof(struct pktap_header)); } if (client->_raw_count > 0 && (pktap_hdr->pth_protocol_family == AF_INET || pktap_hdr->pth_protocol_family == AF_INET6)) { /* * We can play just with the length of the first mbuf in the * chain because bpf_tap_imp() disregard the packet length * of the mbuf packet header. */ if (pktap_hdr->pth_frame_pre_length > mbuf_len(m)) { err = mbuf_pullup(&m, pktap_hdr->pth_frame_pre_length); if (err != 0) { os_log(OS_LOG_DEFAULT, "%s mbuf_pullup failed", __func__); return 0; } } if (mbuf_setdata(m, m_mtod_current(m) + pktap_hdr->pth_frame_pre_length, m->m_len - pktap_hdr->pth_frame_pre_length) == 0) { bpf_tap_fn(client->_ifp, DLT_RAW, m, NULL, 0); mbuf_setdata(m, m_mtod_current(m) - pktap_hdr->pth_frame_pre_length, m->m_len + pktap_hdr->pth_frame_pre_length); } } return err; }