]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/net/if_ipsec.c
xnu-2422.1.72.tar.gz
[apple/xnu.git] / bsd / net / if_ipsec.c
diff --git a/bsd/net/if_ipsec.c b/bsd/net/if_ipsec.c
new file mode 100644 (file)
index 0000000..e1aac83
--- /dev/null
@@ -0,0 +1,926 @@
+/*
+ * 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;
+}