IP masquerading: ICMP fixes, checksums etc

Nigel Metheringham (Nigel.Metheringham@theplanet.net)
Thu, 16 May 1996 14:15:18 +0100


This is a multipart MIME message.

--===_0_Thu_May_16_14:14:13_BST_1996
Content-Type: text/plain; charset=us-ascii

Alan,

I enclose 2 patches here for your delectation - they are against the
2.0.4 kernel, but with the mods by Michael Riepe (posted to
Linux-kernel).

The first patch handles demasquerading of ICMP error messages
relating to masqueraded connections. This gets MTU discovery
working, and should help people a lot. I have been working hard at
breaking things by putting a fragmenting gateway with very low MTU in
the way, and so far have failed to do so with this configuration -
fragment assembly all seems great!
I considered handling ICMP messages in the forward direction, but
decided that it didn't appear worth the work - in general I am
assuming that the network on the inside of a masquerade is a much
simpler place without all the oddities of routing on the internet.

The second patch, for demasquerading only, performs checking of the
TCP/UDP/ICMP checksums before mangling within the demasquerade. It
seemed to me to be very bad practice to modify the TCP/UDP headers
and then force a valid checksum on them, when the original packet
could have been bad. If this happened then we would be validating
invalid data. I have tried to place this so that checksumming is
performed late, so that double checksumming of normal incoming
packets (ie not masqueraded) is avoided. The equivalent work for the
forward masquerade should be performed - but I gave this a miss since
I am not sure how fragmentation interworks with this code.

Apologies for the earlier version of the ICMP patch that escaped - I
replied to all, rather than just to Alan.

Nigel.

--===_0_Thu_May_16_14:14:13_BST_1996
Content-Type: application/x-patch
Content-Description: masq_icmp.patch

Index: linux/include/net/ip_masq.h
diff -c linux/include/net/ip_masq.h:1.1.1.1 linux/include/net/ip_masq.h:1.2
*** linux/include/net/ip_masq.h:1.1.1.1 Wed May 15 10:20:11 1996
--- linux/include/net/ip_masq.h Wed May 15 14:41:44 1996
***************
*** 149,154 ****
--- 149,155 ----
* service routine(s).
*/
extern struct ip_masq * ip_masq_out_get_2(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port);
+ extern struct ip_masq * ip_masq_in_get_2(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port);

/*
* /proc/net entry
Index: linux/net/ipv4/ip_masq.c
diff -c linux/net/ipv4/ip_masq.c:1.1.1.1 linux/net/ipv4/ip_masq.c:1.12
*** linux/net/ipv4/ip_masq.c:1.1.1.1 Wed May 15 10:20:29 1996
--- linux/net/ipv4/ip_masq.c Thu May 16 11:05:13 1996
***************
*** 12,17 ****
--- 12,18 ----
* Juan Jose Ciarlante : Added hashed lookup by proto,maddr,mport and proto,saddr,sport
* Juan Jose Ciarlante : Fixed deadlock if free ports get exhausted
* Juan Jose Ciarlante : Added NO_ADDR status flag.
+ * Nigel Metheringham : Added ICMP handling for demasquerade
*
*
*/
***************
*** 27,34 ****
--- 28,37 ----
#include <linux/in.h>
#include <linux/ip.h>
#include <net/protocol.h>
+ #include <net/icmp.h>
#include <net/tcp.h>
#include <net/udp.h>
+ #include <net/checksum.h>
#include <net/ip_masq.h>

