/*
- * Copyright (c) 2012 Apple Inc. All rights reserved.
+ * Copyright (c) 2012-2015 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
#include <net/kpi_protocol.h>
#include <net/kpi_interface.h>
#include <sys/socket.h>
+#include <sys/socketvar.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/bpf.h>
#include <net/if_ipsec.h>
-#include <libkern/OSMalloc.h>
-#include <libkern/OSAtomic.h>
#include <sys/mbuf.h>
#include <sys/sockio.h>
#include <netinet/in.h>
#include <netinet6/ipsec6.h>
#include <netinet/ip.h>
#include <net/flowadv.h>
+#include <net/necp.h>
+#include <netkey/key.h>
+#include <net/pktap.h>
+
+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,
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) {
}
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;
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);
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;
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);
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;
}
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;
* 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);
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;
}
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);
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;
*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;
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 */
/* 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;
/* 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;
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 */
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;
}
done:
- if (sp != NULL) {
- key_freesp(sp, KEY_SADB_UNLOCKED);
- }
return error;
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 */
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;
}
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;
+ }
+}