]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/net/if_utun.c
xnu-3248.60.10.tar.gz
[apple/xnu.git] / bsd / net / if_utun.c
index 1b35e44b441d1f3ecfef60c96ce117e57bc08dbb..4261be9684da6e84be96b22a0ab17767bea3a2d7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008-2009 Apple Inc. All rights reserved.
+ * Copyright (c) 2008-2014 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
@@ -66,14 +66,17 @@ static errno_t      utun_ctl_getopt(kern_ctl_ref kctlref, u_int32_t unit, void *uniti
                                                                 int opt, void *data, size_t *len);
 static errno_t utun_ctl_setopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
                                                                 int opt, void *data, size_t len);
+static void            utun_ctl_rcvd(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
+                                                               int flags);
 
 /* Network Interface functions */
+static void     utun_start(ifnet_t interface);
 static errno_t utun_output(ifnet_t interface, mbuf_t data);
 static errno_t utun_demux(ifnet_t interface, mbuf_t data, char *frame_header,
                                                   protocol_family_t *protocol);
-static errno_t utun_framer(ifnet_t     interface, mbuf_t *packet,
-                                                       const struct sockaddr *dest, const char *desk_linkaddr,
-                                                       const char *frame_type);
+static errno_t utun_framer(ifnet_t interface, mbuf_t *packet,
+    const struct sockaddr *dest, const char *desk_linkaddr,
+    const char *frame_type, u_int32_t *prepend_len, u_int32_t *postpend_len);
 static errno_t utun_add_proto(ifnet_t interface, protocol_family_t protocol,
                                                           const struct ifnet_demux_desc *demux_array,
                                                           u_int32_t demux_count);
@@ -88,15 +91,7 @@ static errno_t       utun_proto_input(ifnet_t interface, protocol_family_t protocol,
 static errno_t utun_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);
-
-/* Control block allocated for each kernel control connection */
-struct utun_pcb {
-       kern_ctl_ref    utun_ctlref;
-       ifnet_t                 utun_ifp;
-       u_int32_t               utun_unit;
-       u_int32_t               utun_flags;
-       int                             utun_ext_ifdata_stats;
-};
+__private_extern__ errno_t utun_pkt_input (struct utun_pcb *pcb, mbuf_t m);
 
 static kern_ctl_ref    utun_kctlref;
 static u_int32_t       utun_family;
@@ -104,7 +99,7 @@ static OSMallocTag   utun_malloc_tag;
 static SInt32          utun_ifcount = 0;
 
 /* Prepend length */
-static void*
+void*
 utun_alloc(size_t size)
 {
        size_t  *mem = OSMalloc(size + sizeof(size_t), utun_malloc_tag);
@@ -117,7 +112,7 @@ utun_alloc(size_t size)
        return (void*)mem;
 }
 
-static void
+void
 utun_free(void *ptr)
 {
        size_t  *size = ptr;
@@ -142,17 +137,20 @@ utun_register_control(void)
        }
        
        bzero(&kern_ctl, sizeof(kern_ctl));
