tech-net archive

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

RFC 3948 patch for IPsec Nat Traversal transport mode



I am posting this patch here, a continuation of a discussion that began on netbsd-users at

http://mail-index.netbsd.org/netbsd-users/2017/09/22/msg020168.html

The patch I have been using to support Nat traversal in transport mode is attached to this message.

This patch is for the netbsd-7 or netbsd-7-1 branch. It does not work on the netbsd-7-0 branch because of some changes to sys/netipsec/key.c between 7.0 and 7.1, but a patch for the 7.0 branch can be easily derived from this patch. A similar patch also can be applied to netbsd-6 and I have tested that and it works also. I haven't tried applying this type of patch to netbsd-8 or current.

This patch is applicable to L2TP/IPsec VPNs that traverse NAT devices because that type of VPN uses the IPsec transport mode. Please note this patch is a work in progress and is only tested on my very limited scenario which is most of the time a NetBSD box behind a NAT that is acting as the IKE responder using racoon to windows 7 or windows 10 clients that may or may not be behind a NAT and my comments in the patch about who is the initiator and responder apply to that case. I have also tested an earlier version of this patch for the case of NetBSD being the IKE initiator and in that case the patch successfully connected to the NetBSD responder with  the same patch, and I think this patch would also work when NetBSD is the IKE initiator connecting to a responder behind a NAT.

The patch implements the checksum correction using NAT original address (NAT-OA) information that is obtained during the IKE exchange with the peer, as described in RFC 3948 for the transport mode case:

https://tools.ietf.org/html/rfc3948

Most of the work is in netinet/udp_usrreq.c and netipsec/key.c. Some technical notes on the patch based on  what I discovered when debugging and testing:

1. The last hunk of the patch of udp_usrreq.c is not really part of the solution for implementing RFC 3948 but fixes another problem I found which was that the udp encapsulated esp traffic was being dropped without the changes applied by that last hunk of the patch of udp_usrreq.c.

2. The actual nat correction is made by the code added to netinet/udp_usrreq.c, and the rest of the patch is needed so the NAT-OA data is accessible in udp_usrreq.c where the NAT-OA information is needed to correct the udp checksum. The strategy was to store the NAT-OA information in the security association database (SAD) at the time the kernel either adds or updates the SAD with a new phase 2 security association, and that work is accomplished mostly in netipsec/key.c. The NAT-OA addresses are added to a packet tag in netipsec/ipsec_input.c and that makes the NAT-OA addresses accessible to udp_usrreq.c.

3. I tested this patch with two L2TP/IPsec connections from different IP addresses and that worked fine, but it did not work for two connections from the same IP address but different source ports (2 clients from behind the same NAT). The latest change to the patch of key.c attempted to add the ability to differentiate between connections where only the source port is different, but in that case the second connection from the same IP, even though it was using a different source port, caused the system to delete one of the security associations of the first connection, and from that point on neither connection was usable.

Chuck
--- sys/mbuf.h	2017-04-05 15:54:23.000000000 -0400
+++ sys/mbuf.h	2017-04-30 19:44:41.070378412 -0400
@@ -908,6 +908,7 @@
 						    * protocol callback, for
 						    * loop detection/recovery
 						    */
+#define	PACKET_TAG_IPSEC_NAT_T_OA		29 /* two uint32_t */
 
 /*
  * Return the number of bytes in the mbuf chain, m.
--- netinet/udp_usrreq.c	2014-08-09 01:33:01.000000000 -0400
+++ netinet/udp_usrreq.c	2017-08-23 23:40:47.571414000 -0400
@@ -142,6 +142,8 @@
 
 #ifdef INET
 #ifdef IPSEC
+static u_int16_t nat_checksum(u_int32_t iaddr, u_int32_t raddr, u_int32_t natt_oa_i, u_int32_t natt_oa_r);
+extern int key_spiexists (u_int32_t spi); /* Defined in netipsec/key.c */
 static int udp4_espinudp (struct mbuf **, int, struct sockaddr *,
 	struct socket *);
 #endif
@@ -261,6 +263,10 @@
  * Checksum extended UDP header and data.
  */
 
+#ifdef IPSEC
+/* Use a counter for bad checksums */
+int counter_bad_cksum = 0;
+#endif
 static int
 udp4_input_checksum(struct mbuf *m, const struct udphdr *uh,
     int iphlen, int len)