#define IP_MASQ_TAB_SIZE 256 /* must be power of 2 */
***************
*** 192,199 ****
struct ip_masq *
ip_masq_in_get(struct iphdr *iph)
{
- unsigned hash;
- struct ip_masq *ms;
__u16 *portptr;
int protocol;
__u32 s_addr, d_addr;
--- 195,200 ----
***************
*** 206,211 ****
--- 207,235 ----
d_addr = iph->daddr;
d_port = portptr[1];

+ return ip_masq_in_get_2(protocol, s_addr, s_port, d_addr, d_port);
+ }
+
+ /*
+ * Returns ip_masq associated with supplied parameters, either
+ * broken out of the ip/tcp headers or directly supplied for those
+ * pathological protocols with address/port in the data stream
+ * (ftp, irc). addresses and ports are in network order.
+ * called for pkts coming from INside-to-outside the firewall.
+ *
+ * NB. Cannot check destination address, just for the incoming port.
+ * reason: archie.doc.ac.uk has 6 interfaces, you send to
+ * phoenix and get a reply from any other interface(==dst)!
+ *
+ * [Only for UDP] - AC
+ */
+
+ struct ip_masq *
+ ip_masq_in_get_2(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port)
+ {
+ unsigned hash;
+ struct ip_masq *ms;
+
hash = ip_masq_hash_key(protocol, d_addr, d_port);
for(ms = ip_masq_m_tab[hash]; ms ; ms = ms->m_link) {
if ( protocol==ms->protocol &&
***************
*** 293,299 ****
#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Masqueraded %s %lX:%X expired\n",
masq_proto_name(ms->protocol),
! ntohl(ms->src),ntohs(ms->sport));
#endif

save_flags(flags);
--- 317,323 ----
#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Masqueraded %s %lX:%X expired\n",
masq_proto_name(ms->protocol),
! ntohl(ms->saddr),ntohs(ms->sport));
#endif

save_flags(flags);
***************
*** 537,542 ****
--- 561,648 ----
#endif
}