-       strncpy(kern_ctl.ctl_name, UTUN_CONTROL_NAME, sizeof(kern_ctl.ctl_name));
+       strlcpy(kern_ctl.ctl_name, UTUN_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_flags = CTL_FLAG_PRIVILEGED | CTL_FLAG_REG_EXTENDED; /* Require root */
+       kern_ctl.ctl_sendsize = 512 * 1024;
+       kern_ctl.ctl_recvsize = 512 * 1024;
        kern_ctl.ctl_connect = utun_ctl_connect;
        kern_ctl.ctl_disconnect = utun_ctl_disconnect;
        kern_ctl.ctl_send = utun_ctl_send;
        kern_ctl.ctl_setopt = utun_ctl_setopt;
        kern_ctl.ctl_getopt = utun_ctl_getopt;
-       
+       kern_ctl.ctl_rcvd = utun_ctl_rcvd;
+
+       utun_ctl_init_crypto();
+
        result = ctl_register(&kern_ctl, &utun_kctlref);
        if (result != 0) {
                printf("utun_register_control - ctl_register failed: %d\n", result);
@@ -189,7 +187,7 @@ utun_ctl_connect(
        struct sockaddr_ctl     *sac, 
        void                            **unitinfo)
 {
-       struct ifnet_init_params        utun_init;
+       struct ifnet_init_eparams       utun_init;
        struct utun_pcb                         *pcb;
        errno_t                                         result;
        struct ifnet_stats_param        stats;
@@ -204,25 +202,28 @@ utun_ctl_connect(
        *unitinfo = pcb;
        pcb->utun_ctlref = kctlref;
        pcb->utun_unit = sac->sc_unit;
+       pcb->utun_max_pending_packets = 1;
        
        printf("utun_ctl_connect: creating interface utun%d\n", pcb->utun_unit - 1);
 
        /* Create the interface */
        bzero(&utun_init, sizeof(utun_init));
+       utun_init.ver = IFNET_INIT_CURRENT_VERSION;
+       utun_init.len = sizeof (utun_init);
        utun_init.name = "utun";
+       utun_init.start = utun_start;
        utun_init.unit = pcb->utun_unit - 1;
        utun_init.family = utun_family;
        utun_init.type = IFT_OTHER;
-       utun_init.output = utun_output;
        utun_init.demux = utun_demux;
-       utun_init.framer = utun_framer;
+       utun_init.framer_extended = utun_framer;
        utun_init.add_proto = utun_add_proto;
        utun_init.del_proto = utun_del_proto;
        utun_init.softc = pcb;
        utun_init.ioctl = utun_ioctl;
        utun_init.detach = utun_detached;
        
-       result = ifnet_allocate(&utun_init, &pcb->utun_ifp);
+       result = ifnet_allocate_extended(&utun_init, &pcb->utun_ifp);
        if (result != 0) {
                printf("utun_ctl_connect - ifnet_allocate failed: %d\n", result);
                utun_free(pcb);
@@ -366,6 +367,9 @@ utun_cleanup_family(
                goto cleanup;
        }
        
+        /* always set SS_PRIV, we want to close and detach regardless */
+        sock_setpriv(pf_socket, 1);
+
        result = utun_detach_ip(interface, protocol, pf_socket);
        if (result == 0 || result == ENXIO) {
                /* We are done! We either detached or weren't attached. */
@@ -420,7 +424,9 @@ utun_ctl_disconnect(
        struct utun_pcb *pcb = unitinfo;
        ifnet_t                 ifp = pcb->utun_ifp;
        errno_t                 result = 0;
-       
+
+       utun_cleanup_crypto(pcb);
+
        pcb->utun_ctlref = NULL;
        pcb->utun_unit = 0;
        
@@ -452,37 +458,16 @@ utun_ctl_send(
        mbuf_t                                  m,
        __unused int                    flags)
 {
-       struct utun_pcb                                         *pcb = unitinfo;
-       errno_t                                                         result;
-       
-       mbuf_pkthdr_setrcvif(m, pcb->utun_ifp);
-       
-       bpf_tap_in(pcb->utun_ifp, DLT_NULL, m, 0, 0);
-       
-       if (pcb->utun_flags & UTUN_FLAGS_NO_INPUT) {
-               /* flush data */
-               mbuf_freem(m);
-               return 0;
-       }
-       
-       if (!pcb->utun_ext_ifdata_stats) {
-               struct ifnet_stat_increment_param       incs;
-       
-               bzero(&incs, sizeof(incs));
-               incs.packets_in = 1;
-               incs.bytes_in = mbuf_pkthdr_len(m);
-               result = ifnet_input(pcb->utun_ifp, m, &incs);
-       } else {
-               result = ifnet_input(pcb->utun_ifp, m, NULL);
-       }
-       if (result != 0) {
-               ifnet_stat_increment_in(pcb->utun_ifp, 0, 0, 1);
-               
-               printf("utun_ctl_send - ifnet_input failed: %d\n", result);
-               mbuf_freem(m);
-       }
-       
-       return 0;
+       /*
+        * The userland ABI requires the first four bytes have the protocol family 
+        * in network byte order: swap them
+        */
+       if (m_pktlen(m) >= 4)
+               *(protocol_family_t *)mbuf_data(m) = ntohl(*(protocol_family_t *)mbuf_data(m));
+       else
+               printf("%s - unexpected short mbuf pkt len %d\n", __func__, m_pktlen(m) );
+
+       return utun_pkt_input((struct utun_pcb *)unitinfo, m);
 }
 
 static errno_t
@@ -501,6 +486,7 @@ utun_ctl_setopt(
        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;
                        }
@@ -515,6 +501,38 @@ utun_ctl_setopt(
                                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;
@@ -543,10 +561,44 @@ utun_ctl_setopt(
                                        utsp->utsp_bytes, utsp->utsp_errors);
                        break;
                }
-               
-               default:
+               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;
@@ -583,6 +635,14 @@ utun_ctl_getopt(
                        *len = snprintf(data, *len, "%s%d", ifnet_name(pcb->utun_ifp), ifnet_unit(pcb->utun_ifp)) + 1;
                        break;
 
+               case UTUN_OPT_GENERATE_CRYPTO_KEYS_IDX:
+                       result = utun_ctl_generate_crypto_keys_idx(kctlref, unit, unitinfo, opt, data, len);
+                       break;
+               case UTUN_OPT_MAX_PENDING_PACKETS: {
+                       *len = sizeof(u_int32_t);
+                       *((u_int32_t *)data) = pcb->utun_max_pending_packets;
+                       break;
+               }
                default:
                        result = ENOPROTOOPT;
                        break;
@@ -591,7 +651,81 @@ utun_ctl_getopt(
        return result;
 }
 
+static void
+utun_ctl_rcvd(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int flags)
+{
+#pragma unused(flags)
+       bool reenable_output = false;
+       struct utun_pcb *pcb = unitinfo;
+       if (pcb == NULL) {
+               return;
+       }
+       ifnet_lock_exclusive(pcb->utun_ifp);
+
+       u_int32_t utun_packet_cnt;
+       errno_t error_pc = ctl_getenqueuepacketcount(kctlref, unit, &utun_packet_cnt);
+       if (error_pc != 0) {
+               printf("utun_ctl_rcvd: ctl_getenqueuepacketcount returned error %d\n", error_pc);
+               utun_packet_cnt = 0;
+       }
+
+       if (utun_packet_cnt < pcb->utun_max_pending_packets) {
+               reenable_output = true;
+       }
+
+       if (reenable_output) {
+               errno_t error = ifnet_enable_output(pcb->utun_ifp);
+               if (error != 0) {
+                       printf("utun_ctl_rcvd: ifnet_enable_output returned error %d\n", error);
+               }
+       }
+       ifnet_lock_done(pcb->utun_ifp);
+}
+
 /* Network Interface functions */
+static void
+utun_start(ifnet_t interface)
+{
+       mbuf_t data;
+       struct utun_pcb*pcb = ifnet_softc(interface);
+       for (;;) {
+               bool can_accept_packets = true;
+               ifnet_lock_shared(pcb->utun_ifp);
+
+               u_int32_t utun_packet_cnt;
+               errno_t error_pc = ctl_getenqueuepacketcount(pcb->utun_ctlref, pcb->utun_unit, &utun_packet_cnt);
+               if (error_pc != 0) {
+                       printf("utun_start: ctl_getenqueuepacketcount returned error %d\n", error_pc);
+                       utun_packet_cnt = 0;
+               }
+
+               can_accept_packets = (utun_packet_cnt < pcb->utun_max_pending_packets);
+               if (!can_accept_packets && pcb->utun_ctlref) {
+                       u_int32_t difference = 0;
+                       if (ctl_getenqueuereadable(pcb->utun_ctlref, pcb->utun_unit, &difference) == 0) {
+                               if (difference > 0) {
+                                       // If the low-water mark has not yet been reached, we still need to enqueue data
+                                       // into the buffer
+                                       can_accept_packets = true;
+                               }
+                       }
+               }
+               if (!can_accept_packets) {
+                       errno_t error = ifnet_disable_output(interface);
+                       if (error != 0) {
+                               printf("utun_start: ifnet_disable_output returned error %d\n", error);
+                       }
+                       ifnet_lock_done(pcb->utun_ifp);
+                       break;
+               }
+               ifnet_lock_done(pcb->utun_ifp);
+               if (ifnet_dequeue(interface, &data) != 0)
+                       break;
+               if (utun_output(interface, data) != 0)
+                       break;
+       }
+}
+
 static errno_t
 utun_output(
                           ifnet_t      interface,
@@ -600,7 +734,9 @@ utun_output(
        struct utun_pcb *pcb = ifnet_softc(interface);
        errno_t                 result;
        
-       bpf_tap_out(pcb->utun_ifp, DLT_NULL, data, 0, 0);
+       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 */
@@ -608,8 +744,24 @@ utun_output(
                return 0;
        }
 
+       // otherwise, fall thru to ctl_enqueumbuf
        if (pcb->utun_ctlref) {
-               int     length = mbuf_pkthdr_len(data);
+               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);
@@ -628,7 +780,6 @@ utun_output(
        return 0;
 }
 
-/* Network Interface functions */
 static errno_t
 utun_demux(
        __unused ifnet_t        interface,
@@ -644,7 +795,7 @@ utun_demux(
        if (data == NULL)
                return ENOENT;
        
-       *protocol = ntohl(*(u_int32_t *)mbuf_data(data));
+       *protocol = *(u_int32_t *)mbuf_data(data);
        return 0;
 }
 
@@ -654,7 +805,9 @@ utun_framer(
                   mbuf_t                               *packet,
                        __unused const struct sockaddr *dest, 
                        __unused const char *desk_linkaddr,
-                       const char *frame_type)
+                       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");
@@ -664,9 +817,13 @@ utun_framer(
                // 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) = htonl(*(protocol_family_t *)(uintptr_t)(size_t)frame_type);
+    *(protocol_family_t *)mbuf_data(*packet) = *(protocol_family_t *)(uintptr_t)(size_t)frame_type;
     
     return 0;
 }
@@ -705,7 +862,6 @@ utun_ioctl(
        void            *data)
 {
        errno_t result = 0;
-       struct ifaddr   *ifa = (struct ifaddr *)data;
        
        switch(command) {
                case SIOCSIFMTU:
@@ -716,13 +872,6 @@ utun_ioctl(
                        /* ifioctl() takes care of it */
                        break;
                        
-               case SIOCSIFADDR:
-               case SIOCAIFADDR:
-                       /* This will be called for called for IPv6 Address additions */
-                       if (ifa->ifa_addr->sa_family == AF_INET6) 
-                               break;
-                       /* Fall though for other families like IPv4 */
-                       
                default:
                        result = EOPNOTSUPP;
        }
@@ -754,7 +903,8 @@ utun_proto_input(
        // remove protocol family first
        mbuf_adj(m, sizeof(u_int32_t));
        
-       proto_input(protocol, m);
+       if (proto_input(protocol, m) != 0)
+               m_freem(m);
        
        return 0;
 }
@@ -770,8 +920,8 @@ utun_proto_pre_output(
        __unused char *link_layer_dest)
 {
        
-    *(protocol_family_t *)(void *)frame_type = protocol;
-       return 0;
+       *(protocol_family_t *)(void *)frame_type = protocol;
+               return 0;
 }
 
 static errno_t
@@ -795,3 +945,50 @@ utun_attach_proto(
        return result;
 }
 
+errno_t
+utun_pkt_input (struct utun_pcb *pcb, mbuf_t m)
+{
+       errno_t result;
+       protocol_family_t protocol = 0;
+
+       mbuf_pkthdr_setrcvif(m, pcb->utun_ifp);
+
+       if (m_pktlen(m) >= 4)  {
+               protocol = *(u_int32_t *)mbuf_data(m);
+       
+               bpf_tap_in(pcb->utun_ifp, DLT_NULL, m, 0, 0);
+       }
+       if (pcb->utun_flags & UTUN_FLAGS_NO_INPUT) {
+               /* flush data */
+               mbuf_freem(m);
+               return 0;
+       }
+
+       // quick exit for keepalive packets
+       if (protocol == AF_UTUN && pcb->utun_flags & UTUN_FLAGS_CRYPTO) {
+               if (utun_pkt_crypto_output(pcb, &m) == 0) {
+                       return 0;
+               }
+               printf("%s: utun_pkt_crypto_output failed, flags %x\n", __FUNCTION__, pcb->utun_flags);
+               return EINVAL;
+       }
+
+       if (!pcb->utun_ext_ifdata_stats) {
+               struct ifnet_stat_increment_param       incs;
+               
+               bzero(&incs, sizeof(incs));
+               incs.packets_in = 1;
+               incs.bytes_in = mbuf_pkthdr_len(m);
+               result = ifnet_input(pcb->utun_ifp, m, &incs);
+       } else {
+               result = ifnet_input(pcb->utun_ifp, m, NULL);
+       }
+       if (result != 0) {
+               ifnet_stat_increment_in(pcb->utun_ifp, 0, 0, 1);
+               
+               printf("%s - ifnet_input failed: %d\n", __FUNCTION__, result);
+               mbuf_freem(m);
+       }
+
+       return 0;
+}