static char rcsident
[] = "$Header: tcp_states.c,v 1.21 85/07/31 09:42:53 walsh Exp $";
#include "../h/socketvar.h"
#include "../net/route.h"
#include "../bbnnet/in.h"
#include "../bbnnet/in_pcb.h"
#include "../bbnnet/in_var.h"
#include "../bbnnet/net.h"
#include "../bbnnet/fsm.h"
#include "../bbnnet/tcp.h"
#include "../bbnnet/seq.h"
#include "../bbnnet/ip.h"
#include "../bbnnet/macros.h"
#include "../bbnnet/sws.h"
#include "../bbnnet/hmp_traps.h"
extern struct rtentry
*ip_route();
* These are the action routines of the TCP finite state machine. They are
* called from the TCP fsm dispatcher action(). These routines call
* on the routines in tcp_procs.c to do the actual segment processing.
* UNOPENED x IUOPENA == passive open == listen()
register struct tcpcb
*tp
= wp
->w_tcb
;
/* listen() system call */
* Don't know who we're talking to yet, so we don't have a route
tp
->t_maxseg
= TCPMAXSND
;
tp
->t_timers
[TINIT
] = tp
->t_itimeo
;
* UNOPENED x IUOPENR == active open == connect()
register struct work
*wp
;
register struct tcpcb
*tp
;
register struct inpcb
*inp
;
/* connect() system call */
* Know foreign host and have a route to there.
tp
->t_maxseg
= inp
->inp_route
.ro_rt
->rt_ifp
->if_mtu
- TCPIPMAX
;
* Best can do until other guy tells us otherwise.
MIN(inp
->inp_route
.ro_rt
->rt_ifp
->if_mtu
- TCPIPMAX
, TCPMAXSND
);
tp
->t_maxseg
-= inp
->inp_optlen
;
tp
->t_timers
[TINIT
] = (tp
->t_itimeo
? tp
->t_itimeo
: TCP_tvINIT
);
send_tcp(tp
, TCP_CTL
); /* send SYN */
* User close request before receiving foreign SYN
t_close(wp
->w_tcb
, ECONNABORTED
);
* close request on synched connection
register struct tcpcb
*tp
= wp
->w_tcb
;
tp
->snd_fin
= TRUE
; /* send FIN */
tp
->t_noact
= TCP_tvNOACT
;
tp
->t_timers
[TNOACT
] = TCP_tvNOACT
;
* close request after received foreign FIN
register struct tcpcb
*tp
= wp
->w_tcb
;
tp
->snd_fin
= TRUE
; /* send our own FIN */
tp
->t_noact
= TCP_tvNOACT
;
tp
->t_timers
[TNOACT
] = TCP_tvNOACT
;
* User abort request on unsynched connection
t_close(wp
->w_tcb
, ECONNABORTED
);
* User abort request on synched connection
register struct tcpcb
*tp
= wp
->w_tcb
;
tp
->snd_rst
= TRUE
; /* send reset */
(void) send_pkt(tp
, 0, 0);
/* tp->ack_due = FALSE; don't since about to throw tcpcb away */
t_close(tp
, ECONNABORTED
);
* From tcp_input/netprepr, we know the packet is a well formed SYN
register struct tcpcb
*tp
;
register struct inpcb
*inp
;
register struct socket
*so
, *newso
;
extern struct in_addr ip_hops
[];
n
= (struct th
*)wp
->w_dat
;
* Need to route on basis of IP destination -- see ip_send()
* ### What if loose routing and 1st hop not on local net and reroute?
/* source routed SYN packet */
firsthop
= ip_hops
[ip_nhops
];
* O.k., let's get a route back to him
if (!(rt
= ip_route(&n
->t_d
, &firsthop
)))
* Can't talk to him. Leave socket in receive state
* so we can connect to someone else, since we haven't
* been committed to anything yet anyway.
* ### Drop his info on the floor.
* Let the other machine just figure out on it's own that
* it can't reach us that way.
no_route ("tcp", n
->t_d
, firsthop
);
* This socket is in the listen state, so the socket should have
* so_options & SO_ACCEPTCONN set (solisten()).
* The order of sonewconn() and soisconnected() is
* important, in order for the process to be woken up
* at a time when the sleep condition is fulfilled.
* sonewconn() is done here on the original socket, and
* soisconnected() is done later in syr_netr() on the new
if (newso
= sonewconn(so
))
newinp
= (struct inpcb
*) newso
->so_pcb
;
newtp
= (struct tcpcb
*) newinp
->inp_ppcb
;
* Remember our peer for this connection.
newinp
->inp_faddr
= n
->t_s
;
newinp
->inp_fport
= n
->t_src
;
newinp
->inp_laddr
= n
->t_d
;
* optlen includes the source route to be copied
* to the outgoing IP header, not the firsthop
bcopy((caddr_t
)ip_hops
,newinp
->inp_options
, (unsigned)(ip_nhops
+1)*4);
newinp
->inp_optlen
= ip_nhops
* 4;
* and copy fields into the new inpcb
newinp
->inp_lport
= inp
->inp_lport
;
newinp
->inp_route
.ro_rt
= rt
;
* and copy fields to the new tcpcb
newtp
->t_maxfrag
= tp
->t_maxfrag
; /* set in tcp_input() */
newtp
->t_itimeo
= tp
->t_itimeo
;
newtp
->t_noact
= tp
->t_noact
;
newtp
->t_push
= tp
->t_push
;
newtp
->t_noactsig
= tp
->t_noactsig
;
newtp
->t_noactprobe
= tp
->t_noactprobe
;
* and initialize others with new info
* Upward negotiation of t_maxseg in tcp_opt() done
newtp
->t_maxseg
= MIN(rt
->rt_ifp
->if_mtu
- TCPIPMAX
, tp
->t_maxseg
);
newtp
->t_maxseg
-= newinp
->inp_optlen
;
* In case next client doesn't negotiate maxseg.
tp
->t_maxseg
= TCPMAXSND
;
if (!(newtp
->t_template
= tcp_template(newtp
)))
newtp
->sws_qff
= SWS_QFF_DEF
;
* So can debug connection problems without having to change
* every program or apply debugging flag to each program every
dowedebug(newinp
, newso
, &tcp_dfilter
);
* rcv_tcp may set fin_rcvd. If so, We went up and down or
* we got a garbage/misrouted packet. If it's set, it's
* meant for some other socket or some other instantiation
* of it. In any case, ignore it and listen for other
rcv_tcp(newtp
, n
, TCP_DATA
);
* start init timer now that we have foreign host.
* Parent socket might have init timer as zero to
* avoid getting ETIMEDOUT, but we do want this
* child socket to time out on synchronization
* just in case other host just went down.
newtp
->t_timers
[TINIT
] = (newtp
->t_itimeo
!= 0
newtp
->t_state
= L_SYN_RCVD
;
return(LISTEN
); /* original file descriptor stays in LISTEN state */
* from tcp_input/netprepr, we know its a SYN, with perhaps a well formed ACK
register struct tcpcb
*tp
= wp
->w_tcb
;
register struct th
*n
= (struct th
*)wp
->w_dat
;
rcv_tcp(tp
, n
, TCP_DATA
);
/* if good ACK, present any data */
if (SEQ_GT(n
->t_ackno
, tp
->iss
)) /* 32 */
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
tp
->t_timers
[TNOACT
] = tp
->t_noact
;
/* if good ACK, open connection, otherwise wait for one */
tp
->t_timers
[TNOACT
] = tp
->t_noact
;
soisconnected (tp
->t_in_pcb
->inp_socket
);
return(SYN_RCVD
); /* 8 */
* from tcp_input/netprepr, we know its an ACK of our SYN
register struct tcpcb
*tp
= wp
->w_tcb
;
register struct th
*n
= (struct th
*)wp
->w_dat
;
rcv_tcp(tp
, n
, TCP_DATA
);
/* if no FIN, open connection, otherwise wait for user close */
tp
->t_timers
[TNOACT
] = tp
->t_noact
;
if (tp
->fin_rcvd
) /* 33 */
soisconnected (tp
->t_in_pcb
->inp_socket
);
register struct tcpcb
*tp
= wp
->w_tcb
;
rcv_tcp(tp
, (struct th
*)wp
->w_dat
, TCP_DATA
);
/* if no FIN, remain open, otherwise wait for user close */
if (tp
->fin_rcvd
) /* 12 */
* incoming segment after user has closed
register struct tcpcb
*tp
= wp
->w_tcb
;
register struct th
*n
= (struct th
*)wp
->w_dat
;
/* process any incoming data, since we closed but they didn't */
rcv_tcp(tp
, n
, TCP_DATA
);
/* send any data remaining on send buffer */
{ /* our FIN got ACKed */
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
else /* no FIN, wait (27) */
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
* incoming segment while waiting for foreign FIN
register struct tcpcb
*tp
= wp
->w_tcb
;
register struct th
*n
= (struct th
*)wp
->w_dat
;
/* process data since we closed, but they may not have */
rcv_tcp(tp
, n
, TCP_DATA
);
/* if we get the FIN, start the finack timer, else keep waiting */
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
* The close protocol (exchange of FINs) has progressed as far as it can.
* We do not enter CLOSED immediately, but use TIME_WAIT so that if our ack
* of other guys FIN didn't reach him, he can retransmit and we'll ack his
* fin rather than respond with an rst.
* Since we received a packet, apparently our ack of his fin didn't get
* there and we'll have to try again. Restart finack timer in case this
register struct tcpcb
*tp
= wp
->w_tcb
;
rcv_tcp(tp
, (struct th
*) wp
->w_dat
, TCP_DATA
);
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
* incoming segment after receipt of foreign FIN (local end still open)
register struct tcpcb
*tp
= wp
->w_tcb
;
register struct th
*n
= (struct th
*)wp
->w_dat
;
/* either duplicate FIN or data */
if (n
->t_flags
&T_ACK
&& SEQ_LEQ(n
->t_ackno
, tp
->seq_fin
))
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
{ /* duplicate data (39) */
rcv_tcp(tp
, n
, TCP_DATA
);
* incoming segment after we closed
register struct tcpcb
*tp
= wp
->w_tcb
;
register struct th
*n
= (struct th
*)wp
->w_dat
;
{ /* got ACK of our FIN */
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
/* if wait done, see if any data left for user */
t_close(tp
, ECONNABORTED
);
return(RCV_WAIT
); /* 18 */
return(TIME_WAIT
); /* 22 */
{ /* our FIN not ACKed yet */
{ /* rcvd for FIN (30) */
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
{ /* no FIN, just proc new data (39) */
rcv_tcp(tp
, n
, TCP_DATA
);
* incoming segment after both of us have started closing
register struct tcpcb
*tp
= wp
->w_tcb
;
register struct th
*n
= (struct th
*)wp
->w_dat
;
{ /* this is ACK of our fin */
/* if no data left for user, close; otherwise wait */
t_close(tp
, ECONNABORTED
);
{ /* no ACK of our FIN */
/* duplicate FIN or data */
if (n
->t_flags
&T_FIN
) /* 31 */
send_tcp(tp
, TCP_CTL
); /* ACK duplicate FIN */
rcv_tcp(tp
, n
, TCP_DATA
);
rwt_netr(wp
) /* incoming seg while waiting for user rcv (30,21) */
register struct tcpcb
*tp
= wp
->w_tcb
;
register struct th
*n
= (struct th
*)wp
->w_dat
;
/* handle duplicate ACK of our FIN */
if (n
->t_flags
&T_FIN
&& n
->t_flags
&T_ACK
&& SEQ_LEQ(n
->t_ackno
, tp
->seq_fin
))
tp
->t_timers
[TFINACK
] = TCP_tv2ML
;
* and allowing for shutdown()
sss_rcv(wp
) /* rcv request on open connection (42) */
register struct tcpcb
*tp
= wp
->w_tcb
;
/* if last window sent was zero, send an ACK to update window */
tp
->force_ack
= TRUE
; /* don't delay ACK here */
cls_rwt(wp
) /* rcv request after foreign close (20) */
register struct tcpcb
*tp
= wp
->w_tcb
;
present_data(tp
); /* present any remaining data */
t_close(tp
, ECONNABORTED
);
* For SYN_SENT and SYN_RCVD, just want to buffer data until connected.
sss_snd(wp
) /* send request on open connection (40,41) */
register struct tcpcb
*tcp
= wp
->w_tcb
;
register struct inpcb
*inp
= tcp
->t_in_pcb
;
sbappend(&inp
->inp_socket
->so_snd
, (struct mbuf
*) wp
->w_dat
);
last
= tcp
->snd_una
+ inp
->inp_socket
->so_snd
.sb_cc
;
tcp
->snd_urp
= last
; /* this byte is not urgent */
cls_act(wp
) /* net closing open connection (47) */
t_close(wp
->w_tcb
, ECONNABORTED
);
cls_err(wp
) /* invalid user request in closing states */
advise_user(tcpcbtoso(wp
->w_tcb
), ECONNABORTED
);
timers(wp
) /* timer processor (14,17,34,35,36,37,38) */
register struct tcpcb
*tp
= wp
->w_tcb
;
register type
= wp
->w_stype
;
case TINIT
: /* initialization timer */
* Haven't got an ACK of our SYN yet
if (tp
->t_in_pcb
->inp_socket
->so_state
& SS_NOFDREF
)
* was a child socket of a listen(2)er trying to
* establish connection with other end.
/* socket in connect(2) */
advise_user(tcpcbtoso(tp
), ETIMEDOUT
);
tp
->t_timers
[TINIT
] = tp
->t_itimeo
;
case TFINACK
: /* fin-ack timer */
if (tp
->t_state
== TIME_WAIT
)
/* can be sure our ACK of for FIN was rcvd,
can close if no data left for user */
t_close(tp
, ECONNABORTED
);
else if (tp
->t_state
== CLOSING1
) /* 37 */
case TREXMT
: /* retransmission timer */
* If we're retransmitting, then the network
* may be dropping packets because it is overloaded.
* Therefore, increase the retransmission time for
* successive retransmissions. When we get an ACK,
* the srtt and rxmitime will be recalculated.
tp
->t_rxmitime
= tp
->t_rxmitime
<< 1;
if (tp
->t_rxmitime
> TCP_tvRXMAX
)
tp
->t_rxmitime
= TCP_tvRXMAX
;
tp
->snd_nxt
= tp
->snd_una
;
case TREXMTTL
: /* retransmit too long */
/* hmp_trap(T_TCP_REXMTTL, (caddr_t)0, 0); */
/* user has already closed for r/w so abort connection
* usr_closed == closed for w (close or shutdown).
advise_user(tcpcbtoso(tp
), ETIMEDOUT
);
tp
->t_timers
[TREXMTTL
] = tp
->t_rttltimeo
;
case TPERSIST
: /* persist timer */
/* force a byte send through closed window */
tp
->force_one
= TRUE
; /* 38 */
send_tcp(tp
, TCP_DATA
); /* restarts timer */
case TDELACK
: /* ack-delay timer */
/* make sure an ack gets sent now */
case TNOACT
: /* no activity timer */
* This timer is used for 2 reasons:
* 1) by the user to determine if the connection is idle or if the
* other side has aborted/rebooted... This is open states entry.
* 2) by the system to timeout on receipt of ACK of our FIN.
* This is separate from use of FINACK timer for other guy
* to get our ACK of his FIN. If closing has started, finish it.
* usr_closed == TRUE, usr_abort == FALSE
* the user will find out about any problems getting an ACK of our
* FIN through the retransmit took too long timer
* the connection could be idle because it takes the remote end a
* while to compute and produce a reply
* user only gets to crank up protocol close once, but he can
* shutdown and then close, thereby adjusting usr_abort so
* that things get cleaned up if the remote host died.
* usr_closed == TRUE, usr_abort == TRUE
* user could be lingering (and SS_NOFDREF will still be false)
* connection could be idle because the other host failed, and it
* could be down for days. We don't want to wait for it to
* come back up and give us a reset. Release resources now.
advise_user(tcpcbtoso(tp
), ETIMEDOUT
);
tp
->t_timers
[TNOACT
] = tp
->t_noact
;