tcp-sack1.cc

Go to the documentation of this file.
00001 /* -*-  Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */
00002 /*
00003  * Copyright (c) 1990, 1997 Regents of the University of California.
00004  * All rights reserved.
00005  *
00006  * Redistribution and use in source and binary forms are permitted
00007  * provided that the above copyright notice and this paragraph are
00008  * duplicated in all such forms and that any documentation,
00009  * advertising materials, and other materials related to such
00010  * distribution and use acknowledge that the software was developed
00011  * by the University of California, Lawrence Berkeley Laboratory,
00012  * Berkeley, CA.  The name of the University may not be used to
00013  * endorse or promote products derived from this software without
00014  * specific prior written permission.
00015  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
00016  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
00017  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00018  */
00019 
00020 /* 8/02 Tom Kelly - Made scoreboard a general interface to allow
00021  *                  easy swapping of scoreboard algorithms.  
00022  */
00023 
00024 #include <stdio.h>
00025 #include <stdlib.h>
00026 #include <sys/types.h>
00027 
00028 #include "ip.h"
00029 #include "tcp.h"
00030 #include "flags.h"
00031 #include "scoreboard.h"
00032 #include "scoreboard-rq.h"
00033 #include "random.h"
00034 
00035 #define TRUE    1
00036 #define FALSE   0
00037 #define RECOVER_DUPACK  1
00038 #define RECOVER_TIMEOUT 2
00039 #define RECOVER_QUENCH  3
00040 
00041 class Sack1TcpAgent : public TcpAgent {
00042  public:
00043     Sack1TcpAgent();
00044     virtual ~Sack1TcpAgent();
00045     virtual void recv(Packet *pkt, Handler*);
00046     int is_sacked(hdr_tcp *tcph, int seqlo, int seqhi);
00047     void reset();
00048     virtual void timeout(int tno);
00049     virtual void dupack_action();
00050     virtual void partial_ack_action();
00051     void plot();
00052     virtual void send_much(int force, int reason, int maxburst);
00053  protected:
00054     u_char timeout_;    /* boolean: sent pkt from timeout? */
00055     u_char fastrecov_;  /* boolean: doing fast recovery? */
00056     int pipe_;      /* estimate of pipe size (fast recovery) */ 
00057     int partial_ack_;   /* Set to "true" to ensure sending */
00058                 /*  a packet on a partial ACK.     */
00059     int next_pkt_;      /* Next packet to transmit during Fast */
00060                 /*  Retransmit as a result of a partial ack. */
00061     int firstpartial_;  /* First of a series of partial acks. */
00062     ScoreBoard* scb_;
00063     static const int SBSIZE=64; /* Initial scoreboard size */
00064 };
00065 
00066 static class Sack1TcpClass : public TclClass {
00067 public:
00068     Sack1TcpClass() : TclClass("Agent/TCP/Sack1") {}
00069     TclObject* create(int, const char*const*) {     
00070         return (new Sack1TcpAgent());
00071     }
00072 } class_sack;
00073 
00074 Sack1TcpAgent::Sack1TcpAgent() : fastrecov_(FALSE), pipe_(-1), next_pkt_(0), firstpartial_(0)
00075 {
00076     bind_bool("partial_ack_", &partial_ack_);
00077     /* Use the Reassembly Queue based scoreboard as
00078      * ScoreBoard is O(cwnd) which is bad for HSTCP
00079      * scb_ = new ScoreBoard(new ScoreBoardNode[SBSIZE],SBSIZE);
00080      */
00081     scb_ = new ScoreBoardRQ();
00082 }
00083 
00084 Sack1TcpAgent::~Sack1TcpAgent(){
00085     delete scb_;
00086 }
00087 
00088 void Sack1TcpAgent::reset ()
00089 {
00090     scb_->ClearScoreBoard();
00091     TcpAgent::reset ();
00092 }
00093 
00094 
00095 void Sack1TcpAgent::recv(Packet *pkt, Handler*)
00096 {
00097     hdr_tcp *tcph = hdr_tcp::access(pkt);
00098     int valid_ack = 0;
00099 
00100         if (qs_approved_ == 1 && tcph->seqno() > last_ack_)
00101         endQuickStart();
00102         if (qs_requested_ == 1)
00103                 processQuickStart(pkt);
00104 #ifdef notdef
00105     if (pkt->type_ != PT_ACK) {
00106         Tcl::instance().evalf("%s error \"received non-ack\"",
00107                       name());
00108         Packet::free(pkt);
00109         return;
00110     }
00111 #endif
00112         /* W.N.: check if this is from a previous incarnation */
00113         if (tcph->ts() < lastreset_) {
00114                 // Remove packet and do nothing
00115                 Packet::free(pkt);
00116                 return;
00117         }
00118     ++nackpack_;
00119     int ecnecho = hdr_flags::access(pkt)->ecnecho();
00120     if (ecnecho && ecn_)
00121         ecn(tcph->seqno());
00122         if (tcph->seqno() >= last_ack_)
00123         // Check if ACK is valid.  Suggestion by Mark Allman.
00124         valid_ack = 1;
00125     /*
00126      * If DSACK is being used, check for DSACK blocks here.
00127      * Possibilities:  Check for unnecessary Fast Retransmits.
00128      */
00129     if (!fastrecov_) {
00130         /* normal... not fast recovery */
00131         if ((int)tcph->seqno() > last_ack_) {
00132             /*
00133              * regular ACK not in fast recovery... normal
00134              */
00135             firstpartial_ = 0;
00136             recv_newack_helper(pkt);
00137             timeout_ = FALSE;
00138             scb_->ClearScoreBoard();
00139             if (last_ack_ == 0 && delay_growth_) {
00140                 cwnd_ = initial_window();
00141             }
00142         } else if ((int)tcph->seqno() < last_ack_) {
00143             /*NOTHING*/
00144         } else if (timeout_ == FALSE) {
00145             if (tcph->seqno() != last_ack_) {
00146                 fprintf(stderr, "pkt seq %d should be %d\n" ,
00147                     tcph->seqno(), last_ack_);
00148                 abort();
00149             }
00150             scb_->UpdateScoreBoard (highest_ack_, tcph);
00151             /*
00152              * Check for a duplicate ACK.
00153              * Check that the SACK block actually
00154              *  acknowledges new data.
00155              */
00156                 if(scb_->CheckUpdate()) {
00157                 if (++dupacks_ == numdupacks(cwnd_)) {
00158                     /*
00159                      * Assume we dropped just one packet.
00160                      * Retransmit last ack + 1
00161                      * and try to resume the sequence.
00162                      */
00163                     dupack_action();
00164                 } else if (dupacks_ < numdupacks(cwnd_) && singledup_ ) {
00165                     send_one();
00166                 }
00167             }
00168             if (sfrto_enabled_ && frto_ == 2) {
00169                 /*
00170                  * SACK-based F-RTO: If SACK only acknowledges
00171                  * data that was transmitted before RTO and
00172                  * not acknowledged earlier,
00173                  * the timeout was spurious.
00174                  */
00175                 if (scb_->IsChanged() &&
00176                     !is_sacked(tcph, recover_, maxseq_)) {
00177                     spurious_timeout();
00178                 } else {
00179                     t_seqno_ = highest_ack_ + 1;
00180                     cwnd_ = frto_;
00181                     frto_ = 0;
00182                     dupacks_ = 0;
00183                 }
00184             }
00185         }
00186             if (valid_ack || aggressive_maxburst_)
00187             if (dupacks_ == 0)
00188                 send_much(FALSE, 0, maxburst_);
00189     } else {
00190         /* we are in fast recovery */
00191         --pipe_;
00192         if ((int)tcph->seqno() >= recover_) {
00193             /* ACK indicates fast recovery is over */
00194             recover_ = 0;
00195             fastrecov_ = FALSE;
00196             newack(pkt);
00197             /* if the connection is done, call finish() */
00198             if ((highest_ack_ >= curseq_-1) && !closed_) {
00199                 closed_ = 1;
00200                 finish();
00201             }
00202             timeout_ = FALSE;
00203             scb_->ClearScoreBoard();
00204 
00205             /* New window: W/2 - K or W/2? */
00206         } else if ((int)tcph->seqno() > highest_ack_) {
00207             /* Not out of fast recovery yet.
00208              * Update highest_ack_, but not last_ack_. */
00209             highest_ack_ = (int)tcph->seqno();
00210             scb_->UpdateScoreBoard (highest_ack_, tcph);
00211             if (partial_ack_) {
00212               /* partial_ack_ is needed to guarantee that */
00213               /*  a new packet is sent in response to a   */
00214               /*  partial ack.                            */
00215                 partial_ack_action();
00216                 ++pipe_;
00217                 if (firstpartial_ == 0) {
00218                     newtimer(pkt);
00219                     t_backoff_ = 1;
00220                     firstpartial_ = 1;
00221                 }
00222             } else {
00223                 --pipe_;
00224                 newtimer(pkt);
00225                 t_backoff_ = 1;
00226              /* If this partial ACK is from a retransmitted pkt,*/
00227              /* then we decrement pipe_ again, so that we never */
00228              /* do worse than slow-start.  If this partial ACK  */
00229              /* was instead from the original packet, reordered,*/
00230              /* then this might be too aggressive. */
00231             }
00232         } else if (timeout_ == FALSE) {
00233             /* got another dup ack */
00234             scb_->UpdateScoreBoard (highest_ack_, tcph);
00235                 if(scb_->CheckUpdate()) {
00236                 if (dupacks_ > 0)
00237                         dupacks_++;
00238             }
00239         }
00240             if (valid_ack || aggressive_maxburst_)
00241             send_much(FALSE, 0, maxburst_);
00242     }
00243 
00244     Packet::free(pkt);
00245 #ifdef notyet
00246     if (trace_)
00247         plot();
00248 #endif
00249 }
00250 
00251 
00252 /*
00253  * Returns TRUE if any of the SACK blocks in the current packet (tcph)
00254  * cover sequence numbers between seqlo and seqhi
00255  */
00256 int Sack1TcpAgent::is_sacked(hdr_tcp *tcph, int seqlo, int seqhi)
00257 {
00258     int i, sleft, sright;
00259     for (i=0; i < tcph->sa_length(); i++) {
00260         sleft = tcph->sa_left(i);
00261         sright = tcph->sa_right(i);
00262 
00263         if ((sright > seqlo && sright <= seqhi) ||
00264             (sleft >= seqlo && sleft < seqhi) ||
00265             (sleft < seqlo && sright > seqhi)) {
00266             return TRUE;
00267         }
00268     }
00269     return FALSE;
00270 }
00271 
00272 
00273 void
00274 Sack1TcpAgent::dupack_action()
00275 {
00276     int recovered = (highest_ack_ > recover_);
00277     if (recovered || (!bug_fix_ && !ecn_)) {
00278         goto sack_action;
00279     }
00280  
00281     if (ecn_ && last_cwnd_action_ == CWND_ACTION_ECN) {
00282         last_cwnd_action_ = CWND_ACTION_DUPACK;
00283         /*
00284          * What if there is a DUPACK action followed closely by ECN
00285          * followed closely by a DUPACK action?
00286          * The optimal thing to do would be to remember all
00287          * congestion actions from the most recent window
00288          * of data.  Otherwise "bugfix" might not prevent
00289          * all unnecessary Fast Retransmits.
00290          */
00291         reset_rtx_timer(1,0);
00292         /*
00293          * There are three possibilities: 
00294          * (1) pipe_ = int(cwnd_) - numdupacks_;
00295          * (2) pipe_ = window() - numdupacks_;
00296          * (3) pipe_ = maxseq_ - highest_ack_ - numdupacks_;
00297          * equation (2) takes into account the receiver's
00298          * advertised window, and equation (3) takes into
00299          * account a data-limited sender.
00300          */
00301         if (singledup_ && LimTransmitFix_) {
00302           pipe_ = maxseq_ - highest_ack_ - 1;
00303         }
00304         else {
00305           // numdupacks(cwnd_) packets have left the pipe
00306           pipe_ = maxseq_ - highest_ack_ - numdupacks(cwnd_);
00307         }
00308         fastrecov_ = TRUE;
00309         scb_->MarkRetran(highest_ack_+1);
00310         output(last_ack_ + 1, TCP_REASON_DUPACK);
00311         return;
00312     }
00313 
00314     if (bug_fix_) {
00315         /*
00316          * The line below, for "bug_fix_" true, avoids
00317          * problems with multiple fast retransmits in one
00318          * window of data.
00319          */
00320         return;
00321     }
00322 
00323 sack_action:
00324     recover_ = maxseq_;
00325     if (oldCode_) {
00326         pipe_ = int(cwnd_) - numdupacks(cwnd_);
00327     } else if (singledup_ && LimTransmitFix_) {
00328           pipe_ = maxseq_ - highest_ack_ - 1;
00329     }
00330     else {
00331           // numdupacks(cwnd_) packets have left the pipe
00332           pipe_ = maxseq_ - highest_ack_ - numdupacks(cwnd_);
00333     }
00334     reset_rtx_timer(1,0);
00335     fastrecov_ = TRUE;
00336     scb_->MarkRetran(highest_ack_+1);
00337         if (!lossQuickStart()) {
00338                 // we are now going into fast_recovery and will trace that event
00339                 trace_event("FAST_RECOVERY");
00340                 last_cwnd_action_ = CWND_ACTION_DUPACK;
00341                 slowdown(CLOSE_SSTHRESH_HALF|CLOSE_CWND_HALF);
00342         output(last_ack_ + 1, TCP_REASON_DUPACK);   // from top
00343         /*
00344          * If dynamically adjusting numdupacks_, record information
00345          *  at this point.
00346          */
00347     }
00348     return;
00349 }
00350 
00351 /*
00352  * Process a packet that acks previously unacknowleges data, but
00353  * does not take us out of Fast Retransmit.
00354  *
00355  * The need for a mechanism to ensure that Sack TCP sends a packet in
00356  * response to a partial ACK has been discussed in
00357  * "Challenges to Reliable Data Transport over Heterogeneous
00358  * Wireless Networks", Hari Balakrishnan, 1998, and in
00359  * "Responding to Spurious Timeouts in TCP", Andrei Gurtov and 
00360  * Reiner Ludwig, 2003. 
00361  */
00362 void
00363 Sack1TcpAgent::partial_ack_action()
00364 {
00365     if (next_pkt_ < highest_ack_ + 1) {
00366         next_pkt_ = highest_ack_ + 1;
00367     }
00368     // Output two packets in response to a partial ack,
00369     //   so as not to do worse than slow-start.
00370     int i;
00371     for (i = 1; i<=2; i++) {
00372         int getNext = scb_->GetNextUnacked(next_pkt_);
00373         if (getNext > next_pkt_) {
00374             next_pkt_ = getNext;
00375         }
00376         if (t_seqno_ < next_pkt_) {
00377             t_seqno_ = next_pkt_;
00378         }
00379         output(next_pkt_, TCP_REASON_PARTIALACK);   
00380         scb_->MarkRetran(next_pkt_);
00381         ++next_pkt_; 
00382     }
00383     return;
00384 }
00385 
00386 void Sack1TcpAgent::timeout(int tno)
00387 {
00388     // F-RTO is not allowed if earlier SACK fast recovery is underway.
00389     int no_frto = fastrecov_;
00390 
00391     if (tno == TCP_TIMER_RTX) {
00392         /*
00393          * IF DSACK and dynamic adjustment of numdupacks_,
00394          *  check whether a smaller value of numdupacks_
00395          *  would have prevented this retransmit timeout.
00396          * If DSACK and detection of premature retransmit
00397          *  timeouts, then save some info here.
00398          */ 
00399         dupacks_ = 0;
00400         fastrecov_ = FALSE;
00401         timeout_ = TRUE;
00402         if (highest_ack_ > last_ack_)
00403             last_ack_ = highest_ack_;
00404 #ifdef DEBUGSACK1A
00405         printf ("timeout. highest_ack: %i seqno: %i fid: %i\n", 
00406             (int)highest_ack_, (int)t_seqno_, fid_);
00407 #endif
00408         recover_ = maxseq_;
00409         scb_->ClearScoreBoard();
00410     }
00411     TcpAgent::timeout(tno);
00412 
00413     // Overrule frto_ setting that may have been done in TcpAgent
00414     if (no_frto)
00415         frto_ = 0;
00416 }
00417 
00418 void Sack1TcpAgent::send_much(int force, int reason, int maxburst)
00419 {
00420     register int found, npacket = 0;
00421     int win = window();
00422     int xmit_seqno;
00423 
00424     found = 1;
00425     if (!force && delsnd_timer_.status() == TIMER_PENDING)
00426         return;
00427     /*
00428      * as long as the pipe is open and there is app data to send...
00429      */
00430     while (((!fastrecov_ && (t_seqno_ <= last_ack_ + win)) ||
00431             (fastrecov_ && (pipe_ < int(cwnd_)))) 
00432             && t_seqno_ < curseq_ && found) {
00433 
00434         if (overhead_ == 0 || force) {
00435             found = 0;
00436             xmit_seqno = scb_->GetNextRetran ();
00437 
00438 #ifdef DEBUGSACK1A
00439             printf("highest_ack: %d xmit_seqno: %d\n", 
00440             (int)highest_ack_, xmit_seqno);
00441 #endif
00442             if (xmit_seqno == -1) { 
00443                 if ((!fastrecov_ && t_seqno_<=highest_ack_+win)||
00444                     (fastrecov_ && t_seqno_<=highest_ack_+int(wnd_))) {
00445                     found = 1;
00446                     xmit_seqno = t_seqno_++;
00447 #ifdef DEBUGSACK1A
00448                     printf("sending %d fastrecovery: %d win %d\n",
00449                         xmit_seqno, fastrecov_, win);
00450 #endif
00451                 }
00452             } else if (recover_>0 && xmit_seqno<=highest_ack_+int(wnd_)) {
00453                 found = 1;
00454                 scb_->MarkRetran (xmit_seqno);
00455                 win = window();
00456             }
00457             if (found) {
00458                 output(xmit_seqno, reason);
00459                 if (t_seqno_ <= xmit_seqno)
00460                     t_seqno_ = xmit_seqno + 1;
00461                 npacket++;
00462                 pipe_++;
00463                 if (QOption_)
00464                     process_qoption_after_send () ;
00465                             if (qs_approved_ == 1) {
00466                                     double delay = (double) t_rtt_ * tcp_tick_ / cwnd_;
00467                                     delsnd_timer_.resched(delay);
00468                                     return;
00469                             }
00470             }
00471         } else if (!(delsnd_timer_.status() == TIMER_PENDING)) {
00472             /*
00473              * Set a delayed send timeout.
00474              * This is only for the simulator,to add some
00475              *   randomization if speficied.
00476              */
00477             delsnd_timer_.resched(Random::uniform(overhead_));
00478             return;
00479         }
00480         if (maxburst && npacket == maxburst)
00481             break;
00482     } /* while */
00483 }
00484 
00485 void Sack1TcpAgent::plot()
00486 {
00487 #ifdef notyet
00488     double t = Scheduler::instance().clock();
00489     sprintf(trace_->buffer(), "t %g %d rtt %g\n", 
00490         t, class_, t_rtt_ * tcp_tick_);
00491     trace_->dump();
00492     sprintf(trace_->buffer(), "t %g %d dev %g\n", 
00493         t, class_, t_rttvar_ * tcp_tick_);
00494     trace_->dump();
00495     sprintf(trace_->buffer(), "t %g %d win %f\n", t, class_, cwnd_);
00496     trace_->dump();
00497     sprintf(trace_->buffer(), "t %g %d bck %d\n", t, class_, t_backoff_);
00498     trace_->dump();
00499 #endif
00500 }

Generated on Tue Mar 6 16:47:52 2007 for ns2 Network Simulator 2.29 by  doxygen 1.4.6