]> bbs.cooldavid.org Git - net-next-2.6.git/blobdiff - net/ipv4/tcp_ipv4.c
tcp: Fix slowness in read /proc/net/tcp
[net-next-2.6.git] / net / ipv4 / tcp_ipv4.c
index 202cf09c4cd4be9f8a556d2740a51ca4bba96e56..7f976af27bf06432c535a196f20226d9e759323e 100644 (file)
@@ -793,19 +793,20 @@ static void tcp_v4_reqsk_destructor(struct request_sock *req)
        kfree(inet_rsk(req)->opt);
 }
 
-#ifdef CONFIG_SYN_COOKIES
-static void syn_flood_warning(struct sk_buff *skb)
+static void syn_flood_warning(const struct sk_buff *skb)
 {
-       static unsigned long warntime;
+       const char *msg;
 
-       if (time_after(jiffies, (warntime + HZ * 60))) {
-               warntime = jiffies;
-               printk(KERN_INFO
-                      "possible SYN flooding on port %d. Sending cookies.\n",
-                      ntohs(tcp_hdr(skb)->dest));
-       }
-}
+#ifdef CONFIG_SYN_COOKIES
+       if (sysctl_tcp_syncookies)
+               msg = "Sending cookies";
+       else
 #endif
+               msg = "Dropping request";
+
+       pr_info("TCP: Possible SYN flooding on port %d. %s.\n",
+                               ntohs(tcp_hdr(skb)->dest), msg);
+}
 
 /*
  * Save and compile IPv4 options into the request_sock if needed.
@@ -1243,6 +1244,8 @@ int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
         * evidently real one.
         */
        if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
+               if (net_ratelimit())
+                       syn_flood_warning(skb);
 #ifdef CONFIG_SYN_COOKIES
                if (sysctl_tcp_syncookies) {
                        want_cookie = 1;
@@ -1328,7 +1331,6 @@ int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
 
        if (want_cookie) {
 #ifdef CONFIG_SYN_COOKIES
-               syn_flood_warning(skb);
                req->cookie_ts = tmp_opt.tstamp_ok;
 #endif
                isn = cookie_v4_init_sequence(sk, skb, &req->mss);
@@ -1504,7 +1506,7 @@ static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
        }
 
 #ifdef CONFIG_SYN_COOKIES
-       if (!th->rst && !th->syn && th->ack)
+       if (!th->syn)
                sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
 #endif
        return sk;
@@ -1555,6 +1557,7 @@ int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
 #endif
 
        if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
+               sock_rps_save_rxhash(sk, skb->rxhash);
                TCP_CHECK_TIMER(sk);
                if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
                        rsk = sk;
@@ -1579,7 +1582,9 @@ int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
                        }
                        return 0;
                }
-       }
+       } else
+               sock_rps_save_rxhash(sk, skb->rxhash);
+
 
        TCP_CHECK_TIMER(sk);
        if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
@@ -1672,8 +1677,6 @@ process:
 
        skb->dev = NULL;
 
-       sock_rps_save_rxhash(sk, skb->rxhash);
-
        bh_lock_sock_nested(sk);
        ret = 0;
        if (!sock_owned_by_user(sk)) {
@@ -1977,6 +1980,11 @@ static inline struct inet_timewait_sock *tw_next(struct inet_timewait_sock *tw)
                hlist_nulls_entry(tw->tw_node.next, typeof(*tw), tw_node) : NULL;
 }
 
+/*
+ * Get next listener socket follow cur.  If cur is NULL, get first socket
+ * starting from bucket given in st->bucket; when st->bucket is zero the
+ * very first socket in the hash table is returned.
+ */
 static void *listening_get_next(struct seq_file *seq, void *cur)
 {
        struct inet_connection_sock *icsk;
@@ -1987,14 +1995,15 @@ static void *listening_get_next(struct seq_file *seq, void *cur)
        struct net *net = seq_file_net(seq);
 
        if (!sk) {
-               st->bucket = 0;
-               ilb = &tcp_hashinfo.listening_hash[0];
+               ilb = &tcp_hashinfo.listening_hash[st->bucket];
                spin_lock_bh(&ilb->lock);
                sk = sk_nulls_head(&ilb->head);
+               st->offset = 0;
                goto get_sk;
        }
        ilb = &tcp_hashinfo.listening_hash[st->bucket];
        ++st->num;
+       ++st->offset;
 
        if (st->state == TCP_SEQ_STATE_OPENREQ) {
                struct request_sock *req = cur;
@@ -2009,6 +2018,7 @@ static void *listening_get_next(struct seq_file *seq, void *cur)
                                }
                                req = req->dl_next;
                        }
+                       st->offset = 0;
                        if (++st->sbucket >= icsk->icsk_accept_queue.listen_opt->nr_table_entries)
                                break;
 get_req:
@@ -2044,6 +2054,7 @@ start_req:
                read_unlock_bh(&icsk->icsk_accept_queue.syn_wait_lock);
        }
        spin_unlock_bh(&ilb->lock);
