]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/netinet6/in6_src.c
xnu-792.tar.gz
[apple/xnu.git] / bsd / netinet6 / in6_src.c
index ff60c18e0e6e4c9316367512aacd0ff214730c9e..dd0b5c0b2a6a5303c355d86190cc5656a42d41e2 100644 (file)
@@ -1,4 +1,5 @@
-/*     $KAME: in6_src.c,v 1.10 2000/03/28 09:02:23 k-sugyou Exp $      */
+/*     $FreeBSD: src/sys/netinet6/in6_src.c,v 1.1.2.2 2001/07/03 11:01:52 ume Exp $    */
+/*     $KAME: in6_src.c,v 1.37 2001/03/29 05:34:31 itojun Exp $        */
 
 /*
  * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
  *     @(#)in_pcb.c    8.2 (Berkeley) 1/4/94
  */
 
-/* for MIP6 */
-#if (defined(__FreeBSD__) && __FreeBSD__ >= 3) || defined(__NetBSD__)
-#include "opt_inet.h"
-#endif
-
-#ifdef __NetBSD__
-#include "opt_ipsec.h"
-#endif
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/protosw.h>
 #include <sys/socket.h>
 #include <sys/socketvar.h>
-#if !(defined(__FreeBSD__) && __FreeBSD__ >= 4)
-#include <sys/ioctl.h>
-#endif
 #include <sys/errno.h>
 #include <sys/time.h>
 #include <sys/proc.h>
+#include <kern/lock.h>
 
 #include <net/if.h>
 #include <net/route.h>
 #include <netinet/in_pcb.h>
 #include <netinet6/in6_var.h>
 #include <netinet/ip6.h>
-#if !(defined(__OpenBSD__) || (defined(__bsdi__) && _BSDI_VERSION >= 199802))
 #include <netinet6/in6_pcb.h>
-#endif
 #include <netinet6/ip6_var.h>
 #include <netinet6/nd6.h>
+#if ENABLE_DEFAULT_SCOPE
+#include <netinet6/scope6_var.h> 
+#endif
 
 #include <net/net_osdep.h>
 
-#ifndef __bsdi__
 #include "loop.h"
-#endif
-#if defined(__NetBSD__) || defined(__OpenBSD__)
-extern struct ifnet loif[NLOOP];
-#endif
-
-#if MIP6
-#include <netinet6/mip6.h>
-#include <netinet6/mip6_common.h>
-
-extern struct nd_prefix *(*mip6_get_home_prefix_hook) __P((void));
-#endif
+extern lck_mtx_t *rt_mtx;
 
 /*
- * Return an IPv6 address, which is the most appropriate for given
+ * Return an IPv6 address, which is the most appropriate for given
  * destination and user specified options.
- * If necessary, this function lookups the routing table and return
+ * If necessary, this function lookups the routing table and returns
  * an entry to the caller for later use.
  */
 struct in6_addr *
-in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
-       struct sockaddr_in6 *dstsock;
-       struct ip6_pktopts *opts;
-       struct ip6_moptions *mopts;
-       struct route_in6 *ro;
-       struct in6_addr *laddr;
-       int *errorp;
+in6_selectsrc(
+       struct sockaddr_in6 *dstsock,
+       struct ip6_pktopts *opts,
+       struct ip6_moptions *mopts,
+       struct route_in6 *ro,
+       struct in6_addr *laddr,
+       struct in6_addr *src_storage,
+       int *errorp)
 {
        struct in6_addr *dst;
        struct in6_ifaddr *ia6 = 0;
@@ -169,7 +151,8 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
                        *errorp = EADDRNOTAVAIL;
                        return(0);
                }
-               return(&satosin6(&ia6->ia_addr)->sin6_addr);
+               *src_storage = satosin6(&ia6->ia_addr)->sin6_addr;
+               return src_storage;
        }
 
        /*
@@ -198,7 +181,8 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
                        *errorp = EADDRNOTAVAIL;
                        return(0);
                }
-               return(&satosin6(&ia6->ia_addr)->sin6_addr);
+               *src_storage = satosin6(&ia6->ia_addr)->sin6_addr;
+               return src_storage;
        }
 
        /*
@@ -212,21 +196,9 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
         */
        if (IN6_IS_ADDR_MULTICAST(dst)) {
                struct ifnet *ifp = mopts ? mopts->im6o_multicast_ifp : NULL;
-#ifdef __bsdi__
-#if _BSDI_VERSION >= 199802
-               extern struct ifnet *loifp;
-#else
-               extern struct ifnet loif;
-               struct ifnet *loifp = &loif;
-#endif
-#endif
 
                if (ifp == NULL && IN6_IS_ADDR_MC_NODELOCAL(dst)) {
-#ifdef __bsdi__
-                       ifp = loifp;
-#else
                        ifp = &loif[0];
-#endif
                }
 
                if (ifp) {
@@ -235,7 +207,8 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
                                *errorp = EADDRNOTAVAIL;
                                return(0);
                        }
-                       return(&satosin6(&ia6->ia_addr)->sin6_addr);
+                       *src_storage = satosin6(&ia6->ia_addr)->sin6_addr;
+                       return src_storage;
                }
        }
 
@@ -250,7 +223,7 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
 
                if (opts && opts->ip6po_nexthop) {
                        sin6_next = satosin6(opts->ip6po_nexthop);
-                       rt = nd6_lookup(&sin6_next->sin6_addr, 1, NULL);
+                       rt = nd6_lookup(&sin6_next->sin6_addr, 1, NULL, 0);
                        if (rt) {
                                ia6 = in6_ifawithscope(rt->rt_ifp, dst);
                                if (ia6 == 0)
@@ -260,69 +233,44 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
                                *errorp = EADDRNOTAVAIL;
                                return(0);
                        }
-                       return(&satosin6(&ia6->ia_addr)->sin6_addr);
-               }
-       }
-
-#if MIP6
-       /*
-        * This is needed to assure that the Home Address is used for
-        * outgoing packets when not at home. We can't choose any other
-        * address if we want to keep connections up during movement.
-        */
-       if (mip6_get_home_prefix_hook) {        /* Only Mobile Node */
-               struct nd_prefix *pr;
-               if ((pr = (*mip6_get_home_prefix_hook)()) &&
-                   !IN6_IS_ADDR_UNSPECIFIED(&pr->ndpr_addr)) {
-                       if (in6_addrscope(dst) ==
-                           in6_addrscope(&pr->ndpr_addr)) {
-#if MIP6_DEBUG
-                               /* Noisy but useful */
-                               mip6_debug("%s: Local address %s is chosen "
-                                          "for pcb to dest %s.\n",
-                                          __FUNCTION__,
-                                          ip6_sprintf(&pr->ndpr_addr),
-                                          ip6_sprintf(dst));
-#endif
-                               return(&pr->ndpr_addr);
-                       }
+                       *src_storage = satosin6(&ia6->ia_addr)->sin6_addr;
+                       return src_storage;
                }
        }
-#endif /* MIP6 */
 
        /*
         * If route is known or can be allocated now,
         * our src addr is taken from the i/f, else punt.
         */
        if (ro) {
+               lck_mtx_lock(rt_mtx);
                if (ro->ro_rt &&
-                   !IN6_ARE_ADDR_EQUAL(&satosin6(&ro->ro_dst)->sin6_addr, dst)) {
-                       RTFREE(ro->ro_rt);
+                   (!(ro->ro_rt->rt_flags & RTF_UP) ||
+                    satosin6(&ro->ro_dst)->sin6_family != AF_INET6 || 
+                    !IN6_ARE_ADDR_EQUAL(&satosin6(&ro->ro_dst)->sin6_addr,
+                                        dst))) {
+                       rtfree_locked(ro->ro_rt);
                        ro->ro_rt = (struct rtentry *)0;
                }
                if (ro->ro_rt == (struct rtentry *)0 ||
-                   ro->ro_rt->rt_ifp == (struct ifnet *)0) {
+                   ro->ro_rt->rt_ifp == 0) {
+                       struct sockaddr_in6 *sa6;
+
                        /* No route yet, so try to acquire one */
                        bzero(&ro->ro_dst, sizeof(struct sockaddr_in6));
-                       ro->ro_dst.sin6_family = AF_INET6;
-                       ro->ro_dst.sin6_len = sizeof(struct sockaddr_in6);
-                       ro->ro_dst.sin6_addr = *dst;
+                       sa6 = (struct sockaddr_in6 *)&ro->ro_dst;
+                       sa6->sin6_family = AF_INET6;
+                       sa6->sin6_len = sizeof(struct sockaddr_in6);
+                       sa6->sin6_addr = *dst;
+                       sa6->sin6_scope_id = dstsock->sin6_scope_id;
                        if (IN6_IS_ADDR_MULTICAST(dst)) {
-#if defined( __FreeBSD__) || defined (__APPLE__)
-                               ro->ro_rt = rtalloc1(&((struct route *)ro)
+                               ro->ro_rt = rtalloc1_locked(&((struct route *)ro)
                                                     ->ro_dst, 0, 0UL);
-#else
-                               ro->ro_rt = rtalloc1(&((struct route *)ro)
-                                                    ->ro_dst, 0);
-#endif /*__FreeBSD__*/
                        } else {
-#ifdef __bsdi__  /* bsdi needs rtcalloc to make a host route */
-                               rtcalloc((struct route *)ro);
-#else
-                               rtalloc((struct route *)ro);
-#endif
+                               rtalloc_ign_locked((struct route *)ro, 0UL);
                        }
                }
+               lck_mtx_unlock(rt_mtx);
 
                /*
                 * in_pcbconnect() checks out IFF_LOOPBACK to skip using
@@ -333,8 +281,14 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
 
                if (ro->ro_rt) {
                        ia6 = in6_ifawithscope(ro->ro_rt->rt_ifa->ifa_ifp, dst);
-                       if (ia6 == 0) /* xxx scope error ?*/
+                       if (ia6 == 0) {
                                ia6 = ifatoia6(ro->ro_rt->rt_ifa);
+                               if (ia6)
+                                       ifaref(&ia6->ia_ifa);
+                       }
+                       else {
+                               ifaref(&ia6->ia_ifa);
+                       }
                }
 #if 0
                /*
@@ -352,14 +306,17 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
                                ia6 = ifatoia6(ifa_ifwithnet(sin6tosa(&sin6)));
                        if (ia6 == 0)
                                return(0);
-                       return(&satosin6(&ia6->ia_addr)->sin6_addr);
+                       *src_storage = satosin6(&ia6->ia_addr)->sin6_addr;
+                       return src_storage;
                }
 #endif /* 0 */
                if (ia6 == 0) {
                        *errorp = EHOSTUNREACH; /* no route */
                        return(0);
                }
-               return(&satosin6(&ia6->ia_addr)->sin6_addr);
+               *src_storage = satosin6(&ia6->ia_addr)->sin6_addr;
+               ifafree(&ia6->ia_ifa);
+               return src_storage;
        }
 
        *errorp = EADDRNOTAVAIL;
@@ -373,14 +330,10 @@ in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp)
  *     hop limit of the interface specified by router advertisement.
  * 3. The system default hoplimit.
 */
-#if HAVE_NRL_INPCB
-#define in6pcb         inpcb
-#define in6p_hops      inp_hops        
-#endif
 int
-in6_selecthlim(in6p, ifp)
-       struct in6pcb *in6p;
-       struct ifnet *ifp;
+in6_selecthlim(
+       struct in6pcb *in6p,
+       struct ifnet *ifp)
 {
        if (in6p && in6p->in6p_hops >= 0)
                return(in6p->in6p_hops);
@@ -389,107 +342,256 @@ in6_selecthlim(in6p, ifp)
        else
                return(ip6_defhlim);
 }
-#if HAVE_NRL_INPCB
-#undef in6pcb
-#undef in6p_hops
-#endif
 
-#if !(defined(__FreeBSD__) && __FreeBSD__ >= 3) && !defined(__OpenBSD__) && !defined(__APPLE__)
 /*
- * Find an empty port and set it to the specified PCB.
+ * XXX: this is borrowed from in6_pcbbind(). If possible, we should
+ * share this function by all *bsd*...
  */
-#if HAVE_NRL_INPCB     /* XXX: I really hate such ugly macros...(jinmei) */
-#define in6pcb         inpcb
-#define in6p_socket    inp_socket
-#define in6p_lport     inp_lport
-#define in6p_head      inp_head
-#define in6p_flags     inp_flags
-#define IN6PLOOKUP_WILDCARD INPLOOKUP_WILDCARD
-#endif
 int
-in6_pcbsetport(laddr, in6p)
+in6_pcbsetport(laddr, inp, p, locked)
        struct in6_addr *laddr;
-       struct in6pcb *in6p;
+       struct inpcb *inp;
+       struct proc *p;
+       int locked;
 {
-       struct socket *so = in6p->in6p_socket;
-       struct in6pcb *head = in6p->in6p_head;
-       u_int16_t last_port, lport = 0;
-       int wild = 0;
-       void *t;
-       u_int16_t min, max;
-#ifdef __NetBSD__
-       struct proc *p = curproc;               /* XXX */
-#endif
+       struct socket *so = inp->inp_socket;
+       u_int16_t lport = 0, first, last, *lastport;
+       int count, error = 0, wild = 0;
+       struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
+       if (!locked) { /* Make sure we don't run into a deadlock: 4052373 */
+               if (!lck_rw_try_lock_exclusive(pcbinfo->mtx)) {
+                       socket_unlock(inp->inp_socket, 0);
+                       lck_rw_lock_exclusive(pcbinfo->mtx);
+                       socket_lock(inp->inp_socket, 0);
+               }
+       }
 
        /* XXX: this is redundant when called from in6_pcbbind */
-       if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0 &&
-          ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 ||
-           (so->so_options & SO_ACCEPTCONN) == 0))
-               wild = IN6PLOOKUP_WILDCARD;
-
-       if (in6p->in6p_flags & IN6P_LOWPORT) {
-#ifdef __NetBSD__
-               if (p == 0 || (suser(p->p_ucred, &p->p_acflag) != 0))
-                       return (EACCES);
-#else
-               if ((so->so_state & SS_PRIV) == 0)
-                       return (EACCES);
-#endif
-               min = IPV6PORT_RESERVEDMIN;
-               max = IPV6PORT_RESERVEDMAX;
+       if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0)
+               wild = INPLOOKUP_WILDCARD;
+
+       inp->inp_flags |= INP_ANONPORT;
+
+       if (inp->inp_flags & INP_HIGHPORT) {
+               first = ipport_hifirstauto;     /* sysctl */
+               last  = ipport_hilastauto;
+               lastport = &pcbinfo->lasthi;
+       } else if (inp->inp_flags & INP_LOWPORT) {
+                if (p && (error = proc_suser(p))) {
+                       if (!locked)
+                               lck_rw_done(pcbinfo->mtx);
+                       return error;
+               }
+               first = ipport_lowfirstauto;    /* 1023 */
+               last  = ipport_lowlastauto;     /* 600 */
+               lastport = &pcbinfo->lastlow;
+       } else {
+               first = ipport_firstauto;       /* sysctl */
+               last  = ipport_lastauto;
+               lastport = &pcbinfo->lastport;
+       }
+       /*
+        * Simple check to ensure all ports are not used up causing
+        * a deadlock here.
+        *
+        * We split the two cases (up and down) so that the direction
+        * is not being tested on each round of the loop.
+        */
+       if (first > last) {
+               /*
+                * counting down
+                */
+               count = first - last;
+
+               do {
+                       if (count-- < 0) {      /* completely used? */
+                               /*
+                                * Undo any address bind that may have
+                                * occurred above.
+                                */
+                               inp->in6p_laddr = in6addr_any;
+                               if (!locked)
+                                       lck_rw_done(pcbinfo->mtx);
+                               return (EAGAIN);
+                       }
+                       --*lastport;
+                       if (*lastport > first || *lastport < last)
+                               *lastport = first;
+                       lport = htons(*lastport);
+               } while (in6_pcblookup_local(pcbinfo,
+                                            &inp->in6p_laddr, lport, wild));
        } else {
-               min = IPV6PORT_ANONMIN;
-               max = IPV6PORT_ANONMAX;
+               /*
+                        * counting up
+                        */
+               count = last - first;
+
+               do {
+                       if (count-- < 0) {      /* completely used? */
+                               /*
+                                * Undo any address bind that may have
+                                * occurred above.
+                                */
+                               inp->in6p_laddr = in6addr_any;
+                               if (!locked)
+                                       lck_rw_done(pcbinfo->mtx);
+                               return (EAGAIN);
+                       }
+                       ++*lastport;
+                       if (*lastport < first || *lastport > last)
+                               *lastport = first;
+                       lport = htons(*lastport);
+               } while (in6_pcblookup_local(pcbinfo,
+                                            &inp->in6p_laddr, lport, wild));
        }
 