@@ -273,7 +279,10 @@
 
 	if (uh->uh_sum == 0)
 		return 0;
-
+#ifdef IPSEC
+	u_int16_t nat_correction = 0;
+	int swsum = 0;
+#endif
 	switch (m->m_pkthdr.csum_flags &
 	    ((m->m_pkthdr.rcvif->if_csum_flags_rx & M_CSUM_UDPv4) |
 	    M_CSUM_TCP_UDP_BAD | M_CSUM_DATA)) {
@@ -312,7 +321,12 @@
 				     IFF_LOOPBACK) ||
 				   udp_do_loopback_cksum)) {
 			UDP_CSUM_COUNTER_INCR(&udp_swcsum);
+#ifdef IPSEC
+			swsum = in4_cksum(m, IPPROTO_UDP, iphlen, len);
+			if (swsum != 0)
+#else
 			if (in4_cksum(m, IPPROTO_UDP, iphlen, len) != 0)
+#endif
 				goto badcsum;
 		}
 		break;
@@ -321,10 +335,77 @@
 	return 0;
 
 badcsum:
+#ifdef IPSEC
+	if (swsum == 0) /* software checksum has not been computed yet */
+		swsum = in4_cksum(m, IPPROTO_UDP, iphlen, len);
+	const struct ip *ip =
+		mtod(m, const struct ip *);
+	u_int32_t iaddr = ip->ip_src.s_addr;
+	u_int32_t raddr = ip->ip_dst.s_addr;
+	iaddr = ~iaddr;
+	raddr = ~raddr;
+	u_int32_t natt_oa_i = 0;
+	u_int32_t natt_oa_r = 0;
+	struct m_tag * tag = NULL;
+	if ((tag = m_tag_find(m, PACKET_TAG_IPSEC_NAT_T_OA, NULL))) {
+		natt_oa_i = ((u_int32_t *)(tag + 1))[0];
+		natt_oa_r = ((u_int32_t *)(tag + 1))[1];
+		m_tag_delete(m, tag);
+		nat_correction = nat_checksum(iaddr, raddr, natt_oa_i, natt_oa_r);
+	}
+/*	if (counter_bad_cksum == 0) {
+		static char buf[sizeof("123.456.789.123")];
+		snprintf(buf, sizeof(buf), "%d.%d.%d.%d",
+			(ntohl(natt_oa_r) >> 24) & 0xFF,
+			(ntohl(natt_oa_r) >> 16) & 0xFF,
+			(ntohl(natt_oa_r) >>  8) & 0xFF,
+			(ntohl(natt_oa_r) >>  0) & 0xFF);
+		printf("NAT-OAi-responder address: %s\n", buf);
+		snprintf(buf, sizeof(buf), "%d.%d.%d.%d",
+			(ntohl(natt_oa_i) >> 24) & 0xFF,
+			(ntohl(natt_oa_i) >> 16) & 0xFF,
+			(ntohl(natt_oa_i) >>  8) & 0xFF,
+			(ntohl(natt_oa_i) >>  0) & 0xFF);
+		printf("NAT-OAi-initiator address: %s\n", buf);
+		printf("nat_checksum: %x\n",
+			nat_checksum(iaddr, raddr, natt_oa_i, natt_oa_r));
+		printf("in4_cksum (should be equal to nat_checksum): %x\n",
+			in4_cksum(m, IPPROTO_UDP, iphlen, len));
+	} */
+	counter_bad_cksum++;
+	if (swsum == nat_correction)
+		return 0;
+#endif
 	UDP_STATINC(UDP_STAT_BADSUM);
 	return -1;
 }
 
