void in6_pcbdetach(struct in6pcb *in6p) { struct socket *so = in6p->in6p_socket; int s; if (in6p->in6p_af != AF_INET6) return; #if defined(IPSEC) || defined(FAST_IPSEC) ipsec6_delete_pcbpolicy(in6p); #endif /* IPSEC */ so->so_pcb = 0; if (in6p->in6p_options) m_freem(in6p->in6p_options); if (in6p->in6p_outputopts != NULL) { ip6_clearpktopts(in6p->in6p_outputopts, -1); free(in6p->in6p_outputopts, M_IP6OPT); } rtcache_free(&in6p->in6p_route); ip6_freemoptions(in6p->in6p_moptions); s = splnet(); in6_pcbstate(in6p, IN6P_ATTACHED); LIST_REMOVE(&in6p->in6p_head, inph_lhash); CIRCLEQ_REMOVE(&in6p->in6p_table->inpt_queue, &in6p->in6p_head, inph_queue); pool_put(&in6pcb_pool, in6p); splx(s); sofree(so); /* drops the socket's lock */ mutex_enter(softnet_lock); /* reacquire it */ }
void in6_pcbdisconnect(struct in6pcb *in6p) { bzero((void *)&in6p->in6p_faddr, sizeof(in6p->in6p_faddr)); in6p->in6p_fport = 0; in6_pcbstate(in6p, IN6P_BOUND); in6p->in6p_flowinfo &= ~IPV6_FLOWLABEL_MASK; #if defined(IPSEC) || defined(FAST_IPSEC) ipsec_pcbdisconn(in6p->in6p_sp); #endif if (in6p->in6p_socket->so_state & SS_NOFDREF) in6_pcbdetach(in6p); }
int in6_pcballoc(struct socket *so, void *v) { struct inpcbtable *table = v; struct in6pcb *in6p; int s; #if defined(IPSEC) int error; #endif s = splnet(); in6p = pool_get(&in6pcb_pool, PR_NOWAIT); splx(s); if (in6p == NULL) return (ENOBUFS); memset((void *)in6p, 0, sizeof(*in6p)); in6p->in6p_af = AF_INET6; in6p->in6p_table = table; in6p->in6p_socket = so; in6p->in6p_hops = -1; /* use kernel default */ in6p->in6p_icmp6filt = NULL; in6p->in6p_portalgo = PORTALGO_DEFAULT; in6p->in6p_bindportonsend = false; #if defined(IPSEC) error = ipsec_init_pcbpolicy(so, &in6p->in6p_sp); if (error != 0) { s = splnet(); pool_put(&in6pcb_pool, in6p); splx(s); return error; } #endif /* IPSEC */ s = splnet(); CIRCLEQ_INSERT_HEAD(&table->inpt_queue, (struct inpcb_hdr*)in6p, inph_queue); LIST_INSERT_HEAD(IN6PCBHASH_PORT(table, in6p->in6p_lport), &in6p->in6p_head, inph_lhash); in6_pcbstate(in6p, IN6P_ATTACHED); splx(s); if (ip6_v6only) in6p->in6p_flags |= IN6P_IPV6_V6ONLY; so->so_pcb = (void *)in6p; return (0); }
/* * Bind port from sin6 to in6p. */ static int in6_pcbbind_port(struct in6pcb *in6p, struct sockaddr_in6 *sin6, struct lwp *l) { struct inpcbtable *table = in6p->in6p_table; struct socket *so = in6p->in6p_socket; int wild = 0, reuseport = (so->so_options & SO_REUSEPORT); int error; if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0 && ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 || (so->so_options & SO_ACCEPTCONN) == 0)) wild = 1; if (sin6->sin6_port != 0) { enum kauth_network_req req; #ifndef IPNOPRIVPORTS if (ntohs(sin6->sin6_port) < IPV6PORT_RESERVED) req = KAUTH_REQ_NETWORK_BIND_PRIVPORT; else #endif /* IPNOPRIVPORTS */ req = KAUTH_REQ_NETWORK_BIND_PORT; error = kauth_authorize_network(l->l_cred, KAUTH_NETWORK_BIND, req, so, sin6, NULL); if (error) return (EACCES); } if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) { /* * Treat SO_REUSEADDR as SO_REUSEPORT for multicast; * allow compepte duplication of binding if * SO_REUSEPORT is set, or if SO_REUSEADDR is set * and a multicast address is bound on both * new and duplicated sockets. */ if (so->so_options & SO_REUSEADDR) reuseport = SO_REUSEADDR|SO_REUSEPORT; } if (sin6->sin6_port != 0) { if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { #ifdef INET struct inpcb *t; struct vestigial_inpcb vestige; t = in_pcblookup_port(table, *(struct in_addr *)&sin6->sin6_addr.s6_addr32[3], sin6->sin6_port, wild, &vestige); if (t && (reuseport & t->inp_socket->so_options) == 0) return (EADDRINUSE); if (!t && vestige.valid && !(reuseport && vestige.reuse_port)) return EADDRINUSE; #else return (EADDRNOTAVAIL); #endif } { struct in6pcb *t; struct vestigial_inpcb vestige; t = in6_pcblookup_port(table, &sin6->sin6_addr, sin6->sin6_port, wild, &vestige); if (t && (reuseport & t->in6p_socket->so_options) == 0) return (EADDRINUSE); if (!t && vestige.valid && !(reuseport && vestige.reuse_port)) return EADDRINUSE; } } if (sin6->sin6_port == 0) { int e; e = in6_pcbsetport(sin6, in6p, l); if (e != 0) return (e); } else { in6p->in6p_lport = sin6->sin6_port; in6_pcbstate(in6p, IN6P_BOUND); } LIST_REMOVE(&in6p->in6p_head, inph_lhash); LIST_INSERT_HEAD(IN6PCBHASH_PORT(table, in6p->in6p_lport), &in6p->in6p_head, inph_lhash); return (0); }
/* * Connect from a socket to a specified address. * Both address and port must be specified in argument sin6. * If don't have a local address for this socket yet, * then pick one. */ int in6_pcbconnect(void *v, struct mbuf *nam, struct lwp *l) { struct rtentry *rt; struct in6pcb *in6p = v; struct in6_addr *in6a = NULL; struct sockaddr_in6 *sin6 = mtod(nam, struct sockaddr_in6 *); struct ifnet *ifp = NULL; /* outgoing interface */ int error = 0; int scope_ambiguous = 0; #ifdef INET struct in6_addr mapped; #endif struct sockaddr_in6 tmp; (void)&in6a; /* XXX fool gcc */ if (in6p->in6p_af != AF_INET6) return (EINVAL); if (nam->m_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 && !ip6_use_defzone) scope_ambiguous = 1; if ((error = sa6_embedscope(sin6, ip6_use_defzone)) != 0) return(error); /* sanity check for mapped address case */ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { if ((in6p->in6p_flags & IN6P_IPV6_V6ONLY) != 0) return EINVAL; if (IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr)) in6p->in6p_laddr.s6_addr16[5] = htons(0xffff); if (!IN6_IS_ADDR_V4MAPPED(&in6p->in6p_laddr)) return EINVAL; } else { if (IN6_IS_ADDR_V4MAPPED(&in6p->in6p_laddr)) return EINVAL; } /* protect *sin6 from overwrites */ tmp = *sin6; sin6 = &tmp; /* Source address selection. */ if (IN6_IS_ADDR_V4MAPPED(&in6p->in6p_laddr) && in6p->in6p_laddr.s6_addr32[3] == 0) { #ifdef INET struct sockaddr_in sin, *sinp; bzero(&sin, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; bcopy(&sin6->sin6_addr.s6_addr32[3], &sin.sin_addr, sizeof(sin.sin_addr)); sinp = in_selectsrc(&sin, &in6p->in6p_route, in6p->in6p_socket->so_options, NULL, &error); if (sinp == 0) { if (error == 0) error = EADDRNOTAVAIL; return (error); } bzero(&mapped, sizeof(mapped)); mapped.s6_addr16[5] = htons(0xffff); bcopy(&sinp->sin_addr, &mapped.s6_addr32[3], sizeof(sinp->sin_addr)); in6a = &mapped; #else return EADDRNOTAVAIL; #endif } else { /* * XXX: in6_selectsrc might replace the bound local address * with the address specified by setsockopt(IPV6_PKTINFO). * Is it the intended behavior? */ in6a = in6_selectsrc(sin6, in6p->in6p_outputopts, in6p->in6p_moptions, &in6p->in6p_route, &in6p->in6p_laddr, &ifp, &error); if (ifp && scope_ambiguous && (error = in6_setscope(&sin6->sin6_addr, ifp, NULL)) != 0) { return(error); } if (in6a == 0) { if (error == 0) error = EADDRNOTAVAIL; return (error); } } if (ifp == NULL && (rt = rtcache_validate(&in6p->in6p_route)) != NULL) ifp = rt->rt_ifp; in6p->in6p_ip6.ip6_hlim = (u_int8_t)in6_selecthlim(in6p, ifp); if (in6_pcblookup_connect(in6p->in6p_table, &sin6->sin6_addr, sin6->sin6_port, IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr) ? in6a : &in6p->in6p_laddr, in6p->in6p_lport, 0)) return (EADDRINUSE); if (IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr) || (IN6_IS_ADDR_V4MAPPED(&in6p->in6p_laddr) && in6p->in6p_laddr.s6_addr32[3] == 0)) { if (in6p->in6p_lport == 0) { error = in6_pcbbind(in6p, (struct mbuf *)0, l); if (error != 0) return error; } in6p->in6p_laddr = *in6a; } in6p->in6p_faddr = sin6->sin6_addr; in6p->in6p_fport = sin6->sin6_port; in6_pcbstate(in6p, IN6P_CONNECTED); in6p->in6p_flowinfo &= ~IPV6_FLOWLABEL_MASK; if (ip6_auto_flowlabel) in6p->in6p_flowinfo |= (htonl(ip6_randomflowlabel()) & IPV6_FLOWLABEL_MASK); #if defined(IPSEC) || defined(FAST_IPSEC) if (in6p->in6p_socket->so_type == SOCK_STREAM) ipsec_pcbconn(in6p->in6p_sp); #endif return (0); }
int in6_pcbbind(void *v, struct mbuf *nam, struct lwp *l) { struct in6pcb *in6p = v; struct socket *so = in6p->in6p_socket; struct inpcbtable *table = in6p->in6p_table; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)NULL; u_int16_t lport = 0; int wild = 0, reuseport = (so->so_options & SO_REUSEPORT); if (in6p->in6p_af != AF_INET6) return (EINVAL); if (in6p->in6p_lport || !IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr)) return (EINVAL); if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0 && ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 || (so->so_options & SO_ACCEPTCONN) == 0)) wild = 1; if (nam) { int error; sin6 = mtod(nam, struct sockaddr_in6 *); if (nam->m_len != sizeof(*sin6)) return (EINVAL); /* * We should check the family, but old programs * incorrectly fail to intialize it. */ if (sin6->sin6_family != AF_INET6) return (EAFNOSUPPORT); #ifndef INET if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) return (EADDRNOTAVAIL); #endif if ((error = sa6_embedscope(sin6, ip6_use_defzone)) != 0) return (error); lport = sin6->sin6_port; if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) { /* * Treat SO_REUSEADDR as SO_REUSEPORT for multicast; * allow compepte duplication of binding if * SO_REUSEPORT is set, or if SO_REUSEADDR is set * and a multicast address is bound on both * new and duplicated sockets. */ if (so->so_options & SO_REUSEADDR) reuseport = SO_REUSEADDR|SO_REUSEPORT; } else if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { if ((in6p->in6p_flags & IN6P_IPV6_V6ONLY) != 0) return (EINVAL); if (sin6->sin6_addr.s6_addr32[3]) { struct sockaddr_in sin; bzero(&sin, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; bcopy(&sin6->sin6_addr.s6_addr32[3], &sin.sin_addr, sizeof(sin.sin_addr)); if (ifa_ifwithaddr((struct sockaddr *)&sin) == 0) return EADDRNOTAVAIL; } } else if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { struct ifaddr *ia = NULL; sin6->sin6_port = 0; /* yech... */ if ((in6p->in6p_flags & IN6P_FAITH) == 0 && (ia = ifa_ifwithaddr((struct sockaddr *)sin6)) == 0) return (EADDRNOTAVAIL); /* * bind to an anycast address might accidentally * cause sending a packet with an anycast source * address, so we forbid it. * * We should allow to bind to a deprecated address, * since the application dare to use it. * But, can we assume that they are careful enough * to check if the address is deprecated or not? * Maybe, as a safeguard, we should have a setsockopt * flag to control the bind(2) behavior against * deprecated addresses (default: forbid bind(2)). */ if (ia && ((struct in6_ifaddr *)ia)->ia6_flags & (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY|IN6_IFF_DETACHED)) return (EADDRNOTAVAIL); } if (lport) { #ifndef IPNOPRIVPORTS int priv; /* * NOTE: all operating systems use suser() for * privilege check! do not rewrite it into SS_PRIV. */ priv = (l && !kauth_authorize_generic(l->l_cred, KAUTH_GENERIC_ISSUSER, NULL)) ? 1 : 0; /* GROSS */ if (ntohs(lport) < IPV6PORT_RESERVED && !priv) return (EACCES); #endif if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { #ifdef INET struct inpcb *t; t = in_pcblookup_port(table, *(struct in_addr *)&sin6->sin6_addr.s6_addr32[3], lport, wild); if (t && (reuseport & t->inp_socket->so_options) == 0) return (EADDRINUSE); #else return (EADDRNOTAVAIL); #endif } { struct in6pcb *t; t = in6_pcblookup_port(table, &sin6->sin6_addr, lport, wild); if (t && (reuseport & t->in6p_socket->so_options) == 0) return (EADDRINUSE); } } in6p->in6p_laddr = sin6->sin6_addr; } if (lport == 0) { int e; e = in6_pcbsetport(&in6p->in6p_laddr, in6p, l); if (e != 0) return (e); } else { in6p->in6p_lport = lport; in6_pcbstate(in6p, IN6P_BOUND); } LIST_REMOVE(&in6p->in6p_head, inph_lhash); LIST_INSERT_HEAD(IN6PCBHASH_PORT(table, in6p->in6p_lport), &in6p->in6p_head, inph_lhash); #if 0 in6p->in6p_flowinfo = 0; /* XXX */ #endif return (0); }