]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - net/ipv4/ip_fragment.c
[MTD] Add Amstrad Delta NAND support
[linux-2.6-omap-h63xx.git] / net / ipv4 / ip_fragment.c
index 8ce0ce2ee48e1d75308aaa3711b23989a4a07214..da734c4391796e017ef55d5e3e4730bfe16a4490 100644 (file)
@@ -22,6 +22,7 @@
  *             Patrick McHardy :       LRU queue of frag heads for evictor.
  */
 
+#include <linux/compiler.h>
 #include <linux/config.h>
 #include <linux/module.h>
 #include <linux/types.h>
@@ -38,6 +39,7 @@
 #include <net/ip.h>
 #include <net/icmp.h>
 #include <net/checksum.h>
+#include <net/inetpeer.h>
 #include <linux/tcp.h>
 #include <linux/udp.h>
 #include <linux/inet.h>
@@ -56,6 +58,8 @@
 int sysctl_ipfrag_high_thresh = 256*1024;
 int sysctl_ipfrag_low_thresh = 192*1024;
 
+int sysctl_ipfrag_max_dist = 64;
+
 /* Important NOTE! Fragment queue must be destroyed before MSL expires.
  * RFC791 is wrong proposing to prolongate timer each fragment arrival by TTL.
  */
@@ -89,8 +93,10 @@ struct ipq {
        spinlock_t      lock;
        atomic_t        refcnt;
        struct timer_list timer;        /* when will this queue expire?         */
-       int             iif;
        struct timeval  stamp;
+       int             iif;
+       unsigned int    rid;
+       struct inet_peer *peer;
 };
 
 /* Hash table. */
@@ -195,6 +201,9 @@ static void ip_frag_destroy(struct ipq *qp, int *work)
        BUG_TRAP(qp->last_in&COMPLETE);
        BUG_TRAP(del_timer(&qp->timer) == 0);
 
+       if (qp->peer)
+               inet_putpeer(qp->peer);
+
        /* Release all fragment data. */
        fp = qp->fragments;
        while (fp) {
@@ -295,13 +304,17 @@ out:
 
 /* Creation primitives. */
 
-static struct ipq *ip_frag_intern(unsigned int hash, struct ipq *qp_in)
+static struct ipq *ip_frag_intern(struct ipq *qp_in)
 {
        struct ipq *qp;
 #ifdef CONFIG_SMP
        struct hlist_node *n;
 #endif
+       unsigned int hash;
+
        write_lock(&ipfrag_lock);
+       hash = ipqhashfn(qp_in->id, qp_in->saddr, qp_in->daddr,
+                        qp_in->protocol);
 #ifdef CONFIG_SMP
        /* With SMP race we have to recheck hash table, because
         * such entry could be created on other cpu, while we
@@ -336,7 +349,7 @@ static struct ipq *ip_frag_intern(unsigned int hash, struct ipq *qp_in)
 }
 
 /* Add an entry to the 'ipq' queue for a newly received IP datagram. */
-static struct ipq *ip_frag_create(unsigned hash, struct iphdr *iph, u32 user)
+static struct ipq *ip_frag_create(struct iphdr *iph, u32 user)
 {
        struct ipq *qp;
 
@@ -353,6 +366,7 @@ static struct ipq *ip_frag_create(unsigned hash, struct iphdr *iph, u32 user)
        qp->meat = 0;
        qp->fragments = NULL;
        qp->iif = 0;
+       qp->peer = sysctl_ipfrag_max_dist ? inet_getpeer(iph->saddr, 1) : NULL;
 
        /* Initialize a timer for this entry. */
        init_timer(&qp->timer);
@@ -361,7 +375,7 @@ static struct ipq *ip_frag_create(unsigned hash, struct iphdr *iph, u32 user)
        spin_lock_init(&qp->lock);
        atomic_set(&qp->refcnt, 1);
 
-       return ip_frag_intern(hash, qp);
+       return ip_frag_intern(qp);
 
 out_nomem:
        LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
@@ -373,15 +387,16 @@ out_nomem:
  */
 static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
 {
-       __u16 id = iph->id;
+       __be16 id = iph->id;
        __u32 saddr = iph->saddr;
        __u32 daddr = iph->daddr;
        __u8 protocol = iph->protocol;
-       unsigned int hash = ipqhashfn(id, saddr, daddr, protocol);
+       unsigned int hash;
        struct ipq *qp;
        struct hlist_node *n;
 
        read_lock(&ipfrag_lock);
+       hash = ipqhashfn(id, saddr, daddr, protocol);
        hlist_for_each_entry(qp, n, &ipq_hash[hash], list) {
                if(qp->id == id         &&
                   qp->saddr == saddr   &&
@@ -395,7 +410,57 @@ static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
        }
        read_unlock(&ipfrag_lock);
 
-       return ip_frag_create(hash, iph, user);
+       return ip_frag_create(iph, user);
+}
+
+/* Is the fragment too far ahead to be part of ipq? */
+static inline int ip_frag_too_far(struct ipq *qp)
+{
+       struct inet_peer *peer = qp->peer;
+       unsigned int max = sysctl_ipfrag_max_dist;
+       unsigned int start, end;
+
+       int rc;
+
+       if (!peer || !max)
+               return 0;
+
+       start = qp->rid;
+       end = atomic_inc_return(&peer->rid);
+       qp->rid = end;
+
+       rc = qp->fragments && (end - start) > max;
+
+       if (rc) {
+               IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
+       }
+
+       return rc;
+}
+
+static int ip_frag_reinit(struct ipq *qp)
+{
+       struct sk_buff *fp;
+
+       if (!mod_timer(&qp->timer, jiffies + sysctl_ipfrag_time)) {
+               atomic_inc(&qp->refcnt);
+               return -ETIMEDOUT;
+       }
+
+       fp = qp->fragments;
+       do {
+               struct sk_buff *xp = fp->next;
+               frag_kfree_skb(fp, NULL);
+               fp = xp;
+       } while (fp);
+
+       qp->last_in = 0;
+       qp->len = 0;
+       qp->meat = 0;
+       qp->fragments = NULL;
+       qp->iif = 0;
+
+       return 0;
 }
 
 /* Add new segment to existing queue. */
@@ -408,6 +473,12 @@ static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
        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))) {
+               ipq_kill(qp);
+               goto err;
+       }
+
        offset = ntohs(skb->nh.iph->frag_off);
        flags = offset & ~IP_OFFSET;
        offset &= IP_OFFSET;