>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     
a...@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:
_______________________________________________
[email protected] mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-bugs
To unsubscribe, send any mail to "[email protected]"

Reply via email to