]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/netinet6/ip6_output.c
xnu-792.18.15.tar.gz
[apple/xnu.git] / bsd / netinet6 / ip6_output.c
index a079aa4b0b5a816fb7429d3271e0cfcc84cdf047..c8b578e6cb2909510c5e76affc99c2f1cb4182b3 100644 (file)
@@ -1,5 +1,5 @@
-/*     $FreeBSD: src/sys/netinet6/ip6_output.c,v 1.13.2.10 2001/07/15 18:18:34 ume Exp $       */
-/*     $KAME: ip6_output.c,v 1.180 2001/05/21 05:37:50 jinmei Exp $    */
+/*     $FreeBSD: src/sys/netinet6/ip6_output.c,v 1.43 2002/10/31 19:45:48 ume Exp $    */
+/*     $KAME: ip6_output.c,v 1.279 2002/01/26 06:12:30 jinmei Exp $    */
 
 /*
  * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
 #include <sys/systm.h>
 #include <sys/kernel.h>
 #include <sys/proc.h>
+#include <sys/kauth.h>
 
 #include <net/if.h>
 #include <net/route.h>
 
 #include <netinet/in.h>
 #include <netinet/in_var.h>
+#include <netinet/ip_var.h>
 #include <netinet6/in6_var.h>
 #include <netinet/ip6.h>
 #include <netinet/icmp6.h>
 #endif
 #include <netkey/key.h>
 extern int ipsec_bypass;
+extern lck_mtx_t *sadb_mutex;
+extern lck_mtx_t *nd6_mutex;
 #endif /* IPSEC */
 
 #include <netinet6/ip6_fw.h>
 
 #include <net/net_osdep.h>
 
+#include <netinet/kpi_ipfilter_var.h>
+
 #ifndef __APPLE__
 static MALLOC_DEFINE(M_IPMOPTS, "ip6_moptions", "internet multicast options");
 #endif
 
 
