Descrizione della procedura di ricezione di un pacchetto a livello di TCP
-------------------------------------------------------------------------
autore : Angelo Dell'Aera - buffer@users.sourceforge.net
--------------------------------------------------------


TCP Timers
----------

Vedere il sorgente tcp_timer.c.

Questa  l'inizializzazione dei 3 timer per il retransmit, per i delayed
ACKs e per il keepalive probe (che da quanto stabilito da RFC dovrebbe
avvenire dopo 2 ore di connessione idle).

void tcp_init_xmit_timers(struct sock *sk)
{
        struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;

        init_timer(&tp->retransmit_timer);
        tp->retransmit_timer.function=&tcp_write_timer;
        tp->retransmit_timer.data = (unsigned long) sk;
        tp->pending = 0;

        init_timer(&tp->delack_timer);
        tp->delack_timer.function=&tcp_delack_timer;
	tp->delack_timer.data = (unsigned long) sk;
        tp->ack.pending = 0;

        init_timer(&sk->timer);
        sk->timer.function=&tcp_keepalive_timer;
        sk->timer.data = (unsigned long) sk;
}

Andiamo avanti....ricordando che

static inline int timer_pending (const struct timer_list * timer)
        {
                return timer->list.next != NULL;
        }

analizziamo la seguente funzione....

void tcp_clear_xmit_timers(struct sock *sk)
{
        struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;

        tp->pending = 0;

                /* Non ci sono ritrasmissioni da effettuare e
                 * abbiamo rimosso con successo il timer.....
                 * bene allora possiamo concederci un bel
                 * atomic_dec sul refcnt di sk...stessa storia
                 * per gli altri timer.
                 */

        if (timer_pending(&tp->retransmit_timer) &&
            del_timer(&tp->retransmit_timer))
                __sock_put(sk);

        tp->ack.pending = 0;
        tp->ack.blocked = 0;
        if (timer_pending(&tp->delack_timer) &&
            del_timer(&tp->delack_timer))
                __sock_put(sk);

        if(timer_pending(&sk->timer) && del_timer(&sk->timer))
                __sock_put(sk);
}

Vediamo gli handler lanciati alla scadenza dei timer. Per i nostri scopi
 importante RTO handler tcp_write_timer() con parametro sk.
Vediamo il codice....