+       st->offset = 0;
        if (++st->bucket < INET_LHTABLE_SIZE) {
                ilb = &tcp_hashinfo.listening_hash[st->bucket];
                spin_lock_bh(&ilb->lock);
@@ -2057,7 +2068,12 @@ out:
 
 static void *listening_get_idx(struct seq_file *seq, loff_t *pos)
 {
-       void *rc = listening_get_next(seq, NULL);
+       struct tcp_iter_state *st = seq->private;
+       void *rc;
+
+       st->bucket = 0;
+       st->offset = 0;
+       rc = listening_get_next(seq, NULL);
 
        while (rc && *pos) {
                rc = listening_get_next(seq, rc);
@@ -2072,13 +2088,18 @@ static inline int empty_bucket(struct tcp_iter_state *st)
                hlist_nulls_empty(&tcp_hashinfo.ehash[st->bucket].twchain);
 }
 
+/*
+ * Get first established socket starting from bucket given in st->bucket.
+ * If st->bucket is zero, the very first socket in the hash is returned.
+ */
 static void *established_get_first(struct seq_file *seq)
 {
        struct tcp_iter_state *st = seq->private;
        struct net *net = seq_file_net(seq);
        void *rc = NULL;
 
-       for (st->bucket = 0; st->bucket <= tcp_hashinfo.ehash_mask; ++st->bucket) {
+       st->offset = 0;
+       for (; st->bucket <= tcp_hashinfo.ehash_mask; ++st->bucket) {
                struct sock *sk;
                struct hlist_nulls_node *node;
                struct inet_timewait_sock *tw;
@@ -2123,6 +2144,7 @@ static void *established_get_next(struct seq_file *seq, void *cur)
        struct net *net = seq_file_net(seq);
 
        ++st->num;
+       ++st->offset;
 
        if (st->state == TCP_SEQ_STATE_TIME_WAIT) {
                tw = cur;
@@ -2139,6 +2161,7 @@ get_tw:
                st->state = TCP_SEQ_STATE_ESTABLISHED;
 
                /* Look for next non empty bucket */
+               st->offset = 0;
                while (++st->bucket <= tcp_hashinfo.ehash_mask &&
                                empty_bucket(st))
                        ;
@@ -2166,7 +2189,11 @@ out:
 
 static void *established_get_idx(struct seq_file *seq, loff_t pos)
 {
-       void *rc = established_get_first(seq);
+       struct tcp_iter_state *st = seq->private;
+       void *rc;
+
+       st->bucket = 0;
+       rc = established_get_first(seq);
 
        while (rc && pos) {
                rc = established_get_next(seq, rc);
@@ -2191,24 +2218,72 @@ static void *tcp_get_idx(struct seq_file *seq, loff_t pos)
        return rc;
 }
 
+static void *tcp_seek_last_pos(struct seq_file *seq)
+{
+       struct tcp_iter_state *st = seq->private;
+       int offset = st->offset;
+       int orig_num = st->num;
+       void *rc = NULL;
+
+       switch (st->state) {
+       case TCP_SEQ_STATE_OPENREQ:
+       case TCP_SEQ_STATE_LISTENING:
+               if (st->bucket >= INET_LHTABLE_SIZE)
+                       break;
+               st->state = TCP_SEQ_STATE_LISTENING;
+               rc = listening_get_next(seq, NULL);
+               while (offset-- && rc)
+                       rc = listening_get_next(seq, rc);
+               if (rc)
+                       break;
+               st->bucket = 0;
+               /* Fallthrough */
+       case TCP_SEQ_STATE_ESTABLISHED:
+       case TCP_SEQ_STATE_TIME_WAIT:
+               st->state = TCP_SEQ_STATE_ESTABLISHED;
+               if (st->bucket > tcp_hashinfo.ehash_mask)
+                       break;
+               rc = established_get_first(seq);
+               while (offset-- && rc)
+                       rc = established_get_next(seq, rc);
+       }
+
+       st->num = orig_num;
+
+       return rc;
+}
+
 static void *tcp_seq_start(struct seq_file *seq, loff_t *pos)
 {
        struct tcp_iter_state *st = seq->private;
+       void *rc;
+
+       if (*pos && *pos == st->last_pos) {
+               rc = tcp_seek_last_pos(seq);
+               if (rc)
+                       goto out;
+       }
+
        st->state = TCP_SEQ_STATE_LISTENING;
        st->num = 0;
-       return *pos ? tcp_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
+       st->bucket = 0;
+       st->offset = 0;
+       rc = *pos ? tcp_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
+
+out:
+       st->last_pos = *pos;
+       return rc;
 }
 
 static void *tcp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 {
+       struct tcp_iter_state *st = seq->private;
        void *rc = NULL;
-       struct tcp_iter_state *st;
 
        if (v == SEQ_START_TOKEN) {
                rc = tcp_get_idx(seq, 0);
                goto out;
        }
-       st = seq->private;
 
        switch (st->state) {
        case TCP_SEQ_STATE_OPENREQ:
@@ -2216,6 +2291,8 @@ static void *tcp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
                rc = listening_get_next(seq, v);
                if (!rc) {
                        st->state = TCP_SEQ_STATE_ESTABLISHED;
+                       st->bucket = 0;
+                       st->offset = 0;
                        rc        = established_get_first(seq);
                }
                break;
@@ -2226,6 +2303,7 @@ static void *tcp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
        }
 out:
        ++*pos;
+       st->last_pos = *pos;
        return rc;
 }
 
@@ -2264,6 +2342,7 @@ static int tcp_seq_open(struct inode *inode, struct file *file)
 
        s = ((struct seq_file *)file->private_data)->private;
        s->family               = afinfo->family;
+       s->last_pos             = 0;
        return 0;
 }