-static u_long  lo_dl_tag = 0;
+extern u_long  route_generation;
 
 struct ip6_exthdrs {
        struct mbuf *ip6e_ip6;
@@ -117,15 +123,20 @@ struct ip6_exthdrs {
        struct mbuf *ip6e_dest2;
 };
 
-static int ip6_pcbopts __P((struct ip6_pktopts **, struct mbuf *,
-                           struct socket *, struct sockopt *sopt));
-static int ip6_setmoptions __P((int, struct ip6_moptions **, struct mbuf *));
-static int ip6_getmoptions __P((int, struct ip6_moptions *, struct mbuf **));
-static int ip6_copyexthdr __P((struct mbuf **, caddr_t, int));
-static int ip6_insertfraghdr __P((struct mbuf *, struct mbuf *, int,
-                                 struct ip6_frag **));
-static int ip6_insert_jumboopt __P((struct ip6_exthdrs *, u_int32_t));
-static int ip6_splithdr __P((struct mbuf *, struct ip6_exthdrs *));
+static int ip6_pcbopts(struct ip6_pktopts **, struct mbuf *,
+                           struct socket *, struct sockopt *sopt);
+static int ip6_setmoptions(int, struct inpcb *, struct mbuf *);
+static int ip6_getmoptions(int, struct ip6_moptions *, struct mbuf **);
+static int ip6_copyexthdr(struct mbuf **, caddr_t, int);
+static int ip6_insertfraghdr(struct mbuf *, struct mbuf *, int,
+                                 struct ip6_frag **);
+static int ip6_insert_jumboopt(struct ip6_exthdrs *, u_int32_t);
+static int ip6_splithdr(struct mbuf *, struct ip6_exthdrs *);
+
+extern int ip_createmoptions(struct ip_moptions **imop);
+extern int ip_addmembership(struct ip_moptions *imo, struct ip_mreq *mreq);
+extern int ip_dropmembership(struct ip_moptions *imo, struct ip_mreq *mreq);
+extern lck_mtx_t *ip6_mutex;
 
 /*
  * IP6 output. The packet in mbuf chain m contains a skeletal IP6
@@ -139,13 +150,14 @@ static int ip6_splithdr __P((struct mbuf *, struct ip6_exthdrs *));
  * which is rt_rmx.rmx_mtu.
  */
 int
-ip6_output(m0, opt, ro, flags, im6o, ifpp)
-       struct mbuf *m0;
-       struct ip6_pktopts *opt;
-       struct route_in6 *ro;
-       int flags;
-       struct ip6_moptions *im6o;
-       struct ifnet **ifpp;            /* XXX: just for statistics */
+ip6_output(
+       struct mbuf *m0,
+       struct ip6_pktopts *opt,
+       struct route_in6 *ro,
+       int flags,
+       struct ip6_moptions *im6o,
+       struct ifnet **ifpp,    /* XXX: just for statistics */
+       int    locked)          
 {
        struct ip6_hdr *ip6, *mhip6;
        struct ifnet *ifp, *origifp;
@@ -162,20 +174,26 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
        struct route_in6 *ro_pmtu = NULL;
        int hdrsplit = 0;
        int needipsec = 0;
+       ipfilter_t inject_filter_ref;
+       
 #if IPSEC
        int needipsectun = 0;
        struct socket *so = NULL;
        struct secpolicy *sp = NULL;
 
+       if (!locked)
+               lck_mtx_lock(ip6_mutex);
        /* for AH processing. stupid to have "socket" variable in IP layer... */
        if (ipsec_bypass == 0)
        {
                so = ipsec_getsocket(m);
                (void)ipsec_setsocket(m, NULL);
        }
-       ip6 = mtod(m, struct ip6_hdr *);
 #endif /* IPSEC */
 
+       ip6 = mtod(m, struct ip6_hdr *);
+       inject_filter_ref = ipf_get_inject_filter(m);
+
 #define MAKE_EXTHDR(hp, mp)                                            \
     do {                                                               \
        if (hp) {                                                       \
@@ -203,7 +221,8 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
 #if IPSEC
        if (ipsec_bypass != 0)
                goto skip_ipsec;
-
+       
+       lck_mtx_lock(sadb_mutex);
        /* get a security policy for this packet */
        if (so == NULL)
                sp = ipsec6_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND, 0, &error);
@@ -212,6 +231,7 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
 
        if (sp == NULL) {
                ipsec6stat.out_inval++;
+               lck_mtx_unlock(sadb_mutex);
                goto freehdrs;
        }
 
@@ -224,6 +244,7 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
                 * This packet is just discarded.
                 */
                ipsec6stat.out_polvio++;
+               lck_mtx_unlock(sadb_mutex);
                goto freehdrs;
 
        case IPSEC_POLICY_BYPASS:
@@ -236,6 +257,7 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
                if (sp->req == NULL) {
                        /* acquire a policy */
                        error = key_spdacquire(sp);
+                       lck_mtx_unlock(sadb_mutex);
                        goto freehdrs;
                }
                needipsec = 1;
@@ -245,6 +267,7 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
        default:
                printf("ip6_output: Invalid policy found. %d\n", sp->policy);
        }
+       lck_mtx_unlock(sadb_mutex);
        skip_ipsec:
 #endif /* IPSEC */
 
@@ -314,7 +337,8 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
 
                /*
                 * we treat dest2 specially.  this makes IPsec processing
-                * much easier.
+                * much easier.  the goal here is to make mprev point the
+                * mbuf prior to dest2.
                 *
                 * result: IPv6 dest2 payload
                 * m and mprev will point to IPv6 header.
@@ -353,6 +377,61 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
                MAKE_CHAIN(exthdrs.ip6e_rthdr, mprev,
                           nexthdrp, IPPROTO_ROUTING);
 
+               if (!TAILQ_EMPTY(&ipv6_filters)) {
+                       struct ipfilter *filter;
+                       int seen = (inject_filter_ref == 0);
+                       int     fixscope = 0;
+                       struct ipf_pktopts *ippo = 0, ipf_pktopts;
+                                               
+                       if (im6o != NULL && IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+                               ippo = &ipf_pktopts;
+                               ippo->ippo_flags = IPPOF_MCAST_OPTS;
+                               ippo->ippo_mcast_ifnet = im6o->im6o_multicast_ifp;
+                               ippo->ippo_mcast_ttl = im6o->im6o_multicast_hlim;
+                               ippo->ippo_mcast_loop = im6o->im6o_multicast_loop;
+                       }
+
+                       /* Hack: embed the scope_id in the destination */
+                       if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst) &&
+                               (ip6->ip6_dst.s6_addr16[1] == 0) && (ro != NULL)) {
+                               fixscope = 1;
+                               ip6->ip6_dst.s6_addr16[1] = htons(ro->ro_dst.sin6_scope_id);
+                       }
+                       {
+                               lck_mtx_unlock(ip6_mutex);
+                               ipf_ref();
+                               TAILQ_FOREACH(filter, &ipv6_filters, ipf_link) {
+                                       /*
+                                        * No need to proccess packet twice if we've 
+                                        * already seen it
+                                        */
+                                       if (seen == 0) {
+                                               if ((struct ipfilter *)inject_filter_ref == filter)
+                                                       seen = 1;
+                                       } else if (filter->ipf_filter.ipf_output) {
+                                               errno_t result;
+                                               
+                                               result = filter->ipf_filter.ipf_output(filter->ipf_filter.cookie, (mbuf_t*)&m, ippo);
+                                               if (result == EJUSTRETURN) {
+                                                       ipf_unref();
+                                                       locked = 1; /* Don't want to take lock to unlock it right away */
+                                                       goto done;
+                                               }
+                                               if (result != 0) {
+                                                       ipf_unref();
+                                                       locked = 1; /* Don't want to take lock to unlock it right away */
+                                                       goto bad;
+                                               }
+                                       }
+                               }
+                               ipf_unref();
+                               lck_mtx_lock(ip6_mutex);
+                       }
+                       /* Hack: cleanup embedded scope_id if we put it there */
+                       if (fixscope)
+                               ip6->ip6_dst.s6_addr16[1] = 0;
+               }
+
 #if IPSEC
                if (!needipsec)
                        goto skip_ipsec2;
@@ -377,8 +456,12 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
 
                bzero(&state, sizeof(state));
                state.m = m;
+               lck_mtx_unlock(ip6_mutex);
+               lck_mtx_lock(sadb_mutex);
                error = ipsec6_output_trans(&state, nexthdrp, mprev, sp, flags,
                        &needipsectun);
