Subject: [PATCH 2.6]: keep fragment queues private to each user

From: Patrick McHardy <[email-masked]>
Date: Tue, 25 Jan 2005 05:57:47 +0100

Hi Dave,

This patch keeps fragment queues private to each ip_defrag user to avoid
skbs jumping between different callers. It shouldn't change any wanted
behaviour, the only questionable one was ip_call_ra_chain, but the RA
option is included in each fragment. If you're fine with the patch I'm going
to send a 2.4 version later.

Regards
Patrick

PS: The patch should apply in any order with the netfilter patches I 
just sent.

# This is a BitKeeper generated diff -Nru style patch.
#
# ChangeSet
#   2005/01/25 05:35:12+01:00 [email-masked] 
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
# net/ipv4/netfilter/ip_nat_standalone.c
#   2005/01/25 05:35:04+01:00 [email-masked] +1 -1
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
# net/ipv4/netfilter/ip_conntrack_standalone.c
#   2005/01/25 05:35:04+01:00 [email-masked] +4 -7
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
# net/ipv4/netfilter/ip_conntrack_core.c
#   2005/01/25 05:35:04+01:00 [email-masked] +2 -9
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
# net/ipv4/ipvs/ip_vs_core.c
#   2005/01/25 05:35:04+01:00 [email-masked] +11 -8
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
# net/ipv4/ip_input.c
#   2005/01/25 05:35:04+01:00 [email-masked] +2 -2
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
# net/ipv4/ip_fragment.c
#   2005/01/25 05:35:04+01:00 [email-masked] +13 -20
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
# include/net/ip.h
#   2005/01/25 05:35:04+01:00 [email-masked] +14 -3
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
# include/linux/netfilter_ipv4/ip_conntrack.h
#   2005/01/25 05:35:04+01:00 [email-masked] +1 -2
#   [IPV4]: keep fragment queues private to each user
#   
#   Signed-off-by: Patrick McHardy <[email-masked]>
# 
diff -Nru a/include/linux/netfilter_ipv4/ip_conntrack.h b/include/linux/netfilter_ipv4/ip_conntrack.h
--- a/include/linux/netfilter_ipv4/ip_conntrack.h	2005-01-25 05:35:30 +01:00
+++ b/include/linux/netfilter_ipv4/ip_conntrack.h	2005-01-25 05:35:30 +01:00
@@ -262,10 +262,9 @@
 /* Fake conntrack entry for untracked connections */
 extern struct ip_conntrack ip_conntrack_untracked;
 
-extern int ip_ct_no_defrag;
 /* Returns new sk_buff, or NULL */
 struct sk_buff *
-ip_ct_gather_frags(struct sk_buff *skb);
+ip_ct_gather_frags(struct sk_buff *skb, u_int32_t user);
 
 /* Iterate over all conntracks: if iter returns true, it's deleted. */
 extern void
diff -Nru a/include/net/ip.h b/include/net/ip.h
--- a/include/net/ip.h	2005-01-25 05:35:30 +01:00
+++ b/include/net/ip.h	2005-01-25 05:35:30 +01:00
@@ -286,9 +286,20 @@
 /*
  *	Functions provided by ip_fragment.o
  */
- 
-struct sk_buff *ip_defrag(struct sk_buff *skb);
-extern void ipfrag_flush(void);
+
+enum ip_defrag_users
+{
+	IP_DEFRAG_LOCAL_DELIVER,
+	IP_DEFRAG_CALL_RA_CHAIN,
+	IP_DEFRAG_CONNTRACK_IN,
+	IP_DEFRAG_CONNTRACK_OUT,
+	IP_DEFRAG_NAT_OUT,
+	IP_DEFRAG_VS_IN,
+	IP_DEFRAG_VS_OUT,
+	IP_DEFRAG_VS_FWD
+};
+
+struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user);
 extern int ip_frag_nqueues;
 extern atomic_t ip_frag_mem;
 