+ /*
+ * Handle ICMP messages.
+ * Find any that might be relevant, check against existing connections,
+ * forward to masqueraded host if relevant.
+ * Currently handles error types - unreachable, quench, ttl exceeded
+ */
+
+ int ip_fw_demasq_icmp(struct sk_buff **skb_p, struct device *dev)
+ {
+ struct sk_buff *skb = *skb_p;
+ struct iphdr *iph = skb->h.iph;
+ struct icmphdr *icmph = (struct icmphdr *)((char *)iph + (iph->ihl<<2));
+ struct iphdr *ciph; /* The ip header contained within the ICMP */
+ __u16 *pptr; /* port numbers from TCP/UDP contained header */
+ struct ip_masq *ms;
+
+ #ifdef DEBUG_CONFIG_IP_MASQUERADE
+ printk("Incoming ICMP (%d) %lX -> %lX\n",
+ icmph->type,
+ ntohl(iph->saddr), ntohl(iph->daddr));
+ #endif
+
+ if ((icmph->type != ICMP_DEST_UNREACH) &&
+ (icmph->type != ICMP_SOURCE_QUENCH) &&
+ (icmph->type != ICMP_TIME_EXCEEDED))
+ return 0;
+
+ /* Now find the contained IP header */
+ ciph = (struct iphdr *) (icmph + 1);
+
+ /* We are only interested ICMPs generated from TCP or UDP packets */
+ if ((ciph->protocol != IPPROTO_UDP) && (ciph->protocol != IPPROTO_TCP))
+ return 0;
+
+ /*
+ * Find the ports involved - remember this packet was
+ * *outgoing* so the ports are reversed (and addresses)
+ */
+ pptr = (__u16 *)&(((char *)ciph)[ciph->ihl*4]);
+ if (ntohs(pptr[0]) < PORT_MASQ_BEGIN ||
+ ntohs(pptr[0]) > PORT_MASQ_END)
+ return 0;
+
+ #ifdef DEBUG_CONFIG_IP_MASQUERADE
+ printk("Handling ICMP for %lX:%X -> %lX:%X\n",
+ ntohl(ciph->saddr), ntohs(pptr[0]),
+ ntohl(ciph->daddr), ntohs(pptr[1]));
+ #endif
+
+ /* This is pretty much what ip_masq_in_get() does, except params are wrong way round */
+ ms = ip_masq_in_get_2(ciph->protocol, ciph->daddr, pptr[1], ciph->saddr, pptr[0]);
+
+ if (ms == NULL)
+ return 0;
+
+ /* Now we do real damage to this packet...! */
+ /* First change the dest IP address, and recalc checksum */
+ iph->daddr = ms->saddr;
+ ip_send_check(iph);
+
+ /* Now change the *source* address in the contained IP */
+ ciph->saddr = ms->saddr;
+ ip_send_check(ciph);
+
+ /* the TCP/UDP source port - cannot redo check */
+ pptr[0] = ms->sport;
+
+ /* And finally the ICMP checksum */
+ icmph->checksum = 0;
+ icmph->checksum = ip_compute_csum((unsigned char *) icmph,
+ (ntohs(iph->tot_len) - (iph->ihl * 4)));
+
+ #ifdef DEBUG_CONFIG_IP_MASQUERADE
+ printk("Rewrote ICMP to %lX:%X -> %lX:%X\n",
+ ntohl(ciph->saddr), ntohs(pptr[0]),
+ ntohl(ciph->daddr), ntohs(pptr[1]));
+ #endif
+
+ return 1;
+ }
+
+
/*
* Check if it's an masqueraded port, look it up,
* and send it on its way...
***************
*** 554,560 ****
struct ip_masq *ms;
unsigned short frag;

! if (iph->protocol!=IPPROTO_UDP && iph->protocol!=IPPROTO_TCP)
return 0;

/*
--- 660,667 ----
struct ip_masq *ms;
unsigned short frag;

! if (iph->protocol!=IPPROTO_UDP && iph->protocol!=IPPROTO_TCP
! && iph->protocol!=IPPROTO_ICMP)
return 0;

/*
***************
*** 567,572 ****
--- 674,682 ----
{
return 0;
}
+
+ if (iph->protocol == IPPROTO_ICMP)
+ return ip_fw_demasq_icmp(skb_p, dev);

portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
if (ntohs(portptr[1]) < PORT_MASQ_BEGIN ||

--===_0_Thu_May_16_14:14:13_BST_1996
Content-Type: application/x-patch
Content-Description: masq_csum.patch

Index: linux/net/ipv4/ip_input.c
diff -c linux/net/ipv4/ip_input.c:1.1.1.1 linux/net/ipv4/ip_input.c:1.3
*** linux/net/ipv4/ip_input.c:1.1.1.1 Wed May 15 10:20:28 1996
--- linux/net/ipv4/ip_input.c Thu May 16 13:45:01 1996
***************
*** 208,213 ****
--- 208,214 ----
int brd=IS_MYADDR;
struct options * opt = NULL;
int is_frag=0;
+ int ret;
__u32 daddr;

#ifdef CONFIG_FIREWALL
***************
*** 422,440 ****
}
#endif

- #ifdef CONFIG_IP_MASQUERADE
- /*
- * Do we need to de-masquerade this fragment?
- */
- if (ip_fw_demasquerade(&skb,dev))
- {
- struct iphdr *iph=skb->h.iph;
- if (ip_forward(skb, dev, is_frag|IPFWD_MASQUERADED, iph->daddr))
- kfree_skb(skb, FREE_WRITE);
- return(0);
- }
- #endif
-
/*
* Reassemble IP fragments.
*/
--- 423,428 ----
***************
*** 447,462 ****
return 0;
skb->dev = dev;
iph=skb->h.iph;
#ifdef CONFIG_IP_MASQUERADE
! if (ip_fw_demasquerade(&skb,dev))
{
struct iphdr *iph=skb->h.iph;
if (ip_forward(skb, dev, IPFWD_MASQUERADED, iph->daddr))
kfree_skb(skb, FREE_WRITE);
! return 0;
}
- #endif
}

/*
* Point into the IP datagram, just past the header.
--- 435,461 ----
return 0;
skb->dev = dev;
iph=skb->h.iph;
+ }
+
#ifdef CONFIG_IP_MASQUERADE
! /*
! * Do we need to de-masquerade this packet?
! */
! if ((ret = ip_fw_demasquerade(&skb,dev)) != 0) {
! if (ret < 0)
! {
! kfree_skb(skb, FREE_WRITE);
! return(0);
! }
! else
{
struct iphdr *iph=skb->h.iph;
if (ip_forward(skb, dev, IPFWD_MASQUERADED, iph->daddr))
kfree_skb(skb, FREE_WRITE);
! return(0);
}
}
+ #endif

