Commit 1706d587 authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller

[IPV4]: Make ip_defrag return the same packet

This patch is a bit of a hack.  However it is worth it if you consider that
this is the only reason why we have to carry around the struct sk_buff **
pointers in netfilter.

It makes ip_defrag always return the packet that was given to it on input.
It does this by cloning the packet and replacing its original contents with
the head fragment if necessary.
Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent e0053ec0
...@@ -109,6 +109,9 @@ static u32 ipfrag_hash_rnd; ...@@ -109,6 +109,9 @@ static u32 ipfrag_hash_rnd;
static LIST_HEAD(ipq_lru_list); static LIST_HEAD(ipq_lru_list);
int ip_frag_nqueues = 0; int ip_frag_nqueues = 0;
static int ip_frag_reasm(struct ipq *qp, struct sk_buff *prev,
struct net_device *dev);
static __inline__ void __ipq_unlink(struct ipq *qp) static __inline__ void __ipq_unlink(struct ipq *qp)
{ {
hlist_del(&qp->list); hlist_del(&qp->list);
...@@ -464,17 +467,20 @@ static int ip_frag_reinit(struct ipq *qp) ...@@ -464,17 +467,20 @@ static int ip_frag_reinit(struct ipq *qp)
} }
/* Add new segment to existing queue. */ /* Add new segment to existing queue. */
static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb) static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{ {
struct sk_buff *prev, *next; struct sk_buff *prev, *next;
struct net_device *dev;
int flags, offset; int flags, offset;
int ihl, end; int ihl, end;
int err = -ENOENT;
if (qp->last_in & COMPLETE) if (qp->last_in & COMPLETE)
goto err; goto err;
if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) && if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
unlikely(ip_frag_too_far(qp)) && unlikely(ip_frag_reinit(qp))) { unlikely(ip_frag_too_far(qp)) &&
unlikely(err = ip_frag_reinit(qp))) {
ipq_kill(qp); ipq_kill(qp);
goto err; goto err;
} }
...@@ -487,6 +493,7 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb) ...@@ -487,6 +493,7 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
/* Determine the position of this fragment. */ /* Determine the position of this fragment. */
end = offset + skb->len - ihl; end = offset + skb->len - ihl;
err = -EINVAL;
/* Is this the final fragment? */ /* Is this the final fragment? */
if ((flags & IP_MF) == 0) { if ((flags & IP_MF) == 0) {
...@@ -514,9 +521,12 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb) ...@@ -514,9 +521,12 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
if (end == offset) if (end == offset)
goto err; goto err;
err = -ENOMEM;
if (pskb_pull(skb, ihl) == NULL) if (pskb_pull(skb, ihl) == NULL)
goto err; goto err;
if (pskb_trim_rcsum(skb, end-offset))
err = pskb_trim_rcsum(skb, end - offset);
if (err)
goto err; goto err;
/* Find out which fragments are in front and at the back of us /* Find out which fragments are in front and at the back of us
...@@ -539,8 +549,10 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb) ...@@ -539,8 +549,10 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
if (i > 0) { if (i > 0) {
offset += i; offset += i;
err = -EINVAL;
if (end <= offset) if (end <= offset)
goto err; goto err;
err = -ENOMEM;
if (!pskb_pull(skb, i)) if (!pskb_pull(skb, i))
goto err; goto err;
if (skb->ip_summed != CHECKSUM_UNNECESSARY) if (skb->ip_summed != CHECKSUM_UNNECESSARY)
...@@ -548,6 +560,8 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb) ...@@ -548,6 +560,8 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
} }
} }
err = -ENOMEM;
while (next && FRAG_CB(next)->offset < end) { while (next && FRAG_CB(next)->offset < end) {
int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */ int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
...@@ -589,37 +603,62 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb) ...@@ -589,37 +603,62 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
else else
qp->fragments = skb; qp->fragments = skb;
if (skb->dev) dev = skb->dev;
qp->iif = skb->dev->ifindex; if (dev) {
qp->iif = dev->ifindex;
skb->dev = NULL; skb->dev = NULL;
}
qp->stamp = skb->tstamp; qp->stamp = skb->tstamp;
qp->meat += skb->len; qp->meat += skb->len;
atomic_add(skb->truesize, &ip_frag_mem); atomic_add(skb->truesize, &ip_frag_mem);
if (offset == 0) if (offset == 0)
qp->last_in |= FIRST_IN; qp->last_in |= FIRST_IN;
if (qp->last_in == (FIRST_IN | LAST_IN) && qp->meat == qp->len)
return ip_frag_reasm(qp, prev, dev);
write_lock(&ipfrag_lock); write_lock(&ipfrag_lock);
list_move_tail(&qp->lru_list, &ipq_lru_list); list_move_tail(&qp->lru_list, &ipq_lru_list);
write_unlock(&ipfrag_lock); write_unlock(&ipfrag_lock);
return -EINPROGRESS;
return;
err: err:
kfree_skb(skb); kfree_skb(skb);
return err;
} }
/* Build a new IP datagram from all its fragments. */ /* Build a new IP datagram from all its fragments. */
static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev) static int ip_frag_reasm(struct ipq *qp, struct sk_buff *prev,
struct net_device *dev)
{ {
struct iphdr *iph; struct iphdr *iph;
struct sk_buff *fp, *head = qp->fragments; struct sk_buff *fp, *head = qp->fragments;
int len; int len;
int ihlen; int ihlen;
int err;
ipq_kill(qp); ipq_kill(qp);
/* Make the one we just received the head. */
if (prev) {
head = prev->next;
fp = skb_clone(head, GFP_ATOMIC);
if (!fp)
goto out_nomem;
fp->next = head->next;
prev->next = fp;
skb_morph(head, qp->fragments);
head->next = qp->fragments->next;
kfree_skb(qp->fragments);
qp->fragments = head;
}
BUG_TRAP(head != NULL); BUG_TRAP(head != NULL);
BUG_TRAP(FRAG_CB(head)->offset == 0); BUG_TRAP(FRAG_CB(head)->offset == 0);
...@@ -627,10 +666,12 @@ static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev) ...@@ -627,10 +666,12 @@ static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
ihlen = ip_hdrlen(head); ihlen = ip_hdrlen(head);
len = ihlen + qp->len; len = ihlen + qp->len;
err = -E2BIG;
if (len > 65535) if (len > 65535)
goto out_oversize; goto out_oversize;
/* Head of list must not be cloned. */ /* Head of list must not be cloned. */
err = -ENOMEM;
if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC)) if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
goto out_nomem; goto out_nomem;
...@@ -681,7 +722,7 @@ static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev) ...@@ -681,7 +722,7 @@ static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
iph->tot_len = htons(len); iph->tot_len = htons(len);
IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS); IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
qp->fragments = NULL; qp->fragments = NULL;
return head; return 0;
out_nomem: out_nomem:
LIMIT_NETDEBUG(KERN_ERR "IP: queue_glue: no memory for gluing " LIMIT_NETDEBUG(KERN_ERR "IP: queue_glue: no memory for gluing "
...@@ -694,14 +735,13 @@ static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev) ...@@ -694,14 +735,13 @@ static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
NIPQUAD(qp->saddr)); NIPQUAD(qp->saddr));
out_fail: out_fail:
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS); IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
return NULL; return err;
} }
/* Process an incoming IP datagram fragment. */ /* Process an incoming IP datagram fragment. */
struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user) struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user)
{ {
struct ipq *qp; struct ipq *qp;
struct net_device *dev;
IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS); IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);
...@@ -709,23 +749,17 @@ struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user) ...@@ -709,23 +749,17 @@ struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user)
if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh) if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)
ip_evictor(); ip_evictor();
dev = skb->dev;
/* Lookup (or create) queue header */ /* Lookup (or create) queue header */
if ((qp = ip_find(ip_hdr(skb), user)) != NULL) { if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {
struct sk_buff *ret = NULL; int ret;
spin_lock(&qp->lock); spin_lock(&qp->lock);
ip_frag_queue(qp, skb); ret = ip_frag_queue(qp, skb);
if (qp->last_in == (FIRST_IN|LAST_IN) &&
qp->meat == qp->len)
ret = ip_frag_reasm(qp, dev);
spin_unlock(&qp->lock); spin_unlock(&qp->lock);
ipq_put(qp, NULL); ipq_put(qp, NULL);
return ret; return ret ? NULL : skb;
} }
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS); IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment