--- sys/netinet6/in6_pcb.c (svn+ssh://svn.freebsd.org/base/head) (revision 261548) +++ sys/netinet6/in6_pcb.c (working copy) @@ -107,44 +107,49 @@ __FBSDID("$FreeBSD$"); #include #include +static struct inpcb *in6_pcblookup_hash_locked(struct inpcbinfo *, + struct in6_addr *, u_int, struct in6_addr *, u_int, int, struct ifnet *); + int -in6_pcbbind(register struct inpcb *inp, struct sockaddr *nam, - struct ucred *cred) +in6_pcbbind(struct inpcb *inp, struct sockaddr *nam, struct ucred *cred) { struct socket *so = inp->inp_socket; - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)NULL; + struct sockaddr_in6 *sin6 = NULL; struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; - u_short lport = 0; int error, lookupflags = 0; int reuseport = (so->so_options & SO_REUSEPORT); + u_short lport = 0; INP_WLOCK_ASSERT(inp); INP_HASH_WLOCK_ASSERT(pcbinfo); - if (TAILQ_EMPTY(&V_in6_ifaddrhead)) /* XXX broken! */ - return (EADDRNOTAVAIL); if (inp->inp_lport || !IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) return (EINVAL); if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0) lookupflags = INPLOOKUP_WILDCARD; if (nam == NULL) { - if ((error = prison_local_ip6(cred, &inp->in6p_laddr, - ((inp->inp_flags & IN6P_IPV6_V6ONLY) != 0))) != 0) + struct sockaddr_in6 tmp; + + tmp = sa6_any; + error = prison_local_ip6(cred, &tmp, + ((inp->inp_flags & IN6P_IPV6_V6ONLY) != 0)); + if (error != 0) return (error); + inp->in6p_laddr = tmp.sin6_addr; + if (IN6_IS_ADDR_LINKLOCAL(&inp->in6p_laddr)) + inp->in6p_zoneid = tmp.sin6_scope_id; } else { sin6 = (struct sockaddr_in6 *)nam; if (nam->sa_len != sizeof(*sin6)) return (EINVAL); - /* - * family check. - */ if (nam->sa_family != AF_INET6) return (EAFNOSUPPORT); + /* Check sin6_scope_id. The caller must set it properly. */ + if ((error = sa6_checkzone_opts(inp->in6p_outputopts, + inp->in6p_moptions, sin6)) != 0) + return (error); - if ((error = sa6_embedscope(sin6, V_ip6_use_defzone)) != 0) - return(error); - - if ((error = prison_local_ip6(cred, &sin6->sin6_addr, + if ((error = prison_local_ip6(cred, sin6, ((inp->inp_flags & IN6P_IPV6_V6ONLY) != 0))) != 0) return (error); @@ -160,11 +165,11 @@ int if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) != 0) reuseport = SO_REUSEADDR|SO_REUSEPORT; } else if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { - struct ifaddr *ifa; + struct in6_ifaddr *ifa; - sin6->sin6_port = 0; /* yech... */ - if ((ifa = ifa_ifwithaddr((struct sockaddr *)sin6)) == - NULL && + ifa = in6ifa_ifwithaddr(&sin6->sin6_addr, + sin6->sin6_scope_id); + if (ifa == NULL && (inp->inp_flags & INP_BINDANY) == 0) { return (EADDRNOTAVAIL); } @@ -175,14 +180,13 @@ int * We should allow to bind to a deprecated address, since * the application dares to use it. */ - if (ifa != NULL && - ((struct in6_ifaddr *)ifa)->ia6_flags & - (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY|IN6_IFF_DETACHED)) { - ifa_free(ifa); + if (ifa != NULL && (ifa->ia6_flags & (IN6_IFF_ANYCAST | + IN6_IFF_NOTREADY | IN6_IFF_DETACHED))) { + ifa_free(&ifa->ia_ifa); return (EADDRNOTAVAIL); } if (ifa != NULL) - ifa_free(ifa); + ifa_free(&ifa->ia_ifa); } if (lport) { struct inpcb *t; @@ -198,8 +202,8 @@ int priv_check_cred(inp->inp_cred, PRIV_NETINET_REUSEPORT, 0) != 0) { t = in6_pcblookup_local(pcbinfo, - &sin6->sin6_addr, lport, - INPLOOKUP_WILDCARD, cred); + &sin6->sin6_addr, sin6->sin6_scope_id, + lport, INPLOOKUP_WILDCARD, cred); if (t && ((t->inp_flags & INP_TIMEWAIT) == 0) && (so->so_type != SOCK_STREAM || @@ -232,7 +236,7 @@ int #endif } t = in6_pcblookup_local(pcbinfo, &sin6->sin6_addr, - lport, lookupflags, cred); + sin6->sin6_scope_id, lport, lookupflags, cred); if (t && (t->inp_flags & INP_TIMEWAIT)) { /* * XXXRW: If an incpb has had its timewait @@ -274,6 +278,7 @@ int #endif } inp->in6p_laddr = sin6->sin6_addr; + inp->in6p_zoneid = sin6->sin6_scope_id; } if (lport == 0) { if ((error = in6_pcbsetport(&inp->in6p_laddr, inp, cred)) != 0) { @@ -293,82 +298,6 @@ int } /* - * Transform old in6_pcbconnect() into an inner subroutine for new - * in6_pcbconnect(): Do some validity-checking on the remote - * address (in mbuf 'nam') and then determine local host address - * (i.e., which interface) to use to access that remote host. - * - * This preserves definition of in6_pcbconnect(), while supporting a - * slightly different version for T/TCP. (This is more than - * a bit of a kludge, but cleaning up the internal interfaces would - * have forced minor changes in every protocol). - */ -int -in6_pcbladdr(register struct inpcb *inp, struct sockaddr *nam, - struct in6_addr *plocal_addr6) -{ - register struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; - int error = 0; - struct ifnet *ifp = NULL; - int scope_ambiguous = 0; - struct in6_addr in6a; - - INP_WLOCK_ASSERT(inp); - INP_HASH_WLOCK_ASSERT(inp->inp_pcbinfo); /* XXXRW: why? */ - - if (nam->sa_len != sizeof (*sin6)) - return (EINVAL); - if (sin6->sin6_family != AF_INET6) - return (EAFNOSUPPORT); - if (sin6->sin6_port == 0) - return (EADDRNOTAVAIL); - - if (sin6->sin6_scope_id == 0 && !V_ip6_use_defzone) - scope_ambiguous = 1; - if ((error = sa6_embedscope(sin6, V_ip6_use_defzone)) != 0) - return(error); - - if (!TAILQ_EMPTY(&V_in6_ifaddrhead)) { - /* - * If the destination address is UNSPECIFIED addr, - * use the loopback addr, e.g ::1. - */ - if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) - sin6->sin6_addr = in6addr_loopback; - } - if ((error = prison_remote_ip6(inp->inp_cred, &sin6->sin6_addr)) != 0) - return (error); - - error = in6_selectsrc(sin6, inp->in6p_outputopts, - inp, NULL, inp->inp_cred, &ifp, &in6a); - if (error) - return (error); - - if (ifp && scope_ambiguous && - (error = in6_setscope(&sin6->sin6_addr, ifp, NULL)) != 0) { - return(error); - } - - /* - * Do not update this earlier, in case we return with an error. - * - * XXX: this in6_selectsrc result might replace the bound local - * address with the address specified by setsockopt(IPV6_PKTINFO). - * Is it the intended behavior? - */ - *plocal_addr6 = in6a; - - /* - * Don't do pcblookup call here; return interface in - * plocal_addr6 - * and exit to caller, that will do the lookup. - */ - - return (0); -} - -/* - * Outer subroutine: * Connect from a socket to a specified address. * Both address and port must be specified in argument sin. * If don't have a local address for this socket yet, @@ -379,7 +308,8 @@ in6_pcbconnect_mbuf(register struct inpcb *inp, st struct ucred *cred, struct mbuf *m) { struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; - register struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; + struct ifnet *ifp = NULL; struct in6_addr addr6; int error; @@ -386,23 +316,42 @@ in6_pcbconnect_mbuf(register struct inpcb *inp, st INP_WLOCK_ASSERT(inp); INP_HASH_WLOCK_ASSERT(pcbinfo); + if (nam->sa_len != sizeof(*sin6)) + return (EINVAL); + if (sin6->sin6_family != AF_INET6) + return (EAFNOSUPPORT); + if (sin6->sin6_port == 0) + return (EADDRNOTAVAIL); /* - * Call inner routine, to assign local interface address. - * in6_pcbladdr() may automatically fill in sin6_scope_id. + * If the destination address is UNSPECIFIED addr, use the loopback + * addr, e.g ::1. */ - if ((error = in6_pcbladdr(inp, nam, &addr6)) != 0) + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) + sin6->sin6_addr = in6addr_loopback; + /* + * Check sin6_scope_id and automatically fill it, if possible. + */ + error = sa6_checkzone_opts(inp->in6p_outputopts, + inp->in6p_moptions, sin6); + if (error != 0) return (error); + if ((error = prison_remote_ip6(inp->inp_cred, sin6)) != 0) + return (error); + /* + * Determine source address and outgoing interface. + */ + error = in6_selectsrc(sin6, inp->in6p_outputopts, inp, NULL, + inp->inp_cred, &ifp, &addr6); + if (error) + return (error); if (in6_pcblookup_hash_locked(pcbinfo, &sin6->sin6_addr, - sin6->sin6_port, - IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) - ? &addr6 : &inp->in6p_laddr, - inp->inp_lport, 0, NULL) != NULL) { + sin6->sin6_port, IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) ? + &addr6: &inp->in6p_laddr, inp->inp_lport, 0, ifp) != NULL) return (EADDRINUSE); - } if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) { if (inp->inp_lport == 0) { - error = in6_pcbbind(inp, (struct sockaddr *)0, cred); + error = in6_pcbbind(inp, NULL, cred); if (error) return (error); } @@ -410,6 +359,10 @@ in6_pcbconnect_mbuf(register struct inpcb *inp, st } inp->in6p_faddr = sin6->sin6_addr; inp->inp_fport = sin6->sin6_port; + if (IN6_IS_ADDR_LINKLOCAL(&inp->in6p_faddr) || + IN6_IS_ADDR_LINKLOCAL(&inp->in6p_laddr)) + inp->in6p_zoneid = in6_getscopezone(ifp, + IPV6_ADDR_SCOPE_LINKLOCAL); /* update flowinfo - draft-itojun-ipv6-flowlabel-api-00 */ inp->inp_flow &= ~IPV6_FLOWLABEL_MASK; if (inp->inp_flags & IN6P_AUTOFLOWLABEL) @@ -443,19 +396,19 @@ in6_pcbdisconnect(struct inpcb *inp) } struct sockaddr * -in6_sockaddr(in_port_t port, struct in6_addr *addr_p) +in6_sockaddr(in_port_t port, const struct in6_addr *addr_p, uint32_t zoneid) { struct sockaddr_in6 *sin6; - sin6 = malloc(sizeof *sin6, M_SONAME, M_WAITOK); - bzero(sin6, sizeof *sin6); + sin6 = malloc(sizeof *sin6, M_SONAME, M_WAITOK | M_ZERO); sin6->sin6_family = AF_INET6; sin6->sin6_len = sizeof(*sin6); sin6->sin6_port = port; sin6->sin6_addr = *addr_p; - (void)sa6_recoverscope(sin6); /* XXX: should catch errors */ - - return (struct sockaddr *)sin6; + if (IN6_IS_ADDR_LINKLOCAL(addr_p) || + IN6_IS_ADDR_MULTICAST(addr_p)) + sin6->sin6_scope_id = zoneid; + return ((struct sockaddr *)sin6); } struct sockaddr * @@ -480,8 +433,9 @@ in6_v4mapsin6_sockaddr(in_port_t port, struct in_a int in6_getsockaddr(struct socket *so, struct sockaddr **nam) { - register struct inpcb *inp; struct in6_addr addr; + struct inpcb *inp; + uint32_t zoneid; in_port_t port; inp = sotoinpcb(so); @@ -490,17 +444,19 @@ in6_getsockaddr(struct socket *so, struct sockaddr INP_RLOCK(inp); port = inp->inp_lport; addr = inp->in6p_laddr; + zoneid = inp->in6p_zoneid; INP_RUNLOCK(inp); - *nam = in6_sockaddr(port, &addr); - return 0; + *nam = in6_sockaddr(port, &addr, zoneid); + return (0); } int in6_getpeeraddr(struct socket *so, struct sockaddr **nam) { + struct in6_addr addr; struct inpcb *inp; - struct in6_addr addr; + uint32_t zoneid; in_port_t port; inp = sotoinpcb(so); @@ -509,10 +465,11 @@ in6_getpeeraddr(struct socket *so, struct sockaddr INP_RLOCK(inp); port = inp->inp_fport; addr = inp->in6p_faddr; + zoneid = inp->in6p_zoneid; INP_RUNLOCK(inp); - *nam = in6_sockaddr(port, &addr); - return 0; + *nam = in6_sockaddr(port, &addr, zoneid); + return (0); } int @@ -630,8 +587,10 @@ in6_pcbnotify(struct inpcbinfo *pcbinfo, struct so * XXX: should we avoid to notify the value to TCP sockets? */ if (cmd == PRC_MSGSIZE && (inp->inp_flags & IN6P_MTU) != 0 && - (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr) || - IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, &sa6_dst->sin6_addr))) { + (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr) || ( + IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, + &sa6_dst->sin6_addr) && (sa6_dst->sin6_scope_id == 0 || + sa6_dst->sin6_scope_id == inp->in6p_zoneid)))) { ip6_notify_pmtu(inp, (struct sockaddr_in6 *)dst, (u_int32_t *)cmdarg); } @@ -647,7 +606,9 @@ in6_pcbnotify(struct inpcbinfo *pcbinfo, struct so if (lport == 0 && fport == 0 && flowinfo && inp->inp_socket != NULL && flowinfo == (inp->inp_flow & IPV6_FLOWLABEL_MASK) && - IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, &sa6_src.sin6_addr)) + IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, &sa6_src.sin6_addr) && + (sa6_src.sin6_scope_id == 0 || + sa6_src.sin6_scope_id == inp->in6p_zoneid)) goto do_notify; else if (!IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, &sa6_dst->sin6_addr) || @@ -657,6 +618,7 @@ in6_pcbnotify(struct inpcbinfo *pcbinfo, struct so !IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, &sa6_src.sin6_addr)) || (fport && inp->inp_fport != fport)) { + /* XXX: zoneid */ INP_WUNLOCK(inp); continue; } @@ -677,7 +639,7 @@ in6_pcbnotify(struct inpcbinfo *pcbinfo, struct so */ struct inpcb * in6_pcblookup_local(struct inpcbinfo *pcbinfo, struct in6_addr *laddr, - u_short lport, int lookupflags, struct ucred *cred) + uint32_t zoneid, u_short lport, int lookupflags, struct ucred *cred) { register struct inpcb *inp; int matchwild = 3, wildcard; @@ -701,6 +663,7 @@ in6_pcblookup_local(struct inpcbinfo *pcbinfo, str continue; if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr) && IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr) && + inp->in6p_zoneid == zoneid && inp->inp_lport == lport) { /* Found. */ if (cred == NULL || @@ -735,7 +698,6 @@ in6_pcblookup_local(struct inpcbinfo *pcbinfo, str * fit. */ LIST_FOREACH(inp, &phd->phd_pcblist, inp_portlist) { - wildcard = 0; if (cred != NULL && !prison_equal_ip6(cred->cr_prison, inp->inp_cred->cr_prison)) @@ -743,6 +705,7 @@ in6_pcblookup_local(struct inpcbinfo *pcbinfo, str /* XXX inp locking */ if ((inp->inp_vflag & INP_IPV6) == 0) continue; + wildcard = 0; if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) wildcard++; if (!IN6_IS_ADDR_UNSPECIFIED( @@ -752,6 +715,8 @@ in6_pcblookup_local(struct inpcbinfo *pcbinfo, str else if (!IN6_ARE_ADDR_EQUAL( &inp->in6p_laddr, laddr)) continue; + else if (inp->in6p_zoneid != zoneid) + continue; } else { if (!IN6_IS_ADDR_UNSPECIFIED(laddr)) wildcard++; @@ -849,6 +814,7 @@ in6_pcblookup_group(struct inpcbinfo *pcbinfo, str { struct inpcbhead *head; struct inpcb *inp, *tmpinp; + uint32_t zoneid; u_short fport = fport_arg, lport = lport_arg; int faith; @@ -857,6 +823,8 @@ in6_pcblookup_group(struct inpcbinfo *pcbinfo, str else faith = 0; + zoneid = (ifp == NULL) ? 0: + in6_getscopezone(ifp, IPV6_ADDR_SCOPE_LINKLOCAL); /* * First look for an exact match. */ @@ -872,7 +840,8 @@ in6_pcblookup_group(struct inpcbinfo *pcbinfo, str if (IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, faddr) && IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr) && inp->inp_fport == fport && - inp->inp_lport == lport) { + inp->inp_lport == lport && + inp->in6p_zoneid == zoneid) { /* * XXX We should be able to directly return * the inp here, without any checks. @@ -922,8 +891,8 @@ in6_pcblookup_group(struct inpcbinfo *pcbinfo, str injail = prison_flag(inp->inp_cred, PR_IP6); if (injail) { - if (prison_check_ip6(inp->inp_cred, - laddr) != 0) + if (prison_check_in6(inp->inp_cred, + laddr, zoneid) != 0) continue; } else { if (local_exact != NULL) @@ -930,7 +899,8 @@ in6_pcblookup_group(struct inpcbinfo *pcbinfo, str continue; } - if (IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr)) { + if (IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr) && + inp->in6p_zoneid == zoneid) { if (injail) goto found; else @@ -976,7 +946,7 @@ found: /* * Lookup PCB in hash list. */ -struct inpcb * +static struct inpcb * in6_pcblookup_hash_locked(struct inpcbinfo *pcbinfo, struct in6_addr *faddr, u_int fport_arg, struct in6_addr *laddr, u_int lport_arg, int lookupflags, struct ifnet *ifp) @@ -983,6 +953,7 @@ in6_pcblookup_hash_locked(struct inpcbinfo *pcbinf { struct inpcbhead *head; struct inpcb *inp, *tmpinp; + uint32_t zoneid; u_short fport = fport_arg, lport = lport_arg; int faith; @@ -996,6 +967,8 @@ in6_pcblookup_hash_locked(struct inpcbinfo *pcbinf else faith = 0; + zoneid = (ifp == NULL) ? 0: + in6_getscopezone(ifp, IPV6_ADDR_SCOPE_LINKLOCAL); /* * First look for an exact match. */ @@ -1009,8 +982,8 @@ in6_pcblookup_hash_locked(struct inpcbinfo *pcbinf continue; if (IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, faddr) && IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr) && - inp->inp_fport == fport && - inp->inp_lport == lport) { + inp->inp_fport == fport && inp->inp_lport == lport && ( + inp->in6p_zoneid == 0 || inp->in6p_zoneid == zoneid)) { /* * XXX We should be able to directly return * the inp here, without any checks. @@ -1058,8 +1031,8 @@ in6_pcblookup_hash_locked(struct inpcbinfo *pcbinf injail = prison_flag(inp->inp_cred, PR_IP6); if (injail) { - if (prison_check_ip6(inp->inp_cred, - laddr) != 0) + if (prison_check_in6(inp->inp_cred, + laddr, zoneid) != 0) continue; } else { if (local_exact != NULL) @@ -1066,7 +1039,9 @@ in6_pcblookup_hash_locked(struct inpcbinfo *pcbinf continue; } - if (IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr)) { + if (IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr) && ( + inp->in6p_zoneid == 0 || + inp->in6p_zoneid == zoneid)) { if (injail) return (inp); else @@ -1138,6 +1113,7 @@ in6_pcblookup(struct inpcbinfo *pcbinfo, struct in { #if defined(PCBGROUP) struct inpcbgroup *pcbgroup; + uint32_t zoneid; #endif KASSERT((lookupflags & ~INPLOOKUP_MASK) == 0, @@ -1147,8 +1123,10 @@ in6_pcblookup(struct inpcbinfo *pcbinfo, struct in #if defined(PCBGROUP) if (in_pcbgroup_enabled(pcbinfo)) { + zoneid = (ifp == NULL) ? 0: + in6_getscopezone(ifp, IPV6_ADDR_SCOPE_LINKLOCAL); pcbgroup = in6_pcbgroup_bytuple(pcbinfo, laddr, lport, faddr, - fport); + fport, zoneid); return (in6_pcblookup_group(pcbinfo, pcbgroup, faddr, fport, laddr, lport, lookupflags, ifp)); } @@ -1164,6 +1142,7 @@ in6_pcblookup_mbuf(struct inpcbinfo *pcbinfo, stru { #ifdef PCBGROUP struct inpcbgroup *pcbgroup; + uint32_t zoneid; #endif KASSERT((lookupflags & ~INPLOOKUP_MASK) == 0, @@ -1178,8 +1157,10 @@ in6_pcblookup_mbuf(struct inpcbinfo *pcbinfo, stru if (pcbgroup != NULL) return (in6_pcblookup_group(pcbinfo, pcbgroup, faddr, fport, laddr, lport, lookupflags, ifp)); + zoneid = (ifp == NULL) ? 0: + in6_getscopezone(ifp, IPV6_ADDR_SCOPE_LINKLOCAL); pcbgroup = in6_pcbgroup_bytuple(pcbinfo, laddr, lport, faddr, - fport); + fport, zoneid); return (in6_pcblookup_group(pcbinfo, pcbgroup, faddr, fport, laddr, lport, lookupflags, ifp)); } @@ -1198,8 +1179,8 @@ init_sin6(struct sockaddr_in6 *sin6, struct mbuf * sin6->sin6_len = sizeof(*sin6); sin6->sin6_family = AF_INET6; sin6->sin6_addr = ip->ip6_src; - - (void)sa6_recoverscope(sin6); /* XXX: should catch errors... */ - + if (m->m_pkthdr.rcvif != NULL) + sin6->sin6_scope_id = in6_getscopezone(m->m_pkthdr.rcvif, + in6_addrscope(&sin6->sin6_addr)); return; }