/*
* Point into the IP datagram, just past the header.
Index: linux/net/ipv4/ip_masq.c
diff -c linux/net/ipv4/ip_masq.c:1.13 linux/net/ipv4/ip_masq.c:1.16
*** linux/net/ipv4/ip_masq.c:1.13 Thu May 16 11:19:01 1996
--- linux/net/ipv4/ip_masq.c Thu May 16 14:07:42 1996
***************
*** 13,18 ****
--- 13,19 ----
* Juan Jose Ciarlante : Fixed deadlock if free ports get exhausted
* Juan Jose Ciarlante : Added NO_ADDR status flag.
* Nigel Metheringham : Added ICMP handling for demasquerade
+ * Nigel Metheringham : Checksum checking of masqueraded data
*
*
*/
***************
*** 27,32 ****
--- 28,34 ----
#include <linux/proc_fs.h>
#include <linux/in.h>
#include <linux/ip.h>
+ #include <linux/inet.h>
#include <net/protocol.h>
#include <net/icmp.h>
#include <net/tcp.h>
***************
*** 459,464 ****
--- 461,468 ----

/*
* We can only masquerade protocols with ports...
+ * [TODO]
+ * We may need to consider masq-ing some ICMP related to masq-ed protocols
*/

if (iph->protocol!=IPPROTO_UDP && iph->protocol!=IPPROTO_TCP)
***************
*** 578,583 ****
--- 582,588 ----
struct iphdr *ciph; /* The ip header contained within the ICMP */
__u16 *pptr; /* port numbers from TCP/UDP contained header */
struct ip_masq *ms;
+ unsigned short len = ntohs(iph->tot_len) - (iph->ihl * 4);

#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Incoming ICMP (%d) %lX -> %lX\n",
***************
*** 606,611 ****
--- 611,624 ----
ntohs(pptr[0]) > PORT_MASQ_END)
return 0;

+ /* Ensure the checksum is correct */
+ if (ip_compute_csum((unsigned char *) icmph, len))
+ {
+ /* Failed checksum! */
+ printk(KERN_INFO "MASQ: ICMP: failed checksum from %s!\n", in_ntoa(iph->saddr));
+ return(-1);
+ }
+
#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Handling ICMP for %lX:%X -> %lX:%X\n",
ntohl(ciph->saddr), ntohs(pptr[0]),
***************
*** 632,639 ****

/* And finally the ICMP checksum */
icmph->checksum = 0;
! icmph->checksum = ip_compute_csum((unsigned char *) icmph,
! (ntohs(iph->tot_len) - (iph->ihl * 4)));

#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Rewrote ICMP to %lX:%X -> %lX:%X\n",
--- 645,651 ----

/* And finally the ICMP checksum */
icmph->checksum = 0;
! icmph->checksum = ip_compute_csum((unsigned char *) icmph, len);

#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Rewrote ICMP to %lX:%X -> %lX:%X\n",
***************
*** 661,689 ****
__u16 *portptr;
struct ip_masq *ms;
unsigned short frag;

! if (iph->protocol!=IPPROTO_UDP && iph->protocol!=IPPROTO_TCP
! && iph->protocol!=IPPROTO_ICMP)
! return 0;
!
! /*
! * Toss fragments, since we handle them in ip_rcv()
! */
!
! frag = ntohs(iph->frag_off);

! if ((frag & IP_MF) != 0 || (frag & IP_OFFSET) != 0)
! {
return 0;
}

- if (iph->protocol == IPPROTO_ICMP)
- return ip_fw_demasq_icmp(skb_p, dev);
-
- portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
- if (ntohs(portptr[1]) < PORT_MASQ_BEGIN ||
- ntohs(portptr[1]) > PORT_MASQ_END)
- return 0;

#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Incoming %s %lX:%X -> %lX:%X\n",
--- 673,716 ----
__u16 *portptr;
struct ip_masq *ms;
unsigned short frag;
+ unsigned short len;

