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