+               lck_mtx_unlock(sadb_mutex);
+               lck_mtx_lock(ip6_mutex);
                m = state.m;
                if (error) {
                        /* mbuf is already reclaimed in ipsec6_output_trans. */
@@ -392,7 +475,7 @@ ip6_output(m0, opt, ro, flags, im6o, ifpp)
                                break;
                        default:
                                printf("ip6_output (ipsec): error code %d\n", error);
-                               /*fall through*/
+                               /* fall through */
                        case ENOENT:
                                /* don't show these error codes to the user */
                                error = 0;
@@ -468,7 +551,9 @@ skip_ipsec2:;
         * and is still up. If not, free it and try again.
         */
        if (ro->ro_rt && ((ro->ro_rt->rt_flags & RTF_UP) == 0 ||
-                        !IN6_ARE_ADDR_EQUAL(&dst->sin6_addr, &ip6->ip6_dst))) {
+                        dst->sin6_family != AF_INET6 ||
+                        !IN6_ARE_ADDR_EQUAL(&dst->sin6_addr, &ip6->ip6_dst) ||
+                        ro->ro_rt->generation_id != route_generation)) {
                rtfree(ro->ro_rt);
                ro->ro_rt = (struct rtentry *)0;
        }
@@ -502,9 +587,11 @@ skip_ipsec2:;
                state.m = m;
                state.ro = (struct route *)ro;
                state.dst = (struct sockaddr *)dst;
-
+               lck_mtx_unlock(ip6_mutex);
+               lck_mtx_lock(sadb_mutex);
                error = ipsec6_output_tunnel(&state, sp, flags);
-
+               lck_mtx_unlock(sadb_mutex);
+               lck_mtx_lock(ip6_mutex);
                m = state.m;
                ro = (struct route_in6 *)state.ro;
                dst = (struct sockaddr_in6 *)state.dst;
@@ -521,18 +608,19 @@ skip_ipsec2:;
                                break;
                        default:
                                printf("ip6_output (ipsec): error code %d\n", error);
-                               /*fall through*/
+                               /* fall through */
                        case ENOENT:
                                /* don't show these error codes to the user */
                                error = 0;
                                break;
                        }
+                       lck_mtx_unlock(sadb_mutex);
                        goto bad;
                }
 
                exthdrs.ip6e_ip6 = m;
        }
-#endif /*IPSEC*/
+#endif /* IPSEC */
 
        if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
                /* Unicast */
@@ -544,16 +632,18 @@ skip_ipsec2:;
                 * if an interface is specified from an upper layer,
                 * ifp must point it.
                 */
+               lck_mtx_lock(rt_mtx);
                if (ro->ro_rt == 0) {
                        /*
                         * non-bsdi always clone routes, if parent is
                         * PRF_CLONING.
                         */
-                       rtalloc((struct route *)ro);
+                       rtalloc_ign_locked((struct route *)ro, 0UL);
                }
                if (ro->ro_rt == 0) {
                        ip6stat.ip6s_noroute++;
                        error = EHOSTUNREACH;
+                       lck_mtx_unlock(rt_mtx);
                        /* XXX in6_ifstat_inc(ifp, ifs6_out_discard); */
                        goto bad;
                }
@@ -562,6 +652,7 @@ skip_ipsec2:;
                ro->ro_rt->rt_use++;
                if (ro->ro_rt->rt_flags & RTF_GATEWAY)
                        dst = (struct sockaddr_in6 *)ro->ro_rt->rt_gateway;
+               lck_mtx_unlock(rt_mtx);
                m->m_flags &= ~(M_BCAST | M_MCAST);     /* just in case */
 
                in6_ifstat_inc(ifp, ifs6_out_request);
@@ -642,12 +733,14 @@ skip_ipsec2:;
                 * ``net'' ff00::/8).
                 */
                if (ifp == NULL) {
+                       lck_mtx_lock(rt_mtx);
                        if (ro->ro_rt == 0) {
-                               ro->ro_rt = rtalloc1((struct sockaddr *)
+                               ro->ro_rt = rtalloc1_locked((struct sockaddr *)
                                                &ro->ro_dst, 0, 0UL);
                        }
                        if (ro->ro_rt == 0) {
                                ip6stat.ip6s_noroute++;
+                               lck_mtx_unlock(rt_mtx);
                                error = EHOSTUNREACH;
                                /* XXX in6_ifstat_inc(ifp, ifs6_out_discard) */
                                goto bad;
@@ -655,6 +748,7 @@ skip_ipsec2:;
                        ia = ifatoia6(ro->ro_rt->rt_ifa);
                        ifp = ro->ro_rt->rt_ifp;
                        ro->ro_rt->rt_use++;
+                       lck_mtx_unlock(rt_mtx);
                }
 
                if ((flags & IPV6_FORWARDING) == 0)
@@ -670,7 +764,9 @@ skip_ipsec2:;
                        error = ENETUNREACH;
                        goto bad;
                }
+               ifnet_lock_shared(ifp);
                IN6_LOOKUP_MULTI(ip6->ip6_dst, ifp, in6m);
+               ifnet_lock_done(ifp);
                if (in6m != NULL &&
                   (im6o == NULL || im6o->im6o_multicast_loop)) {
                        /*
@@ -785,9 +881,8 @@ skip_ipsec2:;
                 * We eventually have sockaddr_in6 and use the sin6_scope_id
                 * field of the structure here.
                 * We rely on the consistency between two scope zone ids
-                * of source add destination, which should already be assured
-                * larger scopes than link will be supported in the near
-                * future.
+                * of source and destination, which should already be assured.
+                * Larger scopes than link will be supported in the future. 
                 */
                origifp = NULL;
                if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src))
