--- /dev/null
+/*
+ * Copyright (c) 2012 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 <sys/systm.h>
+#include <sys/kern_control.h>
+#include <net/kpi_protocol.h>
+#include <net/kpi_interface.h>
+#include <sys/socket.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 <netinet/ip6.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/ip6_var.h>
+#include <sys/kauth.h>
+#include <netinet6/ipsec.h>
+#include <netinet6/ipsec6.h>
+#include <netinet/ip.h>
+#include <net/flowadv.h>
+
+/* Kernel Control functions */
+static errno_t ipsec_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac,
+ void **unitinfo);
+static errno_t ipsec_ctl_disconnect(kern_ctl_ref kctlref, u_int32_t unit,
+ void *unitinfo);
+static errno_t ipsec_ctl_send(kern_ctl_ref kctlref, u_int32_t unit,
+ void *unitinfo, mbuf_t m, int flags);
+static errno_t ipsec_ctl_getopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
+ int opt, void *data, size_t *len);
+static errno_t ipsec_ctl_setopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
+ int opt, void *data, size_t len);
+
+/* Network Interface functions */
+static void ipsec_start(ifnet_t interface);
+static errno_t ipsec_output(ifnet_t interface, mbuf_t data);
+static errno_t ipsec_demux(ifnet_t interface, mbuf_t data, char *frame_header,
+ protocol_family_t *protocol);
+static errno_t ipsec_add_proto(ifnet_t interface, protocol_family_t protocol,
+ const struct ifnet_demux_desc *demux_array,
+ u_int32_t demux_count);
+static errno_t ipsec_del_proto(ifnet_t interface, protocol_family_t protocol);
+static errno_t ipsec_ioctl(ifnet_t interface, u_long cmd, void *data);
+static void ipsec_detached(ifnet_t interface);
+
+/* Protocol handlers */
+static errno_t ipsec_attach_proto(ifnet_t interface, protocol_family_t proto);
+static errno_t ipsec_proto_input(ifnet_t interface, protocol_family_t protocol,
+ mbuf_t m, char *frame_header);
+static errno_t ipsec_proto_pre_output(ifnet_t interface, protocol_family_t protocol,
+ mbuf_t *packet, const struct sockaddr *dest, void *route,
+ char *frame_type, char *link_layer_dest);
+
+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) {
+ printf("ipsec_register_control - mbuf_tag_id_find_internal failed: %d\n", result);
+ return result;
+ }
+
+ bzero(&kern_ctl, sizeof(kern_ctl));
+ strncpy(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;
+ kern_ctl.ctl_recvsize = 64 * 1024;
+ kern_ctl.ctl_connect = ipsec_ctl_connect;
+ kern_ctl.ctl_disconnect = ipsec_ctl_disconnect;
+ kern_ctl.ctl_send = ipsec_ctl_send;
+ kern_ctl.ctl_setopt = ipsec_ctl_setopt;
+ kern_ctl.ctl_getopt = ipsec_ctl_getopt;
+
+ result = ctl_register(&kern_ctl, &ipsec_kctlref);
+ if (result != 0) {
+ printf("ipsec_register_control - ctl_register failed: %d\n", result);
+ return result;
+ }
+
+ /* Register the protocol plumbers */
+ if ((result = proto_register_plumber(PF_INET, ipsec_family,
+ ipsec_attach_proto, NULL)) != 0) {
+ printf("ipsec_register_control - proto_register_plumber(PF_INET, %d) failed: %d\n",
+ ipsec_family, result);
+ ctl_deregister(ipsec_kctlref);
+ return result;
+ }
+
+ /* Register the protocol plumbers */
+ if ((result = proto_register_plumber(PF_INET6, ipsec_family,
+ ipsec_attach_proto, NULL)) != 0) {
+ proto_unregister_plumber(PF_INET, ipsec_family);
+ ctl_deregister(ipsec_kctlref);
+ printf("ipsec_register_control - proto_register_plumber(PF_INET6, %d) failed: %d\n",
+ ipsec_family, result);
+ return result;
+ }
+
+ return 0;
+}
+
+/* Helpers */
+int
+ipsec_interface_isvalid (ifnet_t interface)
+{
+ struct ipsec_pcb *pcb = NULL;
+
+ if (interface == NULL)
+ return 0;
+
+ pcb = ifnet_softc(interface);
+
+ if (pcb == NULL)
+ return 0;
+
+ /* When ctl disconnects, ipsec_unit is set to 0 */
+ if (pcb->ipsec_unit == 0)
+ return 0;
+
+ return 1;
+}
+
+/* Kernel control functions */
+
+static errno_t
+ipsec_ctl_connect(kern_ctl_ref kctlref,
+ struct sockaddr_ctl *sac,
+ void **unitinfo)
+{
+ struct ifnet_init_eparams ipsec_init;
+ struct ipsec_pcb *pcb;
+ errno_t result;
+ struct ifnet_stats_param stats;
+
+ /* kernel control allocates, interface frees */
+ pcb = ipsec_alloc(sizeof(*pcb));
+ if (pcb == NULL)
+ return ENOMEM;
+
+ /* Setup the protocol control block */
+ bzero(pcb, sizeof(*pcb));
+ *unitinfo = pcb;
+ pcb->ipsec_ctlref = kctlref;
+ pcb->ipsec_unit = sac->sc_unit;
+
+ printf("ipsec_ctl_connect: creating interface ipsec%d\n", pcb->ipsec_unit - 1);
+
+ /* Create the interface */
+ bzero(&ipsec_init, sizeof(ipsec_init));
+ 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.unit = pcb->ipsec_unit - 1;
+ ipsec_init.family = ipsec_family;
+ ipsec_init.type = IFT_OTHER;
+ ipsec_init.demux = ipsec_demux;
+ ipsec_init.add_proto = ipsec_add_proto;
+ ipsec_init.del_proto = ipsec_del_proto;
+ ipsec_init.softc = pcb;
+ ipsec_init.ioctl = ipsec_ioctl;
+ ipsec_init.detach = ipsec_detached;
+
+ 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);
+ return result;
+ }
+ OSIncrementAtomic(&ipsec_ifcount);
+
+ /* Set flags and additional information. */
+ ifnet_set_mtu(pcb->ipsec_ifp, 1500);
+ ifnet_set_flags(pcb->ipsec_ifp, IFF_UP | IFF_MULTICAST | IFF_POINTOPOINT, 0xffff);
+
+ /* The interface must generate its own IPv6 LinkLocal address,
+ * if possible following the recommendation of RFC2472 to the 64bit interface ID
+ */
+ ifnet_set_eflags(pcb->ipsec_ifp, IFEF_NOAUTOIPV6LL, IFEF_NOAUTOIPV6LL);
+
+ /* Reset the stats in case as the interface may have been recycled */
+ bzero(&stats, sizeof(struct ifnet_stats_param));
+ ifnet_set_stat(pcb->ipsec_ifp, &stats);
+
+ /* Attach the interface */
+ result = ifnet_attach(pcb->ipsec_ifp, NULL);
+ 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)
+ bpfattach(pcb->ipsec_ifp, DLT_NULL, 4);
+
+ /* The interfaces resoures allocated, mark it as running */
+ if (result == 0)
+ ifnet_set_flags(pcb->ipsec_ifp, IFF_RUNNING, IFF_RUNNING);
+
+ return result;
+}
+
+static errno_t
+ipsec_detach_ip(ifnet_t interface,
+ protocol_family_t protocol,
+ socket_t pf_socket)
+{
+ errno_t result = EPROTONOSUPPORT;
+
+ /* Attempt a detach */
+ if (protocol == PF_INET) {
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d",
+ ifnet_name(interface), ifnet_unit(interface));
+
+ result = sock_ioctl(pf_socket, SIOCPROTODETACH, &ifr);
+ }
+ else if (protocol == PF_INET6) {
+ struct in6_ifreq ifr6;
+
+ bzero(&ifr6, sizeof(ifr6));
+ snprintf(ifr6.ifr_name, sizeof(ifr6.ifr_name), "%s%d",
+ ifnet_name(interface), ifnet_unit(interface));
+
+ result = sock_ioctl(pf_socket, SIOCPROTODETACH_IN6, &ifr6);
+ }
+
+ return result;
+}
+
+static void
+ipsec_remove_address(ifnet_t interface,
+ protocol_family_t protocol,
+ ifaddr_t address,
+ socket_t pf_socket)
+{
+ errno_t result = 0;
+
+ /* Attempt a detach */
+ if (protocol == PF_INET) {
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d",
+ ifnet_name(interface), ifnet_unit(interface));
+ result = ifaddr_address(address, &ifr.ifr_addr, sizeof(ifr.ifr_addr));
+ if (result != 0) {
+ printf("ipsec_remove_address - ifaddr_address failed: %d", result);
+ }
+ else {
+ result = sock_ioctl(pf_socket, SIOCDIFADDR, &ifr);
+ if (result != 0) {
+ printf("ipsec_remove_address - SIOCDIFADDR failed: %d", result);
+ }
+ }
+ }
+ else if (protocol == PF_INET6) {
+ struct in6_ifreq ifr6;
+
+ bzero(&ifr6, sizeof(ifr6));
+ snprintf(ifr6.ifr_name, sizeof(ifr6.ifr_name), "%s%d",
+ ifnet_name(interface), ifnet_unit(interface));
+ result = ifaddr_address(address, (struct sockaddr*)&ifr6.ifr_addr,
+ sizeof(ifr6.ifr_addr));
+ if (result != 0) {
+ printf("ipsec_remove_address - ifaddr_address failed (v6): %d",
+ result);
+ }
+ else {
+ result = sock_ioctl(pf_socket, SIOCDIFADDR_IN6, &ifr6);
+ if (result != 0) {
+ printf("ipsec_remove_address - SIOCDIFADDR_IN6 failed: %d",
+ result);
+ }
+ }
+ }
+}
+
+static void
+ipsec_cleanup_family(ifnet_t interface,
+ protocol_family_t protocol)
+{
+ errno_t result = 0;
+ socket_t pf_socket = NULL;
+ ifaddr_t *addresses = NULL;
+ int i;
+
+ if (protocol != PF_INET && protocol != PF_INET6) {
+ printf("ipsec_cleanup_family - invalid protocol family %d\n", protocol);
+ return;
+ }
+
+ /* Create a socket for removing addresses and detaching the protocol */
+ result = sock_socket(protocol, SOCK_DGRAM, 0, NULL, NULL, &pf_socket);
+ if (result != 0) {
+ if (result != EAFNOSUPPORT)
+ printf("ipsec_cleanup_family - failed to create %s socket: %d\n",
+ protocol == PF_INET ? "IP" : "IPv6", result);
+ goto cleanup;
+ }
+
+ /* always set SS_PRIV, we want to close and detach regardless */
+ sock_setpriv(pf_socket, 1);
+
+ result = ipsec_detach_ip(interface, protocol, pf_socket);
+ if (result == 0 || result == ENXIO) {
+ /* We are done! We either detached or weren't attached. */
+ goto cleanup;
+ }
+ else if (result != EBUSY) {
+ /* Uh, not really sure what happened here... */
+ printf("ipsec_cleanup_family - ipsec_detach_ip failed: %d\n", result);
+ goto cleanup;
+ }
+
+ /*
+ * At this point, we received an EBUSY error. This means there are
+ * addresses attached. We should detach them and then try again.
+ */
+ result = ifnet_get_address_list_family(interface, &addresses, protocol);
+ if (result != 0) {
+ printf("fnet_get_address_list_family(%s%d, 0xblah, %s) - failed: %d\n",
+ ifnet_name(interface), ifnet_unit(interface),
+ protocol == PF_INET ? "PF_INET" : "PF_INET6", result);
+ goto cleanup;
+ }
+
+ for (i = 0; addresses[i] != 0; i++) {
+ ipsec_remove_address(interface, protocol, addresses[i], pf_socket);
+ }
+ ifnet_free_address_list(addresses);
+ addresses = NULL;
+
+ /*
+ * The addresses should be gone, we should try the remove again.
+ */
+ result = ipsec_detach_ip(interface, protocol, pf_socket);
+ if (result != 0 && result != ENXIO) {
+ printf("ipsec_cleanup_family - ipsec_detach_ip failed: %d\n", result);
+ }
+
+cleanup:
+ if (pf_socket != NULL)
+ sock_close(pf_socket);
+
+ if (addresses != NULL)
+ ifnet_free_address_list(addresses);
+}
+
+static errno_t
+ipsec_ctl_disconnect(__unused kern_ctl_ref kctlref,
+ __unused u_int32_t unit,
+ void *unitinfo)
+{
+ struct ipsec_pcb *pcb = unitinfo;
+ ifnet_t ifp = pcb->ipsec_ifp;
+ errno_t result = 0;
+
+ pcb->ipsec_ctlref = NULL;
+ pcb->ipsec_unit = 0;
+
+ /*
+ * We want to do everything in our power to ensure that the interface
+ * really goes away when the socket is closed. We must remove IP/IPv6
+ * addresses and detach the protocols. Finally, we can remove and
+ * release the interface.
+ */
+ key_delsp_for_ipsec_if(ifp);
+
+ ipsec_cleanup_family(ifp, AF_INET);
+ ipsec_cleanup_family(ifp, AF_INET6);
+
+ if ((result = ifnet_detach(ifp)) != 0) {
+ printf("ipsec_ctl_disconnect - ifnet_detach failed: %d\n", result);
+ }
+
+ return 0;
+}
+
+static errno_t
+ipsec_ctl_send(__unused kern_ctl_ref kctlref,
+ __unused u_int32_t unit,
+ __unused void *unitinfo,
+ mbuf_t m,
+ __unused int flags)
+{
+ /* Receive messages from the control socket. Currently unused. */
+ mbuf_freem(m);
+ return 0;
+}
+
+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:
+ 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;
+ }
+
+ default:
+ result = ENOPROTOOPT;
+ break;
+ }
+
+ return result;
+}
+
+static errno_t
+ipsec_ctl_getopt(__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;
+
+ switch (opt) {
+ case IPSEC_OPT_FLAGS:
+ if (*len != sizeof(u_int32_t))
+ result = EMSGSIZE;
+ else
+ *(u_int32_t *)data = pcb->ipsec_flags;
+ break;
+
+ case IPSEC_OPT_EXT_IFDATA_STATS:
+ if (*len != sizeof(int))
+ result = EMSGSIZE;
+ else
+ *(int *)data = (pcb->ipsec_ext_ifdata_stats) ? 1 : 0;
+ break;
+
+ case IPSEC_OPT_IFNAME:
+ *len = snprintf(data, *len, "%s%d", ifnet_name(pcb->ipsec_ifp), ifnet_unit(pcb->ipsec_ifp)) + 1;
+ 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 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;
+ 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);
+
+ 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 */
+ 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 *)&sp->spidx.dst;
+ bzero(&ipsec_state.ro, sizeof(ipsec_state.ro));
+
+ error = ipsec4_output(&ipsec_state, sp, 0);
+ data = ipsec_state.m;
+ if (error || data == NULL) {
+ printf("ipsec_output: ipsec4_output error.\n");
+ goto ipsec_output_err;
+ }
+
+ /* Set traffic class to OAM, set flow */
+ m_set_service_class(data, MBUF_SC_OAM);
+ 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 */
+
+ if (sp->outgoing_if != NULL) {
+ out_interface_index = sp->outgoing_if->if_index;
+ }
+
+ 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;
+ 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));
+
+ ip6 = mtod(data, struct ip6_hdr *);
+
+ 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;
+ }
+ data = ipsec_state.m;
+ if (error || data == NULL) {
+ printf("ipsec_output: ipsec6_output error.\n");
+ goto ipsec_output_err;
+ }
+
+ /* Set traffic class to OAM, set flow */
+ m_set_service_class(data, MBUF_SC_OAM);
+ 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);
+
+ /* 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;
+
+ 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;
+ }
+
+ 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:
+ if (sp != NULL) {
+ key_freesp(sp, KEY_SADB_UNLOCKED);
+ }
+ return error;
+
+ipsec_output_err:
+ if (data)
+ mbuf_freem(data);
+ goto done;
+}
+
+static void
+ipsec_start(ifnet_t interface)
+{
+ mbuf_t data;
+
+ for (;;) {
+ if (ifnet_dequeue(interface, &data) != 0)
+ break;
+ (void) ipsec_output(interface, data);
+ }
+}
+
+/* Network Interface functions */
+static errno_t
+ipsec_demux(__unused ifnet_t interface,
+ mbuf_t data,
+ __unused char *frame_header,
+ protocol_family_t *protocol)
+{
+ struct ip *ip;
+ u_int ip_version;
+
+ while (data != NULL && mbuf_len(data) < 1) {
+ data = mbuf_next(data);
+ }
+
+ if (data == NULL)
+ return ENOENT;
+
+ ip = mtod(data, struct ip *);
+ ip_version = ip->ip_v;
+
+ switch(ip_version) {
+ case 4:
+ *protocol = PF_INET;
+ return 0;
+ case 6:
+ *protocol = PF_INET6;
+ return 0;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static errno_t
+ipsec_add_proto(__unused ifnet_t interface,
+ protocol_family_t protocol,
+ __unused const struct ifnet_demux_desc *demux_array,
+ __unused u_int32_t demux_count)
+{
+ switch(protocol) {
+ case PF_INET:
+ return 0;
+ case PF_INET6:
+ return 0;
+ default:
+ break;
+ }
+
+ return ENOPROTOOPT;
+}
+
+static errno_t
+ipsec_del_proto(__unused ifnet_t interface,
+ __unused protocol_family_t protocol)
+{
+ return 0;
+}
+
+static errno_t
+ipsec_ioctl(ifnet_t interface,
+ u_long command,
+ void *data)
+{
+ errno_t result = 0;
+
+ switch(command) {
+ case SIOCSIFMTU:
+ ifnet_set_mtu(interface, ((struct ifreq*)data)->ifr_mtu);
+ break;
+
+ case SIOCSIFFLAGS:
+ /* ifioctl() takes care of it */
+ break;
+
+ default:
+ result = EOPNOTSUPP;
+ }
+
+ return result;
+}
+
+static void
+ipsec_detached(
+ ifnet_t interface)
+{
+ 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,
+ protocol_family_t protocol,
+ mbuf_t m,
+ __unused char *frame_header)
+{
+ if (proto_input(protocol, m) != 0)
+ m_freem(m);
+
+ return 0;
+}
+
+static errno_t
+ipsec_proto_pre_output(__unused ifnet_t interface,
+ protocol_family_t protocol,
+ __unused mbuf_t *packet,
+ __unused const struct sockaddr *dest,
+ __unused void *route,
+ __unused char *frame_type,
+ __unused char *link_layer_dest)
+{
+
+ *(protocol_family_t *)(void *)frame_type = protocol;
+ return 0;
+}
+
+static errno_t
+ipsec_attach_proto(ifnet_t interface,
+ protocol_family_t protocol)
+{
+ struct ifnet_attach_proto_param proto;
+ errno_t result;
+
+ bzero(&proto, sizeof(proto));
+ proto.input = ipsec_proto_input;
+ proto.pre_output = ipsec_proto_pre_output;
+
+ result = ifnet_attach_protocol(interface, protocol, &proto);
+ if (result != 0 && result != EEXIST) {
+ printf("ipsec_attach_inet - ifnet_attach_protocol %d failed: %d\n",
+ protocol, result);
+ }
+
+ return result;
+}