kern/146190: [ipsec][patch] NAT traversal does not work in
transport mode
Denis Antrushin
DAntrushin at mail.ru
Fri Apr 30 16:40:02 UTC 2010
>Number: 146190
>Category: kern
>Synopsis: [ipsec][patch] NAT traversal does not work in transport mode
>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: Fri Apr 30 16:40:01 UTC 2010
>Closed-Date:
>Last-Modified:
>Originator: Denis Antrushin
>Release: 8.0-STABLE
>Organization:
>Environment:
FreeBSD XXX 8.0-STABLE FreeBSD 8.0-STABLE #3: Fri Apr 30 13:12:43 MSD 2010 adu at XXX:/usr/obj/usr/src/sys/ADU_NB amd64
>Description:
IPSEC NAT-Traversal does not work in transport mode.
It requires fixing of TCP/UDP checksums of packets protected by ESP
(or ignoring checksum mismatches), which is not implemented in kernel.
>How-To-Repeat:
Try to setup IPSEC transport mode connection between two hosts with NAT box
in the middle. Observe incoming packets dropped by kernel due to checksum
mismatch
>Fix:
There are two ways to handle this issue: recalculate TCP/UDP checksums
using NAT-OA information from IKE exchange or just ignore checksums of
packets protected by ESP.
The former case requires support from IKED daemon.
Attached prototype patch implements both cases.
I didn't tried isakmpd, but racoon currently does not send NAT-OAi/NAT-OAr
info to the kernel, so without racoon patching, only ignoring of checksum
mismatch is available (put under sysctl control in the patch)
Patch attached with submission follows:
--- esp_var.h.orig 2010-04-30 19:42:06.000000000 +0400
+++ esp_var.h 2010-04-30 12:12:23.000000000 +0400
@@ -76,5 +76,7 @@
#define V_esp_enable VNET(esp_enable)
VNET_DECLARE(struct espstat, espstat);
#define V_espstat VNET(espstat)
+VNET_DECLARE(int, esp_ignore_natt_cksum);
+#define V_esp_ignore_natt_cksum VNET(esp_ignore_natt_cksum)
#endif /* _KERNEL */
#endif /*_NETIPSEC_ESP_VAR_H_*/
--- ipsec.c.orig 2010-04-30 19:42:35.000000000 +0400
+++ ipsec.c 2010-04-30 13:11:12.000000000 +0400
@@ -592,7 +592,7 @@
IPSEC_ASSERT(m->m_pkthdr.len >= sizeof(struct ip),("packet too short"));
/* NB: ip_input() flips it into host endian. XXX Need more checking. */
- if (m->m_len < sizeof (struct ip)) {
+ if (m->m_len >= sizeof (struct ip)) {
struct ip *ip = mtod(m, struct ip *);
if (ip->ip_off & (IP_MF | IP_OFFMASK))
goto done;
--- ipsec_input.c.orig 2009-08-03 12:13:06.000000000 +0400
+++ ipsec_input.c 2010-04-30 12:23:24.000000000 +0400
@@ -76,6 +76,11 @@
#include <netinet/icmp6.h>
#endif
+#ifdef IPSEC_NAT_T
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#endif
+
#include <netipsec/ipsec.h>
#ifdef INET6
#include <netipsec/ipsec6.h>
@@ -347,6 +352,34 @@
}
prot = ip->ip_p;
+#ifdef IPSEC_NAT_T
+ if (saidx->mode == IPSEC_MODE_TRANSPORT && sproto == IPPROTO_ESP &&
+ sav->natt_cksum != 0) {
+ if (V_esp_ignore_natt_cksum != 0) {
+ /* Ignore checksum of packet protected by ESP. */
+ if (prot == IPPROTO_TCP || prot == IPPROTO_UDP) {
+ m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | CSUM_PSEUDO_HDR);
+ m->m_pkthdr.csum_data = 0xffff;
+
+ }
+ } else {
+ if (prot == IPPROTO_TCP || prot == IPPROTO_UDP) {
+ u_int16_t proto_cksum;
+ int off = sizeof(struct ip);
+ if (prot == IPPROTO_TCP) {
+ off += offsetof(struct tcphdr, th_sum);
+ } else if (prot == IPPROTO_UDP) {
+ off += offsetof(struct udphdr, uh_sum);
+ }
+ m_copydata(m, off, sizeof(u_int16_t), (caddr_t)&proto_cksum);
+ proto_cksum = in_addword(sav->natt_cksum, ~ntohs(proto_cksum));
+ proto_cksum = ~htons(proto_cksum);
+ m_copyback(m, off, sizeof(u_int16_t), (caddr_t)&proto_cksum);
+ }
+ }
+ }
+#endif
+
#ifdef notyet
/* IP-in-IP encapsulation */
if (prot == IPPROTO_IPIP) {
--- key.c.orig 2009-08-03 12:13:06.000000000 +0400
+++ key.c 2010-04-30 12:09:55.000000000 +0400
@@ -459,6 +459,8 @@
#ifdef IPSEC_NAT_T
static struct mbuf *key_setsadbxport(u_int16_t, u_int16_t);
static struct mbuf *key_setsadbxtype(u_int16_t);
+static u_int16_t key_compute_natt_cksum(struct sockaddr*,
+ struct sockaddr*, struct sockaddr*, struct sockaddr*);
#endif
static void key_porttosaddr(struct sockaddr *, u_int16_t);
#define KEY_PORTTOSADDR(saddr, port) \
@@ -3083,6 +3085,7 @@
/* Initialize even if NAT-T not compiled in: */
sav->natt_type = 0;
sav->natt_esp_frag_len = 0;
+ sav->natt_cksum = 0;
/* SA */
if (mhp->ext[SADB_EXT_SA] != NULL) {
@@ -3505,7 +3508,19 @@
break;
case SADB_X_EXT_NAT_T_OAI:
+ m = key_setsadbaddr(SADB_X_EXT_NAT_T_OAI,
+ &sav->natt_oa_src.sa,
+ FULLMASK, IPSEC_ULPROTO_ANY);
+ if (!m)
+ goto fail;
+ break;
case SADB_X_EXT_NAT_T_OAR:
+ m = key_setsadbaddr(SADB_X_EXT_NAT_T_OAR,
+ &sav->natt_oa_dst.sa,
+ FULLMASK, IPSEC_ULPROTO_ANY);
+ if (!m)
+ goto fail;
+ break;
case SADB_X_EXT_NAT_T_FRAG:
/* We do not (yet) support those. */
continue;
@@ -3786,6 +3801,56 @@
__func__, sa->sa_family));
return (0);
}
+
+/*
+ * Compute checksum delta to be applied to incoming TCP/UDP packet
+ * after packet has been decrypted
+ */
+static u_int16_t
+key_compute_natt_cksum(struct sockaddr *src, struct sockaddr *dst,
+ struct sockaddr *natt_src, struct sockaddr *natt_dst)
+{
+ u_int32_t total_sum = 0;
+ u_int32_t sum_old, sum_new;
+ if (natt_src && key_sockaddrcmp(src, natt_src, 0)) {
+ IPSEC_ASSERT(src->sa.sa_family == AF_INET, ("bad address family"));
+ sum_old = *(u_int32_t*)(&((struct sockaddr_in*)src)->sin_addr);
+ sum_old = ntohl(sum_old);
+ sum_old = (sum_old & 0xFFFF) + (sum_old >> 16);
+ sum_old = (sum_old & 0xFFFF) + (sum_old >> 16);
+
+ sum_new = *(u_int32_t*)(&((struct sockaddr_in*)natt_src)->sin_addr);
+ sum_new = ntohl(sum_new);
+ sum_new = (sum_new & 0xFFFF) + (sum_new >> 16);
+ sum_new = (sum_new & 0xFFFF) + (sum_new >> 16);
+
+ if (sum_new < sum_old)
+ sum_new--;
+
+ total_sum += sum_new - sum_old;
+ }
+ if (natt_dst && key_sockaddrcmp(dst, natt_dst, 0)) {
+ IPSEC_ASSERT(dst->sa.sa_family == AF_INET, ("bad address family"));
+ sum_old = *(u_int32_t*)(&((struct sockaddr_in*)natt_dst)->sin_addr);
+ sum_old = ntohl(sum_old);
+ sum_old = (sum_old & 0xFFFF) + (sum_old >> 16);
+ sum_old = (sum_old & 0xFFFF) + (sum_old >> 16);
+
+ sum_new = *(u_int32_t*)(&((struct sockaddr_in*)dst)->sin_addr);
+ sum_new = ntohl(sum_new);
+ sum_new = (sum_new & 0xFFFF) + (sum_new >> 16);
+ sum_new = (sum_new & 0xFFFF) + (sum_new >> 16);
+
+ if (sum_new < sum_old)
+ sum_new--;
+
+ total_sum += sum_new - sum_old;
+ }
+ total_sum = (total_sum & 0xFFFF) + (total_sum >> 16);
+ total_sum = (total_sum & 0xFFFF) + (total_sum >> 16);
+ return (u_int16_t)total_sum;
+}
+
#endif /* IPSEC_NAT_T */
/*
@@ -4656,7 +4721,7 @@
struct mbuf *m;
const struct sadb_msghdr *mhp;
{
- struct sadb_address *src0, *dst0;
+ struct sadb_address *src0, *dst0, *iaddr, *raddr;
struct secasindex saidx;
struct secashead *newsah;
struct secasvar *newsav;
@@ -4747,10 +4812,24 @@
* We made sure the port numbers are zero above, so we do
* not have to worry in case we do not update them.
*/
- if (mhp->ext[SADB_X_EXT_NAT_T_OAI] != NULL)
+ if (mhp->ext[SADB_X_EXT_NAT_T_OAI] != NULL) {
ipseclog((LOG_DEBUG, "%s: NAT-T OAi present\n", __func__));
- if (mhp->ext[SADB_X_EXT_NAT_T_OAR] != NULL)
+ if (mhp->extlen[SADB_X_EXT_NAT_T_OAI] < sizeof(struct sadb_address)) {
+ ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n",
+ __func__));
+ return key_senderror(so, m, EINVAL);
+ }
+ iaddr = (struct sadb_address *)(mhp->ext[SADB_X_EXT_NAT_T_OAI]);
+ }
+ if (mhp->ext[SADB_X_EXT_NAT_T_OAR] != NULL) {
ipseclog((LOG_DEBUG, "%s: NAT-T OAr present\n", __func__));
+ if (mhp->extlen[SADB_X_EXT_NAT_T_OAR] < sizeof(struct sadb_address)) {
+ ipseclog((LOG_DEBUG, "%s: invalid message is passed.\n",
+ __func__));
+ return key_senderror(so, m, EINVAL);
+ }
+ raddr = (struct sadb_address *)(mhp->ext[SADB_X_EXT_NAT_T_OAR]);
+ }
if (mhp->ext[SADB_X_EXT_NAT_T_TYPE] != NULL &&
mhp->ext[SADB_X_EXT_NAT_T_SPORT] != NULL &&
@@ -5081,6 +5160,11 @@
iaddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAI];
raddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAR];
ipseclog((LOG_DEBUG, "%s: NAT-T OAi/r present\n", __func__));
+ } else if (mhp->ext[SADB_X_EXT_NAT_T_OA] != NULL) {
+ iaddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OA];
+ raddr = NULL;
+ ipseclog((LOG_DEBUG, "%s: NAT-T OA present\n", __func__));
+
} else {
iaddr = raddr = NULL;
}
@@ -5177,6 +5261,16 @@
if (dport)
KEY_PORTTOSADDR(&sav->sah->saidx.dst,
dport->sadb_x_nat_t_port_port);
+ if (iaddr)
+ bcopy(iaddr + 1, &sav->natt_oa_src, ((const struct sockaddr *)(iaddr + 1))->sa_len);
+ if (raddr)
+ bcopy(raddr + 1, &sav->natt_oa_dst, ((const struct sockaddr *)(raddr + 1))->sa_len);
+ if (sav->sah->saidx.src.sa.sa_family == AF_INET) {
+ struct sockaddr *natt_src_sa = iaddr ? &sav->natt_oa_src.sa : NULL;
+ struct sockaddr *natt_dst_sa = raddr ? &sav->natt_oa_dst.sa : NULL;
+ sav->natt_cksum = key_compute_natt_cksum(&sav->sah->saidx.src.sa,
+ &sav->sah->saidx.dst.sa, natt_src_sa, natt_dst_sa);
+ }
#if 0
/*
@@ -5377,6 +5471,11 @@
iaddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAI];
raddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAR];
ipseclog((LOG_DEBUG, "%s: NAT-T OAi/r present\n", __func__));
+ } else if (mhp->ext[SADB_X_EXT_NAT_T_OA] != NULL) {
+ iaddr = (struct sadb_address *)mhp->ext[SADB_X_EXT_NAT_T_OAI];
+ raddr = NULL;
+ ipseclog((LOG_DEBUG, "%s: NAT-T OA present\n", __func__));
+
} else {
iaddr = raddr = NULL;
}
@@ -5436,6 +5535,16 @@
*/
if (type)
newsav->natt_type = type->sadb_x_nat_t_type_type;
+ if (iaddr)
+ bcopy(iaddr + 1, &newsav->natt_oa_src, ((const struct sockaddr *)(iaddr + 1))->sa_len);
+ if (raddr)
+ bcopy(raddr + 1, &newsav->natt_oa_dst, ((const struct sockaddr *)(raddr + 1))->sa_len);
+ if (newsav->sah->saidx.src.sa.sa_family == AF_INET) {
+ struct sockaddr *natt_src_sa = iaddr ? &newsav->natt_oa_src.sa : NULL;
+ struct sockaddr *natt_dst_sa = raddr ? &newsav->natt_oa_dst.sa : NULL;
+ newsav->natt_cksum = key_compute_natt_cksum(&newsav->sah->saidx.src.sa,
+ &newsav->sah->saidx.dst.sa, natt_src_sa, natt_dst_sa);
+ }
#if 0
/*
--- keydb.h.orig 2009-08-03 12:13:06.000000000 +0400
+++ keydb.h 2010-04-30 12:09:55.000000000 +0400
@@ -157,6 +157,9 @@
*/
u_int16_t natt_type; /* IKE/ESP-marker in output. */
u_int16_t natt_esp_frag_len; /* MTU for payload fragmentation. */
+ union sockaddr_union natt_oa_src; /* NATT source address */
+ union sockaddr_union natt_oa_dst; /* NATT destination address */
+ u_int16_t natt_cksum; /* checksum delta for inbound packets */
};
#define SECASVAR_LOCK_INIT(_sav) \
--- xform_esp.c.orig 2010-04-30 19:43:50.000000000 +0400
+++ xform_esp.c 2010-04-30 12:19:36.000000000 +0400
@@ -78,12 +78,16 @@
VNET_DEFINE(int, esp_enable) = 1;
VNET_DEFINE(struct espstat, espstat);
+VNET_DEFINE(int, esp_ignore_natt_cksum) = 0;
SYSCTL_DECL(_net_inet_esp);
SYSCTL_VNET_INT(_net_inet_esp, OID_AUTO,
esp_enable, CTLFLAG_RW, &VNET_NAME(esp_enable), 0, "");
SYSCTL_VNET_STRUCT(_net_inet_esp, IPSECCTL_STATS,
stats, CTLFLAG_RD, &VNET_NAME(espstat), espstat, "");
+SYSCTL_VNET_INT(_net_inet_esp, OID_AUTO,
+ esp_ignore_natt_cksum, CTLFLAG_RW, &VNET_NAME(esp_ignore_natt_cksum), 0,
+ "Do not validate checksums of ESP protected packets in case of NAT-T");
/* max iv length over all algorithms */
static VNET_DEFINE(int, esp_max_ivlen) = 0;
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list