/* Network Interface functions */ static errno_t utun_output( ifnet_t interface, mbuf_t data) { struct utun_pcb *pcb = ifnet_softc(interface); errno_t result; if (m_pktlen(data) >= 4) { bpf_tap_out(pcb->utun_ifp, DLT_NULL, data, 0, 0); } if (pcb->utun_flags & UTUN_FLAGS_NO_OUTPUT) { /* flush data */ mbuf_freem(data); return 0; } // otherwise, fall thru to ctl_enqueumbuf if (pcb->utun_ctlref) { int length; // only pass packets to utun-crypto if crypto is enabled and 'suspend data traffic' is not. if ((pcb->utun_flags & (UTUN_FLAGS_CRYPTO | UTUN_FLAGS_CRYPTO_STOP_DATA_TRAFFIC)) == UTUN_FLAGS_CRYPTO) { if (utun_pkt_crypto_output(pcb, &data) == 0) { return 0; } } /* * The ABI requires the protocol in network byte order */ if (m_pktlen(data) >= 4) *(u_int32_t *)mbuf_data(data) = htonl(*(u_int32_t *)mbuf_data(data)); length = mbuf_pkthdr_len(data); result = ctl_enqueuembuf(pcb->utun_ctlref, pcb->utun_unit, data, CTL_DATA_EOR); if (result != 0) { mbuf_freem(data); printf("utun_output - ctl_enqueuembuf failed: %d\n", result); ifnet_stat_increment_out(interface, 0, 0, 1); } else { if (!pcb->utun_ext_ifdata_stats) ifnet_stat_increment_out(interface, 1, length, 0); } } else mbuf_freem(data); return 0; }
static errno_t utun_output(ifnet_t interface, mbuf_t data) { struct utun_pcb *pcb = ifnet_softc(interface); errno_t result; VERIFY(interface == pcb->utun_ifp); if (m_pktlen(data) >= (int32_t)UTUN_HEADER_SIZE(pcb)) { bpf_tap_out(pcb->utun_ifp, DLT_NULL, data, 0, 0); } if (pcb->utun_flags & UTUN_FLAGS_NO_OUTPUT) { /* flush data */ mbuf_freem(data); return 0; } // otherwise, fall thru to ctl_enqueumbuf if (pcb->utun_ctlref) { int length; /* * The ABI requires the protocol in network byte order */ if (m_pktlen(data) >= (int32_t)UTUN_HEADER_SIZE(pcb)) { *(u_int32_t *)mbuf_data(data) = htonl(*(u_int32_t *)mbuf_data(data)); } length = mbuf_pkthdr_len(data); result = ctl_enqueuembuf(pcb->utun_ctlref, pcb->utun_unit, data, CTL_DATA_EOR); if (result != 0) { mbuf_freem(data); printf("utun_output - ctl_enqueuembuf failed: %d\n", result); ifnet_stat_increment_out(interface, 0, 0, 1); } else { if (!pcb->utun_ext_ifdata_stats) ifnet_stat_increment_out(interface, 1, length, 0); } } else mbuf_freem(data); return 0; }
static errno_t utun_framer( __unused ifnet_t interface, mbuf_t *packet, __unused const struct sockaddr *dest, __unused const char *desk_linkaddr, const char *frame_type, u_int32_t *prepend_len, u_int32_t *postpend_len) { if (mbuf_prepend(packet, sizeof(protocol_family_t), MBUF_DONTWAIT) != 0) { printf("utun_framer - ifnet_output prepend failed\n"); ifnet_stat_increment_out(interface, 0, 0, 1); // just return, because the buffer was freed in mbuf_prepend return EJUSTRETURN; } if (prepend_len != NULL) *prepend_len = sizeof(protocol_family_t); if (postpend_len != NULL) *postpend_len = 0; // place protocol number at the beginning of the mbuf *(protocol_family_t *)mbuf_data(*packet) = *(protocol_family_t *)(uintptr_t)(size_t)frame_type; return 0; }
static errno_t utun_framer( __unused ifnet_t interface, mbuf_t *packet, __unused const struct sockaddr *dest, __unused const char *desk_linkaddr, const char *frame_type, u_int32_t *prepend_len, u_int32_t *postpend_len) { struct utun_pcb *pcb = ifnet_softc(interface); VERIFY(interface == pcb->utun_ifp); u_int32_t header_length = UTUN_HEADER_SIZE(pcb); if (mbuf_prepend(packet, header_length, MBUF_DONTWAIT) != 0) { printf("utun_framer - ifnet_output prepend failed\n"); ifnet_stat_increment_out(interface, 0, 0, 1); // just return, because the buffer was freed in mbuf_prepend return EJUSTRETURN; } if (prepend_len != NULL) *prepend_len = header_length; if (postpend_len != NULL) *postpend_len = 0; // place protocol number at the beginning of the mbuf *(protocol_family_t *)mbuf_data(*packet) = *(protocol_family_t *)(uintptr_t)(size_t)frame_type; return 0; }
static errno_t utun_ctl_setopt( __unused kern_ctl_ref kctlref, __unused u_int32_t unit, void *unitinfo, int opt, void *data, size_t len) { struct utun_pcb *pcb = unitinfo; errno_t result = 0; /* check for privileges for privileged options */ switch (opt) { case UTUN_OPT_FLAGS: case UTUN_OPT_EXT_IFDATA_STATS: case UTUN_OPT_SET_DELEGATE_INTERFACE: if (kauth_cred_issuser(kauth_cred_get()) == 0) { return EPERM; } break; } switch (opt) { case UTUN_OPT_FLAGS: if (len != sizeof(u_int32_t)) result = EMSGSIZE; else pcb->utun_flags = *(u_int32_t *)data; break; case UTUN_OPT_ENABLE_CRYPTO: result = utun_ctl_enable_crypto(kctlref, unit, unitinfo, opt, data, len); break; case UTUN_OPT_CONFIG_CRYPTO_KEYS: result = utun_ctl_config_crypto_keys(kctlref, unit, unitinfo, opt, data, len); break; case UTUN_OPT_UNCONFIG_CRYPTO_KEYS: result = utun_ctl_unconfig_crypto_keys(kctlref, unit, unitinfo, opt, data, len); break; case UTUN_OPT_DISABLE_CRYPTO: result = utun_ctl_disable_crypto(kctlref, unit, unitinfo, opt, data, len); break; case UTUN_OPT_STOP_CRYPTO_DATA_TRAFFIC: result = utun_ctl_stop_crypto_data_traffic(kctlref, unit, unitinfo, opt, data, len); break; case UTUN_OPT_START_CRYPTO_DATA_TRAFFIC: result = utun_ctl_start_crypto_data_traffic(kctlref, unit, unitinfo, opt, data, len); break; case UTUN_OPT_CONFIG_CRYPTO_FRAMER: result = utun_ctl_config_crypto_framer(kctlref, unit, unitinfo, opt, data, len); break; case UTUN_OPT_UNCONFIG_CRYPTO_FRAMER: result = utun_ctl_unconfig_crypto_framer(kctlref, unit, unitinfo, opt, data, len); break; case UTUN_OPT_EXT_IFDATA_STATS: if (len != sizeof(int)) { result = EMSGSIZE; break; } pcb->utun_ext_ifdata_stats = (*(int *)data) ? 1 : 0; break; case UTUN_OPT_INC_IFDATA_STATS_IN: case UTUN_OPT_INC_IFDATA_STATS_OUT: { struct utun_stats_param *utsp = (struct utun_stats_param *)data; if (utsp == NULL || len < sizeof(struct utun_stats_param)) { result = EINVAL; break; } if (!pcb->utun_ext_ifdata_stats) { result = EINVAL; break; } if (opt == UTUN_OPT_INC_IFDATA_STATS_IN) ifnet_stat_increment_in(pcb->utun_ifp, utsp->utsp_packets, utsp->utsp_bytes, utsp->utsp_errors); else ifnet_stat_increment_out(pcb->utun_ifp, utsp->utsp_packets, utsp->utsp_bytes, utsp->utsp_errors); break; } case UTUN_OPT_SET_DELEGATE_INTERFACE: { ifnet_t del_ifp = NULL; char name[IFNAMSIZ]; if (len > IFNAMSIZ - 1) { result = EMSGSIZE; break; } if (len != 0) { /* if len==0, del_ifp will be NULL causing the delegate to be removed */ bcopy(data, name, len); name[len] = 0; result = ifnet_find_by_name(name, &del_ifp); } if (result == 0) { result = ifnet_set_delegate(pcb->utun_ifp, del_ifp); if (del_ifp) ifnet_release(del_ifp); } break; } default: result = ENOPROTOOPT; break; } return result; }
static errno_t utun_ctl_setopt( __unused kern_ctl_ref kctlref, __unused u_int32_t unit, void *unitinfo, int opt, void *data, size_t len) { struct utun_pcb *pcb = unitinfo; errno_t result = 0; /* check for privileges for privileged options */ switch (opt) { case UTUN_OPT_FLAGS: case UTUN_OPT_EXT_IFDATA_STATS: case UTUN_OPT_SET_DELEGATE_INTERFACE: if (kauth_cred_issuser(kauth_cred_get()) == 0) { return EPERM; } break; } switch (opt) { case UTUN_OPT_FLAGS: if (len != sizeof(u_int32_t)) { result = EMSGSIZE; } else { u_int32_t old_flags = pcb->utun_flags; pcb->utun_flags = *(u_int32_t *)data; if (((old_flags ^ pcb->utun_flags) & UTUN_FLAGS_ENABLE_PROC_UUID)) { // If UTUN_FLAGS_ENABLE_PROC_UUID flag changed, update bpf bpfdetach(pcb->utun_ifp); bpfattach(pcb->utun_ifp, DLT_NULL, UTUN_HEADER_SIZE(pcb)); } } break; case UTUN_OPT_EXT_IFDATA_STATS: if (len != sizeof(int)) { result = EMSGSIZE; break; } pcb->utun_ext_ifdata_stats = (*(int *)data) ? 1 : 0; break; case UTUN_OPT_INC_IFDATA_STATS_IN: case UTUN_OPT_INC_IFDATA_STATS_OUT: { struct utun_stats_param *utsp = (struct utun_stats_param *)data; if (utsp == NULL || len < sizeof(struct utun_stats_param)) { result = EINVAL; break; } if (!pcb->utun_ext_ifdata_stats) { result = EINVAL; break; } if (opt == UTUN_OPT_INC_IFDATA_STATS_IN) ifnet_stat_increment_in(pcb->utun_ifp, utsp->utsp_packets, utsp->utsp_bytes, utsp->utsp_errors); else ifnet_stat_increment_out(pcb->utun_ifp, utsp->utsp_packets, utsp->utsp_bytes, utsp->utsp_errors); break; } case UTUN_OPT_SET_DELEGATE_INTERFACE: { ifnet_t del_ifp = NULL; char name[IFNAMSIZ]; if (len > IFNAMSIZ - 1) { result = EMSGSIZE; break; } if (len != 0) { /* if len==0, del_ifp will be NULL causing the delegate to be removed */ bcopy(data, name, len); name[len] = 0; result = ifnet_find_by_name(name, &del_ifp); } if (result == 0) { result = ifnet_set_delegate(pcb->utun_ifp, del_ifp); if (del_ifp) ifnet_release(del_ifp); } break; } case UTUN_OPT_MAX_PENDING_PACKETS: { u_int32_t max_pending_packets = 0; if (len != sizeof(u_int32_t)) { result = EMSGSIZE; break; } max_pending_packets = *(u_int32_t *)data; if (max_pending_packets == 0) { result = EINVAL; break; } pcb->utun_max_pending_packets = max_pending_packets; break; } default: { result = ENOPROTOOPT; break; } } return result; }
/* Network Interface functions */ static errno_t ipsec_output(ifnet_t interface, mbuf_t data) { struct ipsec_pcb *pcb = ifnet_softc(interface); struct ipsec_output_state ipsec_state; struct route ro; struct route_in6 ro6; int length; struct ip *ip; struct ip6_hdr *ip6; struct ip_out_args ipoa; struct ip6_out_args ip6oa; int error = 0; u_int ip_version = 0; uint32_t af; int flags = 0; struct flowadv *adv = NULL; // Make sure this packet isn't looping through the interface if (necp_get_last_interface_index_from_packet(data) == interface->if_index) { error = -1; goto ipsec_output_err; } // Mark the interface so NECP can evaluate tunnel policy necp_mark_packet_from_interface(data, interface); ip = mtod(data, struct ip *); ip_version = ip->ip_v; switch (ip_version) { case 4: /* Tap */ af = AF_INET; bpf_tap_out(pcb->ipsec_ifp, DLT_NULL, data, &af, sizeof(af)); /* Apply encryption */ bzero(&ipsec_state, sizeof(ipsec_state)); ipsec_state.m = data; ipsec_state.dst = (struct sockaddr *)&ip->ip_dst; bzero(&ipsec_state.ro, sizeof(ipsec_state.ro)); error = ipsec4_interface_output(&ipsec_state, interface); data = ipsec_state.m; if (error || data == NULL) { printf("ipsec_output: ipsec4_output error %d.\n", error); goto ipsec_output_err; } /* Set traffic class, set flow */ m_set_service_class(data, pcb->ipsec_output_service_class); data->m_pkthdr.pkt_flowsrc = FLOWSRC_IFNET; data->m_pkthdr.pkt_flowid = interface->if_flowhash; data->m_pkthdr.pkt_proto = ip->ip_p; data->m_pkthdr.pkt_flags = (PKTF_FLOW_ID | PKTF_FLOW_ADV | PKTF_FLOW_LOCALSRC); /* Flip endian-ness for ip_output */ ip = mtod(data, struct ip *); NTOHS(ip->ip_len); NTOHS(ip->ip_off); /* Increment statistics */ length = mbuf_pkthdr_len(data); ifnet_stat_increment_out(interface, 1, length, 0); /* Send to ip_output */ bzero(&ro, sizeof(ro)); flags = IP_OUTARGS | /* Passing out args to specify interface */ IP_NOIPSEC; /* To ensure the packet doesn't go through ipsec twice */ bzero(&ipoa, sizeof(ipoa)); ipoa.ipoa_flowadv.code = 0; ipoa.ipoa_flags = IPOAF_SELECT_SRCIF | IPOAF_BOUND_SRCADDR; if (ipsec_state.outgoing_if) { ipoa.ipoa_boundif = ipsec_state.outgoing_if; ipoa.ipoa_flags |= IPOAF_BOUND_IF; } adv = &ipoa.ipoa_flowadv; (void) ip_output(data, NULL, &ro, flags, NULL, &ipoa); data = NULL; if (adv->code == FADV_FLOW_CONTROLLED || adv->code == FADV_SUSPENDED) { error = ENOBUFS; ifnet_disable_output(interface); } goto done; case 6: af = AF_INET6; bpf_tap_out(pcb->ipsec_ifp, DLT_NULL, data, &af, sizeof(af)); data = ipsec6_splithdr(data); ip6 = mtod(data, struct ip6_hdr *); bzero(&ipsec_state, sizeof(ipsec_state)); ipsec_state.m = data; ipsec_state.dst = (struct sockaddr *)&ip6->ip6_dst; bzero(&ipsec_state.ro, sizeof(ipsec_state.ro)); error = ipsec6_interface_output(&ipsec_state, interface, &ip6->ip6_nxt, ipsec_state.m); if (error == 0 && ipsec_state.tunneled == 4) /* tunneled in IPv4 - packet is gone */ goto done; data = ipsec_state.m; if (error || data == NULL) { printf("ipsec_output: ipsec6_output error %d.\n", error); goto ipsec_output_err; } /* Set traffic class, set flow */ m_set_service_class(data, pcb->ipsec_output_service_class); data->m_pkthdr.pkt_flowsrc = FLOWSRC_IFNET; data->m_pkthdr.pkt_flowid = interface->if_flowhash; data->m_pkthdr.pkt_proto = ip6->ip6_nxt; data->m_pkthdr.pkt_flags = (PKTF_FLOW_ID | PKTF_FLOW_ADV | PKTF_FLOW_LOCALSRC); /* Increment statistics */ length = mbuf_pkthdr_len(data); ifnet_stat_increment_out(interface, 1, length, 0); /* Send to ip6_output */ bzero(&ro6, sizeof(ro6)); flags = IPV6_OUTARGS; bzero(&ip6oa, sizeof(ip6oa)); ip6oa.ip6oa_flowadv.code = 0; ip6oa.ip6oa_flags = IPOAF_SELECT_SRCIF | IPOAF_BOUND_SRCADDR; if (ipsec_state.outgoing_if) { ip6oa.ip6oa_boundif = ipsec_state.outgoing_if; ip6oa.ip6oa_flags |= IPOAF_BOUND_IF; } adv = &ip6oa.ip6oa_flowadv; (void) ip6_output(data, NULL, &ro6, flags, NULL, NULL, &ip6oa); data = NULL; if (adv->code == FADV_FLOW_CONTROLLED || adv->code == FADV_SUSPENDED) { error = ENOBUFS; ifnet_disable_output(interface); } goto done; default: printf("ipsec_output: Received unknown packet version %d.\n", ip_version); error = -1; goto ipsec_output_err; } done: return error; ipsec_output_err: if (data) mbuf_freem(data); goto done; }
static errno_t ipsec_ctl_setopt(__unused kern_ctl_ref kctlref, __unused u_int32_t unit, void *unitinfo, int opt, void *data, size_t len) { struct ipsec_pcb *pcb = unitinfo; errno_t result = 0; /* check for privileges for privileged options */ switch (opt) { case IPSEC_OPT_FLAGS: case IPSEC_OPT_EXT_IFDATA_STATS: case IPSEC_OPT_SET_DELEGATE_INTERFACE: case IPSEC_OPT_OUTPUT_TRAFFIC_CLASS: if (kauth_cred_issuser(kauth_cred_get()) == 0) { return EPERM; } break; } switch (opt) { case IPSEC_OPT_FLAGS: if (len != sizeof(u_int32_t)) result = EMSGSIZE; else pcb->ipsec_flags = *(u_int32_t *)data; break; case IPSEC_OPT_EXT_IFDATA_STATS: if (len != sizeof(int)) { result = EMSGSIZE; break; } pcb->ipsec_ext_ifdata_stats = (*(int *)data) ? 1 : 0; break; case IPSEC_OPT_INC_IFDATA_STATS_IN: case IPSEC_OPT_INC_IFDATA_STATS_OUT: { struct ipsec_stats_param *utsp = (struct ipsec_stats_param *)data; if (utsp == NULL || len < sizeof(struct ipsec_stats_param)) { result = EINVAL; break; } if (!pcb->ipsec_ext_ifdata_stats) { result = EINVAL; break; } if (opt == IPSEC_OPT_INC_IFDATA_STATS_IN) ifnet_stat_increment_in(pcb->ipsec_ifp, utsp->utsp_packets, utsp->utsp_bytes, utsp->utsp_errors); else ifnet_stat_increment_out(pcb->ipsec_ifp, utsp->utsp_packets, utsp->utsp_bytes, utsp->utsp_errors); break; } case IPSEC_OPT_SET_DELEGATE_INTERFACE: { ifnet_t del_ifp = NULL; char name[IFNAMSIZ]; if (len > IFNAMSIZ - 1) { result = EMSGSIZE; break; } if (len != 0) { /* if len==0, del_ifp will be NULL causing the delegate to be removed */ bcopy(data, name, len); name[len] = 0; result = ifnet_find_by_name(name, &del_ifp); } if (result == 0) { result = ifnet_set_delegate(pcb->ipsec_ifp, del_ifp); if (del_ifp) ifnet_release(del_ifp); } break; } case IPSEC_OPT_OUTPUT_TRAFFIC_CLASS: { if (len != sizeof(int)) { result = EMSGSIZE; break; } mbuf_svc_class_t output_service_class = so_tc2msc(*(int *)data); if (output_service_class == MBUF_SC_UNSPEC) { pcb->ipsec_output_service_class = MBUF_SC_OAM; } else { pcb->ipsec_output_service_class = output_service_class; } break; } default: result = ENOPROTOOPT; break; } return result; }