@@ -825,9 +920,9 @@ skip_ipsec2:;
         */
         if (ip6_fw_enable && ip6_fw_chk_ptr) {
                u_short port = 0;
-               m->m_pkthdr.rcvif = NULL;       /*XXX*/
+               m->m_pkthdr.rcvif = NULL;       /* XXX */
                /* If ipfw says divert, we have to just drop packet */
-               if ((*ip6_fw_chk_ptr)(&ip6, ifp, &port, &m)) {
+               if (ip6_fw_chk_ptr(&ip6, ifp, &port, &m)) {
                        m_freem(m);
                        goto done;
                }
@@ -905,7 +1000,7 @@ skip_ipsec2:;
                ipsec_delaux(m);
 #endif
 
-               error = nd6_output(ifp, origifp, m, dst, ro->ro_rt);
+               error = nd6_output(ifp, origifp, m, dst, ro->ro_rt, 1);
                goto done;
        } else if (mtu < IPV6_MMTU) {
                /*
@@ -963,7 +1058,8 @@ skip_ipsec2:;
 
                /*
                 * Loop through length of segment after first fragment,
-                * make new header and copy data of each part and link onto chain.
+                * make new header and copy data of each part and link onto
+                * chain.
                 */
                m0 = m;
                for (off = hlen; off < tlen; off += len) {
@@ -1001,7 +1097,8 @@ skip_ipsec2:;
                        }
                        m_cat(m, m_frgpart);
                        m->m_pkthdr.len = len + hlen + sizeof(*ip6f);
-                       m->m_pkthdr.rcvif = (struct ifnet *)0;
+                       m->m_pkthdr.rcvif = 0;
+                       m->m_pkthdr.socket_id = m0->m_pkthdr.socket_id;
                        ip6f->ip6f_reserved = 0;
                        ip6f->ip6f_ident = id;
                        ip6f->ip6f_nxt = nextproto;
@@ -1034,7 +1131,8 @@ sendorfree:
                        /* clean ipsec history once it goes out of the node */
                        ipsec_delaux(m);
 #endif
-                       error = nd6_output(ifp, origifp, m, dst, ro->ro_rt);
+                       error = nd6_output(ifp, origifp, m, dst, ro->ro_rt, 1);
+
                } else
                        m_freem(m);
        }
@@ -1043,6 +1141,8 @@ sendorfree:
                ip6stat.ip6s_fragmented++;
 
 done:
+       if (!locked)
+               lck_mtx_unlock(ip6_mutex);
        if (ro == &ip6route && ro->ro_rt) { /* brace necessary for rtfree */
                rtfree(ro->ro_rt);
        } else if (ro_pmtu == &ip6route && ro_pmtu->ro_rt) {
@@ -1050,8 +1150,11 @@ done:
        }
 
 #if IPSEC
-       if (sp != NULL)
+       if (sp != NULL) {
+               lck_mtx_lock(sadb_mutex);
                key_freesp(sp);
+               lck_mtx_unlock(sadb_mutex);
+       }
 #endif /* IPSEC */
 
        return(error);
@@ -1240,6 +1343,8 @@ ip6_insertfraghdr(m0, m, hlen, frghdrp)
        return(0);
 }
 
+extern int load_ipfw();
+
 /*
  * IP6 socket option processing.
  */
@@ -1255,18 +1360,18 @@ ip6_ctloutput(so, sopt)
        int optlen;
        struct proc *p;
 
