Index: head/sys/netinet/ip_divert.c =================================================================== --- head/sys/netinet/ip_divert.c +++ head/sys/netinet/ip_divert.c @@ -122,6 +122,10 @@ static eventhandler_tag ip_divert_event_tag; +static int div_output_inbound(int fmaily, struct socket *so, struct mbuf *m, + struct sockaddr_in *sin); +static int div_output_outbound(int family, struct socket *so, struct mbuf *m); + /* * Initialize divert connection block queue. */ @@ -308,10 +312,10 @@ struct mbuf *control) { struct epoch_tracker et; - struct ip *const ip = mtod(m, struct ip *); + const struct ip *ip; struct m_tag *mtag; struct ipfw_rule_ref *dt; - int error = 0; + int error, family; /* * An mbuf may hasn't come from userland, but we pretend @@ -330,8 +334,8 @@ mtag = m_tag_alloc(MTAG_IPFW_RULE, 0, sizeof(struct ipfw_rule_ref), M_NOWAIT | M_ZERO); if (mtag == NULL) { - error = ENOBUFS; - goto cantsend; + m_freem(m); + return (ENOBUFS); } m_tag_prepend(m, mtag); } @@ -349,6 +353,7 @@ dt->chain_id = 0; dt->rulenum = sin->sin_port+1; /* host format ? */ dt->rule_id = 0; + /* XXX: broken for IPv6 */ /* * Find receive interface with the given name, stuffed * (if it exists) in the sin_zero[] field. @@ -361,16 +366,55 @@ m->m_pkthdr.rcvif = ifunit(sin->sin_zero); } + ip = mtod(m, struct ip *); + switch (ip->ip_v) { + case IPVERSION: + family = AF_INET; + break; + case IPV6_VERSION >> 4: + family = AF_INET6; + break; + default: + m_freem(m); + return (EAFNOSUPPORT); + } + /* Reinject packet into the system as incoming or outgoing */ + NET_EPOCH_ENTER(et); if (!sin || sin->sin_addr.s_addr == 0) { - struct mbuf *options = NULL; + dt->info |= IPFW_IS_DIVERT | IPFW_INFO_OUT; + error = div_output_outbound(family, so, m); + } else { + dt->info |= IPFW_IS_DIVERT | IPFW_INFO_IN; + error = div_output_inbound(family, so, m, sin); + } + NET_EPOCH_EXIT(et); + + if (error != 0) + m_freem(m); + + return (error); +} + +/* + * Sends mbuf @m to the wire via ip[6]_output(). + * + * Returns 0 on success, @m is consumed. + * On failure, returns error code. It is caller responsibility to free @m. + */ +static int +div_output_outbound(int family, struct socket *so, struct mbuf *m) +{ + struct ip *const ip = mtod(m, struct ip *); + + struct mbuf *options; struct inpcb *inp; + int error; - dt->info |= IPFW_IS_DIVERT | IPFW_INFO_OUT; inp = sotoinpcb(so); INP_RLOCK(inp); - switch (ip->ip_v) { - case IPVERSION: + switch (family) { + case AF_INET: /* * Don't allow both user specified and setsockopt * options, and don't allow packet length sizes that @@ -379,29 +423,23 @@ if ((((ip->ip_hl << 2) != sizeof(struct ip)) && inp->inp_options != NULL) || ((u_short)ntohs(ip->ip_len) > m->m_pkthdr.len)) { - error = EINVAL; INP_RUNLOCK(inp); - goto cantsend; + return (EINVAL); } break; #ifdef INET6 - case IPV6_VERSION >> 4: + case AF_INET6: { struct ip6_hdr *const ip6 = mtod(m, struct ip6_hdr *); /* Don't allow packet length sizes that will crash */ if (((u_short)ntohs(ip6->ip6_plen) > m->m_pkthdr.len)) { - error = EINVAL; INP_RUNLOCK(inp); - goto cantsend; + return (EINVAL); } break; } #endif - default: - error = EINVAL; - INP_RUNLOCK(inp); - goto cantsend; } /* Send packet to output processing */ @@ -431,61 +469,70 @@ * held so we can use them in ip_output() without * requring a reference to the pcb. */ + options = NULL; if (inp->inp_options != NULL) { options = m_dup(inp->inp_options, M_NOWAIT); if (options == NULL) { INP_RUNLOCK(inp); - error = ENOBUFS; - goto cantsend; + return (ENOBUFS); } } INP_RUNLOCK(inp); - NET_EPOCH_ENTER(et); - switch (ip->ip_v) { - case IPVERSION: + error = 0; + switch (family) { + case AF_INET: error = ip_output(m, options, NULL, ((so->so_options & SO_DONTROUTE) ? IP_ROUTETOIF : 0) | IP_ALLOWBROADCAST | IP_RAWOUTPUT, NULL, NULL); break; #ifdef INET6 - case IPV6_VERSION >> 4: + case AF_INET6: error = ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); break; #endif } - NET_EPOCH_EXIT(et); if (options != NULL) m_freem(options); - } else { - dt->info |= IPFW_IS_DIVERT | IPFW_INFO_IN; + + return (error); +} + +/* + * Schedules mbuf @m for local processing via IPv4/IPv6 netisr queue. + * + * Returns 0 on success, @m is consumed. + * Returns error code on failure. It is caller responsibility to free @m. + */ +static int +div_output_inbound(int family, struct socket *so, struct mbuf *m, + struct sockaddr_in *sin) +{ + const struct ip *ip; + struct ifaddr *ifa; + if (m->m_pkthdr.rcvif == NULL) { /* * No luck with the name, check by IP address. * Clear the port and the ifname to make sure * there are no distractions for ifa_ifwithaddr. */ - struct epoch_tracker et; - struct ifaddr *ifa; + /* XXX: broken for IPv6 */ bzero(sin->sin_zero, sizeof(sin->sin_zero)); sin->sin_port = 0; - NET_EPOCH_ENTER(et); ifa = ifa_ifwithaddr((struct sockaddr *) sin); - if (ifa == NULL) { - error = EADDRNOTAVAIL; - NET_EPOCH_EXIT(et); - goto cantsend; - } + if (ifa == NULL) + return (EADDRNOTAVAIL); m->m_pkthdr.rcvif = ifa->ifa_ifp; - NET_EPOCH_EXIT(et); } #ifdef MAC mac_socket_create_mbuf(so, m); #endif /* Send packet to input processing via netisr */ - switch (ip->ip_v) { - case IPVERSION: + switch (family) { + case AF_INET: + ip = mtod(m, struct ip *); /* * Restore M_BCAST flag when destination address is * broadcast. It is expected by ip_tryforward(). @@ -497,21 +544,15 @@ netisr_queue_src(NETISR_IP, (uintptr_t)so, m); break; #ifdef INET6 - case IPV6_VERSION >> 4: + case AF_INET6: netisr_queue_src(NETISR_IPV6, (uintptr_t)so, m); break; #endif default: - error = EINVAL; - goto cantsend; + return (EINVAL); } - } - return (error); - -cantsend: - m_freem(m); - return (error); + return (0); } static int