📄 tcp.c
字号:
tcb->flags &= ~RETRAN; tcb->backoff = 0; tcb->backedoff = 0;}voidtcpiput(Proto *tcp, Ipifc*, Block *bp){ Tcp seg; Tcp4hdr *h4; Tcp6hdr *h6; int hdrlen; Tcpctl *tcb; ushort length; uchar source[IPaddrlen], dest[IPaddrlen]; Conv *s; Fs *f; Tcppriv *tpriv; uchar version; f = tcp->f; tpriv = tcp->priv; tpriv->stats[InSegs]++; h4 = (Tcp4hdr*)(bp->rp); h6 = (Tcp6hdr*)(bp->rp); if((h4->vihl&0xF0)==IP_VER4) { version = V4; length = nhgets(h4->length); v4tov6(dest, h4->tcpdst); v4tov6(source, h4->tcpsrc); h4->Unused = 0; hnputs(h4->tcplen, length-TCP4_PKT); if(!(bp->flag & Btcpck) && (h4->tcpcksum[0] || h4->tcpcksum[1]) && ptclcsum(bp, TCP4_IPLEN, length-TCP4_IPLEN)) { tpriv->stats[CsumErrs]++; tpriv->stats[InErrs]++; netlog(f, Logtcp, "bad tcp proto cksum\n"); freeblist(bp); return; } hdrlen = ntohtcp4(&seg, &bp); if(hdrlen < 0){ tpriv->stats[HlenErrs]++; tpriv->stats[InErrs]++; netlog(f, Logtcp, "bad tcp hdr len\n"); return; } /* trim the packet to the size claimed by the datagram */ length -= hdrlen+TCP4_PKT; bp = trimblock(bp, hdrlen+TCP4_PKT, length); if(bp == nil){ tpriv->stats[LenErrs]++; tpriv->stats[InErrs]++; netlog(f, Logtcp, "tcp len < 0 after trim\n"); return; } } else { int ttl = h6->ttl; int proto = h6->proto; version = V6; length = nhgets(h6->ploadlen); ipmove(dest, h6->tcpdst); ipmove(source, h6->tcpsrc); h6->ploadlen[0] = h6->ploadlen[1] = h6->proto = 0; h6->ttl = proto; hnputl(h6->vcf, length); if((h6->tcpcksum[0] || h6->tcpcksum[1]) && ptclcsum(bp, TCP6_IPLEN, length+TCP6_PHDRSIZE)) { tpriv->stats[CsumErrs]++; tpriv->stats[InErrs]++; netlog(f, Logtcp, "bad tcp proto cksum\n"); freeblist(bp); return; } h6->ttl = ttl; h6->proto = proto; hnputs(h6->ploadlen, length); hdrlen = ntohtcp6(&seg, &bp); if(hdrlen < 0){ tpriv->stats[HlenErrs]++; tpriv->stats[InErrs]++; netlog(f, Logtcp, "bad tcp hdr len\n"); return; } /* trim the packet to the size claimed by the datagram */ length -= hdrlen; bp = trimblock(bp, hdrlen+TCP6_PKT, length); if(bp == nil){ tpriv->stats[LenErrs]++; tpriv->stats[InErrs]++; netlog(f, Logtcp, "tcp len < 0 after trim\n"); return; } } /* lock protocol while searching for a conversation */ qlock(tcp); /* Look for a matching conversation */ s = iphtlook(&tpriv->ht, source, seg.source, dest, seg.dest); if(s == nil){ netlog(f, Logtcp, "iphtlook failed");reset: qunlock(tcp); sndrst(tcp, source, dest, length, &seg, version, "no conversation"); freeblist(bp); return; } /* if it's a listener, look for the right flags and get a new conv */ tcb = (Tcpctl*)s->ptcl; if(tcb->state == Listen){ if(seg.flags & RST){ limborst(s, &seg, source, dest, version); qunlock(tcp); freeblist(bp); return; } /* if this is a new SYN, put the call into limbo */ if((seg.flags & SYN) && (seg.flags & ACK) == 0){ limbo(s, source, dest, &seg, version); qunlock(tcp); freeblist(bp); return; } /* * if there's a matching call in limbo, tcpincoming will * return it in state Syn_received */ s = tcpincoming(s, &seg, source, dest, version); if(s == nil) goto reset; } /* The rest of the input state machine is run with the control block * locked and implements the state machine directly out of the RFC. * Out-of-band data is ignored - it was always a bad idea. */ tcb = (Tcpctl*)s->ptcl; if(waserror()){ qunlock(s); nexterror(); } qlock(s); qunlock(tcp); /* fix up window */ seg.wnd <<= tcb->rcv.scale; /* every input packet in puts off the keep alive time out */ tcpsetkacounter(tcb); switch(tcb->state) { case Closed: sndrst(tcp, source, dest, length, &seg, version, "sending to Closed"); goto raise; case Syn_sent: if(seg.flags & ACK) { if(!seq_within(seg.ack, tcb->iss+1, tcb->snd.nxt)) { sndrst(tcp, source, dest, length, &seg, version, "bad seq in Syn_sent"); goto raise; } } if(seg.flags & RST) { if(seg.flags & ACK) localclose(s, Econrefused); goto raise; } if(seg.flags & SYN) { procsyn(s, &seg); if(seg.flags & ACK){ update(s, &seg); tcpsynackrtt(s); tcpsetstate(s, Established); tcpsetscale(s, tcb, seg.ws, tcb->scale); } else { tcb->time = NOW; tcpsetstate(s, Syn_received); /* DLP - shouldn't this be a reset? */ } if(length != 0 || (seg.flags & FIN)) break; freeblist(bp); goto output; } else freeblist(bp); qunlock(s); poperror(); return; case Syn_received: /* doesn't matter if it's the correct ack, we're just trying to set timing */ if(seg.flags & ACK) tcpsynackrtt(s); break; } /* * One DOS attack is to open connections to us and then forget about them, * thereby tying up a conv at no long term cost to the attacker. * This is an attempt to defeat these stateless DOS attacks. See * corresponding code in tcpsendka(). */ if(tcb->state != Syn_received && (seg.flags & RST) == 0){ if(tcpporthogdefense && seq_within(seg.ack, tcb->snd.una-(1<<31), tcb->snd.una-(1<<29))){ print("stateless hog %I.%d->%I.%d f %ux %lux - %lux - %lux\n", source, seg.source, dest, seg.dest, seg.flags, tcb->snd.una-(1<<31), seg.ack, tcb->snd.una-(1<<29)); localclose(s, "stateless hog"); } } /* Cut the data to fit the receive window */ if(tcptrim(tcb, &seg, &bp, &length) == -1) { netlog(f, Logtcp, "tcp len < 0, %lud %d\n", seg.seq, length); update(s, &seg); if(qlen(s->wq)+tcb->flgcnt == 0 && tcb->state == Closing) { tcphalt(tpriv, &tcb->rtt_timer); tcphalt(tpriv, &tcb->acktimer); tcphalt(tpriv, &tcb->katimer); tcpsetstate(s, Time_wait); tcb->timer.start = MSL2*(1000 / MSPTICK); tcpgo(tpriv, &tcb->timer); } if(!(seg.flags & RST)) { tcb->flags |= FORCE; goto output; } qunlock(s); poperror(); return; } /* Cannot accept so answer with a rst */ if(length && tcb->state == Closed) { sndrst(tcp, source, dest, length, &seg, version, "sending to Closed"); goto raise; } /* The segment is beyond the current receive pointer so * queue the data in the resequence queue */ if(seg.seq != tcb->rcv.nxt) if(length != 0 || (seg.flags & (SYN|FIN))) { update(s, &seg); if(addreseq(tcb, tpriv, &seg, bp, length) < 0) print("reseq %I.%d -> %I.%d\n", s->raddr, s->rport, s->laddr, s->lport); tcb->flags |= FORCE; goto output; } /* * keep looping till we've processed this packet plus any * adjacent packets in the resequence queue */ for(;;) { if(seg.flags & RST) { if(tcb->state == Established) { tpriv->stats[EstabResets]++; if(tcb->rcv.nxt != seg.seq) print("out of order RST rcvd: %I.%d -> %I.%d, rcv.nxt %lux seq %lux\n", s->raddr, s->rport, s->laddr, s->lport, tcb->rcv.nxt, seg.seq); } localclose(s, Econrefused); goto raise; } if((seg.flags&ACK) == 0) goto raise; switch(tcb->state) { case Syn_received: if(!seq_within(seg.ack, tcb->snd.una+1, tcb->snd.nxt)){ sndrst(tcp, source, dest, length, &seg, version, "bad seq in Syn_received"); goto raise; } update(s, &seg); tcpsetstate(s, Established); case Established: case Close_wait: update(s, &seg); break; case Finwait1: update(s, &seg); if(qlen(s->wq)+tcb->flgcnt == 0){ tcphalt(tpriv, &tcb->rtt_timer); tcphalt(tpriv, &tcb->acktimer); tcpsetkacounter(tcb); tcb->time = NOW; tcpsetstate(s, Finwait2); tcb->katimer.start = MSL2 * (1000 / MSPTICK); tcpgo(tpriv, &tcb->katimer); } break; case Finwait2: update(s, &seg); break; case Closing: update(s, &seg); if(qlen(s->wq)+tcb->flgcnt == 0) { tcphalt(tpriv, &tcb->rtt_timer); tcphalt(tpriv, &tcb->acktimer); tcphalt(tpriv, &tcb->katimer); tcpsetstate(s, Time_wait); tcb->timer.start = MSL2*(1000 / MSPTICK); tcpgo(tpriv, &tcb->timer); } break; case Last_ack: update(s, &seg); if(qlen(s->wq)+tcb->flgcnt == 0) { localclose(s, nil); goto raise; } case Time_wait: tcb->flags |= FORCE; if(tcb->timer.state != TcptimerON) tcpgo(tpriv, &tcb->timer); } if((seg.flags&URG) && seg.urg) { if(seq_gt(seg.urg + seg.seq, tcb->rcv.urg)) { tcb->rcv.urg = seg.urg + seg.seq; pullblock(&bp, seg.urg); } } else if(seq_gt(tcb->rcv.nxt, tcb->rcv.urg)) tcb->rcv.urg = tcb->rcv.nxt; if(length == 0) { if(bp != nil) freeblist(bp); } else { switch(tcb->state){ default: /* Ignore segment text */ if(bp != nil) freeblist(bp); break; case Syn_received: case Established: case Finwait1: /* If we still have some data place on * receive queue */ if(bp) { bp = packblock(bp); if(bp == nil) panic("tcp packblock"); qpassnolim(s->rq, bp); bp = nil; /* * Force an ack every 2 data messages. This is * a hack for rob to make his home system run * faster. * * this also keeps the standard TCP congestion * control working since it needs an ack every * 2 max segs worth. This is not quite that, * but under a real stream is equivalent since * every packet has a max seg in it. */ if(++(tcb->rcv.una) >= 2) tcb->flags |= FORCE; } tcb->rcv.nxt += length; /* * update our rcv window */ tcprcvwin(s); /* * turn on the acktimer if there's something * to ack */ if(tcb->acktimer.state != TcptimerON) tcpgo(tpriv, &tcb->acktimer); break; case Finwait2: /* no process to read the data, send a reset */ if(bp != nil) freeblist(bp); sndrst(tcp, source, dest, length, &seg, version, "send to Finwait2"); qunlock(s); poperror(); return; } } if(seg.flags & FIN) { tcb->flags |= FORCE; switch(tcb->state) { case Syn_received: case Established: tcb->rcv.nxt++; tcpsetstate(s, Close_wait); break; case Finwait1: tcb->rcv.nxt++; if(qlen(s->wq)+tcb->flgcnt == 0) { tcphalt(tpriv, &tcb->rtt_timer); tcphalt(tpriv, &tcb->acktimer); tcphalt(tpriv, &tcb->katimer); tcpsetstate(s, Time_wait); tcb->timer.start = MSL2*(1000/MSPTICK); tcpgo(tpriv, &tcb->timer); } else tcpsetstate(s, Closing); break; case Finwait2: tcb->rcv.nxt++; tcphalt(tpriv, &tcb->rtt_timer); tcphalt(tpriv, &tcb->acktimer); tcphalt(tpriv, &tcb->katimer); tcpsetstate(s, Time_wait); tcb->timer.start = MSL2 * (1000/MSPTICK); tcpgo(tpriv, &tcb->timer); break; case Close_wait: case Closing: case Last_ack: break; case Time_wait: tcpgo(tpriv, &tcb->timer); break; } } /* * get next adjacent segment from the resequence queue. * dump/trim any overlapping segments */ for(;;) { if(tcb->reseq == nil) goto output; if(seq_ge(tcb->rcv.nxt, tcb->reseq->seg.seq) == 0) goto output; getreseq(tcb, &seg, &bp, &length); if(tcptrim(tcb, &seg, &bp, &length) == 0) break; } }output: tcpoutput(s); qunlock(s); poperror(); return;raise: qunlock(s); poperror(); freeblist(bp); tcpkick(s);}/* * always enters and exits with the s locked. We drop * the lock to ipoput the packet so some care has to be * taken by callers. */voidtcpoutput(Conv *s){ Tcp seg; int msgs; Tcpctl *tcb; Block *hbp, *bp; int sndcnt, n; ulong ssize, dsize, usable, sent; Fs *f; Tcppriv *tpriv; uchar version; f = s->p->f; tpriv = s->p->priv; version = s->ipversion; for(msgs = 0; msgs < 100; msgs++) { tcb = (Tcpctl*)s->ptcl; switch(tcb->state) { case Listen: case Closed: case Finwait2: return; } /* force an ack when a window has opened up */ if(tcb->rcv.blocked && tcb->rcv.wnd > 0){ tcb->rcv.blocked = 0; tcb->flags |= FORCE; } sndcnt = qlen(s->wq)+tcb->flgcnt; sent = tcb->snd.ptr - tcb->snd.una; /* Don't send anything else until our SYN has been acked */ if(tcb->snd.ptr != tcb->iss && (tcb->flags & SYNACK) == 0) break; /* Compute usable segment based on offered window and limit * window probes to one */ if(tcb->snd.wnd == 0){ if(sent != 0) { if((tcb->flags&FORCE) == 0) break;// tcb->snd.ptr = tcb->snd.una; } usable = 1; } else { usable = tcb->cwind; if(tcb->snd.wnd < usable) usable = tcb->snd.wnd; usable -= sent; } ssize = sndcnt-sent; if(ssize && usable < 2) netlog(s->p->f, Logtcp, "throttled snd.wnd %lud cwind %lud\n", tcb->snd.wnd, tcb->cwind); if(usable < ssize) ssize = usable; if(tcb->mss < ssize) ssize = tcb->mss; dsize = ssize; seg.urg = 0; if(ssize == 0) if((tcb->flags&FORCE) == 0) break; tcb->flags &= ~FORCE; tcprcvwin(s); /* By default we will generate an ack */ tcphalt(tpriv, &tcb->acktimer); tcb->rcv.una = 0; seg.source = s->lport; seg.dest = s->rport; seg.flags = ACK; seg.mss = 0; seg.ws = 0; switch(tcb->state){ case Syn_sent: seg.flags = 0; if(tcb->snd.ptr == tcb->iss){ seg.flags |= SYN; dsize--; seg.mss = tcb->mss; seg.ws = tcb->scale; } break; case Syn_received: /* * don't send any data with a SYN/ACK packet * because Linux rejects the packet in its * attempt to solve the SYN attack problem */ if(tcb->snd.ptr == tcb->iss){ seg.flags |= SYN; dsize = 0; ssize = 1; seg.mss = tcb->mss; seg.ws = tcb->scale; } break; } seg.seq = tcb->snd.ptr; seg.ack = tcb->rcv.nxt; seg.wnd = tcb->rcv.wnd; /* Pull out data to send */ bp = nil; if(dsize != 0) { bp = qcopy(s->wq, dsize, sent); if(BLEN(bp) != dsize) { seg.flags |= FIN; dsize--; } } if(sent+dsize == sndcnt) seg.flags |= PSH; /* keep track of balance of resent data */ if(seq_lt(tcb->snd.ptr, tcb->snd.nxt)) { n = tcb->snd.nxt - tcb->snd.ptr; if(ssize < n) n = ssize; tcb->resent += n; netlog(f, Logtcp, "rexmit: %I.%d -> %I.%d ptr %lux nxt %lux\n", s->raddr, s->rport, s->laddr, s->lport, tcb->snd.ptr, tcb->snd.nxt); tpriv->stats[RetransSegs]++; } tcb->snd.ptr += ssize; /* Pull up the send pointer so we can accept acks * for this window */ if(seq_gt(tcb->snd.ptr,tcb->snd.nxt)) tcb->snd.nxt = tcb->snd.ptr; /* Build header, link data and compute cksum */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -