patch-2.4.21 linux-2.4.21/net/ipv4/netfilter/ip_nat_helper.c

Next file: linux-2.4.21/net/ipv4/netfilter/ip_nat_rule.c
Previous file: linux-2.4.21/net/ipv4/netfilter/ip_nat_core.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.20/net/ipv4/netfilter/ip_nat_helper.c linux-2.4.21/net/ipv4/netfilter/ip_nat_helper.c
@@ -8,6 +8,9 @@
  *		- add support for SACK adjustment 
  *	14 Mar 2002 Harald Welte <laforge@gnumonks.org>:
  *		- merge SACK support into newnat API
+ *	16 Aug 2002 Brian J. Murrell <netfilter@interlinx.bc.ca>:
+ *		- make ip_nat_resize_packet more generic (TCP and UDP)
+ *		- add ip_nat_mangle_udp_packet
  */
 #include <linux/version.h>
 #include <linux/config.h>
@@ -22,6 +25,7 @@
 #include <net/icmp.h>
 #include <net/ip.h>
 #include <net/tcp.h>
+#include <net/udp.h>
 
 #define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_nat_lock)
 #define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_nat_lock)
@@ -51,18 +55,12 @@
 		     int new_size)
 {
 	struct iphdr *iph;
-	struct tcphdr *tcph;
-	void *data;
 	int dir;
 	struct ip_nat_seq *this_way, *other_way;
 
 	DEBUGP("ip_nat_resize_packet: old_size = %u, new_size = %u\n",
 		(*skb)->len, new_size);
 
-	iph = (*skb)->nh.iph;
-	tcph = (void *)iph + iph->ihl*4;
-	data = (void *)tcph + tcph->doff*4;
-
 	dir = CTINFO2DIR(ctinfo);
 
 	this_way = &ct->nat.info.seq[dir];
@@ -84,37 +82,41 @@
 	}
 
 	iph = (*skb)->nh.iph;
-	tcph = (void *)iph + iph->ihl*4;
-	data = (void *)tcph + tcph->doff*4;
-
-	DEBUGP("ip_nat_resize_packet: Seq_offset before: ");
-	DUMP_OFFSET(this_way);
+	if (iph->protocol == IPPROTO_TCP) {
+		struct tcphdr *tcph = (void *)iph + iph->ihl*4;
+		void *data = (void *)tcph + tcph->doff*4;
+
+		DEBUGP("ip_nat_resize_packet: Seq_offset before: ");
+		DUMP_OFFSET(this_way);
+
+		LOCK_BH(&ip_nat_seqofs_lock);
+
+		/* SYN adjust. If it's uninitialized, of this is after last 
+		 * correction, record it: we don't handle more than one 
+		 * adjustment in the window, but do deal with common case of a 
+		 * retransmit */
+		if (this_way->offset_before == this_way->offset_after
+		    || before(this_way->correction_pos, ntohl(tcph->seq))) {
+			this_way->correction_pos = ntohl(tcph->seq);
+			this_way->offset_before = this_way->offset_after;
+			this_way->offset_after = (int32_t)
+				this_way->offset_before + new_size -
+				(*skb)->len;
+		}
 
-	LOCK_BH(&ip_nat_seqofs_lock);
+		UNLOCK_BH(&ip_nat_seqofs_lock);
 
-	/* SYN adjust. If it's uninitialized, of this is after last 
-	 * correction, record it: we don't handle more than one 
-	 * adjustment in the window, but do deal with common case of a 
-	 * retransmit */
-	if (this_way->offset_before == this_way->offset_after
-	    || before(this_way->correction_pos, ntohl(tcph->seq))) {
-		this_way->correction_pos = ntohl(tcph->seq);
-		this_way->offset_before = this_way->offset_after;
-		this_way->offset_after = (int32_t)
-			this_way->offset_before + new_size - (*skb)->len;
+		DEBUGP("ip_nat_resize_packet: Seq_offset after: ");
+		DUMP_OFFSET(this_way);
 	}
-
-	UNLOCK_BH(&ip_nat_seqofs_lock);
-
-	DEBUGP("ip_nat_resize_packet: Seq_offset after: ");
-	DUMP_OFFSET(this_way);
 	
 	return 1;
 }
 
 
 /* Generic function for mangling variable-length address changes inside
- * NATed connections (like the PORT XXX,XXX,XXX,XXX,XXX,XXX command in FTP).
+ * NATed TCP connections (like the PORT XXX,XXX,XXX,XXX,XXX,XXX
+ * command in FTP).
  *
  * Takes care about all the nasty sequence number changes, checksumming,
  * skb enlargement, ...
@@ -174,10 +176,11 @@
 	tcph = (void *)iph + iph->ihl*4;
 	data = (void *)tcph + tcph->doff*4;
 
-	/* move post-replacement */
-	memmove(data + match_offset + rep_len,
-		 data + match_offset + match_len,
-		 (*skb)->tail - (data + match_offset + match_len));
+	if (rep_len != match_len)
+		/* move post-replacement */
+		memmove(data + match_offset + rep_len,
+			data + match_offset + match_len,
+			(*skb)->tail - (data + match_offset + match_len));
 
 	/* insert data from buffer */
 	memcpy(data + match_offset, rep_buffer, rep_len);
@@ -207,6 +210,114 @@
 
 	return 1;
 }
+			
+/* Generic function for mangling variable-length address changes inside
+ * NATed UDP connections (like the CONNECT DATA XXXXX MESG XXXXX INDEX XXXXX
+ * command in the Amanda protocol)
+ *
+ * Takes care about all the nasty sequence number changes, checksumming,
+ * skb enlargement, ...
+ *
+ * XXX - This function could be merged with ip_nat_mangle_tcp_packet which
+ *       should be fairly easy to do.
+ */
+int 
+ip_nat_mangle_udp_packet(struct sk_buff **skb,
+			 struct ip_conntrack *ct,
+			 enum ip_conntrack_info ctinfo,
+			 unsigned int match_offset,
+			 unsigned int match_len,
+			 char *rep_buffer,
+			 unsigned int rep_len)
+{
+	struct iphdr *iph = (*skb)->nh.iph;
+	struct udphdr *udph = (void *)iph + iph->ihl * 4;
+	unsigned char *data;
+	u_int32_t udplen, newlen, newudplen;
+
+	udplen = (*skb)->len - iph->ihl*4;
+	newudplen = udplen - match_len + rep_len;
+	newlen = iph->ihl*4 + newudplen;
+
+	if (newlen > 65535) {
+		if (net_ratelimit())
+			printk("ip_nat_mangle_udp_packet: nat'ed packet "
+				"exceeds maximum packet size\n");
+		return 0;
+	}
+
+	if ((*skb)->len != newlen) {
+		if (!ip_nat_resize_packet(skb, ct, ctinfo, newlen)) {
+			printk("resize_packet failed!!\n");
+			return 0;
+		}
+	}
+
+	/* Alexey says: if a hook changes _data_ ... it can break
+	   original packet sitting in tcp queue and this is fatal */
+	if (skb_cloned(*skb)) {
+		struct sk_buff *nskb = skb_copy(*skb, GFP_ATOMIC);
+		if (!nskb) {
+			if (net_ratelimit())
+				printk("Out of memory cloning TCP packet\n");
+			return 0;
+		}
+		/* Rest of kernel will get very unhappy if we pass it
+		   a suddenly-orphaned skbuff */
+		if ((*skb)->sk)
+			skb_set_owner_w(nskb, (*skb)->sk);
+		kfree_skb(*skb);
+		*skb = nskb;
+	}
+
+	/* skb may be copied !! */
+	iph = (*skb)->nh.iph;
+	udph = (void *)iph + iph->ihl*4;
+	data = (void *)udph + sizeof(struct udphdr);
+
+	if (rep_len != match_len)
+		/* move post-replacement */
+		memmove(data + match_offset + rep_len,
+			data + match_offset + match_len,
+			(*skb)->tail - (data + match_offset + match_len));
+
+	/* insert data from buffer */
+	memcpy(data + match_offset, rep_buffer, rep_len);
+
+	/* update skb info */
+	if (newlen > (*skb)->len) {
+		DEBUGP("ip_nat_mangle_udp_packet: Extending packet by "
+			"%u to %u bytes\n", newlen - (*skb)->len, newlen);
+		skb_put(*skb, newlen - (*skb)->len);
+	} else {
+		DEBUGP("ip_nat_mangle_udp_packet: Shrinking packet from "
+			"%u to %u bytes\n", (*skb)->len, newlen);
+		skb_trim(*skb, newlen);
+	}
+
+	/* update the length of the UDP and IP packets to the new values*/
+	udph->len = htons((*skb)->len - iph->ihl*4);
+	iph->tot_len = htons(newlen);
+
+	/* fix udp checksum if udp checksum was previously calculated */
+	if ((*skb)->csum != 0) {
+		(*skb)->csum = csum_partial((char *)udph +
+					    sizeof(struct udphdr),
+					    newudplen - sizeof(struct udphdr),
+					    0);
+
+		udph->check = 0;
+		udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
+						newudplen, IPPROTO_UDP,
+						csum_partial((char *)udph,
+							 sizeof(struct udphdr),
+							(*skb)->csum));
+	}
+
+	ip_send_check(iph);
+
+	return 1;
+}
 
 /* Adjust one found SACK option including checksum correction */
 static void
@@ -255,54 +366,49 @@
 }
 			
 
-/* TCP SACK sequence number adjustment, return 0 if sack found and adjusted */
-static inline int
+/* TCP SACK sequence number adjustment. */
+static inline void
 ip_nat_sack_adjust(struct sk_buff *skb,
-			struct ip_conntrack *ct,
-			enum ip_conntrack_info ctinfo)
+		   struct ip_conntrack *ct,
+		   enum ip_conntrack_info ctinfo)
 {
-	struct iphdr *iph;
 	struct tcphdr *tcph;
-	unsigned char *ptr;
-	int length, dir, sack_adjusted = 0;
+	unsigned char *ptr, *optend;
+	unsigned int dir;
 
-	iph = skb->nh.iph;
-	tcph = (void *)iph + iph->ihl*4;
-	length = (tcph->doff*4)-sizeof(struct tcphdr);
+	tcph = (void *)skb->nh.iph + skb->nh.iph->ihl*4;
+	optend = (unsigned char *)tcph + tcph->doff*4;
 	ptr = (unsigned char *)(tcph+1);
 
 	dir = CTINFO2DIR(ctinfo);
 
-	while (length > 0) {
-		int opcode = *ptr++;
+	while (ptr < optend) {
+		int opcode = ptr[0];
 		int opsize;
 
 		switch (opcode) {
 		case TCPOPT_EOL:
-			return !sack_adjusted;
+			return;
 		case TCPOPT_NOP:
-			length--;
+			ptr++;
 			continue;
 		default:
-			opsize = *ptr++;
-			if (opsize > length) /* no partial opts */
-				return !sack_adjusted;
+			opsize = ptr[1];
+			 /* no partial opts */
+			if (ptr + opsize > optend || opsize < 2)
+				return;
 			if (opcode == TCPOPT_SACK) {
 				/* found SACK */
 				if((opsize >= (TCPOLEN_SACK_BASE
 					       +TCPOLEN_SACK_PERBLOCK)) &&
 				   !((opsize - TCPOLEN_SACK_BASE)
 				     % TCPOLEN_SACK_PERBLOCK))
-					sack_adjust(tcph, ptr-2,
+					sack_adjust(tcph, ptr,
 						    &ct->nat.info.seq[!dir]);
-				
-				sack_adjusted = 1;
 			}
-			ptr += opsize-2;
-			length -= opsize;
+			ptr += opsize;
 		}
 	}
-	return !sack_adjusted;
 }
 
 /* TCP sequence number adjustment */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)