svn commit: r225044 - in head: sbin/ipfw sys/netinet sys/netinet/ipfw sys/netinet6 tools/regression/ipfw tools/regression/ipfw/fwd

Bjoern A. Zeeb bz at FreeBSD.org
Sat Aug 20 17:05:12 UTC 2011


Author: bz
Date: Sat Aug 20 17:05:11 2011
New Revision: 225044
URL: http://svn.freebsd.org/changeset/base/225044

Log:
  Add support for IPv6 to ipfw fwd:
  Distinguish IPv4 and IPv6 addresses and optional port numbers in
  user space to set the option for the correct protocol family.
  Add support in the kernel for carrying the new IPv6 destination
  address and port.
  Add support to TCP and UDP for IPv6 and fix UDP IPv4 to not change
  the address in the IP header.
  Add support for IPv6 forwarding to a non-local destination.
  Add a regession test uitilizing VIMAGE to check all 20 possible
  combinations I could think of.
  
  Obtained from:	David Dolson at Sandvine Incorporated
  		(original version for ipfw fwd IPv6 support)
  Sponsored by:	Sandvine Incorporated
  PR:		bin/117214
  MFC after:	4 weeks
  Approved by:	re (kib)

Added:
  head/tools/regression/ipfw/
  head/tools/regression/ipfw/fwd/
  head/tools/regression/ipfw/fwd/vimage-fwd.sh   (contents, props changed)
Modified:
  head/sbin/ipfw/ipfw.8
  head/sbin/ipfw/ipfw2.c
  head/sys/netinet/ip_fw.h
  head/sys/netinet/ipfw/ip_fw2.c
  head/sys/netinet/ipfw/ip_fw_log.c
  head/sys/netinet/ipfw/ip_fw_pfil.c
  head/sys/netinet/ipfw/ip_fw_private.h
  head/sys/netinet/ipfw/ip_fw_sockopt.c
  head/sys/netinet/tcp_input.c
  head/sys/netinet/udp_usrreq.c
  head/sys/netinet6/ip6_forward.c
  head/sys/netinet6/ip6_input.c
  head/sys/netinet6/ip6_output.c
  head/sys/netinet6/udp6_usrreq.c

Modified: head/sbin/ipfw/ipfw.8
==============================================================================
--- head/sbin/ipfw/ipfw.8	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sbin/ipfw/ipfw.8	Sat Aug 20 17:05:11 2011	(r225044)
@@ -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.

Modified: head/sbin/ipfw/ipfw2.c
==============================================================================
--- head/sbin/ipfw/ipfw2.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sbin/ipfw/ipfw2.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -1111,6 +1111,18 @@ show_ipfw(struct ip_fw *rule, int pcwidt
 		    }
 			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 @@ chkarg:
 		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;
 	    }

Modified: head/sys/netinet/ip_fw.h
==============================================================================
--- head/sys/netinet/ip_fw.h	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet/ip_fw.h	Sat Aug 20 17:05:11 2011	(r225044)
@@ -203,6 +203,8 @@ enum ipfw_opcodes {		/* arguments (4 byt
 
 	O_CALLRETURN,		/* arg1=called rule number */
 
+	O_FORWARD_IP6,		/* fwd sockaddr_in6             */
+
 	O_LAST_OPCODE		/* not an opcode!		*/
 };
 
