[DCCP]: Integrate state transitions for passive-close

This adds the necessary state transitions for the two forms of passive-close

 * PASSIVE_CLOSE    - which is entered when a host   receives a Close;
 * PASSIVE_CLOSEREQ - which is entered when a client receives a CloseReq.

Here is a detailed account of what the patch does in each state.
  
1) Receiving CloseReq
----------------------
  The pseudo-code in 8.5 says:

     Step 13: Process CloseReq
          If P.type == CloseReq and S.state < CLOSEREQ,
              Generate Close
              S.state := CLOSING
              Set CLOSING timer.

  This means we need to address what to do in CLOSED, LISTEN, REQUEST, RESPOND, 
PARTOPEN, and OPEN.

   * CLOSED:         silently ignore - it may be a late or duplicate CloseReq;
   * LISTEN/RESPOND: will not appear, since Step 7 is performed first (we know 
we are the client);
   * REQUEST:        perform Step 13 directly (no need to enqueue packet);
   * OPEN/PARTOPEN:  enter PASSIVE_CLOSEREQ so that the application has a 
chance to process unread data.

  When already in PASSIVE_CLOSEREQ, no second CloseReq is enqueued. In any 
other state, the CloseReq is ignored.
  I think that this offers some robustness against rare and pathological cases: 
e.g. a simultaneous close where
  the client sends a Close and the server a CloseReq. The client will then be 
retransmitting its Close until it
  gets the Reset, so ignoring the CloseReq while in state CLOSING is sane.
  
2) Receiving Close
-------------------
  The code below from 8.5 is unconditional.

     Step 14: Process Close
          If P.type == Close,
              Generate Reset(Closed)
              Tear down connection
              Drop packet and return

  Thus we need to consider all states:
   * CLOSED:           silently ignore, since this can happen when a 
