Skip to content

Commit

Permalink
[IPV4]: Make ip_defrag return the same packet
Browse files Browse the repository at this point in the history
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: Herbert Xu <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
herbertx authored and David S. Miller committed Oct 15, 2007
1 parent e0053ec commit 1706d58
Showing 1 changed file with 55 additions and 21 deletions.
76 changes: 55 additions & 21 deletions net/ipv4/ip_fragment.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ static u32 ipfrag_hash_rnd;
static LIST_HEAD(ipq_lru_list);
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)
{
hlist_del(&qp->list);
Expand Down Expand Up @@ -464,17 +467,20 @@ static int ip_frag_reinit(struct ipq *qp)
}

/* 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 net_device *dev;
int flags, offset;
int ihl, end;
int err = -ENOENT;

if (qp->last_in & COMPLETE)
goto err;

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);
goto err;
}
Expand All @@ -487,6 +493,7 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)

/* Determine the position of this fragment. */
end = offset + skb->len - ihl;
err = -EINVAL;

/* Is this the final fragment? */
if ((flags & IP_MF) == 0) {
Expand Down Expand Up @@ -514,9 +521,12 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
if (end == offset)
goto err;

err = -ENOMEM;
if (pskb_pull(skb, ihl) == NULL)
goto err;
if (pskb_trim_rcsum(skb, end-offset))

err = pskb_trim_rcsum(skb, end - offset);
if (err)
goto err;

/* Find out which fragments are in front and at the back of us
Expand All @@ -539,15 +549,19 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)

if (i > 0) {
offset += i;
err = -EINVAL;
if (end <= offset)
goto err;
err = -ENOMEM;
if (!pskb_pull(skb, i))
goto err;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
}

err = -ENOMEM;

while (next && FRAG_CB(next)->offset < end) {
int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */

Expand Down Expand Up @@ -589,48 +603,75 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
else
qp->fragments = skb;

if (skb->dev)
qp->iif = skb->dev->ifindex;
skb->dev = NULL;
dev = skb->dev;
if (dev) {
qp->iif = dev->ifindex;
skb->dev = NULL;
}
qp->stamp = skb->tstamp;
qp->meat += skb->len;
atomic_add(skb->truesize, &ip_frag_mem);
if (offset == 0)
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);
list_move_tail(&qp->lru_list, &ipq_lru_list);
write_unlock(&ipfrag_lock);

return;
return -EINPROGRESS;

err:
kfree_skb(skb);
return err;
}


/* 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 sk_buff *fp, *head = qp->fragments;
int len;
int ihlen;
int err;

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(FRAG_CB(head)->offset == 0);

/* Allocate a new buffer for the datagram. */
ihlen = ip_hdrlen(head);
len = ihlen + qp->len;

err = -E2BIG;
if (len > 65535)
goto out_oversize;

/* Head of list must not be cloned. */
err = -ENOMEM;
if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
goto out_nomem;

Expand Down Expand Up @@ -681,7 +722,7 @@ static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
iph->tot_len = htons(len);
IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
qp->fragments = NULL;
return head;
return 0;

out_nomem:
LIMIT_NETDEBUG(KERN_ERR "IP: queue_glue: no memory for gluing "
Expand All @@ -694,38 +735,31 @@ static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
NIPQUAD(qp->saddr));
out_fail:
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
return NULL;
return err;
}

/* Process an incoming IP datagram fragment. */
struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user)
{
struct ipq *qp;
struct net_device *dev;

IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);

/* Start by cleaning up the memory. */
if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)
ip_evictor();

dev = skb->dev;

/* Lookup (or create) queue header */
if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {
struct sk_buff *ret = NULL;
int ret;

spin_lock(&qp->lock);

ip_frag_queue(qp, skb);

if (qp->last_in == (FIRST_IN|LAST_IN) &&
qp->meat == qp->len)
ret = ip_frag_reasm(qp, dev);
ret = ip_frag_queue(qp, skb);

spin_unlock(&qp->lock);
ipq_put(qp, NULL);
return ret;
return ret ? NULL : skb;
}

IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
Expand Down

0 comments on commit 1706d58

Please sign in to comment.