static void tcp_write_timer(unsigned long data)
        {
                struct sock *sk = (struct sock*)data;
                struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
                int event;


                /* Operazione critica...lock! */

                bh_lock_sock(sk);
                if (sk->lock.users) {
                        /* Try again later */
                        if (!mod_timer(&tp->retransmit_timer, jiffies + (HZ/20)))
                                sock_hold(sk);
                        goto out_unlock;
                }

                /* Il socket  in stato CLOSED? E che ci stiamo a fare
                 * qui? Ovviamente si salta a out anche se pending = 0
                 * pending 'scheduled timer event'
                 */

                if (sk->state == TCP_CLOSE || !tp->pending)
                        goto out;

                /* Timeout > jiffies ?
		 * Aggiornare il timer  d'obbligo allora!
                 */

                if ((long)(tp->timeout - jiffies) > 0) {
                        if (!mod_timer(&tp->retransmit_timer, tp->timeout))
                                sock_hold(sk);
                        goto out;
                }

                event = tp->pending;
                tp->pending = 0;

                switch (event) {
                case TCP_TIME_RETRANS:

                        /* Eccolo qui RTO handling inizia qui */
                        tcp_retransmit_timer(sk);
                        break;
                case TCP_TIME_PROBE0:
                        tcp_probe_timer(sk);
                        break;
                }
                TCP_CHECK_TIMER(sk);

        out:
                tcp_mem_reclaim(sk);
        out_unlock:
                bh_unlock_sock(sk);
                sock_put(sk);
        }

        /*
         *      The TCP retransmit timer.
         */

        static void tcp_retransmit_timer(struct sock *sk)
        {
                struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;

                /* Se snd_nxt - snd_una == 0 usciamo....
                 * l'unica cosa in flight  questo handler
                 * che non DEVE fare nulla!
                 */

                if (tp->packets_out == 0)
                        goto out;

                BUG_TRAP(!skb_queue_empty(&sk->write_queue));

                /* Niente finestra (snd_wnd).
                 * Oltre a questo che significa sk->dead?! Qui Dave
                 * Miller una parolina ce la poteva anche buttare nei
                 * sorgenti....vabb andiamo avanti!
                 * Inoltre lo stato deve essere diverso da
                 * TCP_SYN_SENT | TCP_SYN_RECV (penso a causa del fatto che
                 * in questo caso ci sia un timer a parte di 75 secondi
                 * per la ricezione del SYN|ACK o per il SYN...vedremo!)
                 */

                if (tp->snd_wnd == 0 && !sk->dead &&
                    !((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV))) {
                        /* Receiver dastardly shrinks window. Our retransmits
                         * become zero probes, but we should not timeout this
                         * connection. If the socket is an orphan, time it out,
                         * we cannot allow such beasts to hang infinitely.
                         */

	        /* Saltiamo il codice di debug... */

        #ifdef TCP_DEBUG
                        if (net_ratelimit())
                                printk(KERN_DEBUG "TCP: Treason uncloaked! Peer %u.%u.%u.%u:%u/%u
                                                shrinks window %u:%u. Repaired.\n",
                                       NIPQUAD(sk->daddr), htons(sk->dport), sk->num,
                                       tp->snd_una, tp->snd_nxt);
        #endif

                        /* Che succede adesso? tcp_time_stamp (jiffies) ha superato
                         * rcv_tstamp di pi di TCP_RTO_MAX...se non ricordo male
                         * tcp_write_err restituisce -ETIMEDOUT e mette il sk in
                         * stato CLOSED e cancella i timers.
                         */

                        if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {
                                tcp_write_err(sk);
                                goto out;
                        }

			/* Ecco qui il punto di ingresso..si cambia stato
                         * nella TCP state machine. Si entra in stato Loss!
                         * Subito dopo si ritrasmette.
                         */

                        tcp_enter_loss(sk, 0);
                        tcp_retransmit_skb(sk, skb_peek(&sk->write_queue));
                        __sk_dst_reset(sk);
                        goto out_reset_timer;
                }

                if (tcp_write_timeout(sk))
                        goto out;

                /* retransmits = 'number of unrecovered RTO timeouts' */

                if (tp->retransmits == 0) {
                        if (tp->ca_state == TCP_CA_Disorder || tp->ca_state == TCP_CA_Recovery) {
                                if (tp->sack_ok) {
                                        if (tp->ca_state == TCP_CA_Recovery)
                                                NET_INC_STATS_BH(TCPSackRecoveryFail);
                                        else
                                                NET_INC_STATS_BH(TCPSackFailures);
                                } else {
                                        if (tp->ca_state == TCP_CA_Recovery)
                                                NET_INC_STATS_BH(TCPRenoRecoveryFail);
                                        else
                                                NET_INC_STATS_BH(TCPRenoFailures);
                                }
                        } else if (tp->ca_state == TCP_CA_Loss) {
			        NET_INC_STATS_BH(TCPLossFailures);
                        } else {
                                NET_INC_STATS_BH(TCPTimeouts);
                        }
                }

                tcp_enter_loss(sk, 0);

                if (tcp_retransmit_skb(sk, skb_peek(&sk->write_queue)) > 0) {
                        /* Retransmission failed because of local congestion,
                         * do not backoff.
                         */
                        if (!tp->retransmits)
                                tp->retransmits=1;
                        tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS,
                                             min(tp->rto, TCP_RESOURCE_PROBE_INTERVAL));
                        goto out;
                }

                /* Increase the timeout each time we retransmit.  Note that
                 * we do not increase the rtt estimate.  rto is initialized
                 * from rtt, but increases here.  Jacobson (SIGCOMM 88) suggests
                 * that doubling rto each time is the least we can get away with.
                 * In KA9Q, Karn uses this for the first few times, and then
                 * goes to quadratic.  netBSD doubles, but only goes up to *64,
                 * and clamps at 1 to 64 sec afterwards.  Note that 120 sec is
                 * defined in the protocol as the maximum possible RTT.  I guess
                 * we'll have to use something other than TCP to talk to the
  		 * University of Mars.
                 *
                 * PAWS allows us longer timeouts and large windows, so once
                 * implemented ftp to mars will work nicely. We will have to fix
                 * the 120 second clamps though!
                 */

                tp->backoff++;
                tp->retransmits++;

        out_reset_timer:
                tp->rto = min(tp->rto << 1, TCP_RTO_MAX);
                tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
                if (tp->retransmits > sysctl_tcp_retries1)
                        __sk_dst_reset(sk);

        out:;
        }