retransmitted or late Close arrives;
   * LISTEN:           dccp_rcv_state_process() will generate a Reset ("No 
Connection");
   * REQUEST:          perform Step 14 directly (no need to enqueue packet);
   * RESPOND:          dccp_check_req() will generate a Reset ("Packet Error") 
-- left it at that;
   * OPEN/PARTOPEN:    enter PASSIVE_CLOSE so that application has a chance to 
process unread data;
   * CLOSEREQ:         server performed active-close -- perform Step 14;
   * CLOSING:          simultaneous-close: use a tie-breaker to avoid message 
ping-pong (see comment);
   * PASSIVE_CLOSEREQ: ignore - the peer has a bug (sending first a CloseReq 
and now a Close);
   * TIMEWAIT:         packet is ignored.

   Note that the condition of receiving a packet in state CLOSED here is 
different from the condition "there
   is no socket for such a connection": the socket still exists, but its state 
indicates it is unusable.

   Last, dccp_finish_passive_close sets either DCCP_CLOSED or DCCP_CLOSING = 
TCP_CLOSING, so that 
   sk_stream_wait_close() will wait for the final Reset (which will trigger 
CLOSING => CLOSED).

Signed-off-by: Gerrit Renker <[EMAIL PROTECTED]>
---
 include/linux/dccp.h |    1 
 net/dccp/input.c     |   88 ++++++++++++++++++++++++++++++++++++++++++---------
 net/dccp/proto.c     |   88 +++++++++++++++++++++++++++++++++------------------
 3 files changed, 131 insertions(+), 46 deletions(-)

--- a/net/dccp/input.c
+++ b/net/dccp/input.c
@@ -32,16 +32,56 @@ static void dccp_fin(struct sock *sk, st
        sk->sk_data_ready(sk, 0);
 }
 
-static void dccp_rcv_close(struct sock *sk, struct sk_buff *skb)
+static int dccp_rcv_close(struct sock *sk, struct sk_buff *skb)
 {
-       dccp_send_reset(sk, DCCP_RESET_CODE_CLOSED);
-       dccp_fin(sk, skb);
-       dccp_set_state(sk, DCCP_CLOSED);
-       sk_wake_async(sk, 1, POLL_HUP);
+       int queued = 0;
+
+       switch (sk->sk_state) {
+       /*
+        * We ignore Close when received in one of the following states:
+        *  - CLOSED            (may be a late or duplicate packet)
+        *  - PASSIVE_CLOSEREQ  (the peer has sent a CloseReq earlier)
+        *  - RESPOND           (already handled by dccp_check_req)
+        */
+       case DCCP_CLOSING:
+               /*
+                * Simultaneous-close: receiving a Close after sending one. This
+                * can happen if both client and server perform active-close and
+                * will result in an endless ping-pong of crossing and retrans-
+                * mitted Close packets, which only terminates when one of the
+                * nodes times out (min. 64 seconds). Quicker convergence can be
+                * achieved when one of the nodes acts as tie-breaker.
+                * This is ok as both ends are done with data transfer and each
+                * end is just waiting for the other to acknowledge termination.
+                */
+               if (dccp_sk(sk)->dccps_role != DCCP_ROLE_CLIENT)
+                       break;
+               /* fall through */
+       case DCCP_REQUESTING:
+       case DCCP_ACTIVE_CLOSEREQ:
+               dccp_send_reset(sk, DCCP_RESET_CODE_CLOSED);
+               dccp_done(sk);
+               break;
+       case DCCP_OPEN:
+       case DCCP_PARTOPEN:
+               /* Give waiting application a chance to read pending data */
+               queued = 1;
+               dccp_fin(sk, skb);
+               dccp_set_state(sk, DCCP_PASSIVE_CLOSE);
+               /* fall through */
+       case DCCP_PASSIVE_CLOSE:
+               /*
+                * Retransmitted Close: we have already enqueued the first one.
+                */
+               sk_wake_async(sk, 1, POLL_HUP);
+       }
+       return queued;
 }
 
-static void dccp_rcv_closereq(struct sock *sk, struct sk_buff *skb)
+static int dccp_rcv_closereq(struct sock *sk, struct sk_buff *skb)
 {
+       int queued = 0;
+
        /*
         *   Step 7: Check for unexpected packet types
         *      If (S.is_server and P.type == CloseReq)
@@ -50,12 +90,26 @@ static void dccp_rcv_closereq(struct soc
         */
        if (dccp_sk(sk)->dccps_role != DCCP_ROLE_CLIENT) {
                dccp_send_sync(sk, DCCP_SKB_CB(skb)->dccpd_seq, DCCP_PKT_SYNC);
-               return;
+               return queued;
        }
 
-       if (sk->sk_state != DCCP_CLOSING)
+       /* Step 13: process relevant Client states < CLOSEREQ */
+       switch (sk->sk_state) {
+       case DCCP_REQUESTING:
+               dccp_send_close(sk, 0);
                dccp_set_state(sk, DCCP_CLOSING);
-       dccp_send_close(sk, 0);
+               break;
+       case DCCP_OPEN:
+       case DCCP_PARTOPEN:
+               /* Give waiting application a chance to read pending data */
+               queued = 1;
+               dccp_fin(sk, skb);
+               dccp_set_state(sk, DCCP_PASSIVE_CLOSEREQ);
+               /* fall through */
+       case DCCP_PASSIVE_CLOSEREQ:
+               sk_wake_async(sk, 1, POLL_HUP);
+       }
+       return queued;
 }
 
 static u8 dccp_reset_code_convert(const u8 code)
@@ -231,11 +285,13 @@ static int __dccp_rcv_established(struct
                dccp_rcv_reset(sk, skb);
                return 0;
        case DCCP_PKT_CLOSEREQ:
-               dccp_rcv_closereq(sk, skb);
+               if (dccp_rcv_closereq(sk, skb))
+                       return 0;
                goto discard;
        case DCCP_PKT_CLOSE:
-               dccp_rcv_close(sk, skb);
-               return 0;
+               if (dccp_rcv_close(sk, skb))
+                       return 0;
+               goto discard;
        case DCCP_PKT_REQUEST:
                /* Step 7
                 *   or (S.is_server and P.type == Response)
@@ -577,11 +633,13 @@ int dccp_rcv_state_process(struct sock *
                dccp_send_sync(sk, dcb->dccpd_seq, DCCP_PKT_SYNC);
                goto discard;
        } else if (dh->dccph_type == DCCP_PKT_CLOSEREQ) {
-               dccp_rcv_closereq(sk, skb);
+               if (dccp_rcv_closereq(sk, skb))
+                       return 0;
                goto discard;
        } else if (dh->dccph_type == DCCP_PKT_CLOSE) {
-               dccp_rcv_close(sk, skb);
-               return 0;
+               if (dccp_rcv_close(sk, skb))
+                       return 0;
+               goto discard;
        }
 
        switch (sk->sk_state) {
--- a/net/dccp/proto.c
+++ b/net/dccp/proto.c
@@ -71,7 +71,8 @@ void dccp_set_state(struct sock *sk, con
                break;
 
        case DCCP_CLOSED:
-               if (oldstate == DCCP_CLOSING || oldstate == DCCP_OPEN)
+               if (oldstate == DCCP_OPEN || oldstate == DCCP_ACTIVE_CLOSEREQ ||
+                   oldstate == DCCP_CLOSING)
                        DCCP_INC_STATS(DCCP_MIB_ESTABRESETS);
 
                sk->sk_prot->unhash(sk);
@@ -92,6 +93,24 @@ void dccp_set_state(struct sock *sk, con
 
 EXPORT_SYMBOL_GPL(dccp_set_state);
 
+static void dccp_finish_passive_close(struct sock *sk)
+{
+       switch (sk->sk_state) {
+       case DCCP_PASSIVE_CLOSE:
+               /* Node (client or server) has received Close packet. */
+               dccp_send_reset(sk, DCCP_RESET_CODE_CLOSED);
+               dccp_set_state(sk, DCCP_CLOSED);
+               break;
+       case DCCP_PASSIVE_CLOSEREQ:
+               /*
+                * Client received CloseReq. We set the `active' flag so that
+                * dccp_send_close() retransmits the Close as per RFC 4340, 8.3.
+                */
+               dccp_send_close(sk, 1);
+               dccp_set_state(sk, DCCP_CLOSING);
+       }
+}
+
 void dccp_done(struct sock *sk)
 {
        dccp_set_state(sk, DCCP_CLOSED);
@@ -750,19 +769,26 @@ int dccp_recvmsg(struct kiocb *iocb, str
 
                dh = dccp_hdr(skb);
 
-               if (dh->dccph_type == DCCP_PKT_DATA ||
-                   dh->dccph_type == DCCP_PKT_DATAACK)
+               switch (dh->dccph_type) {
+               case DCCP_PKT_DATA:
+               case DCCP_PKT_DATAACK:
                        goto found_ok_skb;
 
-               if (dh->dccph_type == DCCP_PKT_RESET ||
-                   dh->dccph_type == DCCP_PKT_CLOSE) {
-                       dccp_pr_debug("found fin ok!\n");
+               case DCCP_PKT_CLOSE:
+               case DCCP_PKT_CLOSEREQ:
+                       if (!(flags & MSG_PEEK))
+                               dccp_finish_passive_close(sk);
+                       /* fall through */
+               case DCCP_PKT_RESET:
+                       dccp_pr_debug("found fin (%s) ok!\n",
+                                     dccp_packet_name(dh->dccph_type));
                        len = 0;
                        goto found_fin_ok;
+               default:
+                       dccp_pr_debug("packet_type=%s\n",
+                                     dccp_packet_name(dh->dccph_type));
+                       sk_eat_skb(sk, skb, 0);
                }
-               dccp_pr_debug("packet_type=%s\n",
-                             dccp_packet_name(dh->dccph_type));
-               sk_eat_skb(sk, skb, 0);
 verify_sock_status:
                if (sock_flag(sk, SOCK_DONE)) {
                        len = 0;
@@ -864,28 +890,30 @@ out:
 
 EXPORT_SYMBOL_GPL(inet_dccp_listen);
 
-static const unsigned char dccp_new_state[] = {
-       /* current state:   new state:      action:     */
-       [0]               = DCCP_CLOSED,
-       [DCCP_OPEN]       = DCCP_CLOSING | DCCP_ACTION_FIN,
-       [DCCP_REQUESTING] = DCCP_CLOSED,
-       [DCCP_PARTOPEN]   = DCCP_CLOSING | DCCP_ACTION_FIN,
-       [DCCP_LISTEN]     = DCCP_CLOSED,
-       [DCCP_RESPOND]    = DCCP_CLOSED,
-       [DCCP_CLOSING]    = DCCP_CLOSED,
-       [DCCP_TIME_WAIT]  = DCCP_CLOSED,
-       [DCCP_CLOSED]     = DCCP_CLOSED,
-};
-
-static int dccp_close_state(struct sock *sk)
+static void dccp_terminate_connection(struct sock *sk)
 {
-       const int next = dccp_new_state[sk->sk_state];
-       const int ns = next & DCCP_STATE_MASK;
+       u8 next_state = DCCP_CLOSED;
 
-       if (ns != sk->sk_state)
-               dccp_set_state(sk, ns);
+       switch (sk->sk_state) {
+       case DCCP_PASSIVE_CLOSE:
+       case DCCP_PASSIVE_CLOSEREQ:
+               dccp_finish_passive_close(sk);
+               break;
+       case DCCP_PARTOPEN:
+               dccp_pr_debug("Stop PARTOPEN timer (%p)\n", sk);
+               inet_csk_clear_xmit_timer(sk, ICSK_TIME_DACK);
+               /* fall through */
+       case DCCP_OPEN:
+               dccp_send_close(sk, 1);
 
-       return next & DCCP_ACTION_FIN;
+               if (dccp_sk(sk)->dccps_role == DCCP_ROLE_SERVER)
+                       next_state = DCCP_ACTIVE_CLOSEREQ;
+               else
+                       next_state = DCCP_CLOSING;
+               /* fall through */
+       default:
+               dccp_set_state(sk, next_state);
+       }
 }
 
 void dccp_close(struct sock *sk, long timeout)
@@ -922,8 +950,8 @@ void dccp_close(struct sock *sk, long ti
        if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
                /* Check zero linger _after_ checking for unread data. */
                sk->sk_prot->disconnect(sk, 0);
-       } else if (dccp_close_state(sk)) {
-               dccp_send_close(sk, 1);
+       } else if (sk->sk_state != DCCP_CLOSED) {
+               dccp_terminate_connection(sk);
        }
 
        sk_stream_wait_close(sk, timeout);
--- a/include/linux/dccp.h
+++ b/include/linux/dccp.h
@@ -260,7 +260,6 @@ enum dccp_state {
 };
 
 #define DCCP_STATE_MASK 0x1f
-#define DCCP_ACTION_FIN (1<<7)
 
 enum {
        DCCPF_OPEN            = TCPF_ESTABLISHED,
-
To unsubscribe from this list: send the line "unsubscribe dccp" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to