-       if (sopt) {
+       level = error = optval = 0;
+       if (sopt == NULL) 
+               panic("ip6_ctloutput: arg soopt is NULL");
+       else {
                level = sopt->sopt_level;
                op = sopt->sopt_dir;
                optname = sopt->sopt_name;
                optlen = sopt->sopt_valsize;
                p = sopt->sopt_p;
-       } else {
-               panic("ip6_ctloutput: arg soopt is NULL");
        }
-       error = optval = 0;
 
-       privileged = (p == 0 || suser(p->p_ucred, &p->p_acflag)) ? 0 : 1;
+       privileged = (p == 0 || proc_suser(p)) ? 0 : 1;
 
        if (level == IPPROTO_IPV6) {
                switch (op) {
@@ -1277,6 +1382,10 @@ ip6_ctloutput(so, sopt)
                        {
                                struct mbuf *m;
 
+                               if (sopt->sopt_valsize > MCLBYTES) {
+                                       error = EMSGSIZE;
+                                       break;
+                               }
                                error = soopt_getm(sopt, &m); /* XXX */
                                if (error != NULL)
                                        break;
@@ -1358,12 +1467,11 @@ do { \
                                                error = EINVAL;
                                                break;
                                        }
-                                       /*
-                                        * XXX: BINDV6ONLY should be integrated
-                                        * into V6ONLY.
-                                        */
-                                       OPTSET(IN6P_BINDV6ONLY);
                                        OPTSET(IN6P_IPV6_V6ONLY);
+                                       if (optval)
+                                               in6p->in6p_vflag &= ~INP_IPV4;
+                                       else
+                                               in6p->in6p_vflag |= INP_IPV4;
                                        break;
                                }
                                break;
@@ -1430,9 +1538,7 @@ do { \
                                m->m_len = sopt->sopt_valsize;
                                error = sooptcopyin(sopt, mtod(m, char *),
                                                    m->m_len, m->m_len);
-                               error = ip6_setmoptions(sopt->sopt_name,
-                                                       &in6p->in6p_moptions,
-                                                       m);
+                               error = ip6_setmoptions(sopt->sopt_name, in6p, m);
                                (void)m_free(m);
                            }
                                break;
@@ -1472,16 +1578,22 @@ do { \
                                size_t len = 0;
                                struct mbuf *m;
 
+                                if (sopt->sopt_valsize > MCLBYTES) {
+                                        error = EMSGSIZE;
+                                        break;
+                                }
                                if ((error = soopt_getm(sopt, &m)) != 0) /* XXX */
                                        break;
-                               if (error = soopt_mcopyin(sopt, m)) /* XXX */
+                               if ((error = soopt_mcopyin(sopt, m)) != 0) /* XXX */
                                        break;
                                if (m) {
                                        req = mtod(m, caddr_t);
                                        len = m->m_len;
                                }
+                               lck_mtx_lock(sadb_mutex);
                                error = ipsec6_set_policy(in6p, optname, req,
                                                          len, privileged);
+                               lck_mtx_unlock(sadb_mutex);
                                m_freem(m);
                            }
                                break;
@@ -1491,21 +1603,12 @@ do { \
                        case IPV6_FW_DEL:
                        case IPV6_FW_FLUSH:
                        case IPV6_FW_ZERO:
-                           {
-                               struct mbuf *m;
-                               struct mbuf **mp = &m;
-
-                               if (ip6_fw_ctl_ptr == NULL)
+                               {
+                               if (ip6_fw_ctl_ptr == NULL && load_ipfw() != 0)
                                        return EINVAL;
-                               /* XXX */
-                               if ((error = soopt_getm(sopt, &m)) != 0)
-                                       break;
-                               /* XXX */
-                               if ((error = soopt_mcopyin(sopt, m)) != 0)
-                                       break;
-                               error = (*ip6_fw_ctl_ptr)(optname, mp);
-                               m = *mp;
-                           }
+
+                               error = (*ip6_fw_ctl_ptr)(sopt);
+                               }
                                break;
 
                        default:
@@ -1550,8 +1653,7 @@ do { \
                                        break;
 
                                case IPV6_V6ONLY:
-                                       /* XXX: see the setopt case. */
-                                       optval = OPTBIT(IN6P_BINDV6ONLY);
+                                       optval = OPTBIT(IN6P_IPV6_V6ONLY);
                                        break;
 
                                case IPV6_PORTRANGE:
@@ -1576,8 +1678,8 @@ do { \
                        case IPV6_HOPOPTS:
                        case IPV6_RTHDR:
                        case IPV6_DSTOPTS:
-                               if (optname == IPV6_HOPOPTS ||
-                                   optname == IPV6_DSTOPTS ||
+                               if ((optname == IPV6_HOPOPTS ||
+                                   optname == IPV6_DSTOPTS) &&
                                    !privileged)
                                        return(EPERM);
                                switch (optname) {
@@ -1629,6 +1731,10 @@ do { \
                                struct mbuf *m = NULL;
                                struct mbuf **mp = &m;
 
+                                if (sopt->sopt_valsize > MCLBYTES) {
+                                        error = EMSGSIZE;
+                                        break;
+                                }
                                error = soopt_getm(sopt, &m); /* XXX */
                                if (error != NULL)
                                        break;
@@ -1639,7 +1745,9 @@ do { \
                                        req = mtod(m, caddr_t);
                                        len = m->m_len;
                                }
+                               lck_mtx_lock(sadb_mutex);
                                error = ipsec6_get_policy(in6p, req, len, mp);
+                               lck_mtx_unlock(sadb_mutex);
                                if (error == 0)
                                        error = soopt_mcopyout(sopt, m); /*XXX*/
                                if (error == 0 && m)
@@ -1649,20 +1757,12 @@ do { \
 #endif /* KAME IPSEC */
 
                        case IPV6_FW_GET:
-                         {
-                               struct mbuf *m;
-                               struct mbuf **mp = &m;
-
-                               if (ip6_fw_ctl_ptr == NULL)
-                               {
+                               {
+                               if (ip6_fw_ctl_ptr == NULL && load_ipfw() != 0)
                                        return EINVAL;
+
+                               error = (*ip6_fw_ctl_ptr)(sopt);
                                }
-                               error = (*ip6_fw_ctl_ptr)(optname, mp);
-                               if (error == 0)
-                                       error = soopt_mcopyout(sopt, m); /* XXX */
-                               if (error == 0 && m)
-                                       m_freem(m);
-                         }
                                break;
 
                        default:
@@ -1708,7 +1808,8 @@ ip6_pcbopts(pktopt, m, so, sopt)
 
        if (!m || m->m_len == 0) {
                /*
-                * Only turning off any previous options.
+                * Only turning off any previous options, regardless of
+                * whether the opt is just created or given.
                 */
                if (opt)
                        FREE(opt, M_IP6OPT);
@@ -1716,10 +1817,11 @@ ip6_pcbopts(pktopt, m, so, sopt)
        }
 
        /*  set options specified by user. */
-       if (p && !suser(p->p_ucred, &p->p_acflag))
+       if (p && !proc_suser(p))
                priv = 1;
        if ((error = ip6_setpktoptions(m, opt, priv, 1)) != 0) {
                ip6_clearpktopts(opt, 1, -1); /* XXX: discard all options */
+               FREE(opt, M_IP6OPT);
                return(error);
        }
        *pktopt = opt;
@@ -1811,7 +1913,7 @@ ip6_copypktopts(src, canwait)
 
        dst = _MALLOC(sizeof(*dst), M_IP6OPT, canwait);
        if (dst == NULL && canwait == M_NOWAIT)
-               goto bad;
+               return (NULL);
        bzero(dst, sizeof(*dst));
 
        dst->ip6po_hlim = src->ip6po_hlim;
@@ -1837,13 +1939,13 @@ ip6_copypktopts(src, canwait)
        return(dst);
 
   bad:
-       printf("ip6_copypktopts: copy failed");
        if (dst->ip6po_pktinfo) FREE(dst->ip6po_pktinfo, M_IP6OPT);
        if (dst->ip6po_nexthop) FREE(dst->ip6po_nexthop, M_IP6OPT);
        if (dst->ip6po_hbh) FREE(dst->ip6po_hbh, M_IP6OPT);
        if (dst->ip6po_dest1) FREE(dst->ip6po_dest1, M_IP6OPT);
        if (dst->ip6po_dest2) FREE(dst->ip6po_dest2, M_IP6OPT);
        if (dst->ip6po_rthdr) FREE(dst->ip6po_rthdr, M_IP6OPT);
+       FREE(dst, M_IP6OPT);
        return(NULL);
 }
 #undef PKTOPT_EXTHDRCPY
@@ -1864,16 +1966,18 @@ ip6_freepcbopts(pktopt)
  * Set the IP6 multicast options in response to user setsockopt().
  */
 static int
-ip6_setmoptions(optname, im6op, m)
-       int optname;
-       struct ip6_moptions **im6op;
-       struct mbuf *m;
+ip6_setmoptions(
+       int optname,
+       struct inpcb* in6p,
+       struct mbuf *m)
 {
        int error = 0;
        u_int loop, ifindex;
        struct ipv6_mreq *mreq;
        struct ifnet *ifp;
+       struct ip6_moptions **im6op = &in6p->in6p_moptions;
        struct ip6_moptions *im6o = *im6op;
+       struct ip_moptions *imo;
        struct route_in6 ro;
        struct sockaddr_in6 *dst;
        struct in6_multi_mship *imm;
@@ -1895,6 +1999,18 @@ ip6_setmoptions(optname, im6op, m)
                im6o->im6o_multicast_loop = IPV6_DEFAULT_MULTICAST_LOOP;
                LIST_INIT(&im6o->im6o_memberships);
        }
+       
+       if (in6p->inp_moptions == NULL) {
+               /*
+                * No IPv4 multicast option buffer attached to the pcb;
+                * call ip_createmoptions to allocate one and initialize
+                * to default values.
+                */
+               error = ip_createmoptions(&in6p->inp_moptions);
+               if (error != 0)
+                       return error;
+       }
+       imo = in6p->inp_moptions;
 
        switch (optname) {
 
@@ -1917,6 +2033,7 @@ ip6_setmoptions(optname, im6op, m)
                        break;
                }
                im6o->im6o_multicast_ifp = ifp;
+               imo->imo_multicast_ifp = ifp;
                break;
 
        case IPV6_MULTICAST_HOPS:
@@ -1932,10 +2049,13 @@ ip6_setmoptions(optname, im6op, m)
                bcopy(mtod(m, u_int *), &optval, sizeof(optval));
                if (optval < -1 || optval >= 256)
                        error = EINVAL;
-               else if (optval == -1)
+               else if (optval == -1) {
                        im6o->im6o_multicast_hlim = ip6_defmcasthlim;
-               else
+                       imo->imo_multicast_ttl = IP_DEFAULT_MULTICAST_TTL;
+               } else {
                        im6o->im6o_multicast_hlim = optval;
+                       imo->imo_multicast_ttl = optval;
+               }
                break;
            }
 
@@ -1954,6 +2074,7 @@ ip6_setmoptions(optname, im6op, m)
                        break;
                }
                im6o->im6o_multicast_loop = loop;
+               imo->imo_multicast_loop = loop;
                break;
 
        case IPV6_JOIN_GROUP:
@@ -1966,30 +2087,60 @@ ip6_setmoptions(optname, im6op, m)
                        break;
                }
                mreq = mtod(m, struct ipv6_mreq *);
+               /*
+                * If the interface is specified, validate it.
+                */
+               if (mreq->ipv6mr_interface < 0
+                || if_index < mreq->ipv6mr_interface) {
+                       error = ENXIO;  /* XXX EINVAL? */
+                       break;
+               }
+               
                if (IN6_IS_ADDR_UNSPECIFIED(&mreq->ipv6mr_multiaddr)) {
                        /*
                         * We use the unspecified address to specify to accept
                         * all multicast addresses. Only super user is allowed
                         * to do this.
                         */
-                       if (suser(p->p_ucred, &p->p_acflag))
+                       if (suser(kauth_cred_get(), 0))
                        {
                                error = EACCES;
                                break;
                        }
+               } else if (IN6_IS_ADDR_V4MAPPED(&mreq->ipv6mr_multiaddr)) {
+                       struct ip_mreq v4req;
+                       
+                       v4req.imr_multiaddr.s_addr = mreq->ipv6mr_multiaddr.s6_addr32[3];
+                       v4req.imr_interface.s_addr = INADDR_ANY;
+                       
+                       /* Find an IPv4 address on the specified interface. */
+                       if (mreq->ipv6mr_interface != 0) {
+                               struct in_ifaddr *ifa;
+
+                               ifp = ifindex2ifnet[mreq->ipv6mr_interface];
+
+                               lck_mtx_lock(rt_mtx);
+                               TAILQ_FOREACH(ifa, &in_ifaddrhead, ia_link) {
+                                       if (ifa->ia_ifp == ifp) {
+                                               v4req.imr_interface = IA_SIN(ifa)->sin_addr;
+                                               break;
+                                       }
+                               }
+                               lck_mtx_unlock(rt_mtx);
+                               
+                               if (v4req.imr_multiaddr.s_addr == 0) {
+                                       /* Interface has no IPv4 address. */
+                                       error = EINVAL;
+                                       break;
+                               }
+                       }
+                       
+                       error = ip_addmembership(imo, &v4req);
+                       break;
                } else if (!IN6_IS_ADDR_MULTICAST(&mreq->ipv6mr_multiaddr)) {
                        error = EINVAL;
                        break;
                }
-
-               /*
-                * If the interface is specified, validate it.
-                */
-               if (mreq->ipv6mr_interface < 0
-                || if_index < mreq->ipv6mr_interface) {
-                       error = ENXIO;  /* XXX EINVAL? */
-                       break;
-               }
                /*
                 * If no interface was explicitly specified, choose an
                 * appropriate one according to the given multicast address.
@@ -2041,6 +2192,7 @@ ip6_setmoptions(optname, im6op, m)
                /*
                 * See if the membership already exists.
                 */
+               lck_mtx_lock(nd6_mutex);
                for (imm = im6o->im6o_memberships.lh_first;
                     imm != NULL; imm = imm->i6mm_chain.le_next)
                        if (imm->i6mm_maddr->in6m_ifp == ifp &&
@@ -2049,6 +2201,7 @@ ip6_setmoptions(optname, im6op, m)
                                break;
                if (imm != NULL) {
                        error = EADDRINUSE;
+                       lck_mtx_unlock(nd6_mutex);
                        break;
                }
                /*
@@ -2058,14 +2211,17 @@ ip6_setmoptions(optname, im6op, m)
                imm = _MALLOC(sizeof(*imm), M_IPMADDR, M_WAITOK);
                if (imm == NULL) {
                        error = ENOBUFS;
+                       lck_mtx_unlock(nd6_mutex);
                        break;
                }
                if ((imm->i6mm_maddr =
-                    in6_addmulti(&mreq->ipv6mr_multiaddr, ifp, &error)) == NULL) {
+                    in6_addmulti(&mreq->ipv6mr_multiaddr, ifp, &error, 1)) == NULL) {
                        FREE(imm, M_IPMADDR);
+                       lck_mtx_unlock(nd6_mutex);
                        break;
                }
                LIST_INSERT_HEAD(&im6o->im6o_memberships, imm, i6mm_chain);
+               lck_mtx_unlock(nd6_mutex);
                break;
 
        case IPV6_LEAVE_GROUP:
@@ -2078,15 +2234,6 @@ ip6_setmoptions(optname, im6op, m)
                        break;
                }
                mreq = mtod(m, struct ipv6_mreq *);
-               if (IN6_IS_ADDR_UNSPECIFIED(&mreq->ipv6mr_multiaddr)) {
-                       if (suser(p->p_ucred, &p->p_acflag)) {
-                               error = EACCES;
-                               break;
-                       }
-               } else if (!IN6_IS_ADDR_MULTICAST(&mreq->ipv6mr_multiaddr)) {
-                       error = EINVAL;
-                       break;
-               }
                /*
                 * If an interface address was specified, get a pointer
                 * to its ifnet structure.
@@ -2097,6 +2244,37 @@ ip6_setmoptions(optname, im6op, m)
                        break;
                }
                ifp = ifindex2ifnet[mreq->ipv6mr_interface];
+               
+               if (IN6_IS_ADDR_UNSPECIFIED(&mreq->ipv6mr_multiaddr)) {
+                       if (suser(kauth_cred_get(), 0)) {
+                               error = EACCES;
+                               break;
+                       }
+               } else if (IN6_IS_ADDR_V4MAPPED(&mreq->ipv6mr_multiaddr)) {
+                       struct ip_mreq v4req;
+                       
+                       v4req.imr_multiaddr.s_addr = mreq->ipv6mr_multiaddr.s6_addr32[3];
+                       v4req.imr_interface.s_addr = INADDR_ANY;
+                       
+                       if (ifp != NULL) {
+                               struct in_ifaddr *ifa;
+                               
+                               lck_mtx_lock(rt_mtx);
+                               TAILQ_FOREACH(ifa, &in_ifaddrhead, ia_link) {
+                                       if (ifa->ia_ifp == ifp) {
+                                               v4req.imr_interface = IA_SIN(ifa)->sin_addr;
+                                               break;
+                                       }
+                               }
+                               lck_mtx_unlock(rt_mtx);
+                       }
+                       
+                       error = ip_dropmembership(imo, &v4req);
+                       break;
+               } else if (!IN6_IS_ADDR_MULTICAST(&mreq->ipv6mr_multiaddr)) {
+                       error = EINVAL;
+                       break;
+               }
                /*
                 * Put interface index into the multicast address,
                 * if the address has link-local scope.
@@ -2108,6 +2286,7 @@ ip6_setmoptions(optname, im6op, m)
                /*
                 * Find the membership in the membership list.
                 */
+               lck_mtx_lock(nd6_mutex);
                for (imm = im6o->im6o_memberships.lh_first;
                     imm != NULL; imm = imm->i6mm_chain.le_next) {
                        if ((ifp == NULL ||
@@ -2119,6 +2298,7 @@ ip6_setmoptions(optname, im6op, m)
                if (imm == NULL) {
                        /* Unable to resolve interface */
                        error = EADDRNOTAVAIL;
+                       lck_mtx_unlock(nd6_mutex);
                        break;
                }
                /*
@@ -2126,7 +2306,8 @@ ip6_setmoptions(optname, im6op, m)
                 * membership points.
                 */
                LIST_REMOVE(imm, i6mm_chain);
-               in6_delmulti(imm->i6mm_maddr);
+               in6_delmulti(imm->i6mm_maddr, 1);
+               lck_mtx_unlock(nd6_mutex);
                FREE(imm, M_IPMADDR);
                break;
 
@@ -2138,6 +2319,7 @@ ip6_setmoptions(optname, im6op, m)
        /*
         * If all options have default values, no need to keep the mbuf.
         */
+       lck_mtx_lock(nd6_mutex);
        if (im6o->im6o_multicast_ifp == NULL &&
            im6o->im6o_multicast_hlim == ip6_defmcasthlim &&
            im6o->im6o_multicast_loop == IPV6_DEFAULT_MULTICAST_LOOP &&
@@ -2145,6 +2327,15 @@ ip6_setmoptions(optname, im6op, m)
                FREE(*im6op, M_IPMOPTS);
                *im6op = NULL;
        }
+       if (imo->imo_multicast_ifp == NULL &&
+           imo->imo_multicast_vif == -1 &&
+           imo->imo_multicast_ttl == IP_DEFAULT_MULTICAST_TTL &&
+           imo->imo_multicast_loop == IP_DEFAULT_MULTICAST_LOOP &&
+           imo->imo_num_memberships == 0) {
+               ip_freemoptions(imo);
+               in6p->inp_moptions = 0;
+       }
+       lck_mtx_unlock(nd6_mutex);
 
        return(error);
 }
@@ -2207,13 +2398,15 @@ ip6_freemoptions(im6o)
 
        if (im6o == NULL)
                return;
-
+       
+       lck_mtx_lock(nd6_mutex);
        while ((imm = im6o->im6o_memberships.lh_first) != NULL) {
                LIST_REMOVE(imm, i6mm_chain);
                if (imm->i6mm_maddr)
-                       in6_delmulti(imm->i6mm_maddr);
+                       in6_delmulti(imm->i6mm_maddr, 1);
                FREE(imm, M_IPMADDR);
        }
+       lck_mtx_unlock(nd6_mutex);
        FREE(im6o, M_IPMOPTS);
 }
 
@@ -2292,8 +2485,12 @@ ip6_setpktoptions(control, opt, priv, needcopy)
                                ia6 = (struct in6_ifaddr *)ifa_ifwithaddr(sin6tosa(&sin6));
                                if (ia6 == NULL ||
                                    (ia6->ia6_flags & (IN6_IFF_ANYCAST |
-                                                      IN6_IFF_NOTREADY)) != 0)
+                                                      IN6_IFF_NOTREADY)) != 0) {
+                                       if (ia6) ifafree(&ia6->ia_ifa);
                                        return(EADDRNOTAVAIL);
+                               }
+                               ifafree(&ia6->ia_ifa);
+                               ia6 = NULL;
                        }
                        break;
 
@@ -2439,10 +2636,10 @@ ip6_setpktoptions(control, opt, priv, needcopy)
  * pointer that might NOT be &loif -- easier than replicating that code here.
  */
 void
-ip6_mloopback(ifp, m, dst)
-       struct ifnet *ifp;
-       struct mbuf *m;
-       struct sockaddr_in6 *dst;
+ip6_mloopback(
+       struct ifnet *ifp,
+       struct mbuf *m,
+       struct sockaddr_in6 *dst)
 {
        struct mbuf *copym;
        struct ip6_hdr *ip6;
@@ -2484,19 +2681,19 @@ ip6_mloopback(ifp, m, dst)
 
        /* Makes sure the HW checksum flags are cleaned before sending the packet */
 
-       copym->m_pkthdr.rcvif = (struct ifnet *)0;
+       copym->m_pkthdr.rcvif = 0;
        copym->m_pkthdr.csum_data = 0;
        copym->m_pkthdr.csum_flags = 0;
 
-       if (lo_dl_tag == 0)
-               dlil_find_dltag(APPLE_IF_FAM_LOOPBACK, 0, PF_INET, &lo_dl_tag);
-
-       if (lo_dl_tag) 
-               dlil_output(lo_dl_tag, copym, 0, (struct sockaddr *)&dst, 0);
-       else 
+       if (lo_ifp) {
+               copym->m_pkthdr.rcvif = ifp;
+               lck_mtx_unlock(ip6_mutex);
+               dlil_output(lo_ifp, PF_INET6, copym, 0, (struct sockaddr *)dst, 0);
+               lck_mtx_lock(ip6_mutex);
+       } else
                m_free(copym);
 #else
-               (void)if_simloop(ifp, copym, dst->sin6_family, NULL);
+       (void)if_simloop(ifp, copym, dst->sin6_family, NULL);
 #endif
 }