working version with template
[unix-history] / usr / src / sys / netinet / tcp_input.c
/* tcp_input.c 1.2 81/10/25 */
#include "../h/param.h"
#include "../h/systm.h"
#include "../bbnnet/net.h"
#include "../bbnnet/mbuf.h"
#include "../bbnnet/host.h"
#include "../bbnnet/imp.h"
#include "../bbnnet/ucb.h"
#include "../bbnnet/tcp.h"
#include "../bbnnet/ip.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/inode.h"
#include "../bbnnet/fsm.h"
extern int nosum;
tcp_input(mp)
register struct mbuf *mp;
{
register struct tcb *tp;
register struct th *n;
int nstate;
struct mbuf *m;
struct ucb *up;
int hlen, tlen, j;
u_short lport, fport;
#ifdef TCPDEBUG
struct tcp_debug tdb;
#endif
COUNT(TCP_INPUT);
/*
* Build extended tcp header
*/
n = (struct th *)((int)mp + mp->m_off);
tlen = ((struct ip *)n)->ip_len;
n->t_len = htons(tlen);
n->t_next = NULL;
n->t_prev = NULL;
n->t_x1 = 0;
lport = ntohs(n->t_dst);
fport = ntohs(n->t_src);
/* WONT BE POSSIBLE WHEN MBUFS ARE 256 BYTES */
if ((hlen = n->t_off << 2) > mp->m_len)
{ printf("tcp header overflow\n"); m_freem(mp); return; }
/*
* Checksum extended header and data
*/
j = n->t_sum; n->t_sum = 0;
if (j != cksum(mp, sizeof (struct ip) + tlen)) {
netstat.t_badsum++;
if (nosum == 0) {
m_freem(mp);
return;
}
}
/*
* Find tcb for message (SHOULDN'T USE LINEAR SEARCH!)
*/
for (tp = netcb.n_tcb_head; tp != 0; tp = tp->t_tcb_next)
if (tp->t_lport == lport && tp->t_fport == fport &&
tp->t_ucb->uc_host->h_addr.s_addr == n->t_s.s_addr)
goto found;
for (tp = netcb.n_tcb_head; tp != 0; tp = tp->t_tcb_next)
if (tp->t_lport == lport &&
(tp->t_fport==fport || tp->t_fport==0) &&
(tp->t_ucb->uc_host->h_addr.s_addr == n->t_s.s_addr ||
tp->t_ucb->uc_host->h_addr.s_addr == 0))
goto found;
goto notwanted;
found:
/*
* Byte swap header
*/
n->t_len = tlen - hlen;
n->t_src = fport;
n->t_dst = lport;
n->t_seq = ntohl(n->t_seq);
n->t_ackno = ntohl(n->t_ackno);
n->t_win = ntohs(n->t_win);
n->t_urp = ntohs(n->t_urp);
/*
* Check segment seq # and do rst processing
*/
switch (tp->t_state) {
case LISTEN:
if ((n->th_flags&TH_ACK) || !syn_ok(tp, n)) {
send_rst(tp, n);
goto badseg;
}
if (n->th_flags&TH_RST)
goto badseg;
goto goodseg;
case SYN_SENT:
if (!ack_ok(tp, n) || !syn_ok(tp, n)) {
send_rst(tp, n); /* 71,72,75 */
goto badseg;
}
if (n->th_flags&TH_RST) {
t_close(tp, URESET); /* 70 */
tp->t_state = CLOSED;
goto badseg;
}
goto goodseg;
default:
if ((n->th_flags&TH_RST) == 0)
goto common;
if (n->t_seq < tp->rcv_nxt) /* bad rst */
goto badseg; /* 69 */
switch (tp->t_state) {
case L_SYN_RCVD:
if (ack_ok(tp, n) == 0)
goto badseg; /* 69 */
tp->t_rexmt = 0;
tp->t_rexmttl = 0;
tp->t_persist = 0;
h_free(tp->t_ucb->uc_host);
tp->t_state = LISTEN;
goto badseg;
default:
t_close(tp, URESET); /* 66 */
tp->t_state = CLOSED;
goto badseg;
}
/*NOTREACHED*/
case SYN_RCVD:
common:
if (ack_ok(tp, n) == 0) {
send_rst(tp, n); /* 74 */
goto badseg;
}
if (syn_ok(tp, n) && n->t_seq != tp->irs) {
send_null(tp); /* 74 */
goto badseg;
}
goto goodseg;
}
badseg:
m_freem(mp);
return;
goodseg:
#ifdef notdef
/*
* Defer processing if no buffer space for this connection.
*/
up = tp->t_ucb;
if ((int)up->uc_rcv - (int)up->uc_rsize <= 0
&& n->t_len != 0 && netcb.n_bufs < netcb.n_lowat) {
mp->m_act = (struct mbuf *)0;
if ((m = tp->t_rcv_unack) != NULL) {
while (m->m_act != NULL)
m = m->m_act;
m->m_act = mp;
} else
tp->t_rcv_unack = mp;
return;
}
#endif
/*
* Discard ip header, and do tcp input processing.
*/
hlen += sizeof(struct ip);
mp->m_off += hlen;
mp->m_len -= hlen;
nstate = tp->t_state;
tp->tc_flags &= ~TC_NET_KEEP;
acounts[tp->t_state][INRECV]++;
#ifdef TCPDEBUG
if ((tp->t_ucb->uc_flags & UDEBUG) || tcpconsdebug) {
tdb.td_tod = time;
tdb.td_tcb = tp;
tdb.td_old = nstate;
tdb.td_inp = INRECV;
tdb.td_tim = 0;
tdb.td_sno = n->t_seq;
tdb.td_ano = n->t_ackno;
tdb.td_wno = n->t_win;
tdb.td_lno = n->t_len;
tdb.td_flg = n->th_flags;
} else
tdb.td_tod = 0;
#endif
switch (tp->t_state) {
case LISTEN:
if (!syn_ok(tp, n) ||
((tp->t_ucb->uc_host = h_make(&n->t_s)) == 0)) {
nstate = EFAILEC;
goto done;
}
tp->t_fport = n->t_src;
tp->t_ucb->uc_template = tcp_template(tp);
rcv_ctldat(tp, n, 1);
if (tp->tc_flags&TC_FIN_RCVD) {
tp->t_finack = T_2ML; /* 3 */
tp->tc_flags &= ~TC_WAITED_2_ML;
nstate = CLOSE_WAIT;
} else {
tp->t_init = T_INIT / 2; /* 4 */
nstate = L_SYN_RCVD;
}
goto done;
case SYN_SENT:
if (!syn_ok(tp, n)) {
nstate = EFAILEC;
goto done;
}
rcv_ctldat(tp, n, 1);
if (tp->tc_flags&TC_FIN_RCVD) {
if (n->th_flags&TH_ACK) {
if (n->t_ackno > tp->iss)
present_data(tp); /* 32 */
} else {
tp->t_finack = T_2ML; /* 9 */
tp->tc_flags &= ~TC_WAITED_2_ML;
}
nstate = CLOSE_WAIT;
goto done;
}
if (n->th_flags&TH_ACK) {
present_data(tp); /* 11 */
nstate = ESTAB;
} else
nstate = SYN_RCVD; /* 8 */
goto done;
case SYN_RCVD:
case L_SYN_RCVD:
if ((n->th_flags&TH_ACK) == 0 ||
(n->th_flags&TH_ACK) && n->t_ackno <= tp->iss) {
nstate = EFAILEC;
goto done;
}
goto input;
case ESTAB:
case FIN_W1:
case FIN_W2:
case TIME_WAIT:
input:
rcv_ctldat(tp, n, 1); /* 39 */
present_data(tp);
switch (tp->t_state) {
case ESTAB:
if (tp->tc_flags&TC_FIN_RCVD)
nstate = CLOSE_WAIT;
break;
case SYN_RCVD:
case L_SYN_RCVD:
nstate = (tp->tc_flags&TC_FIN_RCVD) ?
CLOSE_WAIT : ESTAB; /* 33:5 */
break;
case FIN_W1:
j = ack_fin(tp, n);
if ((tp->tc_flags & TC_FIN_RCVD) == 0) {
if (j)
nstate = FIN_W2; /* 27 */
break;
}
tp->t_finack = T_2ML;
tp->tc_flags &= ~TC_WAITED_2_ML;
nstate = j ? TIME_WAIT : CLOSING1; /* 28:26 */
break;
case FIN_W2:
if (tp->tc_flags&TC_FIN_RCVD) {
tp->t_finack = T_2ML; /* 29 */
tp->tc_flags &= ~TC_WAITED_2_ML;
nstate = TIME_WAIT;
break;
}
break;
}
goto done;
case CLOSE_WAIT:
if (n->th_flags&TH_FIN) {
if ((n->th_flags&TH_ACK) &&
n->t_ackno <= tp->seq_fin) {
rcv_ctldat(tp, n, 0); /* 30 */
tp->t_finack = T_2ML;
tp->tc_flags &= ~TC_WAITED_2_ML;
} else
send_ctl(tp); /* 31 */
goto done;
}
goto input;
case CLOSING1:
j = ack_fin(tp, n);
if (n->th_flags&TH_FIN) {
rcv_ctldat(tp, n, 0);
tp->t_finack = T_2ML;
tp->tc_flags &= ~TC_WAITED_2_ML;
if (j)
nstate = TIME_WAIT; /* 23 */
goto done;
}
if (j) {
if (tp->tc_flags&TC_WAITED_2_ML)
if (rcv_empty(tp)) {
t_close(tp, UCLOSED); /* 15 */
nstate = CLOSED;
} else
nstate = RCV_WAIT; /* 18 */
else
nstate = TIME_WAIT;
goto done;
}
goto input;
case CLOSING2:
if (ack_fin(tp, n)) {
if (rcv_empty(tp)) { /* 16 */
t_close(tp, UCLOSED);
nstate = CLOSED;
} else
nstate = RCV_WAIT; /* 19 */
goto done;
}
if (n->th_flags&TH_FIN) {
send_ctl(tp); /* 31 */
goto done;
}
goto input;
case RCV_WAIT:
if ((n->th_flags&TH_FIN) && (n->th_flags&TH_ACK) &&
n->t_ackno <= tp->seq_fin) {
rcv_ctldat(tp, n, 0);
tp->t_finack = T_2ML;
tp->tc_flags &= ~TC_WAITED_2_ML; /* 30 */
}
goto done;
}
panic("tcp_input");
done:
/*
* Done with state*input specific processing.
* Form trace records, free input if not needed,
* and enter new state.
*/
#ifdef TCPDEBUG
if (tdb.td_tod) {
tdb.td_new = nstate;
tcp_debug[tdbx++ % TDBSIZE] = tdb;
if (tcpconsdebug)
tcp_prt(&tdb);
}
#endif
switch (nstate) {
case EFAILEC:
m_freem(mp);
return;
default:
tp->t_state = nstate;
/* fall into ... */
case CLOSED:
/* IF CLOSED CANT LOOK AT tc_flags */
if ((tp->tc_flags&TC_NET_KEEP) == 0)
m_freem(mp);
return;
}
/* NOTREACHED */
/*
* Unwanted packed; free everything
* but the header and return an rst.
*/
notwanted:
m_freem(mp->m_next);
mp->m_next = NULL;
mp->m_len = sizeof(struct th);
#define xchg(a,b) j=a; a=b; b=j
xchg(n->t_d.s_addr, n->t_s.s_addr); xchg(n->t_dst, n->t_src);
#undef xchg
if (n->th_flags&TH_ACK)
n->t_seq = n->t_ackno;
else {
n->t_ackno = htonl(ntohl(n->t_seq) + tlen - hlen);
n->t_seq = 0;
}
n->th_flags = TH_RST; /* not TH_FIN, TH_SYN */
n->th_flags ^= TH_ACK;
n->t_len = htons(TCPSIZE);
n->t_off = 5;
n->t_sum = cksum(mp, sizeof(struct th));
((struct ip *)n)->ip_len = sizeof(struct th);
ip_output(mp);
netstat.t_badsegs++;
}
rcv_ctldat(tp, n, dataok)
register struct tcb *tp;
register struct th *n;
{
register sent;
register struct ucb *up;
register struct mbuf *m, *mn;
register len;
COUNT(RCV_CTLDAT);
tp->tc_flags &= ~(TC_DROPPED_TXT|TC_ACK_DUE|TC_NEW_WINDOW);
/* syn */
if ((tp->tc_flags&TC_SYN_RCVD) == 0 && (n->th_flags&TH_SYN)) {
tp->irs = n->t_seq;
tp->rcv_nxt = n->t_seq + 1;
tp->snd_wl = tp->rcv_urp = tp->irs;
tp->tc_flags |= (TC_SYN_RCVD|TC_ACK_DUE);
}
/* ack */
if ((n->th_flags&TH_ACK) && (tp->tc_flags&TC_SYN_RCVD) &&
n->t_ackno > tp->snd_una) {
up = tp->t_ucb;
/* update snd_una and snd_nxt */
tp->snd_una = n->t_ackno;
if (tp->snd_una > tp->snd_nxt)
tp->snd_nxt = tp->snd_una;
/* if timed msg acked, set retrans time value */
if ((tp->tc_flags&TC_SYN_ACKED) &&
tp->snd_una > tp->t_xmt_val) {
tp->t_xmtime = (tp->t_xmt != 0 ? tp->t_xmt : T_REXMT);
if (tp->t_xmtime > T_REMAX)
tp->t_xmtime = T_REMAX;
}
/* remove acked data from send buf */
len = tp->snd_una - tp->snd_off;
m = up->uc_sbuf;
while (len > 0 && m != NULL)
if (m->m_len <= len) {
len -= m->m_len;
if (m->m_off > MMAXOFF)
up->uc_ssize -= NMBPG;
MFREE(m, mn);
m = mn;
up->uc_ssize--;
} else {
m->m_len -= len;
m->m_off += len;
break;
}
up->uc_sbuf = m;
tp->snd_off = tp->snd_una;
if ((tp->tc_flags&TC_SYN_ACKED) == 0 &&
(tp->snd_una > tp->iss)) {
tp->tc_flags |= TC_SYN_ACKED;
tp->t_init = 0;
}
if (tp->seq_fin != tp->iss && tp->snd_una > tp->seq_fin)
tp->tc_flags &= ~TC_SND_FIN;
tp->t_rexmt = 0;
tp->t_rexmttl = 0;
tp->tc_flags |= TC_CANCELLED;
netwakeup(tp->t_ucb); /* wasteful */
}
/* win */
if ((tp->tc_flags & TC_SYN_RCVD) && n->t_seq >= tp->snd_wl) {
tp->snd_wl = n->t_seq;
tp->snd_wnd = n->t_win;
tp->tc_flags |= TC_NEW_WINDOW;
tp->t_persist = 0;
}
if (dataok) {
/* text */
if (n->t_len != 0)
rcv_text(tp, n);
/* urg */
if (n->th_flags&TH_URG) {
unsigned urgent;
urgent = n->t_urp + n->t_seq;
if (tp->rcv_nxt < urgent) {
if (tp->rcv_urp <= tp->rcv_nxt)
to_user(tp->t_ucb, UURGENT);
tp->rcv_urp = urgent;
}
}
/* eol */
if ((n->th_flags&TH_EOL) &&
(tp->tc_flags&TC_DROPPED_TXT) == 0 &&
tp->t_rcv_prev != (struct th *)tp) {
/* mark last mbuf */
m = dtom(tp->t_rcv_prev);
if (m != NULL) {
while (m->m_next != NULL)
m = m->m_next;
m->m_act =
(struct mbuf *)(m->m_off + m->m_len - 1);
}
}
}
/* fin */
if ((n->th_flags&TH_FIN) && (tp->tc_flags&TC_DROPPED_TXT) == 0) {
int last;
if ((tp->tc_flags&TC_FIN_RCVD) == 0) {
/* do we really have fin ? */
last = firstempty(tp);
if (tp->t_rcv_prev == (struct th *)tp ||
last == t_end(tp->t_rcv_prev)) {
tp->tc_flags |= TC_FIN_RCVD;
netwakeup(tp->t_ucb); /* poke */
}
if ((tp->tc_flags&TC_FIN_RCVD) &&
tp->rcv_nxt >= last) {
tp->rcv_nxt = last + 1; /* fin seq */
tp->tc_flags |= TC_ACK_DUE;
}
} else
tp->tc_flags |= TC_ACK_DUE;
}
/* respond */
if (tp->tc_flags&TC_ACK_DUE)
sent = send_ctl(tp);
else if (tp->tc_flags&TC_NEW_WINDOW)
sent = send(tp);
else
sent = 0;
/* set for retrans */
if (!sent && tp->snd_una < tp->snd_nxt &&
(tp->tc_flags&TC_CANCELLED)) {
tp->t_rexmt = tp->t_xmtime;
tp->t_rexmttl = T_REXMTTL;
tp->t_rexmt_val = tp->t_rtl_val = tp->snd_lst;
tp->tc_flags &= ~TC_CANCELLED;
}
}
rcv_text(tp, t)
register struct tcb *tp;
register struct th *t;
{
register i;
register struct th *p, *q;
register struct mbuf *m, *n;
struct th *savq;
int last, j, k;
COUNT(RCV_TEXT);
/* throw away any data we have already received */
if ((i = tp->rcv_nxt - t->t_seq) > 0) {
if (i >= t->t_len)
return;
t->t_seq += i;
t->t_len -= i;
m_adj(dtom(t), i);
}
last = t_end(t); /* last seq # in incoming seg */
i = rcv_resource(tp); /* # buffers available to con */
/* count buffers in segment */
for (m = dtom(t), j = 0; m != NULL; m = m->m_next)
if (m->m_len != 0) {
j++;
if (m->m_off > MMAXOFF)
j += NMBPG;
}
/* not enough resources to process segment */
if (j > i && netcb.n_bufs < netcb.n_lowat) {
/* if segment preceeds top of seqeuncing queue, try to take
buffers from bottom of queue */
q = tp->t_rcv_next;
if (q != (struct th *)tp && tp->rcv_nxt < q->t_seq &&
t->t_seq < q->t_seq)
for (k=j-i, p = tp->t_rcv_prev; k > 0 &&
p != (struct th *)tp; k--) {
savq = p->t_prev;
tcp_deq(p);
i += m_freem(dtom(p));
p = savq;
}
/* if still not enough room, drop text from end of segment */
if (j > i) {
for (m = dtom(t); i > 0 && m != NULL; i--)
m = m->m_next;
while (m != NULL) {
t->t_len -= m->m_len;
last -= m->m_len;
m->m_len = 0;
m = m->m_next;
}
tp->tc_flags |= TC_DROPPED_TXT;
if (last < t->t_seq)
return;
}
}
/* merge incoming data into the sequence queue */
q = tp->t_rcv_next; /* -> top of sequencing queue */
/* skip frags which new doesn't overlap at end */
while ((q != (struct th *)tp) && (t->t_seq > t_end(q)))
q = q->t_next;
if (q == (struct th *)tp) { /* frag at end of chain */
if (last >= tp->rcv_nxt) {
tp->tc_flags |= TC_NET_KEEP;
tcp_enq(t, tp->t_rcv_prev);
}
} else {
/* frag doesn't overlap any on chain */
if (last < q->t_seq) {
tp->tc_flags |= TC_NET_KEEP;
tcp_enq(t, q->t_prev);
/* new overlaps beginning of next frag only */
} else if (last < t_end(q)) {
if ((i = last - q->t_seq + 1) < t->t_len) {
t->t_len -= i;
m_adj(dtom(t), -i);
tp->tc_flags |= TC_NET_KEEP;
tcp_enq(t, q->t_prev);
}
/* new overlaps end of previous frag */
} else {
savq = q;
if (t->t_seq <= q->t_seq) { /* complete cover */
savq = q->t_prev;
tcp_deq(q);
m_freem(dtom(q));
} else { /* overlap */
if ((i = t_end(q) - t->t_seq + 1) < t->t_len) {
t->t_seq += i;
t->t_len -= i;
m_adj(dtom(t), i);
} else
t->t_len = 0;
}
/* new overlaps at beginning of successor frags */
q = savq->t_next;
while ((q != (struct th *)tp) && (t->t_len != 0) &&
(q->t_seq < last))
/* complete cover */
if (t_end(q) <= last) {
p = q->t_next;
tcp_deq(q);
m_freem(dtom(q));
q = p;
} else { /* overlap */
if ((i = last - q->t_seq + 1) < t->t_len) {
t->t_len -= i;
m_adj(dtom(t), -i);
} else
t->t_len = 0;
break;
}
/* enqueue whatever is left of new before successors */
if (t->t_len != 0) {
tp->tc_flags |= TC_NET_KEEP;
tcp_enq(t, savq);
}
}
}
/* set to ack completed data (no gaps) */
tp->rcv_nxt = firstempty(tp);
tp->tc_flags |= TC_ACK_DUE;
#ifdef notdef
/* THIS CODE CANT POSSIBLY WORK */
/* if any room remaining in rcv buf, take any unprocessed
messages and schedule for later processing */
i = rcv_resource(tp);
while ((m = tp->t_rcv_unack) != NULL && i > 0) {
/* schedule work request */
t = (struct th *)((int)m + m->m_off);
j = (t->t_off << 2) + sizeof(struct ip);
m->m_off += j;
m->m_len -= j;
tp->t_rcv_unack = m->m_act;
m->m_act = (struct mbuf *)0;
netstat.t_unack++;
tcp_work(INRECV, 0, tp, t);
/* remaining buffer space */
for (n = m; n != NULL; n = n->m_next)
i--;
}
#endif
}
present_data(tp)
register struct tcb *tp;
{
register struct th *t;
register struct ucb *up;
register struct mbuf *m, **mp;
seq_t ready;
COUNT(PRESENT_DATA);
/* connection must be synced and data available for user */
if (((tp->tc_flags&TC_SYN_ACKED) == 0) ||
(t = tp->t_rcv_next) == (struct th *)tp)
return;
up = tp->t_ucb;
ready = firstempty(tp); /* seq # of last complete datum */
mp = &up->uc_rbuf;
while (*mp)
mp = &(*mp)->m_next;
while (up->uc_rsize < up->uc_rcv && t != (struct th *) tp &&
t_end(t) < ready) {
tcp_deq(t);
m = dtom(t);
t = t->t_next;
while (m) {
if (m->m_len == 0) {
m = m_free(m);
continue;
}
up->uc_rsize++;
if (m->m_off > MMAXOFF)
up->uc_rsize += NMBPG;
if (*mp == 0)
*mp = m;
mp = &m->m_next;
m = *mp;
}
}
if (up->uc_rsize != 0)
netwakeup(up);
/*
* Let user know about foreign tcp close if no more data.
*/
if ((tp->tc_flags&TC_FIN_RCVD) && (tp->tc_flags&TC_USR_CLOSED) == 0 &&
rcv_empty(tp))
to_user(up, UCLOSED);
}