]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/net/if_stf.c
xnu-3789.1.32.tar.gz
[apple/xnu.git] / bsd / net / if_stf.c
index 5f86780c039166fc32eadac7b44b8c9d0b382e51..62f66211549260b2bf5183a00bbd904e683ad7ae 100644 (file)
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2000-2016 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@
+ */
+
 /*     $FreeBSD: src/sys/net/if_stf.c,v 1.1.2.6 2001/07/24 19:10:18 brooks Exp $       */
 /*     $KAME: if_stf.c,v 1.62 2001/06/07 22:32:16 itojun Exp $ */
 
 
 #include <sys/malloc.h>
 
+#include <kern/locks.h>
+
 #include <net/if.h>
 #include <net/route.h>
 #include <net/if_types.h>
 #include <security/mac_framework.h>
 #endif
 
-#define IN6_IS_ADDR_6TO4(x)    (ntohs((x)->s6_addr16[0]) == 0x2002)
-#define GET_V4(x)      ((const struct in_addr *)(&(x)->s6_addr16[1]))
+#define GET_V4(x) ((const struct in_addr *)(const void *)(&(x)->s6_addr16[1])) 
+
+static lck_grp_t *stf_mtx_grp;
 
 struct stf_softc {
        ifnet_t                         sc_if;     /* common area */
-       u_long                          sc_protocol_family; /* dlil protocol attached */
+       u_int32_t                               sc_protocol_family; /* dlil protocol attached */
        union {
                struct route  __sc_ro4;
                struct route_in6 __sc_ro6; /* just for safety */
        } __sc_ro46;
 #define sc_ro  __sc_ro46.__sc_ro4
+       decl_lck_mtx_data(, sc_ro_mtx);
        const struct encaptab *encap_cookie;
        bpf_tap_mode            tap_mode;
        bpf_packet_func         tap_callback;
@@ -139,17 +171,20 @@ struct stf_softc {
 void stfattach (void);
 
 static int ip_stf_ttl = 40;
+static int stf_init_done;
 
 static void in_stf_input(struct mbuf *, int);
-extern  struct domain inetdomain;
-struct protosw in_stf_protosw =
-{ SOCK_RAW,    &inetdomain,    IPPROTO_IPV6,   PR_ATOMIC|PR_ADDR,
-  in_stf_input, NULL,  NULL,           rip_ctloutput,
-  NULL,
-  NULL,                NULL,   NULL,   NULL,
-  NULL,
-  &rip_usrreqs,
-  NULL,                rip_unlock,     NULL, {NULL, NULL}, NULL, {0}
+static void stfinit(void);
+
+static struct protosw in_stf_protosw =
+{
+       .pr_type =              SOCK_RAW,
+       .pr_protocol =          IPPROTO_IPV6,
+       .pr_flags =             PR_ATOMIC|PR_ADDR,
+       .pr_input =             in_stf_input,
+       .pr_ctloutput =         rip_ctloutput,
+       .pr_usrreqs =           &rip_usrreqs,
+       .pr_unlock =            rip_unlock,
 };
 
 static int stf_encapcheck(const struct mbuf *, int, int, void *);
@@ -161,9 +196,18 @@ static int stf_checkaddr4(struct stf_softc *, const struct in_addr *,
 static int stf_checkaddr6(struct stf_softc *, struct in6_addr *,
        struct ifnet *);
 static void stf_rtrequest(int, struct rtentry *, struct sockaddr *);
-static errno_t stf_ioctl(ifnet_t ifp, u_int32_t cmd, void *data);
+static errno_t stf_ioctl(ifnet_t ifp, u_long cmd, void *data);
 static errno_t stf_output(ifnet_t ifp, mbuf_t m);
 
+static void
+stfinit(void)
+{
+       if (!stf_init_done) {
+               stf_mtx_grp = lck_grp_alloc_init("stf", LCK_GRP_ATTR_NULL);
+               stf_init_done = 1;
+       }
+}
+
 /*
  * gif_input is the input handler for IP and IPv6 attached to gif
  */
@@ -174,7 +218,8 @@ stf_media_input(
        mbuf_t                          m,
        __unused char           *frame_header)
 {
-       proto_input(protocol_family, m);
+       if (proto_input(protocol_family, m) != 0)
+               m_freem(m);
 
        return (0);
 }
@@ -269,19 +314,19 @@ stfattach(void)
        const struct encaptab *p;
        struct ifnet_init_params        stf_init;
 
+       stfinit();
+
        error = proto_register_plumber(PF_INET6, APPLE_IF_FAM_STF,
                                                                   stf_attach_inet6, NULL);
        if (error != 0)
                printf("proto_register_plumber failed for AF_INET6 error=%d\n", error);
 
-       sc = _MALLOC(sizeof(struct stf_softc), M_DEVBUF, M_WAITOK);
+       sc = _MALLOC(sizeof(struct stf_softc), M_DEVBUF, M_WAITOK | M_ZERO);
        if (sc == 0) {
                printf("stf softc attach failed\n" );
                return;
        }
        
-       bzero(sc, sizeof(*sc));
-       
        p = encap_attach_func(AF_INET, IPPROTO_IPV6, stf_encapcheck,
            &in_stf_protosw, sc);
        if (p == NULL) {
@@ -290,6 +335,7 @@ stfattach(void)
                return;
        }
        sc->encap_cookie = p;
+       lck_mtx_init(&sc->sc_ro_mtx, stf_mtx_grp, LCK_ATTR_NULL);
        
        bzero(&stf_init, sizeof(stf_init));
        stf_init.name = "stf";
@@ -308,6 +354,7 @@ stfattach(void)
        if (error != 0) {
                printf("stfattach, ifnet_allocate failed - %d\n", error);
                encap_detach(sc->encap_cookie);
+               lck_mtx_destroy(&sc->sc_ro_mtx, stf_mtx_grp);
                FREE(sc, M_DEVBUF);
                return;
        }
@@ -327,6 +374,7 @@ stfattach(void)
                printf("stfattach: ifnet_attach returned error=%d\n", error);
                encap_detach(sc->encap_cookie);
                ifnet_release(sc->sc_if);
+               lck_mtx_destroy(&sc->sc_ro_mtx, stf_mtx_grp);
                FREE(sc, M_DEVBUF);
                return;
        }
@@ -362,8 +410,7 @@ stf_encapcheck(
        if (proto != IPPROTO_IPV6)
                return 0;
 
-       /* LINTED const cast */
-       mbuf_copydata(m, 0, sizeof(ip), &ip);
+       mbuf_copydata((struct mbuf *)(size_t)m, 0, sizeof(ip), &ip);
 
        if (ip.ip_v != 4)
                return 0;
@@ -377,10 +424,13 @@ stf_encapcheck(
         * local 6to4 address.
         * success on: dst = 10.1.1.1, ia6->ia_addr = 2002:0a01:0101:...
         */
+       IFA_LOCK(&ia6->ia_ifa);
        if (bcmp(GET_V4(&ia6->ia_addr.sin6_addr), &ip.ip_dst,
-           sizeof(ip.ip_dst)) != 0)
+           sizeof(ip.ip_dst)) != 0) {
+               IFA_UNLOCK(&ia6->ia_ifa);
+               IFA_REMREF(&ia6->ia_ifa);
                return 0;
-
+       }
        /*
         * check if IPv4 src matches the IPv4 address derived from the
         * local 6to4 address masked by prefixmask.
@@ -392,10 +442,14 @@ stf_encapcheck(
        a.s_addr &= GET_V4(&ia6->ia_prefixmask.sin6_addr)->s_addr;
        b = ip.ip_src;
        b.s_addr &= GET_V4(&ia6->ia_prefixmask.sin6_addr)->s_addr;
-       if (a.s_addr != b.s_addr)
+       if (a.s_addr != b.s_addr) {
+               IFA_UNLOCK(&ia6->ia_ifa);
+               IFA_REMREF(&ia6->ia_ifa);
                return 0;
-
+       }
        /* stf interface makes single side match only */
+       IFA_UNLOCK(&ia6->ia_ifa);
+       IFA_REMREF(&ia6->ia_ifa);
        return 32;
 }
 
@@ -408,37 +462,46 @@ stf_getsrcifa6(struct ifnet *ifp)
        struct in_addr in;
 
        ifnet_lock_shared(ifp);
-       for (ia = ifp->if_addrlist.tqh_first;
-            ia;
-            ia = ia->ifa_list.tqe_next)
-       {
-               if (ia->ifa_addr == NULL)
+       for (ia = ifp->if_addrlist.tqh_first; ia; ia = ia->ifa_list.tqe_next) {
+               IFA_LOCK(ia);
+               if (ia->ifa_addr == NULL) {
+                       IFA_UNLOCK(ia);
                        continue;
-               if (ia->ifa_addr->sa_family != AF_INET6)
+               }
+               if (ia->ifa_addr->sa_family != AF_INET6) {
+                       IFA_UNLOCK(ia);
                        continue;
-               sin6 = (struct sockaddr_in6 *)ia->ifa_addr;
-               if (!IN6_IS_ADDR_6TO4(&sin6->sin6_addr))
+               }
+               sin6 = (struct sockaddr_in6 *)(void *)ia->ifa_addr;
+               if (!IN6_IS_ADDR_6TO4(&sin6->sin6_addr)) {
+                       IFA_UNLOCK(ia);
                        continue;
-
+               }
                bcopy(GET_V4(&sin6->sin6_addr), &in, sizeof(in));
-               lck_mtx_lock(rt_mtx);
+               IFA_UNLOCK(ia);
+               lck_rw_lock_shared(in_ifaddr_rwlock);
                for (ia4 = TAILQ_FIRST(&in_ifaddrhead);
                     ia4;
                     ia4 = TAILQ_NEXT(ia4, ia_link))
                {
-                       if (ia4->ia_addr.sin_addr.s_addr == in.s_addr)
+                       IFA_LOCK(&ia4->ia_ifa);
+                       if (ia4->ia_addr.sin_addr.s_addr == in.s_addr) {
+                               IFA_UNLOCK(&ia4->ia_ifa);
                                break;
+                       }
+                       IFA_UNLOCK(&ia4->ia_ifa);
                }
-               lck_mtx_unlock(rt_mtx);
+               lck_rw_done(in_ifaddr_rwlock);
                if (ia4 == NULL)
                        continue;
 
+               IFA_ADDREF(ia);         /* for caller */
                ifnet_lock_done(ifp);
-               return (struct in6_ifaddr *)ia;
+               return ((struct in6_ifaddr *)ia);
        }
        ifnet_lock_done(ifp);
 
-       return NULL;
+       return (NULL);
 }
 
 int
@@ -460,10 +523,13 @@ stf_pre_output(
        struct ip6_hdr *ip6;
        struct in6_ifaddr *ia6;
        struct sockaddr_in      *dst4;
+       struct ip_out_args ipoa =
+           { IFSCOPE_NONE, { 0 }, IPOAF_SELECT_SRCIF, 0,
+           SO_TC_UNSPEC, _NET_SERVICE_TYPE_UNSPEC };
        errno_t                         result = 0;
 
        sc = ifnet_softc(ifp);
-       dst6 = (const struct sockaddr_in6 *)dst;
+       dst6 = (const struct sockaddr_in6 *)(const void *)dst;
 
        /* just in case */
        if ((ifnet_flags(ifp) & IFF_UP) == 0) {
@@ -485,6 +551,7 @@ stf_pre_output(
                m = m_pullup(m, sizeof(*ip6));
                if (!m) {
                        *m0 = NULL; /* makes sure this won't be double freed */
+                       IFA_REMREF(&ia6->ia_ifa);
                        return ENOBUFS;
                }
        }
@@ -500,6 +567,7 @@ stf_pre_output(
        else if (IN6_IS_ADDR_6TO4(&dst6->sin6_addr))
                in4 = GET_V4(&dst6->sin6_addr);
        else {
+               IFA_REMREF(&ia6->ia_ifa);
                return ENETUNREACH;
        }
 
@@ -510,55 +578,51 @@ stf_pre_output(
                bpf_tap_out(ifp, 0, m, &af, sizeof(af));
        }
 
-       M_PREPEND(m, sizeof(struct ip), M_DONTWAIT);
+       M_PREPEND(m, sizeof(struct ip), M_DONTWAIT, 1);
        if (m && mbuf_len(m) < sizeof(struct ip))
                m = m_pullup(m, sizeof(struct ip));
        if (m == NULL) {
                *m0 = NULL; 
+               IFA_REMREF(&ia6->ia_ifa);
                return ENOBUFS;
        }
        ip = mtod(m, struct ip *);
 
        bzero(ip, sizeof(*ip));
 
+       IFA_LOCK_SPIN(&ia6->ia_ifa);
        bcopy(GET_V4(&((struct sockaddr_in6 *)&ia6->ia_addr)->sin6_addr),
            &ip->ip_src, sizeof(ip->ip_src));
+       IFA_UNLOCK(&ia6->ia_ifa);
        bcopy(in4, &ip->ip_dst, sizeof(ip->ip_dst));
        ip->ip_p = IPPROTO_IPV6;
        ip->ip_ttl = ip_stf_ttl;
        ip->ip_len = m->m_pkthdr.len;   /*host order*/
        if (ifp->if_flags & IFF_LINK1)
-               ip_ecn_ingress(ECN_ALLOWED, &ip->ip_tos, &tos);
+               ip_ecn_ingress(ECN_NORMAL, &ip->ip_tos, &tos);
        else
                ip_ecn_ingress(ECN_NOCARE, &ip->ip_tos, &tos);
 
-       dst4 = (struct sockaddr_in *)&sc->sc_ro.ro_dst;
-       if (dst4->sin_family != AF_INET ||
+       lck_mtx_lock(&sc->sc_ro_mtx);
+       dst4 = (struct sockaddr_in *)(void *)&sc->sc_ro.ro_dst;
+       if (ROUTE_UNUSABLE(&sc->sc_ro) || dst4->sin_family != AF_INET ||
            bcmp(&dst4->sin_addr, &ip->ip_dst, sizeof(ip->ip_dst)) != 0) {
-               /* cache route doesn't match */
-               printf("stf_output: cached route doesn't match \n");
+               ROUTE_RELEASE(&sc->sc_ro);
+               /* cache route doesn't match: always the case during the first use */
                dst4->sin_family = AF_INET;
                dst4->sin_len = sizeof(struct sockaddr_in);
                bcopy(&ip->ip_dst, &dst4->sin_addr, sizeof(dst4->sin_addr));
-               if (sc->sc_ro.ro_rt) {
-                       rtfree(sc->sc_ro.ro_rt);
-                       sc->sc_ro.ro_rt = NULL;
-               }
        }
 
-       if (sc->sc_ro.ro_rt == NULL) {
-               rtalloc(&sc->sc_ro);
-               if (sc->sc_ro.ro_rt == NULL) {
-                       return ENETUNREACH;
-               }
-       }
+       result = ip_output(m, NULL, &sc->sc_ro, IP_OUTARGS, NULL, &ipoa);
+       lck_mtx_unlock(&sc->sc_ro_mtx);
 
-       result = ip_output_list(m, 0, NULL, &sc->sc_ro, 0, NULL, NULL);
        /* Assumption: ip_output will free mbuf on errors */
        /* All the output processing is done here, don't let stf_output be called */
        if (result == 0)
                result = EJUSTRETURN;
        *m0 = NULL;
+       IFA_REMREF(&ia6->ia_ifa);
        return result;
 }
 static errno_t
@@ -594,19 +658,24 @@ stf_checkaddr4(
        /*
         * reject packets with broadcast
         */
-       lck_mtx_lock(rt_mtx);
+       lck_rw_lock_shared(in_ifaddr_rwlock);
        for (ia4 = TAILQ_FIRST(&in_ifaddrhead);
             ia4;
             ia4 = TAILQ_NEXT(ia4, ia_link))
        {
-               if ((ia4->ia_ifa.ifa_ifp->if_flags & IFF_BROADCAST) == 0)
+               IFA_LOCK(&ia4->ia_ifa);
+               if ((ia4->ia_ifa.ifa_ifp->if_flags & IFF_BROADCAST) == 0) {
+                       IFA_UNLOCK(&ia4->ia_ifa);
                        continue;
+               }
                if (in->s_addr == ia4->ia_broadaddr.sin_addr.s_addr) {
-                       lck_mtx_unlock(rt_mtx);
+                       IFA_UNLOCK(&ia4->ia_ifa);
+                       lck_rw_done(in_ifaddr_rwlock);
                        return -1;
                }
+               IFA_UNLOCK(&ia4->ia_ifa);
        }
-       lck_mtx_unlock(rt_mtx);
+       lck_rw_done(in_ifaddr_rwlock);
 
        /*
         * perform ingress filter
@@ -619,17 +688,22 @@ stf_checkaddr4(
                sin.sin_family = AF_INET;
                sin.sin_len = sizeof(struct sockaddr_in);
                sin.sin_addr = *in;
-               rt = rtalloc1((struct sockaddr *)&sin, 0, 0UL);
-               if (!rt || rt->rt_ifp != inifp) {
+               rt = rtalloc1((struct sockaddr *)&sin, 0, 0);
+               if (rt != NULL)
+                       RT_LOCK(rt);
+               if (rt == NULL || rt->rt_ifp != inifp) {
 #if 1
                        log(LOG_WARNING, "%s: packet from 0x%x dropped "
                            "due to ingress filter\n", if_name(sc->sc_if),
                            (u_int32_t)ntohl(sin.sin_addr.s_addr));
 #endif
-                       if (rt)
+                       if (rt != NULL) {
+                               RT_UNLOCK(rt);
                                rtfree(rt);
+                       }
                        return -1;
                }
+               RT_UNLOCK(rt);
                rtfree(rt);
        }
 
@@ -721,7 +795,7 @@ in_stf_input(
 
        itos = (ntohl(ip6.ip6_flow) >> 20) & 0xff;
        if ((ifnet_flags(ifp) & IFF_LINK1) != 0)
-               ip_ecn_egress(ECN_ALLOWED, &otos, &itos);
+               ip_ecn_egress(ECN_NORMAL, &otos, &itos);
        else
                ip_ecn_egress(ECN_NOCARE, &otos, &itos);
        ip6.ip6_flow &= ~htonl(0xff << 20);
@@ -758,15 +832,16 @@ stf_rtrequest(
        struct rtentry *rt,
        __unused struct sockaddr *sa)
 {
-
-       if (rt)
+       if (rt != NULL) {
+               RT_LOCK_ASSERT_HELD(rt);
                rt->rt_rmx.rmx_mtu = IPV6_MMTU;
+       }
 }
 
 static errno_t
 stf_ioctl(
        ifnet_t         ifp,
-       u_int32_t       cmd,
+       u_long          cmd,
        void            *data)
 {
        struct ifaddr *ifa;
@@ -778,19 +853,31 @@ stf_ioctl(
        switch (cmd) {
        case SIOCSIFADDR:
                ifa = (struct ifaddr *)data;
-               if (ifa == NULL || ifa->ifa_addr->sa_family != AF_INET6) {
+               if (ifa == NULL) {
                        error = EAFNOSUPPORT;
                        break;
                }
-               sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+               IFA_LOCK(ifa);
+               if (ifa->ifa_addr->sa_family != AF_INET6) {
+                       IFA_UNLOCK(ifa);
+                       error = EAFNOSUPPORT;
+                       break;
+               }
+               sin6 = (struct sockaddr_in6 *)(void *)ifa->ifa_addr;
                if (IN6_IS_ADDR_6TO4(&sin6->sin6_addr)) {
                         if ( !(ifnet_flags( ifp ) & IFF_UP) ) {
                                 /* do this only if the interface is not already up */
                                ifa->ifa_rtrequest = stf_rtrequest;
+                               IFA_UNLOCK(ifa);
                                ifnet_set_flags(ifp, IFF_UP, IFF_UP);
+                       } else {
+                               IFA_UNLOCK(ifa);
                        }
-               } else
+               } else {
+                       IFA_UNLOCK(ifa);
                        error = EINVAL;
+               }
+               IFA_LOCK_ASSERT_NOTHELD(ifa);
                break;
 
        case SIOCADDMULTI:
@@ -803,7 +890,7 @@ stf_ioctl(
                break;
 
        default:
-               error = EINVAL;
+               error = EOPNOTSUPP;
                break;
        }