kern/188511: divert-reply implementation for pf

PiBa-NL PiBa.NL.dev at gmail.com
Sat Apr 12 13:30:01 UTC 2014


>Number:         188511
>Category:       kern
>Synopsis:       divert-reply implementation for pf
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Sat Apr 12 13:30:00 UTC 2014
>Closed-Date:
>Last-Modified:
>Originator:     PiBa-NL
>Release:        10.0
>Organization:
none
>Environment:
FreeBSD FreeBSD10 10.0-RELEASE FreeBSD 10.0-RELEASE #0 r260789: Thu Jan 16 22:34:59 UTC 2014     root at snap.freebsd.org:/usr/obj/usr/src/sys/GENERIC  amd64
>Description:
It is currently not possible to bind to a nonlocal ip and succesfully connect a TCP socket.
This patch will include a new option for pf 'divert-reply'.

This should work with the haproxy-devel port 'source 0.0.0.0 usesrc clientip' option.

But for easy testing i also include a python program that contacts a webserver from a non-local ip using the IP_BINDANY or IPV6_BINDANY socket option. A single firewall rule needs to be made that matches outbound traffic, and has the divert-reply option. Like this: "pass out  quick  on em0 inet proto tcp  from any to 192.168.0.40 port 80 keep state divert-reply"

I hope this is OK and can be included in next release, if not please let me know if and what to adjust.

p.s.
I took pretty much all the code from here: http://lists.freebsd.org/pipermail/freebsd-net/2009-June/022166.html
Adapted it to FreeBSD v10 , and removed the parts i was unable to test.. UDP and bridge support.
>How-To-Repeat:
The python program below uses/demonstrates the function, make sure the machine is in the return-path of the webserver traffic a.k.a. it is the default route.

-------------------
import socket
HOST = '192.168.0.40'    # The remote host
PORT = 80              # The same port as used by the server
SOURCE = '192.168.108.20'
#s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#s.setsockopt(socket.IPPROTO_IP, 24,1) # IP_BINDANY=24 flag to allow binding to nonlocal sockets.

HOST = 'fd00:1::40'    # The remote host
PORT = 80              # The same port as used by the server
SOURCE = 'fd00:108::abcd'
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s.setsockopt(41, 64,1) # IPV6_BINDANY=64 flag to allow binding to nonlocal sockets.

s.bind((SOURCE,0)) # port 0 so pick a random client port..
s.connect((HOST, PORT))
s.sendall('GET / HTTP/1.0\r\nhost: test\r\n\r\n')
x = 0
while True:
	data = s.recv(1024)
	x = x + len(data)
	if not data: break
	print repr(data)
s.close()
print 'Received', repr(x), "bytes"
-------------------

>Fix:
The attached patch.

Patch attached with submission follows:

Index: sbin/pfctl/parse.y
===================================================================
--- sbin/pfctl/parse.y	(revision 261814)
+++ sbin/pfctl/parse.y	(working copy)
@@ -2389,12 +2389,7 @@
 			}
 		}
 		| DIVERTREPLY {
-#ifdef __FreeBSD__
-			yyerror("divert-reply has no meaning in FreeBSD pf(4)");
-			YYERROR;
-#else
 			filter_opts.divert.port = 1;	/* some random value */
-#endif
 		}
 		;
 
Index: sys/netpfil/pf/pf.c
===================================================================
--- sys/netpfil/pf/pf.c	(revision 261814)
+++ sys/netpfil/pf/pf.c	(working copy)
@@ -271,6 +271,7 @@
 			    struct pf_addr *);
 static int		 pf_check_proto_cksum(struct mbuf *, int, int,
 			    u_int8_t, sa_family_t);
+static struct pf_divert	*pf_get_divert(struct mbuf *);
 static void		 pf_print_state_parts(struct pf_state *,
 			    struct pf_state_key *, struct pf_state_key *);
 static int		 pf_addr_wrap_neq(struct pf_addr_wrap *,
@@ -5619,7 +5620,23 @@
 	return (0);
 }
 
+struct pf_divert *
+pf_get_divert(struct mbuf *m)
+{
+	struct m_tag	*mtag;
 
+	if ((mtag = m_tag_find(m, PACKET_TAG_PF_DIVERT, NULL)) == NULL) {
+		mtag = m_tag_get(PACKET_TAG_PF_DIVERT, sizeof(struct pf_divert),
+		    M_NOWAIT);
+		if (mtag == NULL)
+			return (NULL);
+		bzero(mtag + 1, sizeof(struct pf_divert));
+		m_tag_prepend(m, mtag);
+	}
+
+	return ((struct pf_divert *)(mtag + 1));
+}
+
 #ifdef INET
 int
 pf_test(int dir, struct ifnet *ifp, struct mbuf **m0, struct inpcb *inp)
@@ -5904,6 +5921,15 @@
 		}
 	}
 
+	if (action == PF_PASS && r->divert.port && dir == PF_IN /*&& r->direction == PF_OUT*/ ) {
+		struct pf_divert *divert;
+		if ((divert = pf_get_divert(m))) {
+			m->m_flags |= M_FASTFWD_OURS;
+			divert->port = r->divert.port;
+			divert->addr.ipv4 = r->divert.addr.v4;
+		}
+	}
+
 	if (log) {
 		struct pf_rule *lr;
 
@@ -6275,9 +6301,14 @@
 	    IN6_IS_ADDR_LOOPBACK(&pd.dst->v6))
 		m->m_flags |= M_SKIP_FIREWALL;
 
-	/* XXX: Anybody working on it?! */
-	if (r->divert.port)
-		printf("pf: divert(9) is not supported for IPv6\n");
+	if (action == PF_PASS && r->divert.port && dir == PF_IN /*&& r->direction == PF_OUT*/) {
+		struct pf_divert *divert;
+		if ((divert = pf_get_divert(m))) {
+			m->m_flags |= M_FASTFWD_OURS;
+			divert->port = r->divert.port;
+			divert->addr.ipv6 = r->divert.addr.v6;
+		}
+	}
 
 	if (log) {
 		struct pf_rule *lr;
Index: sys/sys/mbuf.h
===================================================================
--- sys/sys/mbuf.h	(revision 261814)
+++ sys/sys/mbuf.h	(working copy)
@@ -1023,6 +1023,7 @@
 #define	PACKET_TAG_DUMMYNET			15 /* dummynet info */
 #define	PACKET_TAG_DIVERT			17 /* divert info */
 #define	PACKET_TAG_IPFORWARD			18 /* ipforward info */
+#define	PACKET_TAG_PF_DIVERT			PACKET_TAG_IPFORWARD
 #define	PACKET_TAG_MACLABEL	(19 | MTAG_PERSISTENT) /* MAC label */
 #define	PACKET_TAG_PF		(21 | MTAG_PERSISTENT) /* PF/ALTQ information */
 #define	PACKET_TAG_RTSOCKFAM			25 /* rtsock sa family */


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list