diff -Nru a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c
--- a/net/ipv4/ip_fragment.c	2005-01-25 05:35:30 +01:00
+++ b/net/ipv4/ip_fragment.c	2005-01-25 05:35:30 +01:00
@@ -73,6 +73,7 @@
 struct ipq {
 	struct ipq	*next;		/* linked list pointers			*/
 	struct list_head lru_list;	/* lru list member 			*/
+	u32		user;
 	u32		saddr;
 	u32		daddr;
 	u16		id;
@@ -243,13 +244,13 @@
 /* Memory limiting on fragments.  Evictor trashes the oldest 
  * fragment queue until we are back under the threshold.
  */
-static void __ip_evictor(int threshold)
+static void ip_evictor(void)
 {
 	struct ipq *qp;
 	struct list_head *tmp;
 	int work;
 
-	work = atomic_read(&ip_frag_mem) - threshold;
+	work = atomic_read(&ip_frag_mem) - sysctl_ipfrag_low_thresh;
 	if (work <= 0)
 		return;
 
@@ -274,11 +275,6 @@
 	}
 }
 
-static inline void ip_evictor(void)
-{
-	__ip_evictor(sysctl_ipfrag_low_thresh);
-}
-
 /*
  * Oops, a fragment queue timed out.  Kill it and send an ICMP reply.
  */
@@ -325,7 +321,8 @@
 		if(qp->id == qp_in->id		&&
 		   qp->saddr == qp_in->saddr	&&
 		   qp->daddr == qp_in->daddr	&&
-		   qp->protocol == qp_in->protocol) {
+		   qp->protocol == qp_in->protocol &&
+		   qp->user == user) {
 			atomic_inc(&qp->refcnt);
 			write_unlock(&ipfrag_lock);
 			qp_in->last_in |= COMPLETE;
@@ -352,7 +349,7 @@
 }
 
 /* Add an entry to the 'ipq' queue for a newly received IP datagram. */
-static struct ipq *ip_frag_create(unsigned hash, struct iphdr *iph)
+static struct ipq *ip_frag_create(unsigned hash, struct iphdr *iph, u32 user)
 {
 	struct ipq *qp;
 
@@ -364,6 +361,7 @@
 	qp->id = iph->id;
 	qp->saddr = iph->saddr;
 	qp->daddr = iph->daddr;
+	qp->user = user;
 	qp->len = 0;
 	qp->meat = 0;
 	qp->fragments = NULL;
@@ -386,7 +384,7 @@
 /* Find the correct entry in the "incomplete datagrams" queue for
  * this IP datagram, and create new one, if nothing is found.
  */
-static inline struct ipq *ip_find(struct iphdr *iph)
+static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
 {
 	__u16 id = iph->id;
 	__u32 saddr = iph->saddr;
@@ -400,7 +398,8 @@
 		if(qp->id == id		&&
 		   qp->saddr == saddr	&&
 		   qp->daddr == daddr	&&
-		   qp->protocol == protocol) {
+		   qp->protocol == protocol &&
+		   qp->user == user) {
 			atomic_inc(&qp->refcnt);
 			read_unlock(&ipfrag_lock);
 			return qp;
@@ -408,7 +407,7 @@
 	}
 	read_unlock(&ipfrag_lock);
 
-	return ip_frag_create(hash, iph);
+	return ip_frag_create(hash, iph, user);
 }
 
 /* Add new segment to existing queue. */
@@ -642,7 +641,7 @@
 }
 
 /* Process an incoming IP datagram fragment. */
-struct sk_buff *ip_defrag(struct sk_buff *skb)
+struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user)
 {
 	struct iphdr *iph = skb->nh.iph;
 	struct ipq *qp;
@@ -657,7 +656,7 @@
 	dev = skb->dev;
 
 	/* Lookup (or create) queue header */
-	if ((qp = ip_find(iph)) != NULL) {
+	if ((qp = ip_find(iph, user)) != NULL) {
 		struct sk_buff *ret = NULL;
 
 		spin_lock(&qp->lock);
@@ -689,10 +688,4 @@
 	add_timer(&ipfrag_secret_timer);
 }
 
-void ipfrag_flush(void)
-{
-	__ip_evictor(0);
-}
-
 EXPORT_SYMBOL(ip_defrag);
-EXPORT_SYMBOL(ipfrag_flush);
diff -Nru a/net/ipv4/ip_input.c b/net/ipv4/ip_input.c
--- a/net/ipv4/ip_input.c	2005-01-25 05:35:30 +01:00
+++ b/net/ipv4/ip_input.c	2005-01-25 05:35:30 +01:00
@@ -172,7 +172,7 @@
 		    (!sk->sk_bound_dev_if ||
 		     sk->sk_bound_dev_if == skb->dev->ifindex)) {
 			if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
-				skb = ip_defrag(skb);
+				skb = ip_defrag(skb, IP_DEFRAG_CALL_RA_CHAIN);
 				if (skb == NULL) {
 					read_unlock(&ip_ra_lock);
 					return 1;
@@ -273,7 +273,7 @@
 	 */
 
 	if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
-		skb = ip_defrag(skb);
+		skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);
 		if (!skb)
 			return 0;
 	}
diff -Nru a/net/ipv4/ipvs/ip_vs_core.c b/net/ipv4/ipvs/ip_vs_core.c
--- a/net/ipv4/ipvs/ip_vs_core.c	2005-01-25 05:35:30 +01:00
+++ b/net/ipv4/ipvs/ip_vs_core.c	2005-01-25 05:35:30 +01:00
@@ -544,9 +544,9 @@
 }
 
 static inline struct sk_buff *
-ip_vs_gather_frags(struct sk_buff *skb)
+ip_vs_gather_frags(struct sk_buff *skb, u_int32_t user)
 {
-	skb = ip_defrag(skb);
+	skb = ip_defrag(skb, user);
 	if (skb)
 		ip_send_check(skb->nh.iph);
 	return skb;
@@ -620,7 +620,7 @@
 
 	/* reassemble IP fragments */
 	if (skb->nh.iph->frag_off & __constant_htons(IP_MF|IP_OFFSET)) {
-		skb = ip_vs_gather_frags(skb);
+		skb = ip_vs_gather_frags(skb, IP_DEFRAG_VS_OUT);
 		if (!skb)
 			return NF_STOLEN;
 		*pskb = skb;
@@ -759,7 +759,7 @@
 	/* reassemble IP fragments */
 	if (unlikely(iph->frag_off & __constant_htons(IP_MF|IP_OFFSET) &&
 		     !pp->dont_defrag)) {
-		skb = ip_vs_gather_frags(skb);
+		skb = ip_vs_gather_frags(skb, IP_DEFRAG_VS_OUT);
 		if (!skb)
 			return NF_STOLEN;
 		iph = skb->nh.iph;
@@ -839,7 +839,8 @@
  *	forward to the right destination host if relevant.
  *	Currently handles error types - unreachable, quench, ttl exceeded.
  */
-static int ip_vs_in_icmp(struct sk_buff **pskb, int *related)
+static int 
+ip_vs_in_icmp(struct sk_buff **pskb, int *related, unsigned int hooknum)
 {
 	struct sk_buff *skb = *pskb;
 	struct iphdr *iph;
@@ -853,7 +854,9 @@
 
 	/* reassemble IP fragments */
 	if (skb->nh.iph->frag_off & __constant_htons(IP_MF|IP_OFFSET)) {
-		skb = ip_vs_gather_frags(skb);
+		skb = ip_vs_gather_frags(skb,
+		                         hooknum == NF_IP_LOCAL_IN ?
+					 IP_DEFRAG_VS_IN : IP_DEFRAG_VS_FWD);
 		if (!skb)
 			return NF_STOLEN;
 		*pskb = skb;
@@ -962,7 +965,7 @@
 
 	iph = skb->nh.iph;
 	if (unlikely(iph->protocol == IPPROTO_ICMP)) {
-		int related, verdict = ip_vs_in_icmp(pskb, &related);
+		int related, verdict = ip_vs_in_icmp(pskb, &related, hooknum);
 
 		if (related)
 			return verdict;
@@ -1057,7 +1060,7 @@
 	if ((*pskb)->nh.iph->protocol != IPPROTO_ICMP)
 		return NF_ACCEPT;
 
-	return ip_vs_in_icmp(pskb, &r);
+	return ip_vs_in_icmp(pskb, &r, hooknum);
 }

diff -Nru a/net/ipv4/netfilter/ip_conntrack_core.c b/net/ipv4/netfilter/ip_conntrack_core.c
--- a/net/ipv4/netfilter/ip_conntrack_core.c	2005-01-25 05:35:30 +01:00
+++ b/net/ipv4/netfilter/ip_conntrack_core.c	2005-01-25 05:35:30 +01:00
@@ -936,29 +936,22 @@
 	}
 }
 
-int ip_ct_no_defrag;
-
 /* Returns new sk_buff, or NULL */
 struct sk_buff *
-ip_ct_gather_frags(struct sk_buff *skb)
+ip_ct_gather_frags(struct sk_buff *skb, u_int32_t user)
 {
 	struct sock *sk = skb->sk;
 #ifdef CONFIG_NETFILTER_DEBUG
 	unsigned int olddebug = skb->nf_debug;
 #endif
 
-	if (unlikely(ip_ct_no_defrag)) {
-		kfree_skb(skb);
-		return NULL;
-	}
-
 	if (sk) {
 		sock_hold(sk);
 		skb_orphan(skb);
 	}
 
 	local_bh_disable(); 
-	skb = ip_defrag(skb);
+	skb = ip_defrag(skb, user);
 	local_bh_enable();
 
 	if (!skb) {
diff -Nru a/net/ipv4/netfilter/ip_conntrack_standalone.c b/net/ipv4/netfilter/ip_conntrack_standalone.c
--- a/net/ipv4/netfilter/ip_conntrack_standalone.c	2005-01-25 05:35:30 +01:00
+++ b/net/ipv4/netfilter/ip_conntrack_standalone.c	2005-01-25 05:35:30 +01:00
@@ -391,7 +391,10 @@
 
 	/* Gather fragments. */
 	if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
-		*pskb = ip_ct_gather_frags(*pskb);
+		*pskb = ip_ct_gather_frags(*pskb,
+		                           hooknum == NF_IP_PRE_ROUTING ? 
+					   IP_DEFRAG_CONNTRACK_IN :
+					   IP_DEFRAG_CONNTRACK_OUT);
 		if (!*pskb)
 			return NF_STOLEN;
 	}
@@ -823,12 +826,6 @@
  cleanup_defraglocalops:
 	nf_unregister_hook(&ip_conntrack_defrag_local_out_ops);
  cleanup_defragops:
-	/* Frag queues may hold fragments with skb->dst == NULL */
-	ip_ct_no_defrag = 1;
-	synchronize_net();
-	local_bh_disable();
-	ipfrag_flush();
-	local_bh_enable();
 	nf_unregister_hook(&ip_conntrack_defrag_ops);
  cleanup_proc_stat:
 #ifdef CONFIG_PROC_FS
diff -Nru a/net/ipv4/netfilter/ip_nat_standalone.c b/net/ipv4/netfilter/ip_nat_standalone.c
--- a/net/ipv4/netfilter/ip_nat_standalone.c	2005-01-25 05:35:30 +01:00
+++ b/net/ipv4/netfilter/ip_nat_standalone.c	2005-01-25 05:35:30 +01:00
@@ -195,7 +195,7 @@
 	   I'm starting to have nightmares about fragments.  */
 
 	if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
-		*pskb = ip_ct_gather_frags(*pskb);
+		*pskb = ip_ct_gather_frags(*pskb, IP_DEFRAG_NAT_OUT);
 
 		if (!*pskb)
 			return NF_STOLEN;