NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/50198: SIOCGNATL broken in IPFilter 5



>Number:         50198
>Category:       kern
>Synopsis:       SIOCGNATL broken in IPFilter 5
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu Sep 03 17:15:00 +0000 2015
>Originator:     Patrick Welche
>Release:        NetBSD 7.0_RC3
>Organization:
>Environment:
Tested 5.2_STABLE/i386 vs 7.0_RC3/i386 (and -current/i386)
>Description:
ipnat SIOCGNATL from IPFilter v5 doesn't look up redirected destination
IP addresses correctly, which breaks transparent proxies such as squid.

IPFilter 4 in NetBSD 5 returns:
           real: 192.168.204.87:80
IPFilter 5 in NetBSD 7 returns:
           real: 127.0.0.1:1234
>How-To-Repeat:
Set up a trivial redirect rule:

# cat /etc/ipnat.conf
rdr xennet0 0/0 port 80 -> 127.0.0.1 port 1234 tcp

and run the following server, which listens on port 1234, and does a
SIOCGNATL call:

================================================================================
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ipl.h>
#include <netinet/ip_fil.h>
#include <netinet/ip_nat.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define MYADDR "127.0.0.1"
#define MYPORT 1234

int main()
{
	int i, sock, con, natfd;
	struct sockaddr_storage connstore;
	struct sockaddr_in lo, *conn;
	socklen_t connlen;
	ipfobj_t obj;
	natlookup_t nat;

	connlen   = sizeof(connstore);
	conn   = (struct sockaddr_in *)&connstore;

	natfd = open("/dev/ipnat", O_RDONLY);
	if (natfd == -1)
		err(1, "/dev/ipnat");

	if (inet_aton(MYADDR, &lo.sin_addr) == 0)
		errx(1,"inet_aton received invalid string");

	lo.sin_len    = sizeof(lo);
	lo.sin_family = AF_INET;
	lo.sin_port   = htons(MYPORT);
	memset(lo.sin_zero, 0, sizeof(lo.sin_zero));

	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (bind(sock, (struct sockaddr *)&lo, sizeof(lo)) == -1)
		err(1, "bind");
	if (listen(sock, 10) == -1)
		err(1, "listen");

	printf("IPFilter version %u\n", IPFILTER_VERSION);
	printf("listening on   : %s:%d\n", MYADDR, MYPORT);
	for (i = 0; i < 1; ++i) {
		con = accept(sock, (struct sockaddr *)conn, &connlen);
		if (con == -1)
			err(1, "accept");
		printf("connection from: %s:%hu\n", inet_ntoa(conn->sin_addr),
			ntohs(conn->sin_port));

		memset(&nat, 0, sizeof(nat));
		nat.nl_outip   = conn->sin_addr;
		nat.nl_outport = conn->sin_port;
		nat.nl_inip    = lo.sin_addr;
		nat.nl_inport  = lo.sin_port;
		nat.nl_flags   = IPN_TCP;
#if IPFILTER_VERSION >= 5000003
		nat.nl_v       = 4;
#endif

		memset(&obj, 0, sizeof(obj));
		obj.ipfo_rev = IPFILTER_VERSION;
		obj.ipfo_size = sizeof(nat);
		obj.ipfo_ptr = &nat;
		obj.ipfo_type = IPFOBJ_NATLOOKUP;

		if (ioctl(natfd, SIOCGNATL, &obj) == -1)
			err(1, "SIOCGNATL");

		printf("           real: %s:%d\n", inet_ntoa(nat.nl_realip),
			ntohs(nat.nl_realport));

		close(con);
	}

	if (close(natfd) == -1)
		err(1, "close nat");
	if (close(sock) == -1)
		err(1, "close server");
	
	return 0;
}
================================================================================

Add a few debug printfs in ip_nat.c:

