X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/39236c6e673c41db228275375ab7fdb0f837b292..008676633c2ad2c325837c2b64915f7ded690a8f:/bsd/net/if_ipsec.c diff --git a/bsd/net/if_ipsec.c b/bsd/net/if_ipsec.c index e1aac8335..9e98a05b9 100644 --- a/bsd/net/if_ipsec.c +++ b/bsd/net/if_ipsec.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Apple Inc. All rights reserved. + * Copyright (c) 2012-2015 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -31,12 +31,11 @@ #include #include #include +#include #include #include #include #include -#include -#include #include #include #include @@ -48,6 +47,12 @@ #include #include #include +#include +#include +#include + +extern int net_qos_policy_restricted; +extern int net_qos_policy_restrict_avapps; /* Kernel Control functions */ static errno_t ipsec_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac, @@ -83,42 +88,15 @@ static errno_t ipsec_proto_pre_output(ifnet_t interface, protocol_family_t proto static kern_ctl_ref ipsec_kctlref; static u_int32_t ipsec_family; -static OSMallocTag ipsec_malloc_tag; -static SInt32 ipsec_ifcount = 0; #define IPSECQ_MAXLEN 256 -/* Prepend length */ -static void* -ipsec_alloc(size_t size) -{ - size_t *mem = OSMalloc(size + sizeof(size_t), ipsec_malloc_tag); - - if (mem) { - *mem = size + sizeof(size_t); - mem++; - } - - return (void*)mem; -} - -static void -ipsec_free(void *ptr) -{ - size_t *size = ptr; - size--; - OSFree(size, *size, ipsec_malloc_tag); -} - errno_t ipsec_register_control(void) { struct kern_ctl_reg kern_ctl; errno_t result = 0; - /* Create a tag to allocate memory */ - ipsec_malloc_tag = OSMalloc_Tagalloc(IPSEC_CONTROL_NAME, OSMT_DEFAULT); - /* Find a unique value for our interface family */ result = mbuf_tag_id_find(IPSEC_CONTROL_NAME, &ipsec_family); if (result != 0) { @@ -127,7 +105,7 @@ ipsec_register_control(void) } bzero(&kern_ctl, sizeof(kern_ctl)); - strncpy(kern_ctl.ctl_name, IPSEC_CONTROL_NAME, sizeof(kern_ctl.ctl_name)); + strlcpy(kern_ctl.ctl_name, IPSEC_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; /* Require root */ kern_ctl.ctl_sendsize = 64 * 1024; @@ -200,15 +178,13 @@ ipsec_ctl_connect(kern_ctl_ref kctlref, struct ifnet_stats_param stats; /* kernel control allocates, interface frees */ - pcb = ipsec_alloc(sizeof(*pcb)); - if (pcb == NULL) - return ENOMEM; - + MALLOC(pcb, struct ipsec_pcb *, sizeof(*pcb), M_DEVBUF, M_WAITOK | M_ZERO); + /* Setup the protocol control block */ - bzero(pcb, sizeof(*pcb)); *unitinfo = pcb; pcb->ipsec_ctlref = kctlref; pcb->ipsec_unit = sac->sc_unit; + pcb->ipsec_output_service_class = MBUF_SC_OAM; printf("ipsec_ctl_connect: creating interface ipsec%d\n", pcb->ipsec_unit - 1); @@ -217,8 +193,7 @@ ipsec_ctl_connect(kern_ctl_ref kctlref, ipsec_init.ver = IFNET_INIT_CURRENT_VERSION; ipsec_init.len = sizeof (ipsec_init); ipsec_init.name = "ipsec"; - ipsec_init.start = ipsec_start; - ipsec_init.sndq_maxlen = IPSECQ_MAXLEN; + ipsec_init.start = ipsec_start; ipsec_init.unit = pcb->ipsec_unit - 1; ipsec_init.family = ipsec_family; ipsec_init.type = IFT_OTHER; @@ -232,10 +207,10 @@ ipsec_ctl_connect(kern_ctl_ref kctlref, result = ifnet_allocate_extended(&ipsec_init, &pcb->ipsec_ifp); if (result != 0) { printf("ipsec_ctl_connect - ifnet_allocate failed: %d\n", result); - ipsec_free(pcb); + *unitinfo = NULL; + FREE(pcb, M_DEVBUF); return result; } - OSIncrementAtomic(&ipsec_ifcount); /* Set flags and additional information. */ ifnet_set_mtu(pcb->ipsec_ifp, 1500); @@ -255,16 +230,15 @@ ipsec_ctl_connect(kern_ctl_ref kctlref, if (result != 0) { printf("ipsec_ctl_connect - ifnet_allocate failed: %d\n", result); ifnet_release(pcb->ipsec_ifp); - ipsec_free(pcb); - } - - /* Attach to bpf */ - if (result == 0) + *unitinfo = NULL; + FREE(pcb, M_DEVBUF); + } else { + /* Attach to bpf */ bpfattach(pcb->ipsec_ifp, DLT_NULL, 4); - /* The interfaces resoures allocated, mark it as running */ - if (result == 0) + /* The interfaces resoures allocated, mark it as running */ ifnet_set_flags(pcb->ipsec_ifp, IFF_RUNNING, IFF_RUNNING); + } return result; } @@ -424,9 +398,14 @@ ipsec_ctl_disconnect(__unused kern_ctl_ref kctlref, void *unitinfo) { struct ipsec_pcb *pcb = unitinfo; - ifnet_t ifp = pcb->ipsec_ifp; + ifnet_t ifp = NULL; errno_t result = 0; - + + if (pcb == NULL) + return EINVAL; + + ifp = pcb->ipsec_ifp; + VERIFY(ifp != NULL); pcb->ipsec_ctlref = NULL; pcb->ipsec_unit = 0; @@ -436,7 +415,7 @@ ipsec_ctl_disconnect(__unused kern_ctl_ref kctlref, * addresses and detach the protocols. Finally, we can remove and * release the interface. */ - key_delsp_for_ipsec_if(ifp); + key_delsp_for_ipsec_if(ifp); ipsec_cleanup_family(ifp, AF_INET); ipsec_cleanup_family(ifp, AF_INET6); @@ -476,6 +455,7 @@ ipsec_ctl_setopt(__unused kern_ctl_ref kctlref, 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; } @@ -533,6 +513,10 @@ ipsec_ctl_setopt(__unused kern_ctl_ref kctlref, result = ifnet_find_by_name(name, &del_ifp); } if (result == 0) { + printf("%s IPSEC_OPT_SET_DELEGATE_INTERFACE %s to %s\n", + __func__, pcb->ipsec_ifp->if_xname, + del_ifp->if_xname); + result = ifnet_set_delegate(pcb->ipsec_ifp, del_ifp); if (del_ifp) ifnet_release(del_ifp); @@ -540,6 +524,23 @@ ipsec_ctl_setopt(__unused kern_ctl_ref kctlref, 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; + } + printf("%s IPSEC_OPT_OUTPUT_TRAFFIC_CLASS %s svc %d\n", + __func__, pcb->ipsec_ifp->if_xname, + pcb->ipsec_output_service_class); + break; + } + default: result = ENOPROTOOPT; break; @@ -578,6 +579,14 @@ ipsec_ctl_getopt(__unused kern_ctl_ref kctlref, *len = snprintf(data, *len, "%s%d", ifnet_name(pcb->ipsec_ifp), ifnet_unit(pcb->ipsec_ifp)) + 1; break; + case IPSEC_OPT_OUTPUT_TRAFFIC_CLASS: { + if (*len != sizeof(int)) { + result = EMSGSIZE; + break; + } + *(int *)data = so_svc2tc(pcb->ipsec_output_service_class); + break; + } default: result = ENOPROTOOPT; break; @@ -598,36 +607,26 @@ ipsec_output(ifnet_t interface, int length; struct ip *ip; struct ip6_hdr *ip6; - struct secpolicy *sp = NULL; struct ip_out_args ipoa; struct ip6_out_args ip6oa; int error = 0; u_int ip_version = 0; uint32_t af; - int flags = 0;; - int out_interface_index = 0; + int flags = 0; struct flowadv *adv = NULL; - uint32_t policy_id = 0; - - /* Find policy using ID in mbuf */ - policy_id = data->m_pkthdr.ipsec_policy; - sp = key_getspbyid(policy_id); + // 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); - if (sp == NULL) { - printf("ipsec_output: No policy specified, dropping packet.\n"); - goto ipsec_output_err; - } - - /* Validate policy */ - if (sp->ipsec_if != pcb->ipsec_ifp) { - printf("ipsec_output: Selected policy does not match %s interface.\n", pcb->ipsec_ifp->if_xname); - goto ipsec_output_err; - } - ip = mtod(data, struct ip *); ip_version = ip->ip_v; - + switch (ip_version) { case 4: /* Tap */ @@ -637,18 +636,23 @@ ipsec_output(ifnet_t interface, /* Apply encryption */ bzero(&ipsec_state, sizeof(ipsec_state)); ipsec_state.m = data; - ipsec_state.dst = (struct sockaddr *)&sp->spidx.dst; + ipsec_state.dst = (struct sockaddr *)&ip->ip_dst; bzero(&ipsec_state.ro, sizeof(ipsec_state.ro)); - error = ipsec4_output(&ipsec_state, sp, 0); + error = ipsec4_interface_output(&ipsec_state, interface); + /* Tunneled in IPv6 - packet is gone */ + if (error == 0 && ipsec_state.tunneled == 6) { + goto done; + } + data = ipsec_state.m; if (error || data == NULL) { - printf("ipsec_output: ipsec4_output error.\n"); + printf("ipsec_output: ipsec4_output error %d.\n", error); goto ipsec_output_err; } - /* Set traffic class to OAM, set flow */ - m_set_service_class(data, MBUF_SC_OAM); + /* 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; @@ -666,20 +670,17 @@ ipsec_output(ifnet_t interface, /* 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 */ - - if (sp->outgoing_if != NULL) { - out_interface_index = sp->outgoing_if->if_index; - } + 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 (out_interface_index) { - ipoa.ipoa_boundif = out_interface_index; + if (ipsec_state.outgoing_if) { + ipoa.ipoa_boundif = ipsec_state.outgoing_if; ipoa.ipoa_flags |= IPOAF_BOUND_IF; } + ipsec_set_ipoa_for_interface(pcb->ipsec_ifp, &ipoa); adv = &ipoa.ipoa_flowadv; @@ -696,29 +697,33 @@ ipsec_output(ifnet_t interface, af = AF_INET6; bpf_tap_out(pcb->ipsec_ifp, DLT_NULL, data, &af, sizeof(af)); + data = ipsec6_splithdr(data); + if (data == NULL) { + printf("ipsec_output: ipsec6_splithdr returned NULL\n"); + goto ipsec_output_err; + } + 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)); - u_char *nexthdrp = &ip6->ip6_nxt; - struct mbuf *mprev = data; - - int needipsectun = 0; - error = ipsec6_output_trans(&ipsec_state, nexthdrp, mprev, sp, flags, &needipsectun); - if (needipsectun) { - error = ipsec6_output_tunnel(&ipsec_state, sp, flags); - if (ipsec_state.tunneled == 4) /* tunneled in IPv4 - packet is gone */ - goto done; - } + 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.\n"); + printf("ipsec_output: ipsec6_output error %d.\n", error); goto ipsec_output_err; } - /* Set traffic class to OAM, set flow */ - m_set_service_class(data, MBUF_SC_OAM); + /* 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_proto = ip6->ip6_nxt; data->m_pkthdr.pkt_flags = (PKTF_FLOW_ID | PKTF_FLOW_ADV | PKTF_FLOW_LOCALSRC); /* Increment statistics */ @@ -730,17 +735,14 @@ ipsec_output(ifnet_t interface, flags = IPV6_OUTARGS; - if (sp->outgoing_if != NULL) { - out_interface_index = sp->outgoing_if->if_index; - } - bzero(&ip6oa, sizeof(ip6oa)); ip6oa.ip6oa_flowadv.code = 0; - ip6oa.ip6oa_flags = IPOAF_SELECT_SRCIF | IPOAF_BOUND_SRCADDR; - if (out_interface_index) { - ip6oa.ip6oa_boundif = out_interface_index; - ip6oa.ip6oa_flags |= IPOAF_BOUND_IF; + ip6oa.ip6oa_flags = IP6OAF_SELECT_SRCIF | IP6OAF_BOUND_SRCADDR; + if (ipsec_state.outgoing_if) { + ip6oa.ip6oa_boundif = ipsec_state.outgoing_if; + ip6oa.ip6oa_flags |= IP6OAF_BOUND_IF; } + ipsec_set_ip6oa_for_interface(pcb->ipsec_ifp, &ip6oa); adv = &ip6oa.ip6oa_flowadv; @@ -760,9 +762,6 @@ ipsec_output(ifnet_t interface, } done: - if (sp != NULL) { - key_freesp(sp, KEY_SADB_UNLOCKED); - } return error; ipsec_output_err: @@ -774,13 +773,14 @@ ipsec_output_err: static void ipsec_start(ifnet_t interface) { - mbuf_t data; - - for (;;) { - if (ifnet_dequeue(interface, &data) != 0) - break; - (void) ipsec_output(interface, data); - } + mbuf_t data; + + for (;;) { + if (ifnet_dequeue(interface, &data) != 0) + break; + if (ipsec_output(interface, data) != 0) + break; + } } /* Network Interface functions */ @@ -872,21 +872,34 @@ ipsec_detached( struct ipsec_pcb *pcb = ifnet_softc(interface); ifnet_release(pcb->ipsec_ifp); - ipsec_free(pcb); - - OSDecrementAtomic(&ipsec_ifcount); } /* Protocol Handlers */ static errno_t -ipsec_proto_input(__unused ifnet_t interface, +ipsec_proto_input(ifnet_t interface, protocol_family_t protocol, - mbuf_t m, - __unused char *frame_header) + mbuf_t m, + __unused char *frame_header) { - if (proto_input(protocol, m) != 0) + struct ip *ip; + uint32_t af = 0; + ip = mtod(m, struct ip *); + if (ip->ip_v == 4) + af = AF_INET; + else if (ip->ip_v == 6) + af = AF_INET6; + + mbuf_pkthdr_setrcvif(m, interface); + bpf_tap_in(interface, DLT_NULL, m, &af, sizeof(af)); + pktap_input(interface, protocol, m, NULL); + + if (proto_input(protocol, m) != 0) { + ifnet_stat_increment_in(interface, 0, 0, 1); m_freem(m); + } else { + ifnet_stat_increment_in(interface, 1, m->m_pkthdr.len, 0); + } return 0; } @@ -924,3 +937,80 @@ ipsec_attach_proto(ifnet_t interface, return result; } + +errno_t +ipsec_inject_inbound_packet(ifnet_t interface, + mbuf_t packet) +{ + errno_t error; + protocol_family_t protocol; + if ((error = ipsec_demux(interface, packet, NULL, &protocol)) != 0) { + return error; + } + + return ipsec_proto_input(interface, protocol, packet, NULL); +} + +void +ipsec_set_pkthdr_for_interface(ifnet_t interface, mbuf_t packet, int family) +{ + if (packet != NULL && interface != NULL) { + struct ipsec_pcb *pcb = ifnet_softc(interface); + if (pcb != NULL) { + /* Set traffic class, set flow */ + m_set_service_class(packet, pcb->ipsec_output_service_class); + packet->m_pkthdr.pkt_flowsrc = FLOWSRC_IFNET; + packet->m_pkthdr.pkt_flowid = interface->if_flowhash; + if (family == AF_INET) { + struct ip *ip = mtod(packet, struct ip *); + packet->m_pkthdr.pkt_proto = ip->ip_p; + } else if (family == AF_INET6) { + struct ip6_hdr *ip6 = mtod(packet, struct ip6_hdr *); + packet->m_pkthdr.pkt_proto = ip6->ip6_nxt; + } + packet->m_pkthdr.pkt_flags = (PKTF_FLOW_ID | PKTF_FLOW_ADV | PKTF_FLOW_LOCALSRC); + } + } +} + +void +ipsec_set_ipoa_for_interface(ifnet_t interface, struct ip_out_args *ipoa) +{ + struct ipsec_pcb *pcb; + + if (interface == NULL || ipoa == NULL) + return; + pcb = ifnet_softc(interface); + + if (net_qos_policy_restricted == 0) { + ipoa->ipoa_flags |= IPOAF_QOSMARKING_ALLOWED; + ipoa->ipoa_sotc = so_svc2tc(pcb->ipsec_output_service_class); + } else if (pcb->ipsec_output_service_class != MBUF_SC_VO || + net_qos_policy_restrict_avapps != 0) { + ipoa->ipoa_flags &= ~IPOAF_QOSMARKING_ALLOWED; + } else { + ipoa->ipoa_flags |= IP6OAF_QOSMARKING_ALLOWED; + ipoa->ipoa_sotc = SO_TC_VO; + } +} + +void +ipsec_set_ip6oa_for_interface(ifnet_t interface, struct ip6_out_args *ip6oa) +{ + struct ipsec_pcb *pcb; + + if (interface == NULL || ip6oa == NULL) + return; + pcb = ifnet_softc(interface); + + if (net_qos_policy_restricted == 0) { + ip6oa->ip6oa_flags |= IPOAF_QOSMARKING_ALLOWED; + ip6oa->ip6oa_sotc = so_svc2tc(pcb->ipsec_output_service_class); + } else if (pcb->ipsec_output_service_class != MBUF_SC_VO || + net_qos_policy_restrict_avapps != 0) { + ip6oa->ip6oa_flags &= ~IPOAF_QOSMARKING_ALLOWED; + } else { + ip6oa->ip6oa_flags |= IP6OAF_QOSMARKING_ALLOWED; + ip6oa->ip6oa_sotc = SO_TC_VO; + } +}