-       /* value out of range */
-       if (head->in6p_lport < min)
-               head->in6p_lport = min;
-       else if (head->in6p_lport > max)
-               head->in6p_lport = min;
-       last_port = head->in6p_lport;
-       goto startover; /*to randomize*/
-       for (;;) {
-               lport = htons(head->in6p_lport);
-               if (IN6_IS_ADDR_V4MAPPED(laddr)) {
-#if 0
-                       t = in_pcblookup_bind(&tcbtable,
-                                             (struct in_addr *)&in6p->in6p_laddr.s6_addr32[3],
-                                             lport);
+       inp->inp_lport = lport;
+       if (in_pcbinshash(inp, 1) != 0) {
+               inp->in6p_laddr = in6addr_any;
+               inp->inp_lport = 0;
+               if (!locked)
+                       lck_rw_done(pcbinfo->mtx);
+               return (EAGAIN);
+       }
+
+       if (!locked)
+               lck_rw_done(pcbinfo->mtx);
+       return(0);
+}
+
+/*
+ * generate kernel-internal form (scopeid embedded into s6_addr16[1]).
+ * If the address scope of is link-local, embed the interface index in the
+ * address.  The routine determines our precedence
+ * between advanced API scope/interface specification and basic API
+ * specification.
+ *
+ * this function should be nuked in the future, when we get rid of
+ * embedded scopeid thing.
+ *
+ * XXX actually, it is over-specification to return ifp against sin6_scope_id.
+ * there can be multiple interfaces that belong to a particular scope zone
+ * (in specification, we have 1:N mapping between a scope zone and interfaces).
+ * we may want to change the function to return something other than ifp.
+ */
+int
+in6_embedscope(
+       struct in6_addr *in6,
+       const struct sockaddr_in6 *sin6,
+#ifdef HAVE_NRL_INPCB
+       struct inpcb *in6p,
+#define in6p_outputopts        inp_outputopts6
+#define in6p_moptions  inp_moptions6
 #else
-                       t = NULL;
+       struct in6pcb *in6p,
 #endif
-               } else {
-#if HAVE_NRL_INPCB
-                       /* XXX: ugly cast... */
-                       t = in_pcblookup(head, (struct in_addr *)&zeroin6_addr,
-                                        0, (struct in_addr *)laddr,
-                                        lport, wild | INPLOOKUP_IPV6);
-#else
-                       t = in6_pcblookup(head, &zeroin6_addr, 0, laddr,
-                                         lport, wild);
+       struct ifnet **ifpp)
+{
+       struct ifnet *ifp = NULL;
+       u_int32_t scopeid;
+
+       *in6 = sin6->sin6_addr;
+       scopeid = sin6->sin6_scope_id;
+       if (ifpp)
+               *ifpp = NULL;
+
+       /*
+        * don't try to read sin6->sin6_addr beyond here, since the caller may
+        * ask us to overwrite existing sockaddr_in6
+        */
+
+#ifdef ENABLE_DEFAULT_SCOPE
+       if (scopeid == 0)
+               scopeid = scope6_addr2default(in6);
 #endif
+
+       if (IN6_IS_SCOPE_LINKLOCAL(in6)) {
+               struct in6_pktinfo *pi;
+
+               /*
+                * KAME assumption: link id == interface id
+                */
+
+               if (in6p && in6p->in6p_outputopts &&
+                   (pi = in6p->in6p_outputopts->ip6po_pktinfo) &&
+                   pi->ipi6_ifindex) {
+                       ifp = ifindex2ifnet[pi->ipi6_ifindex];
+                       in6->s6_addr16[1] = htons(pi->ipi6_ifindex);
+               } else if (in6p && IN6_IS_ADDR_MULTICAST(in6) &&
+                          in6p->in6p_moptions &&
+                          in6p->in6p_moptions->im6o_multicast_ifp) {
+                       ifp = in6p->in6p_moptions->im6o_multicast_ifp;
+                       in6->s6_addr16[1] = htons(ifp->if_index);
+               } else if (scopeid) {
+                       /* boundary check */
+                       if (scopeid < 0 || if_index < scopeid)
+                               return ENXIO;  /* XXX EINVAL? */
+                       ifp = ifindex2ifnet[scopeid];
+                       /*XXX assignment to 16bit from 32bit variable */
+                       in6->s6_addr16[1] = htons(scopeid & 0xffff);
                }
-               if (t == 0)
-                       break;
-         startover:
-               if (head->in6p_lport >= max)
-                       head->in6p_lport = min;
-               else
-                       head->in6p_lport++;
-               if (head->in6p_lport == last_port)
-                       return (EADDRINUSE);
+
+               if (ifpp)
+                       *ifpp = ifp;
        }
 
-       in6p->in6p_lport = lport;
-       return(0);              /* success */
+       return 0;
 }
 #if HAVE_NRL_INPCB
-#undef in6pcb
-#undef in6p_socket
-#undef in6p_lport
-#undef in6p_head
-#undef in6p_flags
-#undef IN6PLOOKUP_WILDCARD
+#undef in6p_outputopts
+#undef in6p_moptions
 #endif
-#endif /* !FreeBSD3 && !OpenBSD*/
+
+/*
+ * generate standard sockaddr_in6 from embedded form.
+ * touches sin6_addr and sin6_scope_id only.
+ *
+ * this function should be nuked in the future, when we get rid of
+ * embedded scopeid thing.
+ */
+int
+in6_recoverscope(
+       struct sockaddr_in6 *sin6,
+       const struct in6_addr *in6,
+       struct ifnet *ifp)
+{
+       u_int32_t scopeid;
+
+       sin6->sin6_addr = *in6;
+
+       /*
+        * don't try to read *in6 beyond here, since the caller may
+        * ask us to overwrite existing sockaddr_in6
+        */
+
+       sin6->sin6_scope_id = 0;
+       if (IN6_IS_SCOPE_LINKLOCAL(in6)) {
+               /*
+                * KAME assumption: link id == interface id
+                */
+               scopeid = ntohs(sin6->sin6_addr.s6_addr16[1]);
+               if (scopeid) {
+                       /* sanity check */
+                       if (scopeid < 0 || if_index < scopeid)
+                               return ENXIO;
+                       if (ifp && ifp->if_index != scopeid)
+                               return ENXIO;
+                       sin6->sin6_addr.s6_addr16[1] = 0;
+                       sin6->sin6_scope_id = scopeid;
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * just clear the embedded scope identifer.
+ * XXX: currently used for bsdi4 only as a supplement function.
+ */
+void
+in6_clearscope(addr)
+       struct in6_addr *addr;
+{
+       if (IN6_IS_SCOPE_LINKLOCAL(addr))
+               addr->s6_addr16[1] = 0;
+}