+#ifdef IPSEC
+static u_int16_t 
+nat_checksum(u_int32_t iaddr, u_int32_t raddr, u_int32_t natt_oa_i, u_int32_t natt_oa_r)
+{
+	u_int32_t sum = 0;
+	u_int16_t checksum = 0;
+	u_int16_t *p_iaddr = (u_int16_t *) &iaddr;
+	u_int16_t *p_raddr = (u_int16_t *) &raddr;
+	u_int16_t *p_natt_oa_i = (u_int16_t *) &natt_oa_i;
+	u_int16_t *p_natt_oa_r = (u_int16_t *) &natt_oa_r;
+	sum += p_iaddr[0];
+	sum += p_iaddr[1];
+	sum += p_raddr[0];
+	sum += p_raddr[1];
+	sum += p_natt_oa_i[0];
+	sum += p_natt_oa_i[1];
+	sum += p_natt_oa_r[0];
+	sum += p_natt_oa_r[1];
+	while (sum > 0xffff) {
+		sum -= 0xffff;
+	}
+	checksum = sum;
+	return checksum;
+}
+#endif
+
 void
 udp_input(struct mbuf *m, ...)
 {
@@ -1324,10 +1405,13 @@
 		u_int32_t *st = (u_int32_t *)data;
 
 		if ((len <= sizeof(u_int64_t) + sizeof(struct esp))
-		    || ((st[0] | st[1]) != 0))
-			return 0; /* Normal UDP processing */
-
-		skip = sizeof(struct udphdr) + sizeof(u_int64_t);
+		    || ((st[0] | st[1]) != 0)) {
+			/* From rfc 2406, 4303 for esp, st[0] is the spi */
+			/* From rfc 2406, 4303 for esp, st[1] is the sequence number */
+			if (!key_spiexists(st[0])) /* This is not an espinudp packet */
+				return 0; /* Normal UDP processing */
+		}
+		skip = sizeof(struct udphdr);
 	}
 
 	/*
--- netipsec/keydb.h	2013-06-04 18:47:37.000000000 -0400
+++ netipsec/keydb.h	2017-04-30 20:11:41.177685127 -0400
@@ -130,6 +130,8 @@
 
 	u_int16_t natt_type;
 	u_int16_t esp_frag;
+	u_int32_t natt_oai_r;	/* Initiator's (peer's) address for responder (me) */
+	u_int32_t natt_oai_i;	/* Initiator's (peer's) address for initiator (peer) */
 };
 
 /* replay prevention */
--- netipsec/key.c	2016-03-13 07:59:22.000000000 -0400
+++ netipsec/key.c	2017-08-29 15:39:15.563808000 -0400
@@ -465,6 +465,8 @@
 	const struct sadb_msghdr *);
 static u_int32_t key_do_getnewspi (const struct sadb_spirange *,
 					const struct secasindex *);
+static struct secasvar *key_getsavbysrc (in_addr_t addr, u_int16_t port);
+int key_spiexists (u_int32_t spi);
 static int key_handle_natt_info (struct secasvar *,
 				     const struct sadb_msghdr *);
 static int key_set_natt_ports (union sockaddr_union *,
@@ -3212,6 +3214,8 @@
 	sav->tdb_compalgxform = NULL;	/* compression algorithm */
 	sav->natt_type = 0;
 	sav->esp_frag = 0;
+	sav->natt_oai_r = 0;
+	sav->natt_oai_i = 0;
 
 	/* SA */
 	if (mhp->ext[SADB_EXT_SA] != NULL) {
@@ -5141,6 +5145,49 @@
 	return newspi;
 }
 
+/* This function searches the database for a sav associated with an sah whose
+ * src address is equal to the internet address given in the argument. It
+ * searches the mature sa entries first, then the dying entries. If found,
+ * returns a secasvar *, otherwise NULL
+*/
+static struct secasvar *
+key_getsavbysrc(in_addr_t addr, u_int16_t port)
+{
+	struct secashead *sah;
+	struct secasvar *sav;
+	LIST_FOREACH(sah, &sahtree, chain) {
+		if ((sah->saidx.src.sin.sin_addr.s_addr != addr) || /* Select SA with src address = addr */
+		    (sah->saidx.src.sin.sin_port != port)) /* Select SA with src port = port */
+			continue;
+		LIST_FOREACH(sav, &sah->savtree[SADB_SASTATE_MATURE], chain) {
+			return sav;
+		}
+		LIST_FOREACH(sav, &sah->savtree[SADB_SASTATE_DYING], chain) {
+			return sav;
+		}
+	}
+	return NULL;
+}
+
+int key_spiexists(u_int32_t spi)
+{
+	struct secashead *sah;
+	struct secasvar *sav;
+	LIST_FOREACH(sah, &sahtree, chain) {
+		if (!key_ismyaddr((struct sockaddr *)&sah->saidx.dst)) /* select incoming SA */
+			continue;
+		LIST_FOREACH(sav, &sah->savtree[SADB_SASTATE_MATURE], chain) {
+			if (sav->spi == spi)
+				return 1;
+		}
+		LIST_FOREACH(sav, &sah->savtree[SADB_SASTATE_DYING], chain) {
+			if (sav->spi == spi)
+				return 1;
+		}
+	}
+	return 0;
+}
+
 static int
 key_handle_natt_info(struct secasvar *sav,
       		     const struct sadb_msghdr *mhp)
@@ -5198,6 +5245,7 @@
 	sport = (struct sadb_x_nat_t_port *)mhp->ext[SADB_X_EXT_NAT_T_SPORT];
 	dport = (struct sadb_x_nat_t_port *)mhp->ext[SADB_X_EXT_NAT_T_DPORT];
 	iaddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAI];