! switch (iph->protocol) {
! case IPPROTO_ICMP:
! return(ip_fw_demasq_icmp(skb_p, dev));
! case IPPROTO_TCP:
! case IPPROTO_UDP:
! /* Make sure packet is in the masq range */
! portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
! if (ntohs(portptr[1]) < PORT_MASQ_BEGIN ||
! ntohs(portptr[1]) > PORT_MASQ_END)
! return 0;
! /* Check that the checksum is OK */
! if ((iph->protocol == IPPROTO_UDP) && (portptr[3] == 0))
! /* No UDP checksum */
! break;

! len = ntohs(iph->tot_len) - (iph->ihl * 4);
! switch (skb->ip_summed)
! {
! case CHECKSUM_NONE:
! skb->csum = csum_partial((char *)portptr, len, 0);
! case CHECKSUM_HW:
! if (csum_tcpudp_magic(iph->saddr, iph->daddr, len,
! iph->protocol, skb->csum))
! {
! printk(KERN_INFO "MASQ: failed TCP/UDP checksum from %s!\n",
! in_ntoa(iph->saddr));
! return -1;
! }
! default:
! /* CHECKSUM_UNNECESSARY */
! }
! break;
! default:
return 0;
}


#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Incoming %s %lX:%X -> %lX:%X\n",
***************
*** 699,706 ****

if (ms != NULL)
{
- int size;
-
/*
* Set dport if not defined yet.
*/
--- 726,731 ----
***************
*** 721,727 ****
ntohs(ms->daddr));
#endif
}
- size = skb->len - ((unsigned char *)portptr - skb->h.raw);
iph->daddr = ms->saddr;
portptr[1] = ms->sport;

--- 746,751 ----
***************
*** 739,745 ****
skb = *skb_p;
iph = skb->h.iph;
portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
! size = skb->len - ((unsigned char *)portptr-skb->h.raw);
}

/*
--- 763,769 ----
skb = *skb_p;
iph = skb->h.iph;
portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
! len = ntohs(iph->tot_len) - (iph->ihl * 4);
}

/*
***************
*** 751,757 ****
*/
if (iph->protocol==IPPROTO_UDP)
{
! recalc_check((struct udphdr *)portptr,iph->saddr,iph->daddr,size);
ip_masq_set_expire(ms, 0);
ip_masq_set_expire(ms, ip_masq_expire->udp_timeout);
}
--- 775,781 ----
*/
if (iph->protocol==IPPROTO_UDP)
{
! recalc_check((struct udphdr *)portptr,iph->saddr,iph->daddr,len);
ip_masq_set_expire(ms, 0);
ip_masq_set_expire(ms, ip_masq_expire->udp_timeout);
}
***************
*** 759,766 ****
{
struct tcphdr *th;
skb->csum = csum_partial((void *)(((struct tcphdr *)portptr) + 1),
! size - sizeof(struct tcphdr), 0);
! tcp_send_check((struct tcphdr *)portptr,iph->saddr,iph->daddr,size,skb);
/* Check if TCP RST */
th = (struct tcphdr *)portptr;
if (th->rst)
--- 783,790 ----
{
struct tcphdr *th;
skb->csum = csum_partial((void *)(((struct tcphdr *)portptr) + 1),
! len - sizeof(struct tcphdr), 0);
! tcp_send_check((struct tcphdr *)portptr,iph->saddr,iph->daddr,len,skb);
/* Check if TCP RST */
th = (struct tcphdr *)portptr;
if (th->rst)

--===_0_Thu_May_16_14:14:13_BST_1996
Content-Type: text/plain; charset=us-ascii

[ Nigel.Metheringham@theplanet.net - Unix Applications Engineer ]
[ *Views expressed here are personal and not supported by PLAnet* ]
[ PLAnet Online : The White House Tel : +44 113 2345566 x 612 ]
[ Melbourne Street, Leeds LS2 7PS UK. Fax : +44 113 2345656 ]

--===_0_Thu_May_16_14:14:13_BST_1996--