================================================================================
NetBSD 5 patch:
diff --git a/sys/dist/ipf/netinet/ip_nat.c b/sys/dist/ipf/netinet/ip_nat.c
index 9b30e07..5c5f2a8 100644
--- a/sys/dist/ipf/netinet/ip_nat.c
+++ b/sys/dist/ipf/netinet/ip_nat.c
@@ -3599,6 +3599,15 @@ natlookup_t *np;
 					np->nl_realip, np->nl_outip))) {
 			np->nl_inip = nat->nat_inip;
 			np->nl_inport = nat->nat_inport;
+printf("%s IPN_IN:\n", __func__);
+printf("  inip: %s:%d\n",
+	inet_ntoa(nat->nat_inip), ntohs(nat->nat_inport));
+printf(" outip: %s:%d\n",
+	inet_ntoa(nat->nat_outip), ntohs(nat->nat_outport));
+printf("   oip: %s:%d\n",
+	inet_ntoa(nat->nat_oip), ntohs(nat->nat_oport));
+printf("==> in: %s:%d\n",
+	inet_ntoa(np->nl_inip), ntohs(np->nl_inport));
 		}
 	} else {
 		/*
@@ -3623,6 +3632,15 @@ natlookup_t *np;
 
 			np->nl_realip = nat->nat_outip;
 			np->nl_realport = nat->nat_outport;
+printf("%s !IPN_IN:\n", __func__);
+printf("  inip: %s:%d\n",
+	inet_ntoa(nat->nat_inip), ntohs(nat->nat_inport));
+printf(" outip: %s:%d\n",
+	inet_ntoa(nat->nat_outip), ntohs(nat->nat_outport));
+printf("   oip: %s:%d\n",
+	inet_ntoa(nat->nat_oip), ntohs(nat->nat_oport));
+printf("> real: %s:%d\n",
+	inet_ntoa(np->nl_realip), ntohs(np->nl_realport));
 		}
  	}
 
================================================================================
NetBSD 7 patch:
diff --git a/sys/external/bsd/ipf/netinet/ip_nat.c b/sys/external/bsd/ipf/netinet/ip_nat.c
index 0fa9033..b202a8e 100644
--- a/sys/external/bsd/ipf/netinet/ip_nat.c
+++ b/sys/external/bsd/ipf/netinet/ip_nat.c
@@ -4605,6 +4606,17 @@ ipf_nat_lookupredir(ipf_main_softc_t *softc, natlookup_t *np)
 					    np->nl_realip, np->nl_outip))) {
 			np->nl_inip = nat->nat_odstip;
 			np->nl_inport = nat->nat_odport;
+printf("%s IPN_IN:\n", __func__);
+printf("     osrc: %s:%d",
+	intoa(nat->nat_osrcaddr), ntohs(nat->nat_osport));
+printf(" odst: %s:%d\n",
+	intoa(nat->nat_odstaddr), ntohs(nat->nat_odport));
+printf("     nsrc: %s:%d",
+	intoa(nat->nat_nsrcaddr), ntohs(nat->nat_nsport));
+printf(" ndst: %s:%d\n",
+	intoa(nat->nat_ndstaddr), ntohs(nat->nat_ndport));
+printf("===> in  : %s:%d\n",
+	intoa(np->nl_inip.s_addr), ntohs(np->nl_inport));
 		}
 	} else {
 		/*
@@ -4627,8 +4639,25 @@ ipf_nat_lookupredir(ipf_main_softc_t *softc, natlookup_t *np)
 				}
 			}
 
+#define XXXPWHACK 0
+#if XXXPWHACK
+			np->nl_realip = nat->nat_odstip;
+			np->nl_realport = nat->nat_odport;
+#else
 			np->nl_realip = nat->nat_ndstip;
 			np->nl_realport = nat->nat_ndport;
+#endif
+printf("%s !IPN_IN:\n", __func__);
+printf("     osrc: %s:%d",
+	intoa(nat->nat_osrcaddr), ntohs(nat->nat_osport));
+printf(" odst: %s:%d\n",
+	intoa(nat->nat_odstaddr), ntohs(nat->nat_odport));
+printf("     nsrc: %s:%d",
+	intoa(nat->nat_nsrcaddr), ntohs(nat->nat_nsport));
+printf(" ndst: %s:%d\n",
+	intoa(nat->nat_ndstaddr), ntohs(nat->nat_ndport));
+printf("===> real: %s:%d\n",
+	intoa(np->nl_realip.s_addr), ntohs(np->nl_realport));
 		}
  	}
 
================================================================================

Now telnet in to port 80 from another computer (192.168.204.1), and
observe the different results (running on console, hence kernel printfs
are interleaved):

================================================================================
netbsd5# uname -rm
5.2_STABLE i386
netbsd5# ./ipfbug
IPFilter version 4012900
listening on   : 127.0.0.1:1234
connection from: 192.168.204.1:65392
nat_lookupredir !IPN_IN:
  inip: 127.0.0.1:1234
 outip: 192.168.204.87:80
   oip: 192.168.204.1:65392
> real: 192.168.204.87:80
           real: 192.168.204.87:80
netbsd5# ipnat -l
List of active MAP/Redirect filters:
rdr xennet0 0.0.0.0/0 port 80 -> 127.0.0.1 port 1234 tcp

List of active sessions:
RDR 127.0.0.1       1234  <- -> 192.168.204.87  80    [192.168.204.1 65392]
================================================================================
netbsd7# uname -rm
7.0_RC3 i386
netbsd7# ./ipfbug
IPFilter version 5010200
listening on   : 127.0.0.1:1234
connection from: 192.168.204.1:65386
ipf_nat_lookupredir !IPN_IN:
     osrc: 192.168.204.1:65386 odst: 192.168.204.86:80
     nsrc: 192.168.204.1:65386 ndst: 127.0.0.1:1234
===> real: 127.0.0.1:1234
           real: 127.0.0.1:1234
netbsd7# ipnat -l
List of active MAP/Redirect filters:
rdr xennet0 0/0 port 80 -> 127.0.0.1/32 port 1234 tcp

List of active sessions:
RDR 127.0.0.1       1234  <- -> 192.168.204.86  80    [192.168.204.1 65386]
================================================================================
>Fix:
I think ipf_nat_lookupredir() is simply returning the wrong variables.
Flipping XXXPWHACK to 1 should give the correct answer.
Something similar will be necessary in the untested IPN_IN case.


There is also an upgrade issue: IPFilter 5 grew a nl_v member, hence the
necessity of

#if IPFILTER_VERSION >= 5000003
                nat.nl_v       = 4;
#endif

in my toy server above. This means that any transparent proxy servers
written against IPFilter 4 will stop working on an upgrade to IPFilter 5.

It might be worth adding a "case 0:" to print a warning and fall through
to "case 4:" in ip_nat.c "switch (nl.nl_v)", rather than assume IPv0.



Home | Main Index | Thread Index | Old Index