@@ -299,6 +301,14 @@ typedef struct  _ipfw_insn_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.
  */
 typedef struct	_ipfw_insn_mac {

Modified: head/sys/netinet/ipfw/ip_fw2.c
==============================================================================
--- head/sys/netinet/ipfw/ip_fw2.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet/ipfw/ip_fw2.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -796,6 +796,7 @@ set_match(struct ip_fw_args *args, int s
  *
  *	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);

Modified: head/sys/netinet/ipfw/ip_fw_log.c
==============================================================================
--- head/sys/netinet/ipfw/ip_fw_log.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet/ipfw/ip_fw_log.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -167,7 +167,7 @@ ipfw_log(struct ip_fw *f, u_int hlen, st
 {
 	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, st
 				    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);

Modified: head/sys/netinet/ipfw/ip_fw_pfil.c
==============================================================================
--- head/sys/netinet/ipfw/ip_fw_pfil.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet/ipfw/ip_fw_pfil.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -152,13 +152,26 @@ again:
 	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 @@ again:
 		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;

Modified: head/sys/netinet/ipfw/ip_fw_private.h
==============================================================================
--- head/sys/netinet/ipfw/ip_fw_private.h	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet/ipfw/ip_fw_private.h	Sat Aug 20 17:05:11 2011	(r225044)
@@ -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.

Modified: head/sys/netinet/ipfw/ip_fw_sockopt.c
==============================================================================
--- head/sys/netinet/ipfw/ip_fw_sockopt.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet/ipfw/ip_fw_sockopt.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -723,6 +723,17 @@ check_ipfw_struct(struct ip_fw *rule, in
 			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)

Modified: head/sys/netinet/tcp_input.c
==============================================================================
--- head/sys/netinet/tcp_input.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet/tcp_input.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -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 @@ findpcb:
 	}
 #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 @@ findpcb:
 		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

Modified: head/sys/netinet/udp_usrreq.c
==============================================================================
--- head/sys/netinet/udp_usrreq.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet/udp_usrreq.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -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"];

Modified: head/sys/netinet6/ip6_forward.c
==============================================================================
--- head/sys/netinet6/ip6_forward.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet6/ip6_forward.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -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 <sys/syslog.h>
 
 #include <net/if.h>
+#include <net/netisr.h>
 #include <net/route.h>
 #include <net/pfil.h>
 
@@ -98,11 +100,17 @@ 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];
 
 #ifdef IPSEC
@@ -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 @@ skip_routing:
 	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 @@ skip_routing:
 		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) {

Modified: head/sys/netinet6/ip6_input.c
==============================================================================
--- head/sys/netinet6/ip6_input.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet6/ip6_input.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -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 <net/vnet.h>
 
 #include <netinet/in.h>
+#include <netinet/ip_var.h>
 #include <netinet/in_systm.h>
 #include <net/if_llatbl.h>
 #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).

Modified: head/sys/netinet6/ip6_output.c
==============================================================================
--- head/sys/netinet6/ip6_output.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet6/ip6_output.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -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 <netinet/in.h>
 #include <netinet/in_var.h>
+#include <netinet/ip_var.h>
 #include <netinet6/in6_var.h>
 #include <netinet/ip6.h>
 #include <netinet/icmp6.h>
@@ -229,6 +231,9 @@ ip6_output(struct mbuf *m0, struct ip6_p
 	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 @@ again:
 	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 @@ again:
 			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:
 	/*

Modified: head/sys/netinet6/udp6_usrreq.c
==============================================================================
--- head/sys/netinet6/udp6_usrreq.c	Sat Aug 20 16:43:47 2011	(r225043)
+++ head/sys/netinet6/udp6_usrreq.c	Sat Aug 20 17:05:11 2011	(r225044)
@@ -71,6 +71,7 @@ __FBSDID("$FreeBSD$");
 
 #include "opt_inet.h"
 #include "opt_inet6.h"
+#include "opt_ipfw.h"
 #include "opt_ipsec.h"
 
 #include <sys/param.h>
@@ -181,6 +182,9 @@ udp6_input(struct mbuf **mp, int *offp, 
 	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, 
 	/*
 	 * 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];

Added: head/tools/regression/ipfw/fwd/vimage-fwd.sh
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/tools/regression/ipfw/fwd/vimage-fwd.sh	Sat Aug 20 17:05:11 2011	(r225044)
@@ -0,0 +1,369 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010, "Bjoern A. Zeeb" <bz at FreeBSD.org>
+# 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"

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-head mailing list