diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 6fc12c82c132..9d0028736d01 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -1,7 +1,7 @@ .\" .\" $FreeBSD$ .\" -.Dd June 29, 2011 +.Dd August 20, 2011 .Dt IPFW 8 .Os .Sh NAME @@ -726,7 +726,7 @@ The search terminates. Change the next-hop on matching packets to .Ar ipaddr , which can be an IP address or a host name. -The next hop can also be supplied by the last table +For IPv4, the next hop can also be supplied by the last table looked up for the packet by using the .Cm tablearg keyword instead of an explicit address. diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index e389e99f81ab..615c4d1292d5 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -1111,6 +1111,18 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth) } break; + case O_FORWARD_IP6: + { + char buf[4 + INET6_ADDRSTRLEN + 1]; + ipfw_insn_sa6 *s = (ipfw_insn_sa6 *)cmd; + + printf("fwd %s", inet_ntop(AF_INET6, &s->sa.sin6_addr, + buf, sizeof(buf))); + if (s->sa.sin6_port) + printf(",%d", s->sa.sin6_port); + } + break; + case O_LOG: /* O_LOG is printed last */ logptr = (ipfw_insn_log *)cmd; break; @@ -2809,40 +2821,96 @@ ipfw_add(char *av[]) break; case TOK_FORWARD: { - ipfw_insn_sa *p = (ipfw_insn_sa *)action; + /* + * Locate the address-port separator (':' or ','). + * Could be one of the following: + * hostname:port + * IPv4 a.b.c.d,port + * IPv4 a.b.c.d:port + * IPv6 w:x:y::z,port + * The ':' can only be used with hostname and IPv4 address. + * XXX-BZ Should we also support [w:x:y::z]:port? + */ + struct sockaddr_storage result; + struct addrinfo *res; char *s, *end; + int family; + u_short port_number; NEED1("missing forward address[:port]"); - action->opcode = O_FORWARD_IP; - action->len = F_INSN_SIZE(ipfw_insn_sa); - - /* - * In the kernel we assume AF_INET and use only - * sin_port and sin_addr. Remember to set sin_len as - * the routing code seems to use it too. - */ - p->sa.sin_family = AF_INET; - p->sa.sin_len = sizeof(struct sockaddr_in); - p->sa.sin_port = 0; /* * locate the address-port separator (':' or ',') */ - s = strchr(*av, ':'); - if (s == NULL) - s = strchr(*av, ','); + s = strchr(*av, ','); + if (s == NULL) { + /* Distinguish between IPv4:port and IPv6 cases. */ + s = strchr(*av, ':'); + if (s && strchr(s+1, ':')) + s = NULL; /* no port */ + } + + port_number = 0; if (s != NULL) { + /* Terminate host portion and set s to start of port. */ *(s++) = '\0'; i = strtoport(s, &end, 0 /* base */, 0 /* proto */); if (s == end) errx(EX_DATAERR, "illegal forwarding port ``%s''", s); - p->sa.sin_port = (u_short)i; + port_number = (u_short)i; + } + + if (_substrcmp(*av, "tablearg") == 0) { + family = PF_INET; + ((struct sockaddr_in*)&result)->sin_addr.s_addr = + INADDR_ANY; + } else { + /* + * Resolve the host name or address to a family and a + * network representation of the addres. + */ + if (getaddrinfo(*av, NULL, NULL, &res)) + errx(EX_DATAERR, NULL); + /* Just use the first host in the answer. */ + family = res->ai_family; + memcpy(&result, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + } + + if (family == PF_INET) { + ipfw_insn_sa *p = (ipfw_insn_sa *)action; + + action->opcode = O_FORWARD_IP; + action->len = F_INSN_SIZE(ipfw_insn_sa); + + /* + * In the kernel we assume AF_INET and use only + * sin_port and sin_addr. Remember to set sin_len as + * the routing code seems to use it too. + */ + p->sa.sin_len = sizeof(struct sockaddr_in); + p->sa.sin_family = AF_INET; + p->sa.sin_port = port_number; + p->sa.sin_addr.s_addr = + ((struct sockaddr_in *)&result)->sin_addr.s_addr; + } else if (family == PF_INET6) { + ipfw_insn_sa6 *p = (ipfw_insn_sa6 *)action; + + action->opcode = O_FORWARD_IP6; + action->len = F_INSN_SIZE(ipfw_insn_sa6); + + p->sa.sin6_len = sizeof(struct sockaddr_in6); + p->sa.sin6_family = AF_INET6; + p->sa.sin6_port = port_number; + p->sa.sin6_flowinfo = 0; + p->sa.sin6_scope_id = 0; + /* No table support for v6 yet. */ + bcopy(&((struct sockaddr_in6*)&result)->sin6_addr, + &p->sa.sin6_addr, sizeof(p->sa.sin6_addr)); + } else { + errx(EX_DATAERR, "Invalid address family in forward action"); } - if (_substrcmp(*av, "tablearg") == 0) - p->sa.sin_addr.s_addr = INADDR_ANY; - else - lookup_host(*av, &(p->sa.sin_addr)); av++; break; } diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index ff3a67fe61ce..f6f8fcd876f7 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -203,6 +203,8 @@ enum ipfw_opcodes { /* arguments (4 byte each) */ O_CALLRETURN, /* arg1=called rule number */ + O_FORWARD_IP6, /* fwd sockaddr_in6 */ + O_LAST_OPCODE /* not an opcode! */ }; @@ -298,6 +300,14 @@ typedef struct _ipfw_insn_sa { struct sockaddr_in sa; } ipfw_insn_sa; +/* + * This is used to forward to a given address (ipv6). + */ +typedef struct _ipfw_insn_sa6 { + ipfw_insn o; + struct sockaddr_in6 sa; +} ipfw_insn_sa6; + /* * This is used for MAC addr-mask pairs. */ diff --git a/sys/netinet/ipfw/ip_fw2.c b/sys/netinet/ipfw/ip_fw2.c index e4e64e44f16d..c3123dfa11b0 100644 --- a/sys/netinet/ipfw/ip_fw2.c +++ b/sys/netinet/ipfw/ip_fw2.c @@ -796,6 +796,7 @@ set_match(struct ip_fw_args *args, int slot, * * args->rule Pointer to the last matching rule (in/out) * args->next_hop Socket we are forwarding to (out). + * args->next_hop6 IPv6 next hop we are forwarding to (out). * args->f_id Addresses grabbed from the packet (out) * args->rule.info a cookie depending on rule action * @@ -2281,6 +2282,23 @@ do { \ done = 1; /* exit outer loop */ break; +#ifdef INET6 + case O_FORWARD_IP6: + if (args->eh) /* not valid on layer2 pkts */ + break; + if (q == NULL || q->rule != f || + dyn_dir == MATCH_FORWARD) { + struct sockaddr_in6 *sin6; + + sin6 = &(((ipfw_insn_sa6 *)cmd)->sa); + args->next_hop6 = sin6; + } + retval = IP_FW_PASS; + l = 0; /* exit inner loop */ + done = 1; /* exit outer loop */ + break; +#endif + case O_NETGRAPH: case O_NGTEE: set_match(args, f_pos, chain); diff --git a/sys/netinet/ipfw/ip_fw_log.c b/sys/netinet/ipfw/ip_fw_log.c index edfa07d183cd..64548135c287 100644 --- a/sys/netinet/ipfw/ip_fw_log.c +++ b/sys/netinet/ipfw/ip_fw_log.c @@ -167,7 +167,7 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args, { char *action; int limit_reached = 0; - char action2[40], proto[128], fragment[32]; + char action2[92], proto[128], fragment[32]; if (V_fw_verbose == 0) { #ifndef WITHOUT_BPF @@ -290,6 +290,21 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args, sa->sa.sin_port); } break; +#ifdef INET6 + case O_FORWARD_IP6: { + char buf[INET6_ADDRSTRLEN]; + ipfw_insn_sa6 *sa = (ipfw_insn_sa6 *)cmd; + int len; + + len = snprintf(SNPARGS(action2, 0), "Forward to [%s]", + ip6_sprintf(buf, &sa->sa.sin6_addr)); + + if (sa->sa.sin6_port) + snprintf(SNPARGS(action2, len), ":%u", + sa->sa.sin6_port); + } + break; +#endif case O_NETGRAPH: snprintf(SNPARGS(action2, 0), "Netgraph %d", cmd->arg1); diff --git a/sys/netinet/ipfw/ip_fw_pfil.c b/sys/netinet/ipfw/ip_fw_pfil.c index 736615b947a2..2aaa8457ea73 100644 --- a/sys/netinet/ipfw/ip_fw_pfil.c +++ b/sys/netinet/ipfw/ip_fw_pfil.c @@ -152,13 +152,26 @@ ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, switch (ipfw) { case IP_FW_PASS: /* next_hop may be set by ipfw_chk */ - if (args.next_hop == NULL) + if (args.next_hop == NULL && args.next_hop6 == NULL) break; /* pass */ #ifndef IPFIREWALL_FORWARD ret = EACCES; #else { struct m_tag *fwd_tag; + size_t len; + + KASSERT(args.next_hop == NULL || args.next_hop6 == NULL, + ("%s: both next_hop=%p and next_hop6=%p not NULL", __func__, + args.next_hop, args.next_hop6)); +#ifdef INET6 + if (args.next_hop6 != NULL) + len = sizeof(struct sockaddr_in6); +#endif +#ifdef INET + if (args.next_hop != NULL) + len = sizeof(struct sockaddr_in); +#endif /* Incoming packets should not be tagged so we do not * m_tag_find. Outgoing packets may be tagged, so we @@ -169,18 +182,28 @@ ipfw_check_hook(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, if (fwd_tag != NULL) { m_tag_unlink(*m0, fwd_tag); } else { - fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, - sizeof(struct sockaddr_in), M_NOWAIT); + fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD, len, + M_NOWAIT); if (fwd_tag == NULL) { ret = EACCES; break; /* i.e. drop */ } } - bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in)); +#ifdef INET6 + if (args.next_hop6 != NULL) { + bcopy(args.next_hop6, (fwd_tag+1), len); + if (in6_localip(&args.next_hop6->sin6_addr)) + (*m0)->m_flags |= M_FASTFWD_OURS; + } +#endif +#ifdef INET + if (args.next_hop != NULL) { + bcopy(args.next_hop, (fwd_tag+1), len); + if (in_localip(args.next_hop->sin_addr)) + (*m0)->m_flags |= M_FASTFWD_OURS; + } +#endif m_tag_prepend(*m0, fwd_tag); - - if (in_localip(args.next_hop->sin_addr)) - (*m0)->m_flags |= M_FASTFWD_OURS; } #endif break; diff --git a/sys/netinet/ipfw/ip_fw_private.h b/sys/netinet/ipfw/ip_fw_private.h index 16ef46d574e1..fdb2b77f88b0 100644 --- a/sys/netinet/ipfw/ip_fw_private.h +++ b/sys/netinet/ipfw/ip_fw_private.h @@ -86,6 +86,7 @@ struct ip_fw_args { struct mbuf *m; /* the mbuf chain */ struct ifnet *oif; /* output interface */ struct sockaddr_in *next_hop; /* forward address */ + struct sockaddr_in6 *next_hop6; /* ipv6 forward address */ /* * On return, it points to the matching rule. diff --git a/sys/netinet/ipfw/ip_fw_sockopt.c b/sys/netinet/ipfw/ip_fw_sockopt.c index 143285850113..375b9c506e4d 100644 --- a/sys/netinet/ipfw/ip_fw_sockopt.c +++ b/sys/netinet/ipfw/ip_fw_sockopt.c @@ -723,6 +723,17 @@ check_ipfw_struct(struct ip_fw *rule, int size) return EINVAL; #endif +#ifdef INET6 + case O_FORWARD_IP6: +#ifdef IPFIREWALL_FORWARD + if (cmdlen != F_INSN_SIZE(ipfw_insn_sa6)) + goto bad_size; + goto check_action; +#else + return (EINVAL); +#endif +#endif /* INET6 */ + case O_DIVERT: case O_TEE: if (ip_divert_ptr == NULL) diff --git a/sys/netinet/tcp_input.c b/sys/netinet/tcp_input.c index e3e9aa6c745a..24680c24fabc 100644 --- a/sys/netinet/tcp_input.c +++ b/sys/netinet/tcp_input.c @@ -573,11 +573,9 @@ tcp_input(struct mbuf *m, int off0) uint8_t sig_checked = 0; #endif uint8_t iptos = 0; -#ifdef INET #ifdef IPFIREWALL_FORWARD struct m_tag *fwd_tag; #endif -#endif /* INET */ #ifdef INET6 struct ip6_hdr *ip6 = NULL; int isipv6; @@ -776,14 +774,55 @@ tcp_input(struct mbuf *m, int off0) } #endif -#ifdef INET #ifdef IPFIREWALL_FORWARD /* * Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain. */ fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL); +#endif /* IPFIREWALL_FORWARD */ - if (fwd_tag != NULL && isipv6 == 0) { /* IPv6 support is not yet */ +#ifdef INET6 +#ifdef IPFIREWALL_FORWARD + if (isipv6 && fwd_tag != NULL) { + struct sockaddr_in6 *next_hop6; + + next_hop6 = (struct sockaddr_in6 *)(fwd_tag + 1); + /* + * Transparently forwarded. Pretend to be the destination. + * Already got one like this? + */ + inp = in6_pcblookup_mbuf(&V_tcbinfo, + &ip6->ip6_src, th->th_sport, &ip6->ip6_dst, th->th_dport, + INPLOOKUP_WLOCKPCB, m->m_pkthdr.rcvif, m); + if (!inp) { + /* + * It's new. Try to find the ambushing socket. + * Because we've rewritten the destination address, + * any hardware-generated hash is ignored. + */ + inp = in6_pcblookup(&V_tcbinfo, &ip6->ip6_src, + th->th_sport, &next_hop6->sin6_addr, + next_hop6->sin6_port ? ntohs(next_hop6->sin6_port) : + th->th_dport, INPLOOKUP_WILDCARD | + INPLOOKUP_WLOCKPCB, m->m_pkthdr.rcvif); + } + /* Remove the tag from the packet. We don't need it anymore. */ + m_tag_delete(m, fwd_tag); + } else +#endif /* IPFIREWALL_FORWARD */ + if (isipv6) { + inp = in6_pcblookup_mbuf(&V_tcbinfo, &ip6->ip6_src, + th->th_sport, &ip6->ip6_dst, th->th_dport, + INPLOOKUP_WILDCARD | INPLOOKUP_WLOCKPCB, + m->m_pkthdr.rcvif, m); + } +#endif /* INET6 */ +#if defined(INET6) && defined(INET) + else +#endif +#ifdef INET +#ifdef IPFIREWALL_FORWARD + if (fwd_tag != NULL) { struct sockaddr_in *next_hop; next_hop = (struct sockaddr_in *)(fwd_tag+1); @@ -810,25 +849,11 @@ tcp_input(struct mbuf *m, int off0) m_tag_delete(m, fwd_tag); } else #endif /* IPFIREWALL_FORWARD */ + inp = in_pcblookup_mbuf(&V_tcbinfo, ip->ip_src, + th->th_sport, ip->ip_dst, th->th_dport, + INPLOOKUP_WILDCARD | INPLOOKUP_WLOCKPCB, + m->m_pkthdr.rcvif, m); #endif /* INET */ - { -#ifdef INET6 - if (isipv6) - inp = in6_pcblookup_mbuf(&V_tcbinfo, &ip6->ip6_src, - th->th_sport, &ip6->ip6_dst, th->th_dport, - INPLOOKUP_WILDCARD | INPLOOKUP_WLOCKPCB, - m->m_pkthdr.rcvif, m); -#endif -#if defined(INET) && defined(INET6) - else -#endif -#ifdef INET - inp = in_pcblookup_mbuf(&V_tcbinfo, ip->ip_src, - th->th_sport, ip->ip_dst, th->th_dport, - INPLOOKUP_WILDCARD | INPLOOKUP_WLOCKPCB, - m->m_pkthdr.rcvif, m); -#endif - } /* * If the INPCB does not exist then all data in the incoming diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c index 28eb8fd19f88..701bc358ab9e 100644 --- a/sys/netinet/udp_usrreq.c +++ b/sys/netinet/udp_usrreq.c @@ -441,28 +441,6 @@ udp_input(struct mbuf *m, int off) } else UDPSTAT_INC(udps_nosum); -#ifdef IPFIREWALL_FORWARD - /* - * Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain. - */ - fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL); - if (fwd_tag != NULL) { - struct sockaddr_in *next_hop; - - /* - * Do the hack. - */ - next_hop = (struct sockaddr_in *)(fwd_tag + 1); - ip->ip_dst = next_hop->sin_addr; - uh->uh_dport = ntohs(next_hop->sin_port); - - /* - * Remove the tag from the packet. We don't need it anymore. - */ - m_tag_delete(m, fwd_tag); - } -#endif - if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) || in_broadcast(ip->ip_dst, ifp)) { struct inpcb *last; @@ -568,9 +546,41 @@ udp_input(struct mbuf *m, int off) /* * Locate pcb for datagram. */ - inp = in_pcblookup_mbuf(&V_udbinfo, ip->ip_src, uh->uh_sport, - ip->ip_dst, uh->uh_dport, INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB, - ifp, m); +#ifdef IPFIREWALL_FORWARD + /* + * Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain. + */ + fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL); + if (fwd_tag != NULL) { + struct sockaddr_in *next_hop; + + next_hop = (struct sockaddr_in *)(fwd_tag + 1); + + /* + * Transparently forwarded. Pretend to be the destination. + * Already got one like this? + */ + inp = in_pcblookup_mbuf(&V_udbinfo, ip->ip_src, uh->uh_sport, + ip->ip_dst, uh->uh_dport, INPLOOKUP_RLOCKPCB, ifp, m); + if (!inp) { + /* + * It's new. Try to find the ambushing socket. + * Because we've rewritten the destination address, + * any hardware-generated hash is ignored. + */ + inp = in_pcblookup(&V_udbinfo, ip->ip_src, + uh->uh_sport, next_hop->sin_addr, + next_hop->sin_port ? htons(next_hop->sin_port) : + uh->uh_dport, INPLOOKUP_WILDCARD | + INPLOOKUP_RLOCKPCB, ifp); + } + /* Remove the tag from the packet. We don't need it anymore. */ + m_tag_delete(m, fwd_tag); + } else +#endif /* IPFIREWALL_FORWARD */ + inp = in_pcblookup_mbuf(&V_udbinfo, ip->ip_src, uh->uh_sport, + ip->ip_dst, uh->uh_dport, INPLOOKUP_WILDCARD | + INPLOOKUP_RLOCKPCB, ifp, m); if (inp == NULL) { if (udp_log_in_vain) { char buf[4*sizeof "123"]; diff --git a/sys/netinet6/ip6_forward.c b/sys/netinet6/ip6_forward.c index cff29e19be52..77cb92663076 100644 --- a/sys/netinet6/ip6_forward.c +++ b/sys/netinet6/ip6_forward.c @@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" +#include "opt_ipfw.h" #include "opt_ipsec.h" #include "opt_ipstealth.h" @@ -50,6 +51,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include @@ -98,10 +100,16 @@ ip6_forward(struct mbuf *m, int srcrt) struct mbuf *mcopy = NULL; struct ifnet *origifp; /* maybe unnecessary */ u_int32_t inzone, outzone; - struct in6_addr src_in6, dst_in6; + struct in6_addr src_in6, dst_in6, odst; #ifdef IPSEC struct secpolicy *sp = NULL; int ipsecrt = 0; +#endif +#ifdef SCTP + int sw_csum; +#endif +#ifdef IPFIREWALL_FORWARD + struct m_tag *fwd_tag; #endif char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN]; @@ -345,13 +353,15 @@ ip6_forward(struct mbuf *m, int srcrt) goto skip_routing; skip_ipsec: #endif - +again: bzero(&rin6, sizeof(struct route_in6)); dst = (struct sockaddr_in6 *)&rin6.ro_dst; dst->sin6_len = sizeof(struct sockaddr_in6); dst->sin6_family = AF_INET6; dst->sin6_addr = ip6->ip6_dst; - +#ifdef IPFIREWALL_FORWARD +again2: +#endif rin6.ro_rt = rtalloc1((struct sockaddr *)dst, 0, 0); if (rin6.ro_rt != NULL) RT_UNLOCK(rin6.ro_rt); @@ -554,6 +564,7 @@ ip6_forward(struct mbuf *m, int srcrt) if (!PFIL_HOOKED(&V_inet6_pfil_hook)) goto pass; + odst = ip6->ip6_dst; /* Run through list of hooks for output packets. */ error = pfil_run_hooks(&V_inet6_pfil_hook, &m, rt->rt_ifp, PFIL_OUT, NULL); if (error != 0) @@ -562,6 +573,59 @@ ip6_forward(struct mbuf *m, int srcrt) goto freecopy; ip6 = mtod(m, struct ip6_hdr *); + /* See if destination IP address was changed by packet filter. */ + if (!IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst)) { + m->m_flags |= M_SKIP_FIREWALL; + /* If destination is now ourself drop to ip6_input(). */ + if (in6_localip(&ip6->ip6_dst)) { + m->m_flags |= M_FASTFWD_OURS; + if (m->m_pkthdr.rcvif == NULL) + m->m_pkthdr.rcvif = V_loif; + if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { + m->m_pkthdr.csum_flags |= + CSUM_DATA_VALID | CSUM_PSEUDO_HDR; + m->m_pkthdr.csum_data = 0xffff; + } + m->m_pkthdr.csum_flags |= + CSUM_IP_CHECKED | CSUM_IP_VALID; +#ifdef SCTP + if (m->m_pkthdr.csum_flags & CSUM_SCTP) + m->m_pkthdr.csum_flags |= CSUM_SCTP_VALID; +#endif + error = netisr_queue(NETISR_IPV6, m); + goto out; + } else + goto again; /* Redo the routing table lookup. */ + } + +#ifdef IPFIREWALL_FORWARD + /* See if local, if yes, send it to netisr. */ + if (m->m_flags & M_FASTFWD_OURS) { + if (m->m_pkthdr.rcvif == NULL) + m->m_pkthdr.rcvif = V_loif; + if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { + m->m_pkthdr.csum_flags |= + CSUM_DATA_VALID | CSUM_PSEUDO_HDR; + m->m_pkthdr.csum_data = 0xffff; + } +#ifdef SCTP + if (m->m_pkthdr.csum_flags & CSUM_SCTP) + m->m_pkthdr.csum_flags |= CSUM_SCTP_VALID; +#endif + error = netisr_queue(NETISR_IPV6, m); + goto out; + } + /* Or forward to some other address? */ + fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL); + if (fwd_tag) { + dst = (struct sockaddr_in6 *)&rin6.ro_dst; + bcopy((fwd_tag+1), dst, sizeof(struct sockaddr_in6)); + m->m_flags |= M_SKIP_FIREWALL; + m_tag_delete(m, fwd_tag); + goto again2; + } +#endif /* IPFIREWALL_FORWARD */ + pass: error = nd6_output(rt->rt_ifp, origifp, m, dst, rt); if (error) { diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c index de3a622706b1..1fdde1662283 100644 --- a/sys/netinet6/ip6_input.c +++ b/sys/netinet6/ip6_input.c @@ -65,6 +65,7 @@ __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" +#include "opt_ipfw.h" #include "opt_ipsec.h" #include "opt_route.h" @@ -91,6 +92,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include #ifdef INET @@ -357,6 +359,17 @@ ip6_input(struct mbuf *m) */ ip6_delaux(m); + if (m->m_flags & M_FASTFWD_OURS) { + /* + * Firewall changed destination to local. + */ + m->m_flags &= ~M_FASTFWD_OURS; + ours = 1; + deliverifp = m->m_pkthdr.rcvif; + ip6 = mtod(m, struct ip6_hdr *); + goto hbhcheck; + } + /* * mbuf statistics */ @@ -533,6 +546,24 @@ ip6_input(struct mbuf *m) ip6 = mtod(m, struct ip6_hdr *); srcrt = !IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst); +#ifdef IPFIREWALL_FORWARD + if (m->m_flags & M_FASTFWD_OURS) { + m->m_flags &= ~M_FASTFWD_OURS; + ours = 1; + deliverifp = m->m_pkthdr.rcvif; + goto hbhcheck; + } + if (m_tag_find(m, PACKET_TAG_IPFORWARD, NULL) != NULL) { + /* + * Directly ship the packet on. This allows forwarding + * packets originally destined to us to some other directly + * connected host. + */ + ip6_forward(m, 1); + goto out; + } +#endif /* IPFIREWALL_FORWARD */ + passin: /* * Disambiguate address scope zones (if there is ambiguity). diff --git a/sys/netinet6/ip6_output.c b/sys/netinet6/ip6_output.c index 973689c7aa9b..85161247d7e6 100644 --- a/sys/netinet6/ip6_output.c +++ b/sys/netinet6/ip6_output.c @@ -65,6 +65,7 @@ __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" +#include "opt_ipfw.h" #include "opt_ipsec.h" #include "opt_sctp.h" #include "opt_route.h" @@ -90,6 +91,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include @@ -229,6 +231,9 @@ ip6_output(struct mbuf *m0, struct ip6_pktopts *opt, int segleft_org = 0; struct secpolicy *sp = NULL; #endif /* IPSEC */ +#ifdef IPFIREWALL_FORWARD + struct m_tag *fwd_tag; +#endif ip6 = mtod(m, struct ip6_hdr *); if (ip6 == NULL) { @@ -850,7 +855,8 @@ skip_ipsec2:; if (!IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst)) { m->m_flags |= M_SKIP_FIREWALL; /* If destination is now ourself drop to ip6_input(). */ - if (in6_localaddr(&ip6->ip6_dst)) { + if (in6_localip(&ip6->ip6_dst)) { + m->m_flags |= M_FASTFWD_OURS; if (m->m_pkthdr.rcvif == NULL) m->m_pkthdr.rcvif = V_loif; if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { @@ -870,7 +876,33 @@ skip_ipsec2:; goto again; /* Redo the routing table lookup. */ } - /* XXX: IPFIREWALL_FORWARD */ +#ifdef IPFIREWALL_FORWARD + /* See if local, if yes, send it to netisr. */ + if (m->m_flags & M_FASTFWD_OURS) { + if (m->m_pkthdr.rcvif == NULL) + m->m_pkthdr.rcvif = V_loif; + if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { + m->m_pkthdr.csum_flags |= + CSUM_DATA_VALID | CSUM_PSEUDO_HDR; + m->m_pkthdr.csum_data = 0xffff; + } +#ifdef SCTP + if (m->m_pkthdr.csum_flags & CSUM_SCTP) + m->m_pkthdr.csum_flags |= CSUM_SCTP_VALID; +#endif + error = netisr_queue(NETISR_IPV6, m); + goto done; + } + /* Or forward to some other address? */ + fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL); + if (fwd_tag) { + dst = (struct sockaddr_in6 *)&ro->ro_dst; + bcopy((fwd_tag+1), dst, sizeof(struct sockaddr_in6)); + m->m_flags |= M_SKIP_FIREWALL; + m_tag_delete(m, fwd_tag); + goto again; + } +#endif /* IPFIREWALL_FORWARD */ passout: /* diff --git a/sys/netinet6/udp6_usrreq.c b/sys/netinet6/udp6_usrreq.c index 672300748186..2a4c0a55a469 100644 --- a/sys/netinet6/udp6_usrreq.c +++ b/sys/netinet6/udp6_usrreq.c @@ -71,6 +71,7 @@ __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" +#include "opt_ipfw.h" #include "opt_ipsec.h" #include @@ -181,6 +182,9 @@ udp6_input(struct mbuf **mp, int *offp, int proto) int off = *offp; int plen, ulen; struct sockaddr_in6 fromsa; +#ifdef IPFIREWALL_FORWARD + struct m_tag *fwd_tag; +#endif ifp = m->m_pkthdr.rcvif; ip6 = mtod(m, struct ip6_hdr *); @@ -377,9 +381,43 @@ udp6_input(struct mbuf **mp, int *offp, int proto) /* * Locate pcb for datagram. */ - inp = in6_pcblookup_mbuf(&V_udbinfo, &ip6->ip6_src, uh->uh_sport, - &ip6->ip6_dst, uh->uh_dport, INPLOOKUP_WILDCARD | - INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif, m); +#ifdef IPFIREWALL_FORWARD + /* + * Grab info from PACKET_TAG_IPFORWARD tag prepended to the chain. + */ + fwd_tag = m_tag_find(m, PACKET_TAG_IPFORWARD, NULL); + if (fwd_tag != NULL) { + struct sockaddr_in6 *next_hop6; + + next_hop6 = (struct sockaddr_in6 *)(fwd_tag + 1); + + /* + * Transparently forwarded. Pretend to be the destination. + * Already got one like this? + */ + inp = in6_pcblookup_mbuf(&V_udbinfo, + &ip6->ip6_src, uh->uh_sport, &ip6->ip6_dst, uh->uh_dport, + INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif, m); + if (!inp) { + /* + * It's new. Try to find the ambushing socket. + * Because we've rewritten the destination address, + * any hardware-generated hash is ignored. + */ + inp = in6_pcblookup(&V_udbinfo, &ip6->ip6_src, + uh->uh_sport, &next_hop6->sin6_addr, + next_hop6->sin6_port ? htons(next_hop6->sin6_port) : + uh->uh_dport, INPLOOKUP_WILDCARD | + INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif); + } + /* Remove the tag from the packet. We don't need it anymore. */ + m_tag_delete(m, fwd_tag); + } else +#endif /* IPFIREWALL_FORWARD */ + inp = in6_pcblookup_mbuf(&V_udbinfo, &ip6->ip6_src, + uh->uh_sport, &ip6->ip6_dst, uh->uh_dport, + INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB, + m->m_pkthdr.rcvif, m); if (inp == NULL) { if (udp_log_in_vain) { char ip6bufs[INET6_ADDRSTRLEN]; diff --git a/tools/regression/ipfw/fwd/vimage-fwd.sh b/tools/regression/ipfw/fwd/vimage-fwd.sh new file mode 100755 index 000000000000..5dbfcd77b453 --- /dev/null +++ b/tools/regression/ipfw/fwd/vimage-fwd.sh @@ -0,0 +1,369 @@ +#!/bin/sh +#- +# Copyright (c) 2010, "Bjoern A. Zeeb" +# Copyright (c) 2011, Sandvine Incorporated ULC. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +# +# Test ipfw fwd for IPv4 and IPv6 using VIMAGE, testing that as well. +# For no test the packet header contents must be changed but always +# keeping the original destination. +# + +case `id -u` in +0) ;; +*) echo "ERROR: Must be run as superuser." >&2 + exit 2 +esac + +epair_base() +{ + local ep + + ep=`ifconfig epair create` + expr ${ep} : '\(.*\).' +} + +debug_err() +{ + local _p + _p="$1" + + case "${DEBUG}" in + "") ;; + *) + echo " ~~ start of debug ~~" + echo " ~~ left:" + jexec ${ljid} /sbin/ipfw show + echo " ~~ middle:" + jexec ${mjid} /sbin/ipfw show + echo " ~~ right:" + jexec ${rjid} /sbin/ipfw show + echo " ~~ result file:" + cat ${_p}.1 + echo " ~~ log file:" + cat ${_p} + echo " ~~ end of debug ~~" + ;; + esac +} + +check_cleanup_result_file() +{ + local _p + _p="$1" + + if test ! -s ${_p}.1; then + echo "FAIL (output file empty)." + debug_err ${_p} + else + read line < ${_p}.1 + # Netcat adds 'X's in udp mode. + l="/${line#*/}" + if test "${l}" = "${_p}"; then + echo "PASS." + else + echo "FAIL (expected: '${_p}' got '${l}')." + debug_err ${_p} + fi + fi + + rm -f ${_p}.1 + rm -f ${_p} +} + +# Transparent proxy scenario (local address). +run_test_tp() +{ + local _descr + local _sip _dip _fip _fport _dport _p + local _nc_af _nc_p + local _lport + descr="$1" + _sip="$2" + _dip="$3" + _fip="$4" + _fport="$5" + _dport="$6" + _p="$7" + _nc_af="$8" + + _lport=${_dport} + case "${_fport}" in + "") _lport="${_dport}" ;; + *) _lport="${_fport#,}" ;; + esac + + case "${_p}" in + udp) _nc_p="-u" ;; + esac + + OUT=`mktemp -t "ipfwfwd$$-XXXXXX"` + echo -n "${descr} (${OUT}).." + ( + jexec ${ljid} /sbin/ipfw -f flush + jexec ${ljid} /sbin/ipfw -f zero + jexec ${mjid} /sbin/ipfw -f flush + jexec ${mjid} /sbin/ipfw -f zero + jexec ${rjid} /sbin/ipfw -f flush + jexec ${rjid} /sbin/ipfw -f zero + jexec ${mjid} /sbin/ipfw add 100 fwd ${_fip}${_fport} ${_p} from ${_sip} to ${_dip} + + jexec ${mjid} /bin/sh -c "nc -w 10 ${_nc_af} -n ${_nc_p} -l ${_fip} ${_lport} > ${OUT}.1 &" + jexec ${rjid} /bin/sh -c "echo '${OUT}' | nc -w 1 -v ${_nc_af} -n ${_nc_p} ${_dip} ${_dport}" + ) > ${OUT} 2>&1 + check_cleanup_result_file "${OUT}" +} + +# Transparent redirect scenario (non-local address). +run_test_nh() +{ + local _descr + local _sip _dip _fip _fport _dport _p + local _nc_af _nc_p + local _lport + descr="$1" + _sip="$2" + _dip="$3" + _fip="$4" + _fport="$5" + _dport="$6" + _p="$7" + _nc_af="$8" + + _lport=${_dport} + case "${_fport}" in + "") _lport="${_dport}" ;; + *) _lport="${_fport#,}" ;; + esac + + case "${_p}" in + udp) _nc_p="-u" ;; + esac + + OUT=`mktemp -t "ipfwfwd$$-XXXXXX"` + echo -n "${descr} (${OUT}).." + ( + jexec ${ljid} /sbin/ipfw -f flush + jexec ${ljid} /sbin/ipfw -f zero + jexec ${mjid} /sbin/ipfw -f flush + jexec ${mjid} /sbin/ipfw -f zero + jexec ${rjid} /sbin/ipfw -f flush + jexec ${rjid} /sbin/ipfw -f zero + jexec ${mjid} /sbin/ipfw add 100 fwd ${_fip} ${_p} from ${_sip} to ${_dip} + + jexec ${ljid} /bin/sh -c "nc -w 10 ${_nc_af} -n ${_nc_p} -l ${_dip} ${_lport} > ${OUT}.1 &" + jexec ${rjid} /bin/sh -c "echo '${OUT}' | nc -w 1 -v ${_nc_af} -n ${_nc_p} ${_dip} ${_dport}" + ) > ${OUT} 2>&1 + check_cleanup_result_file "${OUT}" +} + +echo "==> Setting up test network" +kldload -q ipfw > /dev/null 2>&1 + +# Start left (sender) jail. +ljid=`jail -i -c -n lef$$ host.hostname=left.example.net vnet persist` + +# Start middle (ipfw) jail. +mjid=`jail -i -c -n mid$$ host.hostname=center.example.net vnet persist` + +# Start right (non-local ip redirects go to here) jail. +rjid=`jail -i -c -n right$$ host.hostname=right.example.net vnet persist` + +echo "left ${ljid} middle ${mjid} right ${rjid}" + +# Create networking. +# +# jail: left middle right +# ifaces: lmep:a ---- lmep:b mrep:a ---- mrep:b +# + +jexec ${mjid} sysctl net.inet.ip.forwarding=1 +jexec ${mjid} sysctl net.inet6.ip6.forwarding=1 +jexec ${mjid} sysctl net.inet6.ip6.accept_rtadv=0 + +lmep=$(epair_base) +ifconfig ${lmep}a vnet ${ljid} +ifconfig ${lmep}b vnet ${mjid} + +jexec ${ljid} ifconfig lo0 inet 127.0.0.1/8 +jexec ${ljid} ifconfig lo0 inet 192.0.2.5/32 alias # Test 9-10 +jexec ${ljid} ifconfig lo0 inet6 2001:db8:1::1/128 alias # Test 11-12 +jexec ${ljid} ifconfig ${lmep}a inet 192.0.2.1/30 up +jexec ${ljid} ifconfig ${lmep}a inet6 2001:db8::1/64 alias + +jexec ${ljid} route add default 192.0.2.2 +jexec ${ljid} route add -inet6 default 2001:db8::2 + +jexec ${mjid} ifconfig lo0 inet 127.0.0.1/8 +jexec ${mjid} ifconfig lo0 inet 192.0.2.255/32 alias # Test 1-4 +jexec ${mjid} ifconfig lo0 inet6 2001:db8:ffff::1/128 alias # Test 5-8 +jexec ${mjid} ifconfig ${lmep}b inet 192.0.2.2/30 up +jexec ${mjid} ifconfig ${lmep}b inet6 2001:db8::2/64 alias +jexec ${mjid} route add default 192.0.2.1 + +mrep=$(epair_base) +ifconfig ${mrep}a vnet ${mjid} +ifconfig ${mrep}b vnet ${rjid} + +jexec ${mjid} ifconfig ${mrep}a inet 192.0.2.5/30 up +jexec ${mjid} ifconfig ${mrep}a inet6 2001:db8:1::1/64 alias + +jexec ${rjid} ifconfig lo0 inet 127.0.0.1/8 +jexec ${rjid} ifconfig ${mrep}b inet 192.0.2.6/30 up +jexec ${rjid} ifconfig ${mrep}b inet6 2001:db8:1::2/64 alias + +jexec ${rjid} route add default 192.0.2.5 +jexec ${rjid} route add -inet6 default 2001:db8:1::1 + +# ------------------------------------------------------------------------------ +# Tests +# +# The jails are not chrooted to they all share the same base filesystem. +# This means we can put results into /tmp and just collect them from here. +# +echo "==> Running tests" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=1 +run_test_tp "TEST ${i} IPv4 UDP redirect local to other local address, same port" \ + 192.0.2.6 192.0.2.5 192.0.2.255 "" 12345 udp "-4" + +i=$((i + 1)) +run_test_tp "TEST ${i} IPv4 UDP redirect local to other local address, different port" \ + 192.0.2.6 192.0.2.5 192.0.2.255 ",65534" 12345 udp "-4" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_tp "TEST ${i} IPv4 TCP redirect local to other local address, same port" \ + 192.0.2.6 192.0.2.5 192.0.2.255 "" 12345 tcp "-4" + +i=$((i + 1)) +run_test_tp "TEST ${i} IPv4 TCP redirect local to other local address, different port" \ + 192.0.2.6 192.0.2.5 192.0.2.255 ",65534" 12345 tcp "-4" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_tp "TEST ${i} IPv4 UDP redirect foreign to local address, same port" \ + 192.0.2.6 192.0.2.1 192.0.2.255 "" 12345 udp "-4" + +i=$((i + 1)) +run_test_tp "TEST ${i} IPv4 UDP redirect foreign to local address, different port" \ + 192.0.2.6 192.0.2.1 192.0.2.255 ",65534" 12345 udp "-4" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_tp "TEST ${i} IPv4 TCP redirect foreign to local address, same port" \ + 192.0.2.6 192.0.2.1 192.0.2.255 "" 12345 tcp "-4" + +i=$((i + 1)) +run_test_tp "TEST ${i} IPv4 TCP redirect foreign to local address, different port" \ + 192.0.2.6 192.0.2.1 192.0.2.255 ",65534" 12345 tcp "-4" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_tp "TEST ${i} IPv6 UDP redirect local to other local address, same port" \ + 2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 "" 12345 udp "-6" + +i=$((i + 1)) +run_test_tp "TEST ${i} IPv6 UDP redirect local to other local address, different port" \ + 2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 ",65534" 12345 udp "-6" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_tp "TEST ${i} IPv6 TCP redirect local to other local address, same port" \ + 2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 "" 12345 tcp "-6" + +i=$((i + 1)) +run_test_tp "TEST ${i} IPv6 TCP redirect local to other local address, different port" \ + 2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 ",65534" 12345 tcp "-6" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_tp "TEST ${i} IPv6 UDP redirect foreign to local address, same port" \ + 2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 "" 12345 udp "-6" + +i=$((i + 1)) +run_test_tp "TEST ${i} IPv6 UDP redirect foreign to local address, different port" \ + 2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 ",65534" 12345 udp "-6" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_tp "TEST ${i} IPv6 TCP redirect foreign to local address, same port" \ + 2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 "" 12345 tcp "-6" + +i=$((i + 1)) +run_test_tp "TEST ${i} IPv6 TCP redirect foreign to local address, different port" \ + 2001:db8:1::2 2001:db8::1 2001:db8:ffff::1 ",65534" 12345 tcp "-6" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_nh "TEST ${i} IPv4 UDP redirect to foreign address" \ + 192.0.2.6 192.0.2.5 192.0.2.1 "" 12345 udp "-4" + +i=$((i + 1)) +run_test_nh "TEST ${i} IPv4 TCP redirect to foreign address" \ + 192.0.2.6 192.0.2.5 192.0.2.1 "" 12345 tcp "-4" + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +i=$((i + 1)) +run_test_nh "TEST ${i} IPv6 UDP redirect to foreign address" \ + 2001:db8:1::2 2001:db8:1::1 2001:db8::1 "" 12345 udp "-6" + +i=$((i + 1)) +run_test_nh "TEST ${i} IPv6 TCP redirect to foreign address" \ + 2001:db8:1::2 2001:db8:1::1 2001:db8::1 "" 12345 tcp "-6" + +################################################################################ +# +# Cleanup +# +echo "==> Cleaning up in 3 seconds" +# Let VIMAGE network stacks settle to avoid panics while still "experimental". +sleep 3 + +jail -r ${rjid} +jail -r ${mjid} +jail -r ${ljid} + +for jid in ${rjid} ${mjid} ${ljid}; do + while : ; do + x=`jls -as -j ${jid} jid 2>/dev/null` + case "${x}" in + jid=*) echo "Waiting for jail ${jid} to stop." >&2 + sleep 1 + continue + ;; + esac + break + done +done + +ifconfig ${lmep}a destroy +ifconfig ${mrep}a destroy + +# end