+	u_int32_t *natt_oa_addr = (u_int32_t *)iaddr;
 	raddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAR];
 	frag = (struct sadb_x_nat_t_frag *)mhp->ext[SADB_X_EXT_NAT_T_FRAG];
 
@@ -5215,6 +5263,34 @@
 		sav->esp_frag = frag->sadb_x_nat_t_frag_fraglen;
 	else
 		sav->esp_frag = IP_MAXPACKET;
+	if (iaddr) {
+		struct secashead *sah;
+		if((sah = sav->sah) != NULL) {
+			if (key_ismyaddr((struct sockaddr *)&sah->saidx.dst)) {
+				/* This is an incoming SA, and the NAT address should be my public IP address */
+				sav->natt_oai_r = natt_oa_addr[3];
+			}
+			else {
+				/* This is an outgoing SA, and the NAT address should be the peer's OA address
+				* I don't want to save the OA information in this sav, but in the sav associated
+				* with the corresponding input SA */
+				/* Unkown peer public IP address */
+				in_addr_t dst = sah->saidx.dst.sin.sin_addr.s_addr;
+				u_int16_t dport_nat_t = sah->saidx.dst.sin.sin_port;
+				struct secasvar *sav2 = key_getsavbysrc(dst, dport_nat_t);
+				if (sav2 != NULL) {
+					sav2->natt_oai_i = natt_oa_addr[3];
+					printf("key.c: key_getsavbysrc succeeded. Port = %d\n",
+						ntohs(dport_nat_t));
+				}
+				else {
+					printf("key.c: key_getsavbysrc failed. Port = %d\n",
+						ntohs(dport_nat_t));
+					return -1;
+				}
+			}
+		}
+	}
 
 	return 0;
 bad:
--- netipsec/ipsec_input.c	2014-03-08 07:18:04.000000000 -0500
+++ netipsec/ipsec_input.c	2017-04-30 21:25:32.434685004 -0400
@@ -132,6 +132,7 @@
 	u_int16_t sport;
 	u_int16_t dport;
 	int s, error;
+	struct m_tag * tag2 = NULL; /* Added for NAT-T-OA data */
 
 	IPSEC_ISTAT(sproto, ESP_STAT_INPUT, AH_STAT_INPUT,
 		IPCOMP_STAT_INPUT);
@@ -240,6 +241,23 @@
 	}
 
 	/*
+	* Attach a tag to the mbuf with the NAT-OA information, so it can
+	* used to fix the udp checksum in udp_usrreq.c
+	*/
+	if ((sav->natt_oai_i | sav->natt_oai_r) != 0) {
+		if ((tag2 = m_tag_get(PACKET_TAG_IPSEC_NAT_T_OA,
+		    sizeof(sav->natt_oai_i) + sizeof(sav->natt_oai_r), M_DONTWAIT)) == NULL) {
+			printf("ipsec_common_input: m_tag_get failed\n");
+			KEY_FREESAV(&sav);
+			splx(s);
+			return ENOMEM;
+		}
+		((u_int32_t *)(tag2 + 1))[0] = sav->natt_oai_i;
+		((u_int32_t *)(tag2 + 1))[1] = sav->natt_oai_r;
+		m_tag_prepend(m, tag2);
+	}
+
+	/*
 	 * Call appropriate transform and return -- callback takes care of
 	 * everything else.
 	 */


Home | Main Index | Thread Index | Old Index