MODULE TCP;
IMPORT SYSTEM, Machine, KernelLog, Clock, Modules, Objects, Kernel, Commands, Network, IP, Streams, ActiveTimers, Strings;
CONST
StrongChecks = FALSE;
SystemMove = FALSE;
TraceProtocol = FALSE;
TraceError = FALSE;
TracePacket = FALSE;
TraceTimer = FALSE;
TraceCongestion = FALSE;
Trace = TraceProtocol OR TraceError OR TracePacket OR TraceTimer;
HandleCongestion = TRUE;
MinEphemeralPort = 1024;
MaxEphemeralPort = 50000;
HashTableSize = 1024 * 16;
Acceptable = 500;
NilPort* = 0;
Ok* = 0;
ConnectionRefused* = 3701;
ConnectionReset* = 3702;
WrongInterface* = 3703;
TimedOut* = 3704;
NotConnected* = 3705;
NoInterface* = 3706;
InterfaceClosed* = 3707;
MinError = 3700;
MaxError = 3735;
NumErrors = MaxError-MinError+1;
NumStates* = 12;
Closed* = 0;
Listen* = 1;
SynSent* = 2;
SynReceived* = 3;
Established* = 4;
CloseWait* = 5;
FinWait1* = 6;
Closing* = 7;
LastAck* = 8;
FinWait2* = 9;
TimeWait* = 10;
Unused* = 11;
OpenStates* = {Listen, SynReceived, Established, CloseWait, FinWait1, FinWait2};
ClosedStates* = {Unused, Closed, Closing, LastAck, TimeWait};
HalfClosedStates* = ClosedStates + {FinWait1, FinWait2};
FinStates* = {Unused, Closed, CloseWait, Closing, LastAck, TimeWait};
Fin = 0; Syn = 1; Rst = 2; Psh = 3; Ack = 4; Urg = 5;
DoRFC1323 = TRUE;
ProcOptions = TRUE;
GenOptions = TRUE;
AckNow = 0;
DelAck = 1;
NoDelay = 2;
SentFin = 3;
Force = 4;
RcvdScale = 5;
RcvdTstmp = 6;
ReqScale = 7;
ReqTstmp = 8;
DoKeepAlive = 9;
AcceptConn = 10;
Timeout = 14;
NumTimers = 4;
ReXmt = 0; Persist = 1; Keep = 2; MSL2 = 3;
FastPeriod = 5;
SlowPeriod = 2;
TimerPeriod = 10;
MinTime = 1*SlowPeriod;
ReXmtMax = 64*SlowPeriod;
ReXmtThresh = 3;
KeepInit = 75*SlowPeriod;
KeepIntvl = 75*SlowPeriod;
KeepIdle = 2*60*60*SlowPeriod;
KeepCnt = 8;
MaxIdle = KeepCnt * KeepIntvl;
MSL = 30*SlowPeriod;
MaxPersistIdle = KeepIdle;
PawsIdle = 24*24*60*60*SlowPeriod;
SRTTBase = 0;
SRTTDflt = 3*SlowPeriod;
RTTShift = 3;
RTTVarShift = 2;
PersMin = 5*SlowPeriod;
PersMax = 60*SlowPeriod;
MSS = 536-12;
MaxRxtShift = 12;
MaxWin = 65535;
MaxWinShift = 14;
MaxSendSpace = 80000H;
MaxRecvSpace = 80000H;
SegsPerBuf = 4;
ISSInc = 128000;
IPTypeTCP = 6;
MinTCPHdrLen = 20;
MaxTCPHdrLen = 60;
MaxPseudoHdrLen = 40;
NewZeros = FALSE;
BroadcastReceived = 3708;
InvalidParameter = 3709;
AllPortsInUse = 3710;
AddressInUse = 3711;
DuplicateSegment = 3712;
DuplicatePartialSegment = 3713;
DuplicateSegmentPAWS = 3714;
DataBeyondWindow1 = 3715;
DataBeyondWindow2 = 3716;
DataBeyondWindow3 = 3717;
BadChecksum = 3718;
DuplicateAck = 3719;
OutOfRangeAck = 3720;
TimeOutKeepAlive = 3721;
TimeoutEstablished = 3722;
SegmentTooBig = 3723;
SegmentTooSmall = 3724;
BadHeaderLength = 3725;
ConnectionGone = 3726;
NIYNewIncarnation = 3727;
NIYOutOfBand = 3728;
NIYMSS = 3729;
ConnectionAborted = 3730;
NotInitialized = 3731;
DataDuplicatePrevComplete = 3732;
DataDuplicatePrevPartial = 3733;
DataDuplicateNextComplete = 3734;
DataDuplicateNextPartial = 3735;
TYPE
SendData = IP.Packet;
SendBuffer = POINTER TO RECORD
next: SendBuffer;
ofs, len: LONGINT;
seq: LONGINT;
pf: SET;
data: SendData
END;
TYPE
ISS = OBJECT
VAR iss: LONGINT;
PROCEDURE Update(hz: LONGINT);
BEGIN {EXCLUSIVE}
INC(iss, ISSInc DIV hz)
END Update;
PROCEDURE Get(): LONGINT;
VAR t: LONGINT;
BEGIN {EXCLUSIVE}
t := iss; INC(iss, ISSInc);
RETURN t
END Get;
PROCEDURE &Init*(iss: LONGINT);
BEGIN
SELF.iss := iss
END Init;
END ISS;
TYPE
Timer = OBJECT
VAR
lastFast, lastSlow: LONGINT;
now: LONGINT;
timer: ActiveTimers.Timer;
PROCEDURE CallDelayedAck(p: Connection);
BEGIN
p.DelayedAck();
END CallDelayedAck;
PROCEDURE CallSlowTimer(p: Connection);
BEGIN
p.SlowTimer();
END CallSlowTimer;
PROCEDURE HandleTimeout;
VAR t: LONGINT;
BEGIN {EXCLUSIVE}
t := Kernel.GetTicks();
IF t - lastFast >= Kernel.second DIV FastPeriod THEN
lastFast := t;
pool.Enumerate(CallDelayedAck);
END;
IF t - lastSlow >= Kernel.second DIV SlowPeriod THEN
lastSlow := t;
pool.Enumerate(CallSlowTimer);
issSource.Update(SlowPeriod);
INC(now)
END;
timer.SetTimeout(HandleTimeout, Kernel.second DIV TimerPeriod)
END HandleTimeout;
PROCEDURE Finalize;
BEGIN {EXCLUSIVE}
timer.Finalize
END Finalize;
PROCEDURE &Init*;
BEGIN
now := 0;
lastSlow := Kernel.GetTicks() - Kernel.second;
lastFast := lastSlow;
NEW(timer);
timer.SetTimeout(HandleTimeout, Kernel.second DIV TimerPeriod)
END Init;
END Timer;
TYPE
Connection* = OBJECT(Streams.Connection)
VAR
poolNext, parent, acceptNext: Connection;
int-: IP.Interface;
lport-: LONGINT;
fip-: IP.Adr;
fport-: LONGINT;
state*: SHORTINT;
timer: ARRAY NumTimers OF LONGINT;
rxtshift-: LONGINT;
rxtcur-: LONGINT;
dupacks-: LONGINT;
maxseg-: LONGINT;
flags: SET;
error: LONGINT;
acceptable: LONGINT;
snduna-: LONGINT;
sndnxt-: LONGINT;
sndup: LONGINT;
sndwl1-: LONGINT;
sndwl2-: LONGINT;
iss-: LONGINT;
sndwnd-: LONGINT;
sndmax-: LONGINT;
rcvwnd-: LONGINT;
rcvnxt-: LONGINT;
rcvup: LONGINT;
irs-: LONGINT;
rcvadv-: LONGINT;
sndcwnd-: LONGINT;
sndssthresh-: LONGINT;
idle-: LONGINT;
rtt-: LONGINT;
rtseq-: LONGINT;
srtt-: LONGINT;
rttvar-: LONGINT;
rttmin-: LONGINT;
maxsndwnd: LONGINT;
sndscale: LONGINT;
rcvscale: LONGINT;
requestrscale: LONGINT;
requestedsscale: LONGINT;
tsrecent: LONGINT;
tsrecentage: LONGINT;
lastacksent-: LONGINT;
sndcc-: LONGINT;
sndspace-: LONGINT;
sndhead, sndtail: SendBuffer;
sndcontig: SendData;
rcvspace-: LONGINT;
rcvhiwat-: LONGINT;
rcvhead, rcvreasm, rcvtail: Network.Buffer;
rcvheadFragment: Network.Buffer;
timeout: ActiveTimers.Timer;
traceflow-: LONGINT;
PROCEDURE &Init*;
BEGIN
state := Unused;
END Init;
PROCEDURE Open*(lport: LONGINT; fip: IP.Adr; fport: LONGINT; VAR res: LONGINT);
BEGIN {EXCLUSIVE}
ASSERT((state = Unused) & (lport >= 0) & (lport < 10000H) & (fport >= 0) & (fport < 10000H));
IF timeSource # NIL THEN
InitConnection(SELF);
IF (~IP.IsNilAdr(fip)) & (fport # NilPort) THEN
int := IP.InterfaceByDstIP(fip);
IF int # NIL THEN
SELF.fip := fip;
pool.Add(SELF, lport, fport, res);
IF res = Ok THEN
Machine.AtomicInc(NTCPConnectAttempt);
state := SynSent; timer[Keep] := KeepInit;
iss := issSource.Get();
snduna := iss; sndnxt := iss; sndmax := iss; sndup := iss;
Output(SELF)
END;
ELSE
res := NoInterface;
END;
ELSE
ASSERT((fport = NilPort) & (IP.IsNilAdr(fip)));
SELF.int := NIL;
SELF.fip := IP.NilAdr;
pool.Add(SELF, lport, NilPort, res);
IF res = Ok THEN
INCL(flags, AcceptConn);
acceptable := Acceptable;
state := Listen;
END
END;
IF TraceProtocol THEN
TraceTCP("Open", SELF, empty^, empty^, 0, 0, 0)
END
ELSE
res := NotInitialized;
END
END Open;
PROCEDURE Send*(CONST data: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT);
VAR buf: SendBuffer; len0: LONGINT;
BEGIN {EXCLUSIVE}
IF StrongChecks THEN Invariant(SELF) END;
ASSERT(ofs+len <= LEN(data));
LOOP
IF len <= 0 THEN EXIT END;
IF len <= maxseg THEN len0 := len ELSE len0 := maxseg END;
IF ~((state IN {Established, CloseWait}) & (sndspace >= len0)) THEN
AWAIT(((state IN {Established, CloseWait}) & (sndspace >= len0)) OR ~(state IN {SynSent..CloseWait}));
IF StrongChecks THEN Invariant(SELF) END;
IF ~(state IN {SynSent..CloseWait}) THEN
IF error # Ok THEN res := error ELSE res := NotConnected END;
RETURN
END
END;
buf := sndtail;
IF LEN(buf.data^) - (buf.ofs+buf.len) >= len0 THEN
IF SystemMove THEN
SYSTEM.MOVE(SYSTEM.ADR(data[ofs]), SYSTEM.ADR(buf.data[buf.ofs+buf.len]), len0)
ELSE
Network.Copy(data, buf.data^, ofs, buf.ofs+buf.len, len0)
END;
INC(buf.len, len0)
ELSE
buf := buf.next;
IF buf # sndhead THEN
ASSERT((buf.ofs = 0) & (buf.len = 0));
ASSERT(LEN(buf.data^) >= len0)
ELSE
Machine.AtomicInc(NTCPNewBufs);
NEW(buf); NEW(buf.data, MSS * SegsPerBuf);
IF ~NewZeros THEN buf.ofs := 0; END;
buf.next := sndtail.next; sndtail.next := buf;
ASSERT(LEN(buf.data^) >= len0)
END;
IF SystemMove THEN
SYSTEM.MOVE(SYSTEM.ADR(data[ofs]), SYSTEM.ADR(buf.data[0]), len0)
ELSE
Network.Copy(data, buf.data^, ofs, 0, len0)
END;
buf.len := len0; sndtail := buf
END;
INC(sndcc, len0); DEC(sndspace, len0);
Output(SELF);
INC(ofs, len0); DEC(len, len0)
END;
IF TraceProtocol THEN
TraceTCP("Send", SELF, empty^, data, 0, ofs, len)
END;
res := Ok
END Send;
PROCEDURE Receive*(VAR data: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
VAR
buf: Network.Buffer;
rlen: LONGINT;
BEGIN {EXCLUSIVE}
IF StrongChecks THEN Invariant(SELF) END;
ASSERT((ofs >= 0) & (ofs+size <= LEN(data)) & (min <= size));
len := 0;
LOOP
WHILE (rcvhead # NIL) & (rcvhead # rcvreasm) & (size > 0) DO
IF rcvhead.nextFragment = NIL THEN
rlen := Strings.Min(rcvhead.len, size);
IF SystemMove THEN
SYSTEM.MOVE(SYSTEM.ADR(rcvhead.data[rcvhead.ofs]), SYSTEM.ADR(data[ofs]), rlen);
ELSE
Network.Copy(rcvhead.data, data, rcvhead.ofs, ofs, rlen);
END;
INC(len, rlen);
INC(ofs, rlen);
DEC(size, rlen);
INC(rcvhead.ofs, rlen);
DEC(rcvhead.len, rlen);
INC(rcvhead.int, rlen);
INC(rcvspace, rlen);
IF rcvhead.len = 0 THEN
buf := rcvhead;
rcvhead := rcvhead.next;
IF rcvhead # NIL THEN
rcvhead.prev := NIL;
END;
Network.ReturnBuffer(buf);
Output(SELF);
END;
ELSE
IF rcvheadFragment = NIL THEN
rcvheadFragment := rcvhead;
END;
rlen := Strings.Min(rcvheadFragment.len, size);
IF SystemMove THEN
SYSTEM.MOVE(SYSTEM.ADR(rcvheadFragment.data[rcvheadFragment.ofs]), SYSTEM.ADR(data[ofs]), rlen);
ELSE
Network.Copy(rcvheadFragment.data, data, rcvheadFragment.ofs, ofs, rlen);
END;
INC(len, rlen);
INC(ofs, rlen);
DEC(size, rlen);
INC(rcvheadFragment.ofs, rlen);
DEC(rcvheadFragment.len, rlen);
INC(rcvheadFragment.int, rlen);
INC(rcvspace, rlen);
IF rcvheadFragment.len = 0 THEN
IF rcvheadFragment.nextFragment # NIL THEN
rcvheadFragment := rcvheadFragment.nextFragment;
ELSE
buf := rcvhead;
rcvhead := rcvhead.next;
IF rcvhead # NIL THEN
rcvhead.prev := NIL;
END;
Network.ReturnBuffer(buf);
Output(SELF);
END;
END;
END;
END;
IF size = 0 THEN
EXIT;
END;
IF len >= min THEN
EXIT;
ELSE
AWAIT(((rcvhead # NIL) & (rcvhead # rcvreasm)) OR ~(state IN {SynSent, SynReceived, Established, FinWait1, FinWait2}));
IF StrongChecks THEN Invariant(SELF) END;
IF (rcvhead # NIL) & (rcvhead # rcvreasm) THEN
ELSE
IF error # Ok THEN res := error ELSE res := Streams.EOF END;
RETURN;
END;
END;
END;
IF StrongChecks THEN Invariant(SELF) END;
IF TraceProtocol THEN
TraceTCP("Receive", SELF, empty^, data, 0, ofs, len)
END;
res := Ok
END Receive;
PROCEDURE DelaySend*(enable: BOOLEAN);
BEGIN {EXCLUSIVE}
IF enable THEN
EXCL(flags, NoDelay);
ELSE
INCL(flags, NoDelay);
END;
END DelaySend;
PROCEDURE KeepAlive*(enable: BOOLEAN);
BEGIN {EXCLUSIVE}
IF enable THEN
INCL(flags, DoKeepAlive);
ELSE
EXCL(flags, DoKeepAlive);
END;
END KeepAlive;
PROCEDURE Available*(): LONGINT;
VAR
len: LONGINT;
item: Network.Buffer;
fragmentBuffer: Network.Buffer;
reassembledLength: LONGINT;
BEGIN {EXCLUSIVE}
len := 0;
item := rcvhead;
WHILE(item # NIL) & (item # rcvreasm) DO
IF item.nextFragment # NIL THEN
INC(len, item.len);
ELSE
fragmentBuffer := item;
reassembledLength := 0;
WHILE fragmentBuffer # NIL DO
INC(len, fragmentBuffer.len);
fragmentBuffer := fragmentBuffer.nextFragment;
END;
END;
item := item.next;
END;
RETURN len;
END Available;
PROCEDURE State*(): LONGINT;
BEGIN {EXCLUSIVE}
IF (state IN FinStates) & (rcvhead # NIL) & (rcvhead.len # 0) THEN
IF state = CloseWait THEN
RETURN Established
ELSE
RETURN FinWait1
END
ELSE
RETURN state
END
END State;
PROCEDURE AwaitState*(good, bad: SET; ms: LONGINT; VAR res: LONGINT);
BEGIN {EXCLUSIVE}
IF ~(state IN (good+bad)) THEN
IF ms # -1 THEN
IF timeout = NIL THEN NEW(timeout) END;
timeout.SetTimeout(SELF.HandleTimeout, ms);
END;
EXCL(flags, Timeout);
AWAIT((state IN (good+bad)) OR (Timeout IN flags));
IF ms # -1 THEN timeout.CancelTimeout(); END
END;
IF state IN good THEN
res := Ok
ELSIF state IN bad THEN
res := NotConnected
ELSE
res := TimedOut
END
END AwaitState;
PROCEDURE HandleTimeout;
BEGIN {EXCLUSIVE}
INCL(flags, Timeout)
END HandleTimeout;
PROCEDURE Close*;
BEGIN {EXCLUSIVE}
IF state < Established THEN
CloseConnection(SELF)
ELSIF FALSE THEN
Drop(SELF, 0)
ELSE
UsrClosed(SELF);
IF state # Closed THEN Output(SELF) END
END;
IF TraceProtocol THEN
TraceTCP("Close", SELF, empty^, empty^, 0, 0, 0)
END
END Close;
PROCEDURE Discard*;
BEGIN {EXCLUSIVE}
IF state < Established THEN
CloseConnection(SELF)
ELSE
Drop(SELF, ConnectionReset)
END;
IF TraceProtocol THEN
TraceTCP("Discard", SELF, empty^, empty^, 0, 0, 0)
END
END Discard;
PROCEDURE Accept*(VAR client: Connection; VAR res: LONGINT);
BEGIN {EXCLUSIVE}
AWAIT((state # Listen) OR (acceptNext # NIL));
IF acceptNext # NIL THEN
client := acceptNext; acceptNext := acceptNext.acceptNext;
INC(acceptable); res := Ok
ELSE
client := NIL; res := ConnectionRefused
END
END Accept;
PROCEDURE Requested*(): BOOLEAN;
BEGIN {EXCLUSIVE}
RETURN (state = Listen) & (acceptNext # NIL)
END Requested;
PROCEDURE Input(int: IP.Interface; fip: IP.Adr; hdrLen: LONGINT; buffer: Network.Buffer);
VAR
bufferQueued: BOOLEAN;
p: Connection;
BEGIN {EXCLUSIVE}
IF StrongChecks THEN Invariant(SELF) END;
bufferQueued := FALSE;
IF AcceptConn IN flags THEN
IF acceptable > 0 THEN
NEW(p); InitConnection(p);
p.int := int;
p.fip := fip;
p.state := Listen; p.parent := SELF;
ProcessInput(p, hdrLen, buffer, TRUE, bufferQueued);
IF p.state = SynReceived THEN
DEC(acceptable)
END
ELSE
Machine.AtomicInc(NTCPUnacceptable)
END
ELSE
IF SELF = nilpcb THEN
SELF.int := int;
SELF.fport := Network.GetNet2(buffer.data, buffer.ofs);
SELF.lport := Network.GetNet2(buffer.data, buffer.ofs+2);
END;
IF SELF.int # int THEN
Error(WrongInterface, 0, SELF);
ELSE
SELF.fip := fip;
ProcessInput(SELF, hdrLen, buffer, FALSE, bufferQueued);
END;
END;
IF StrongChecks THEN Invariant(SELF) END;
IF ~bufferQueued THEN
Network.ReturnBuffer(buffer);
END;
END Input;
PROCEDURE DelayedAck;
BEGIN {EXCLUSIVE}
IF StrongChecks THEN Invariant(SELF) END;
IF DelAck IN flags THEN
flags := (flags - {DelAck}) + {AckNow};
Machine.AtomicInc(NTCPDelAck);
Output(SELF)
END
END DelayedAck;
PROCEDURE SlowTimer;
VAR dropit: BOOLEAN;
oldie: LONGINT;
BEGIN {EXCLUSIVE}
oldie := sndnxt;
IF StrongChecks THEN Invariant(SELF) END;
IF Expired(timer[ReXmt]) THEN
INC(rxtshift);
IF rxtshift > MaxRxtShift THEN
rxtshift := MaxRxtShift; Error(TimedOut, 0, SELF); Drop(SELF, TimedOut)
ELSE
Machine.AtomicInc(NTCPReXmtTimer);
RangeSet(rxtcur, (ASH(srtt, -RTTShift) + rttvar) * backoff[rxtshift], rttmin, ReXmtMax);
timer[ReXmt] := rxtcur;
IF rxtshift > MaxRxtShift DIV 4 THEN
INC(rttvar, ASH(srtt, -RTTShift));
srtt := 0
END;
sndnxt := snduna; rtt := 0;
sndcwnd := maxseg; dupacks := 0;
sndssthresh := Strings.Max(Strings.Min(sndwnd, sndcwnd) DIV 2 DIV maxseg, 2) * maxseg;
IF TraceCongestion THEN
KernelLog.String("ST sndssthresh := "); KernelLog.Int(sndssthresh, 1); KernelLog.Ln
END;
Output(SELF)
END
END;
IF Expired(timer[Persist]) THEN
Machine.AtomicInc(NTCPPersistTimer);
IF (rxtshift = MaxRxtShift) & ((idle >= MaxPersistIdle) OR (idle >= (ASH(srtt, -RTTShift) + rttvar) * totbackoff)) THEN
Machine.AtomicInc(NTCPPersistDrop);
Drop(SELF, TimedOut)
ELSE
SetPersist(SELF); INCL(flags, Force); Output(SELF); EXCL(flags, Force)
END
END;
traceflow := 0;
IF Expired(timer[Keep]) THEN
Machine.AtomicInc(NTCPKeepTimer); dropit := FALSE;
IF state >= Established THEN
IF (DoKeepAlive IN flags) & (state <= CloseWait) THEN
IF idle < KeepIdle + MaxIdle THEN
traceflow := 1;
Machine.AtomicInc(NTCPKeepProbe);
Respond(SELF, rcvnxt, snduna-1, {});
timer[Keep] := KeepIntvl
ELSE
traceflow := 2;
dropit := TRUE; Error(TimeOutKeepAlive, 0, SELF)
END
ELSE
traceflow := 3;
timer[Keep] := KeepIdle
END
ELSE
traceflow := 4;
dropit := TRUE; Error(TimeoutEstablished, 0, SELF)
END;
IF dropit THEN Drop(SELF, TimedOut) END
END;
IF Expired(timer[MSL2]) THEN
IF (state # TimeWait) & (idle <= MaxIdle) THEN
timer[MSL2] := KeepIntvl
ELSE
IF state = FinWait2 THEN Machine.AtomicInc(NTCPFinWait2Timer) ELSE Machine.AtomicInc(NTCPTimeWaitTimer) END;
CloseConnection(SELF)
END
END;
INC(idle);
IF rtt # 0 THEN INC(rtt) END;
IF (int # NIL) & (int.closed) THEN
Drop(SELF, InterfaceClosed);
END;
END SlowTimer;
PROCEDURE Finalize;
BEGIN
IF timeout # NIL THEN timeout.Finalize; END;
Discard();
END Finalize;
END Connection;
TYPE
ConnectionHandler* = PROCEDURE {DELEGATE} (p: Connection);
ConnectionPool* = OBJECT
VAR
eport: LONGINT;
table: ARRAY HashTableSize OF Connection;
PROCEDURE &Init*;
VAR i: LONGINT;
BEGIN
FOR i:= 0 TO HashTableSize-1 DO
table[i] := NIL;
END;
eport := MinEphemeralPort;
END Init;
PROCEDURE Finalize;
VAR i: LONGINT;
BEGIN
FOR i:= 0 TO HashTableSize-1 DO
WHILE table[i] # NIL DO
table[i].Finalize();
END;
END;
END Finalize;
PROCEDURE Lookup(lport, fport: LONGINT; fip: IP.Adr): Connection;
VAR
item: Connection;
BEGIN
item := table[HashPool(lport, fport, fip)];
WHILE (item # NIL) & ((~IP.AdrsEqual(item.fip, fip)) OR (item.fport # fport) OR (item.lport # lport)) DO
item := item.poolNext;
END;
IF item = NIL THEN
RETURN nilpcb;
ELSE
RETURN item;
END;
END Lookup;
PROCEDURE Enumerate*(handle: ConnectionHandler);
VAR
i: LONGINT;
item: Connection;
BEGIN
FOR i:= 0 TO HashTableSize-1 DO
item := table[i];
WHILE item # NIL DO
handle(item);
item := item.poolNext;
END;
END;
END Enumerate;
PROCEDURE Add(p: Connection; lport, fport: LONGINT; VAR res: LONGINT);
VAR i, sport: LONGINT;
BEGIN {EXCLUSIVE}
IF ((fport # NilPort) & (IP.IsNilAdr(p.fip))) OR
((fport = NilPort) & (~IP.IsNilAdr(p.fip))) THEN
res := InvalidParameter;
Error(res, 0, p);
RETURN;
END;
IF lport = NilPort THEN
sport := eport;
LOOP
lport := eport;
INC(eport);
IF eport > MaxEphemeralPort THEN
eport := MinEphemeralPort;
END;
IF Lookup(lport, fport, p.fip) = nilpcb THEN
EXIT;
END;
IF eport = sport THEN
res := AllPortsInUse;
Error(res, 0, p);
RETURN;
END;
END;
ELSE
IF Lookup(lport, fport, p.fip) # nilpcb THEN
res := AddressInUse;
Error(res, 0, p);
RETURN;
END;
END;
p.lport := lport;
p.fport := fport;
i := HashPool(lport, fport, p.fip);
p.poolNext := table[i];
table[i] := p;
res := Ok;
END Add;
PROCEDURE Remove(p: Connection);
VAR
i: LONGINT;
item: Connection;
BEGIN {EXCLUSIVE}
i := HashPool(p.lport, p.fport, p.fip);
IF table[i] # NIL THEN
IF table[i] = p THEN
table[i] := table[i].poolNext;
RETURN;
ELSE
item := table[i];
WHILE (item.poolNext # NIL) & (item.poolNext # p) DO
item := item.poolNext;
END;
IF item.poolNext # NIL THEN
item.poolNext := item.poolNext.poolNext;
RETURN;
END;
END;
END;
Error(ConnectionGone, 0, p);
END Remove;
END ConnectionPool;
TYPE
PacketDumpListener* = PROCEDURE (fip: IP.Adr; buffer: Network.Buffer);
VAR
pool*: ConnectionPool;
timeSource: Timer;
issSource: ISS;
lastpcb: Connection;
nilpcb: Connection;
empty: SendData;
backoff: ARRAY MaxRxtShift+1 OF LONGINT;
totbackoff: LONGINT;
outflags: ARRAY NumStates OF SET;
NTCPError-: ARRAY NumErrors OF LONGINT;
NTCPConnectAttempt-, NTCPPersistTimer-, NTCPFinWait2Timer-, NTCPSendProbe-, NTCPReXmtPack-,
NTCPReXmtByte-, NTCPSendPack-, NTCPSendByte-, NTCPAcks-, NTCPSendCtrl-, NTCPSendUrg-,
NTCPSendWinUp-, NTCPSegsTimed-, NTCPSendTotal-, NTCPKeepTimer-, NTCPKeepProbe-,
NTCPReXmtTimer-, NTCPRcvTotal-, NTCPRcvOptions-, NTCPCacheMiss-, NTCPPredAck-, NTCPAckPack-,
NTCPAckByte-, NTCPPredData-, NTCPRcvPackFast-, NTCPRcvByteFast-, NTCPConnects-, NTCPRcvWinProbe-,
NTCPDrops-, NTCPRcvWinUpd-, NTCPRTTUpdated-, NTCPDelAck-, NTCPConnDrops-, NTCPClosed-, NTCPSplitBuffer-,
NTCPRcvPackSlow-, NTCPRcvByteSlow-, NTCPNewBufs-, NTCPTimeWaitTimer-,
NTCPUnacceptable-, NTCPAccepts-, NTCPPersistDrop-: LONGINT;
trace: BOOLEAN;
packetDumpListener : PacketDumpListener;
PROCEDURE SetDefaultListener*(pdl : PacketDumpListener);
BEGIN
packetDumpListener := pdl
END SetDefaultListener;
PROCEDURE Invariant(p: Connection);
VAR
rcvbuf: Network.Buffer;
sndbuf: SendBuffer;
found: BOOLEAN;
BEGIN
IF StrongChecks & (p # nilpcb) THEN
rcvbuf := p.rcvhead;
IF rcvbuf # NIL THEN
ASSERT((rcvbuf.len > 0) OR (Fin IN rcvbuf.set));
ASSERT(rcvbuf.prev = NIL);
IF rcvbuf.next = NIL THEN
ASSERT(p.rcvtail = rcvbuf);
ELSE
rcvbuf := rcvbuf.next;
IF rcvbuf.next = NIL THEN
ASSERT(rcvbuf.prev.next = rcvbuf);
ASSERT(p.rcvtail = rcvbuf);
ELSE
REPEAT
ASSERT(rcvbuf.next.prev = rcvbuf);
ASSERT(rcvbuf.prev.next = rcvbuf);
rcvbuf := rcvbuf.next;
UNTIL rcvbuf.next = NIL;
ASSERT(p.rcvtail = rcvbuf);
END;
END;
END;
sndbuf := p.sndhead; found := FALSE;
LOOP
found := found OR (sndbuf = p.sndtail);
sndbuf := sndbuf.next;
ASSERT(sndbuf # NIL);
IF sndbuf = p.sndhead THEN EXIT END;
ASSERT(~found OR ((sndbuf.ofs = 0) & (sndbuf.len = 0)))
END;
ASSERT(found);
END
END Invariant;
PROCEDURE HashPool(lport, fport: LONGINT; fip:IP.Adr): LONGINT;
VAR
i: LONGINT;
hash: LONGINT;
BEGIN
hash := lport + fport;
CASE fip.usedProtocol OF
IP.IPv4:
INC(hash, fip.ipv4Adr);
|IP.IPv6:
FOR i := 0 TO 15 DO
INC(hash, ORD(fip.ipv6Adr[i]));
END;
ELSE
END;
RETURN hash MOD HashTableSize;
END HashPool;
PROCEDURE RangeSet(VAR x: LONGINT; val, min, max: LONGINT);
BEGIN
IF val < min THEN x := min
ELSIF val > max THEN x := max
ELSE x := val
END
END RangeSet;
PROCEDURE WriteTime(t: LONGINT);
VAR s: ARRAY 8 OF CHAR;
BEGIN
KernelLog.Int(t DIV Kernel.second, 1);
s[0] := "."; t := (t MOD Kernel.second)*1000 DIV Kernel.second;
s[1] := CHR(48+t DIV 100 MOD 10); s[2] := CHR(48+t DIV 10 MOD 10);
s[3] := CHR(48+t MOD 10); s[4] := 0X;
KernelLog.String(s)
END WriteTime;
PROCEDURE Error(err, n: LONGINT; p: Connection);
BEGIN
IF trace THEN
KernelLog.Enter; KernelLog.String("TCP: "); WriteTime(Kernel.GetTicks());
KernelLog.String(" result "); KernelLog.Int(err, 1);
KernelLog.Char(" "); KernelLog.Int(n, 1); KernelLog.Exit
END;
IF TraceError & (p # NIL) THEN TraceTCP("", p, empty^, empty^, 0, 0, 0) END;
Machine.AtomicInc(NTCPError[0]); Machine.AtomicInc(NTCPError[err-MinError])
END Error;
PROCEDURE SetPersist(p: Connection);
BEGIN
ASSERT(p.timer[ReXmt] = 0);
RangeSet(p.timer[Persist], (p.srtt DIV 4 + p.rttvar) DIV 2 * backoff[p.rxtshift], PersMin, PersMax);
IF p.rxtshift < MaxRxtShift THEN INC(p.rxtshift) END
END SetPersist;
PROCEDURE TraceConnection(p: Connection);
VAR i: LONGINT;
BEGIN
IF Trace THEN
KernelLog.String(" state=");
CASE p.state OF
Closed: KernelLog.String("Closed")
|Listen: KernelLog.String("Listen")
|SynSent: KernelLog.String("SynSent")
|SynReceived: KernelLog.String("SynReceived")
|Established: KernelLog.String("Established")
|CloseWait: KernelLog.String("CloseWait")
|FinWait1: KernelLog.String("FinWait1")
|Closing: KernelLog.String("Closing")
|LastAck: KernelLog.String("LastAck")
|FinWait2: KernelLog.String("FinWait2")
|TimeWait: KernelLog.String("TimeWait")
END;
KernelLog.String(" maxseg="); KernelLog.Int(p.maxseg, 1);
KernelLog.String(" flags={");
IF AckNow IN p.flags THEN KernelLog.String(" AckNow") END;
IF DelAck IN p.flags THEN KernelLog.String(" DelAck") END;
IF NoDelay IN p.flags THEN KernelLog.String(" NoDelay") END;
IF SentFin IN p.flags THEN KernelLog.String(" SentFin") END;
IF Force IN p.flags THEN KernelLog.String(" Force") END;
IF RcvdScale IN p.flags THEN KernelLog.String(" RcvdScale") END;
IF RcvdTstmp IN p.flags THEN KernelLog.String(" RcvdTstmp") END;
IF ReqScale IN p.flags THEN KernelLog.String(" ReqScale") END;
IF ReqTstmp IN p.flags THEN KernelLog.String(" ReqTstmp") END;
IF DoKeepAlive IN p.flags THEN KernelLog.String(" DoKeepAlive") END;
IF AcceptConn IN p.flags THEN KernelLog.String(" AcceptConn") END;
FOR i := 11 TO 31 DO
IF i IN p.flags THEN KernelLog.Char(" "); KernelLog.Int(i, 1) END
END;
KernelLog.String(" } error="); KernelLog.Int(p.error, 1);
KernelLog.Ln;
KernelLog.String(" iss="); KernelLog.Int(p.iss, 1);
KernelLog.String(" snduna="); KernelLog.Int(p.snduna-p.iss, 1);
KernelLog.String(" sndnxt="); KernelLog.Int(p.sndnxt-p.iss, 1);
KernelLog.String(" sndmax="); KernelLog.Int(p.sndmax-p.iss, 1);
KernelLog.String(" sndup="); KernelLog.Int(p.sndup-p.iss, 1);
KernelLog.String(" sndwl2="); KernelLog.Int(p.sndwl2-p.iss, 1);
KernelLog.String(" rtseq="); KernelLog.Int(p.rtseq-p.iss, 1);
KernelLog.Ln;
KernelLog.String(" sndwnd="); KernelLog.Int(p.sndwnd, 1);
KernelLog.String(" sndcwnd="); KernelLog.Int(p.sndcwnd, 1);
KernelLog.String(" sndcc="); KernelLog.Int(p.sndcc, 1);
KernelLog.String(" sndspace="); KernelLog.Int(p.sndspace, 1);
KernelLog.String(" sndssthresh="); KernelLog.Int(p.sndssthresh, 1);
KernelLog.Ln;
KernelLog.String(" irs="); KernelLog.Int(p.irs, 1);
KernelLog.String(" rcvnxt="); KernelLog.Int(p.rcvnxt-p.irs, 1);
KernelLog.String(" rcvup="); KernelLog.Int(p.rcvup-p.irs, 1);
KernelLog.String(" sndwl1="); KernelLog.Int(p.sndwl1-p.irs, 1);
KernelLog.String(" rcvadv="); KernelLog.Int(p.rcvadv-p.irs, 1);
KernelLog.String(" lastacksent="); KernelLog.Int(p.lastacksent-p.irs, 1);
KernelLog.Ln;
KernelLog.String(" rcvwnd="); KernelLog.Int(p.rcvwnd, 1);
KernelLog.String(" rcvspace="); KernelLog.Int(p.rcvspace, 1);
KernelLog.String(" rcvhiwat="); KernelLog.Int(p.rcvhiwat, 1);
KernelLog.String(" idle="); KernelLog.Int(p.idle, 1);
KernelLog.String(" rtt="); KernelLog.Int(p.rtt, 1);
KernelLog.String(" srtt="); KernelLog.Int(p.srtt, 1);
KernelLog.Ln;
KernelLog.String(" rttvar="); KernelLog.Int(p.rttvar, 1);
KernelLog.String(" rttmin="); KernelLog.Int(p.rttmin, 1);
KernelLog.String(" maxsndwnd="); KernelLog.Int(p.maxsndwnd, 1);
KernelLog.String(" sndscale="); KernelLog.Int(p.sndscale, 1);
KernelLog.String(" rcvscale="); KernelLog.Int(p.rcvscale, 1);
KernelLog.String(" requestrscale="); KernelLog.Int(p.requestrscale, 1);
KernelLog.Ln;
KernelLog.String(" requestedsscale="); KernelLog.Int(p.requestedsscale, 1);
KernelLog.String(" tsrecent="); KernelLog.Int(p.tsrecent, 1);
KernelLog.String(" tsrecentage="); KernelLog.Int(p.tsrecentage, 1);
KernelLog.String(" rxtshift="); KernelLog.Int(p.rxtshift, 1);
KernelLog.String(" rxtcur="); KernelLog.Int(p.rxtcur, 1);
KernelLog.String(" dupacks="); KernelLog.Int(p.dupacks, 1);
KernelLog.Ln; KernelLog.Ln
END
END TraceConnection;
PROCEDURE TraceTCP(msg: ARRAY OF CHAR; p: Connection; CONST hdr, data: ARRAY OF CHAR; hlen, ofs, len: LONGINT);
BEGIN
IF Trace THEN
WriteTime(Kernel.GetTicks()); KernelLog.Char(" ");
KernelLog.String(msg); TraceConnection(p);
IF TracePacket THEN
KernelLog.Memory(SYSTEM.ADR(hdr[0]), hlen);
KernelLog.Memory(SYSTEM.ADR(data[ofs]), len)
END
END
END TraceTCP;
PROCEDURE Output(p: Connection);
VAR
idle, sendalot: BOOLEAN;
optLen, off, sum, win, len, adv, x, startseq, left: LONGINT;
pf: SET; buf: SendBuffer; data: SendData;
pseudoHdr: ARRAY MaxPseudoHdrLen OF CHAR;
hdr: ARRAY MaxTCPHdrLen OF CHAR;
pseudoHdrLen: LONGINT;
BEGIN
idle := (p.sndmax = p.snduna);
IF idle & (p.idle >= p.rxtcur) THEN p.sndcwnd := p.maxseg END;
REPEAT
sendalot := FALSE;
LOOP
off := p.sndnxt - p.snduna;
win := p.sndwnd;
IF HandleCongestion & (p.sndcwnd < win) THEN win := p.sndcwnd END;
pf := outflags[p.state];
IF Force IN p.flags THEN
IF win = 0 THEN
IF off < p.sndcc THEN EXCL(pf, Fin) END;
win := 1
ELSE
p.timer[Persist] := 0; p.rxtshift := 0
END
END;
len := p.sndcc;
IF win < len THEN len := win END;
DEC(len, off);
IF len < 0 THEN
len := 0;
IF win = 0 THEN p.timer[ReXmt] := 0; p.sndnxt := p.snduna END;
END;
IF len > p.maxseg THEN len := p.maxseg; sendalot := TRUE END;
IF (p.sndnxt + len) - (p.snduna + p.sndcc) < 0 THEN EXCL(pf, Fin) END;
win := p.rcvspace;
IF len # 0 THEN
IF len = p.maxseg THEN EXIT END;
IF (idle OR (NoDelay IN p.flags)) & (len + off >= p.sndcc) THEN EXIT END;
IF Force IN p.flags THEN EXIT END;
IF len >= p.maxsndwnd DIV 2 THEN EXIT END;
IF p.sndnxt - p.sndmax < 0 THEN EXIT END
END;
IF win > 0 THEN
adv := ASH(MaxWin, p.rcvscale);
IF win < adv THEN adv := win END;
DEC(adv, p.rcvadv - p.rcvnxt);
IF adv >= 2*p.maxseg THEN EXIT END;
IF 2*adv >= p.rcvhiwat THEN EXIT END
END;
IF AckNow IN p.flags THEN EXIT END;
IF pf * {Syn,Rst} # {} THEN EXIT END;
IF p.sndup - p.snduna > 0 THEN EXIT END;
IF (Fin IN pf) & (~(SentFin IN p.flags) OR (p.sndnxt = p.snduna)) THEN EXIT END;
IF (p.sndcc # 0) & (p.timer[ReXmt] = 0) & (p.timer[Persist] = 0) THEN
p.rxtshift := 0; SetPersist(p)
END;
RETURN
END;
optLen := 0;
IF Syn IN pf THEN
p.sndnxt := p.iss;
IF GenOptions THEN
hdr[MinTCPHdrLen+optLen] := 2X;
hdr[MinTCPHdrLen+optLen+1] := 4X;
Network.PutNet2(hdr, MinTCPHdrLen+optLen+2, p.int.dev.mtu-120);
INC(optLen, 4);
IF ((ReqScale IN p.flags) & (~(Ack IN pf) OR (RcvdScale IN p.flags))) THEN
hdr[MinTCPHdrLen+optLen] := 1X;
hdr[MinTCPHdrLen+optLen+1] := 3X;
hdr[MinTCPHdrLen+optLen+2] := 3X;
hdr[MinTCPHdrLen+optLen+3] := CHR(p.requestrscale);
INC(optLen, 4);
END;
END;
END;
IF GenOptions & DoRFC1323 & (ReqTstmp IN p.flags) & ~(Rst IN pf) & ((pf * {Syn, Ack} = {Syn}) OR (RcvdTstmp IN p.flags)) THEN
hdr[MinTCPHdrLen+optLen] := 1X;
hdr[MinTCPHdrLen+optLen+1] := 1X;
hdr[MinTCPHdrLen+optLen+2] := 8X;
hdr[MinTCPHdrLen+optLen+3] := 0AX;
Network.PutNet4(hdr, MinTCPHdrLen+optLen+4, timeSource.now);
Network.PutNet4(hdr, MinTCPHdrLen+optLen+8, p.tsrecent);
INC(optLen, 12);
END;
IF len # 0 THEN
IF (Force IN p.flags) & (len = 1) THEN Machine.AtomicInc(NTCPSendProbe)
ELSIF p.sndnxt - p.sndmax < 0 THEN Machine.AtomicInc(NTCPReXmtPack); Machine.AtomicAdd(NTCPReXmtByte, len)
ELSE Machine.AtomicInc(NTCPSendPack); Machine.AtomicAdd(NTCPSendByte, len)
END;
IF off + len = p.sndcc THEN INCL(pf, Psh) END;
buf := p.sndhead; WHILE off >= buf.len DO DEC(off, buf.len); buf := buf.next END;
IF off+len <= buf.len THEN
data := buf.data; INC(off, buf.ofs)
ELSE
Machine.AtomicInc(NTCPSplitBuffer);
data := p.sndcontig;
ASSERT(len <= LEN(data^));
ASSERT(buf.len-off <= len);
IF SystemMove THEN
SYSTEM.MOVE(SYSTEM.ADR(buf.data[off+buf.ofs]), SYSTEM.ADR(data[0]), buf.len-off)
ELSE
Network.Copy(buf.data^, data^, off+buf.ofs, 0, buf.len-off)
END;
off := buf.len-off; left := len-off;
WHILE left # 0 DO
buf := buf.next; IF left <= buf.len THEN x := left ELSE x := buf.len END;
ASSERT(off+x <= len);
IF SystemMove THEN
SYSTEM.MOVE(SYSTEM.ADR(buf.data[buf.ofs]), SYSTEM.ADR(data[off]), x)
ELSE
Network.Copy(buf.data^, data^, buf.ofs, off, x)
END;
INC(off, x); DEC(left, x)
END;
off := 0
END
ELSE
IF AckNow IN p.flags THEN Machine.AtomicInc(NTCPAcks)
ELSIF pf * {Syn,Fin,Rst} # {} THEN Machine.AtomicInc(NTCPSendCtrl)
ELSIF p.sndup - p.snduna > 0 THEN Machine.AtomicInc(NTCPSendUrg)
ELSE Machine.AtomicInc(NTCPSendWinUp)
END;
data := empty; off := 0
END;
IF (Fin IN pf) & (SentFin IN p.flags) & (p.sndnxt = p.sndmax) THEN DEC(p.sndnxt) END;
IF (len # 0) OR (pf * {Syn,Fin} # {}) OR (p.timer[Persist] # 0) THEN
Network.PutNet4(hdr, 4, p.sndnxt)
ELSE
Network.PutNet4(hdr, 4, p.sndmax)
END;
Network.PutNet4(hdr, 8, p.rcvnxt);
IF (win < p.rcvhiwat DIV 4) & (win < p.maxseg) THEN win := 0 END;
IF win > ASH(MaxWin, p.rcvscale) THEN win := ASH(MaxWin, p.rcvscale) END;
IF win < p.rcvadv - p.rcvnxt THEN win := p.rcvadv - p.rcvnxt END;
Network.PutNet2(hdr, 14, ASH(win, -p.rcvscale));
IF p.sndup - p.sndnxt > 0 THEN
x := p.sndup - p.sndnxt;
IF x > 65535 THEN x := 65535 END;
Network.PutNet2(hdr, 18, x);
INCL(pf, Urg)
ELSE
p.sndup := p.snduna
END;
hdr[13] := CHR(SHORT(SHORT(SYSTEM.VAL(LONGINT, pf))));
Network.PutNet2(hdr, 0, p.lport);
Network.PutNet2(hdr, 2, p.fport);
hdr[12] := CHR((MinTCPHdrLen+optLen) DIV 4*10H);
Network.Put2(hdr, 16, 0);
IF ~(Network.ChecksumTCP IN p.int.dev.calcChecksum) THEN
pseudoHdrLen := p.int.WritePseudoHeader(pseudoHdr, p.int.localAdr, p.fip, IPTypeTCP, MinTCPHdrLen+optLen+len);
sum := IP.Checksum1(pseudoHdr, 0, pseudoHdrLen, 0);
sum := IP.Checksum1(hdr, 0, MinTCPHdrLen+optLen, sum);
sum := IP.Checksum2(data^, off, len, sum);
Network.Put2(hdr, 16, sum);
END;
IF ~(Force IN p.flags) OR (p.timer[Persist] = 0) THEN
startseq := p.sndnxt;
IF pf * {Syn,Fin} # {} THEN
IF Syn IN pf THEN INC(p.sndnxt) END;
IF Fin IN pf THEN INC(p.sndnxt); INCL(p.flags, SentFin) END
END;
INC(p.sndnxt, len);
IF p.sndnxt - p.sndmax > 0 THEN
p.sndmax := p.sndnxt;
IF p.rtt = 0 THEN p.rtt := 1; p.rtseq := startseq; Machine.AtomicInc(NTCPSegsTimed) END
END;
IF (p.timer[ReXmt] = 0) & (p.sndnxt # p.snduna) THEN
p.timer[ReXmt] := p.rxtcur;
IF p.timer[Persist] # 0 THEN p.timer[Persist] := 0; p.rxtshift := 0 END
END
ELSIF (p.sndnxt + len) - p.sndmax > 0 THEN
p.sndmax := p.sndnxt + len
ELSE
END;
IF TraceProtocol THEN
TraceTCP("Output", p, hdr, data^, MinTCPHdrLen+optLen, off, len)
END;
p.int.Send(IPTypeTCP, p.fip, hdr, data^, MinTCPHdrLen+optLen, off, len, IP.MaxTTL);
Machine.AtomicInc(NTCPSendTotal);
IF (win > 0) & ((p.rcvnxt + win) - p.rcvadv > 0) THEN p.rcvadv := p.rcvnxt + win END;
p.lastacksent := p.rcvnxt;
p.flags := p.flags - {AckNow,DelAck}
UNTIL ~sendalot;
END Output;
PROCEDURE Respond(p: Connection; ack, seq: LONGINT; rf: SET);
VAR
win, sum: LONGINT;
pseudoHdr: ARRAY MaxPseudoHdrLen OF CHAR;
hdr: ARRAY MaxTCPHdrLen OF CHAR;
pseudoHdrLen: LONGINT;
BEGIN
win := ASH(p.rcvspace, -p.rcvscale);
IF rf = {} THEN
INCL(rf, Ack)
ELSE
END;
Network.PutNet2(hdr, 0, p.lport);
Network.PutNet2(hdr, 2, p.fport);
Network.PutNet4(hdr, 4, seq);
Network.PutNet4(hdr, 8, ack);
hdr[12] := CHR(MinTCPHdrLen DIV 4*10H);
hdr[13] := CHR(SHORT(SHORT(SYSTEM.VAL(LONGINT, rf))));
Network.PutNet2(hdr, 14, win);
Network.Put2(hdr, 16, 0);
Network.Put2(hdr, 18, 0);
IF ~(Network.ChecksumTCP IN p.int.dev.calcChecksum) THEN
pseudoHdrLen := p.int.WritePseudoHeader(pseudoHdr, p.int.localAdr, p.fip, IPTypeTCP, MinTCPHdrLen);
sum := IP.Checksum1(pseudoHdr, 0, pseudoHdrLen, 0);
sum := IP.Checksum2(hdr, 0, MinTCPHdrLen, sum);
Network.Put2(hdr, 16, sum);
END;
p.int.Send(IPTypeTCP, p.fip, hdr, hdr, MinTCPHdrLen, 0, 0, IP.MaxTTL);
END Respond;
PROCEDURE CancelTimers(p: Connection);
VAR i: LONGINT;
BEGIN
FOR i := 0 TO NumTimers-1 DO p.timer[i] := 0 END
END CancelTimers;
PROCEDURE XmitTimer(p: Connection; rtt: LONGINT);
VAR delta: LONGINT;
BEGIN
Machine.AtomicInc(NTCPRTTUpdated);
IF p.srtt # 0 THEN
delta := rtt - 1 - ASH(p.srtt, -RTTShift);
INC(p.srtt, delta);
IF p.srtt <= 0 THEN p.srtt := 1 END;
IF delta < 0 THEN delta := -delta END;
DEC(delta, ASH(p.rttvar, -RTTVarShift));
INC(p.rttvar, delta);
IF p.rttvar <= 0 THEN p.rttvar := 1 END
ELSE
p.srtt := ASH(rtt, RTTShift); p.rttvar := ASH(rtt, RTTVarShift-1)
END;
p.rtt := 0; p.rxtshift := 0;
RangeSet(p.rxtcur, ASH(p.srtt, -RTTShift) + p.rttvar, MinTime, ReXmtMax);
END XmitTimer;
PROCEDURE SbDrop(p: Connection; len: LONGINT);
VAR buf: SendBuffer;
BEGIN
DEC(p.sndcc, len); INC(p.sndspace, len);
buf := p.sndhead;
LOOP
IF buf.len > len THEN
INC(buf.ofs, len); DEC(buf.len, len);
EXIT
END;
DEC(len, buf.len);
buf.ofs := 0; buf.len := 0;
IF buf # p.sndtail THEN buf := buf.next END;
IF len = 0 THEN EXIT END
END;
p.sndhead := buf
END SbDrop;
PROCEDURE CloseConnection(p: Connection);
VAR
buf: Network.Buffer;
BEGIN
IF FALSE THEN
END;
pool.Remove(p);
IF p = lastpcb THEN lastpcb := nilpcb END;
p.state := Closed;
p.rcvreasm := NIL;
p.rcvtail := NIL;
WHILE p.rcvhead # NIL DO
buf := p.rcvhead;
p.rcvhead := p.rcvhead.next;
Network.ReturnBuffer(buf);
END;
Machine.AtomicInc(NTCPClosed);
END CloseConnection;
PROCEDURE Drop(p: Connection; err: LONGINT);
BEGIN
IF p.state >= SynReceived THEN
p.state := Closed; Output(p); Machine.AtomicInc(NTCPDrops)
ELSE
Machine.AtomicInc(NTCPConnDrops)
END;
p.error := err; CloseConnection(p)
END Drop;
PROCEDURE SetMSS(p: Connection; mss: LONGINT);
END SetMSS;
PROCEDURE Input(int: IP.Interface; type: LONGINT; fip, lip: IP.Adr; buffer: Network.Buffer);
VAR
lport, fport, hdrLen: LONGINT;
p: Connection;
BEGIN
ASSERT(type = IPTypeTCP);
Machine.AtomicInc(NTCPRcvTotal);
IF IP.AdrsEqual(int.localAdr, lip) THEN
IF buffer.len >= MinTCPHdrLen THEN
hdrLen := LONG(ORD(buffer.data[buffer.ofs+12])) DIV 10H * 4;
IF (hdrLen < MinTCPHdrLen) OR (hdrLen > buffer.len) THEN
Error(BadHeaderLength, hdrLen, NIL);
ELSE
p := lastpcb;
fport := Network.GetNet2(buffer.data, buffer.ofs);
lport := Network.GetNet2(buffer.data, buffer.ofs+2);
IF (p = nilpcb) OR (~IP.AdrsEqual(p.fip, fip)) OR (p.lport # lport) OR (p.fport # fport) THEN
p := pool.Lookup(lport, fport, fip);
IF p = nilpcb THEN
p := pool.Lookup(lport, NilPort, IP.NilAdr);
END;
lastpcb := p;
Machine.AtomicInc(NTCPCacheMiss);
END;
p.Input(int, fip, hdrLen, buffer);
RETURN;
END;
ELSE
Error(SegmentTooSmall, buffer.len, NIL);
END;
ELSE
Error(BroadcastReceived, buffer.len, NIL);
END;
Network.ReturnBuffer(buffer);
END Input;
PROCEDURE ProcessInput(p: Connection; hdrLen: LONGINT; buffer: Network.Buffer; drop: BOOLEAN; VAR bufferQueued: BOOLEAN);
CONST
Options = 0; TSPresent = 1; NeedOutput = 2;
VAR
win, sum, urp, seq, ack, tsval, tsecr, acked, discard, tlen, optLen: LONGINT;
pseudoHdr: ARRAY MaxPseudoHdrLen OF CHAR;
pseudoHdrLen: LONGINT;
pf, lf: SET;
reassembledLength: LONGINT;
fragmentBuffer: Network.Buffer;
PROCEDURE GotoDrop;
BEGIN
IF TraceProtocol THEN TraceTCP("Drop", p, empty^, empty^, 0, 0, 0) END;
IF drop THEN Drop(p, ConnectionAborted) END
END GotoDrop;
PROCEDURE GotoDropReset;
BEGIN
IF (Rst IN pf) THEN
GotoDrop
ELSE
IF Ack IN pf THEN
Respond(p, 0, ack, {Rst})
ELSE
IF Syn IN pf THEN INC(tlen) END;
Respond(p, seq + tlen, 0, {Rst,Ack})
END;
IF drop THEN Drop(p, ConnectionAborted) END
END
END GotoDropReset;
PROCEDURE GotoDropAfterAck;
BEGIN
IF Rst IN pf THEN
GotoDrop
ELSE
INCL(p.flags, AckNow); Output(p)
END
END GotoDropAfterAck;
PROCEDURE ProcessOptions;
VAR opt: CHAR; i, m, optlen: LONGINT;
BEGIN
i := buffer.ofs+MinTCPHdrLen; m := buffer.ofs+hdrLen;
LOOP
IF i >= m THEN EXIT END;
opt := buffer.data[i];
IF opt = 0X THEN
EXIT;
ELSIF opt = 1X THEN
optlen := 1;
ELSE
optlen := ORD(buffer.data[i+1]);
IF optlen = 0 THEN EXIT END;
END;
CASE opt OF
2X:
IF (optlen = 4) & (Syn IN pf) THEN
SetMSS(p, Network.GetNet2(buffer.data, i+2));
END;
|3X:
IF (optlen = 3) & (Syn IN pf) THEN
INCL(p.flags, RcvdScale);
p.requestedsscale := Strings.Min(LONG(ORD(buffer.data[i+2])), MaxWinShift);
END;
|8X:
IF DoRFC1323 & (optlen = 10) THEN
INCL(lf, TSPresent);
tsval := Network.GetNet4(buffer.data, i+2);
tsecr := Network.GetNet4(buffer.data, i+6);
IF Syn IN pf THEN
INCL(p.flags, RcvdTstmp);
p.tsrecent := tsval;
p.tsrecentage := timeSource.now;
END;
END;
ELSE
END;
INC(i, optlen);
END
END ProcessOptions;
PROCEDURE ProcessListen(): BOOLEAN;
VAR
res: LONGINT;
q: Connection;
BEGIN
IF Rst IN pf THEN GotoDrop; RETURN FALSE END;
IF Ack IN pf THEN GotoDropReset; RETURN FALSE END;
IF ~(Syn IN pf) THEN GotoDrop; RETURN FALSE END;
pool.Add(p, Network.GetNet2(buffer.data, buffer.ofs+2), Network.GetNet2(buffer.data, buffer.ofs), res);
IF res # Ok THEN
GotoDrop;
RETURN FALSE;
END;
IF ProcOptions & (Options IN lf) THEN ProcessOptions END;
p.iss := issSource.Get();
p.snduna := p.iss; p.sndnxt := p.iss; p.sndmax := p.iss; p.sndup := p.iss;
p.irs := seq; p.rcvnxt := seq+1; p.rcvadv := p.rcvnxt;
INCL(p.flags, AckNow);
p.state := SynReceived;
p.timer[Keep] := KeepInit;
drop := FALSE;
ASSERT(Objects.LockedByCurrent(p.parent));
q := p.parent.acceptNext; p.acceptNext := NIL;
IF q = NIL THEN
p.parent.acceptNext := p
ELSE
WHILE q.acceptNext # NIL DO q := q.acceptNext END;
q.acceptNext := p
END;
Machine.AtomicInc(NTCPAccepts);
RETURN TRUE
END ProcessListen;
PROCEDURE ProcessSynSent(): BOOLEAN;
BEGIN
IF (Ack IN pf) & ((ack - p.iss <= 0) OR (ack - p.sndmax > 0)) THEN GotoDropReset; RETURN FALSE END;
IF Rst IN pf THEN
IF Ack IN pf THEN Error(ConnectionRefused, 0, p); Drop(p, ConnectionRefused) END;
GotoDrop; RETURN FALSE
END;
IF ~(Syn IN pf) THEN GotoDrop; RETURN FALSE END;
IF Ack IN pf THEN
p.snduna := ack;
IF p.sndnxt - p.snduna < 0 THEN p.sndnxt := p.snduna END;
END;
p.timer[ReXmt] := 0;
p.irs := seq; p.rcvnxt := seq+1; p.rcvadv := p.rcvnxt;
INCL(p.flags, AckNow);
IF (Ack IN pf) & (p.snduna - p.iss > 0) THEN
Machine.AtomicInc(NTCPConnects);
p.state := Established;
IF p.flags * {RcvdScale,ReqScale} = {RcvdScale,ReqScale} THEN
p.sndscale := p.requestedsscale; p.rcvscale := p.requestrscale
END;
IF p.rtt # 0 THEN XmitTimer(p, p.rtt) END
ELSE
p.state := SynReceived
END;
RETURN TRUE
END ProcessSynSent;
PROCEDURE Trim1;
BEGIN
INC(seq);
IF tlen > p.rcvwnd THEN
Error(DataBeyondWindow1, tlen - p.rcvwnd, p);
tlen := p.rcvwnd; EXCL(pf, Fin)
END;
p.sndwl1 := seq-1; p.rcvup := seq
END Trim1;
PROCEDURE Paws(): BOOLEAN;
BEGIN
IF (TSPresent IN lf) & ~(Rst IN pf) & (p.tsrecent # 0) & (tsval - p.tsrecent < 0) THEN
IF (timeSource.now - p.tsrecentage) > PawsIdle THEN
p.tsrecent := 0
ELSE
Error(DuplicateSegmentPAWS, tlen, p);
GotoDropAfterAck; RETURN FALSE
END
END;
RETURN TRUE
END Paws;
PROCEDURE Trim2(todrop: LONGINT): BOOLEAN;
BEGIN
IF Syn IN pf THEN
EXCL(pf, Syn); INC(seq);
IF urp > 1 THEN DEC(urp) ELSE EXCL(pf, Urg) END;
DEC(todrop)
END;
IF (todrop > tlen) OR ((todrop = tlen) & ~(Fin IN pf)) THEN
EXCL(pf, Fin); INCL(p.flags, AckNow); todrop := tlen;
Error(DuplicateSegment, todrop, p)
ELSE
Error(DuplicatePartialSegment, todrop, p)
END;
INC(discard, todrop); INC(seq, todrop); DEC(tlen, todrop);
IF urp > todrop THEN DEC(urp, todrop) ELSE EXCL(pf, Urg); urp := 0 END;
RETURN TRUE
END Trim2;
PROCEDURE Trim3(todrop: LONGINT): BOOLEAN;
BEGIN
IF todrop >= tlen THEN
IF (Syn IN pf) & (p.state = TimeWait) & (seq - p.rcvnxt > 0) THEN
Error(NIYNewIncarnation, 0, p);
GotoDropAfterAck; RETURN FALSE
END;
IF (p.rcvwnd = 0) & (seq = p.rcvnxt) THEN
INCL(p.flags, AckNow); Machine.AtomicInc(NTCPRcvWinProbe)
ELSE
Error(DataBeyondWindow2, tlen, p);
GotoDropAfterAck; RETURN FALSE
END
ELSE
Error(DataBeyondWindow3, todrop, p)
END;
DEC(tlen, todrop); pf := pf - {Psh,Fin};
RETURN TRUE
END Trim3;
PROCEDURE RecordTS;
VAR x: LONGINT;
BEGIN
IF DoRFC1323 THEN
IF pf * {Syn,Fin} # {} THEN x := 1 ELSE x := 0 END;
IF p.lastacksent - (seq + tlen + x) < 0 THEN
p.tsrecentage := timeSource.now;
p.tsrecent := tsval;
END
END
END RecordTS;
PROCEDURE ProcessRst(): BOOLEAN;
BEGIN
CASE p.state OF
SynReceived, Established, FinWait1, FinWait2, CloseWait:
IF p.state = SynReceived THEN p.error := ConnectionRefused ELSE p.error := ConnectionReset END;
p.state := Closed; Error(p.error, 0, p);
CloseConnection(p); GotoDrop; RETURN FALSE
|Closing, LastAck:
CloseConnection(p); GotoDrop; RETURN FALSE
ELSE
END;
RETURN TRUE
END ProcessRst;
PROCEDURE ProcessAck(): BOOLEAN;
VAR onxt, cw, incr: LONGINT; finacked: BOOLEAN;
BEGIN
IF p.state IN {SynReceived..TimeWait} THEN
IF p.state = SynReceived THEN
IF (p.snduna - ack > 0) OR (ack - p.sndmax > 0) THEN
GotoDropReset; RETURN FALSE
END;
Machine.AtomicInc(NTCPConnects);
p.state := Established;
IF p.flags * {RcvdScale,ReqScale} = {RcvdScale,ReqScale} THEN
p.sndscale := p.requestedsscale; p.rcvscale := p.requestrscale
END;
p.sndwl1 := seq-1;
END;
IF ack - p.snduna <= 0 THEN
IF (tlen = 0) & (win = p.sndwnd) THEN
Error(DuplicateAck, 0, p);
IF (p.timer[ReXmt] = 0) OR (ack # p.snduna) THEN
p.dupacks := 0
ELSE
INC(p.dupacks);
IF p.dupacks = ReXmtThresh THEN
onxt := p.sndnxt;
p.sndssthresh := Strings.Max(Strings.Min(p.sndwnd, p.sndcwnd) DIV 2 DIV p.maxseg, 2) * p.maxseg;
IF TraceCongestion THEN
KernelLog.String("DA sndssthresh := "); KernelLog.Int(p.sndssthresh, 1); KernelLog.Ln
END;
p.timer[ReXmt] := 0; p.rtt := 0; p.sndnxt := ack; p.sndcwnd := p.maxseg;
Output(p);
p.sndcwnd := p.sndssthresh + p.maxseg * p.dupacks;
IF onxt - p.sndnxt > 0 THEN p.sndnxt := onxt END;
GotoDrop; RETURN FALSE
ELSIF p.dupacks > ReXmtThresh THEN
INC(p.sndcwnd, p.maxseg);
Output(p);
GotoDrop; RETURN FALSE
ELSE
END
END
ELSE
p.dupacks := 0
END;
RETURN TRUE
END;
IF (p.dupacks > ReXmtThresh) & (p.sndcwnd > p.sndssthresh) THEN
p.sndcwnd := p.sndssthresh
END;
p.dupacks := 0;
IF ack - p.sndmax > 0 THEN
Error(OutOfRangeAck, ack - p.sndmax, p); GotoDropAfterAck;
RETURN FALSE;
END;
acked := ack - p.snduna;
Machine.AtomicInc(NTCPAckPack); Machine.AtomicAdd(NTCPAckByte, acked);
IF TSPresent IN lf THEN XmitTimer(p, timeSource.now - tsecr + 1)
ELSIF (p.rtt # 0) & (ack - p.rtseq > 0) THEN XmitTimer(p, p.rtt)
ELSE
END;
IF ack = p.sndmax THEN p.timer[ReXmt] := 0; INCL(lf, NeedOutput)
ELSIF p.timer[Persist] = 0 THEN
p.timer[ReXmt] := p.rxtcur;
ELSE
END;
cw := p.sndcwnd; incr := p.maxseg;
IF cw > p.sndssthresh THEN incr := incr * incr DIV cw END;
p.sndcwnd := Strings.Min(cw + incr, ASH(MaxWin, p.sndscale));
IF acked > p.sndcc THEN
DEC(p.sndwnd, p.sndcc); SbDrop(p, p.sndcc); finacked := TRUE
ELSE
SbDrop(p, acked); DEC(p.sndwnd, acked); finacked := FALSE
END;
p.snduna := ack;
IF p.sndnxt - p.snduna < 0 THEN p.sndnxt := p.snduna END;
CASE p.state OF
FinWait1:
IF finacked THEN
p.timer[MSL2] := MaxIdle;
p.state := FinWait2
END
|Closing:
IF finacked THEN
p.state := TimeWait; CancelTimers(p); p.timer[MSL2] := 2 * MSL;
END
|LastAck:
IF finacked THEN
CloseConnection(p); GotoDrop; RETURN FALSE
END
|TimeWait:
p.timer[MSL2] := 2 * MSL; GotoDropAfterAck; RETURN FALSE
ELSE
END
END;
RETURN TRUE
END ProcessAck;
PROCEDURE GotoPresent;
VAR
buf: Network.Buffer;
BEGIN
buf := p.rcvreasm;
IF (p.state >= Established) & (buf # NIL) & (buf.int = p.rcvnxt) THEN
REPEAT
DEC(p.rcvspace, buf.len);
INC(p.rcvnxt, buf.len);
pf := buf.set * {Fin};
buf := buf.next;
UNTIL (buf = NIL) OR (buf.int # p.rcvnxt);
p.rcvreasm := buf;
ELSE
EXCL(pf, Fin);
END;
END GotoPresent;
PROCEDURE Reasm;
VAR
pos, last: Network.Buffer;
lap: LONGINT;
BEGIN
buffer.set := pf;
buffer.int := seq;
INC(buffer.ofs, hdrLen + discard);
buffer.len := tlen;
IF p.rcvhead = NIL THEN
buffer.next := NIL;
buffer.prev := NIL;
p.rcvhead := buffer;
p.rcvreasm := buffer;
p.rcvtail := buffer;
bufferQueued := TRUE;
ELSE
pos := p.rcvreasm;
IF pos = NIL THEN
last := p.rcvtail;
ELSE
last := pos.prev;
WHILE (pos # NIL) & (pos.int < seq) DO
last := pos;
pos := pos.next;
END;
END;
IF last # NIL THEN
lap := (last.int + last.len) - seq;
IF lap > 0 THEN
IF lap >= tlen THEN
Error(DataDuplicatePrevComplete, tlen, p);
RETURN;
ELSE
Error(DataDuplicatePrevPartial, lap, p);
INC(buffer.ofs, lap);
DEC(buffer.len, lap);
DEC(tlen, lap);
INC(seq, lap);
buffer.int := seq;
END;
END;
END;
IF pos # NIL THEN
lap := (seq + tlen) - pos.int;
IF lap > 0 THEN
IF lap >= tlen THEN
Error(DataDuplicateNextComplete, lap, p);
RETURN;
ELSE
Error(DataDuplicateNextPartial, lap, p);
DEC(tlen, lap);
DEC(buffer.len, lap);
END;
END;
END;
Machine.AtomicInc(NTCPRcvPackSlow);
Machine.AtomicAdd(NTCPRcvByteSlow, tlen);
IF pos = NIL THEN
ASSERT(last = p.rcvtail);
buffer.next := NIL;
buffer.prev := last;
buffer.prev.next := buffer;
p.rcvtail := buffer;
IF p.rcvreasm = NIL THEN
p.rcvreasm := buffer;
END;
ELSIF last = NIL THEN
ASSERT((pos = p.rcvhead) & (pos = p.rcvreasm));
buffer.prev := NIL;
buffer.next := pos;
buffer.next.prev := buffer;
p.rcvhead := buffer;
p.rcvreasm := buffer;
ELSE
ASSERT((last.next = pos) & (pos.prev = last));
last.next := buffer;
buffer.prev := last;
pos.prev := buffer;
buffer.next := pos;
IF buffer.next = p.rcvreasm THEN
p.rcvreasm := buffer;
END;
END;
bufferQueued := TRUE;
END;
GotoPresent;
END Reasm;
PROCEDURE DoData;
BEGIN
IF ((tlen # 0) OR (Fin IN pf)) & (p.state < TimeWait) THEN
IF (seq = p.rcvnxt) & (p.rcvreasm = NIL) & (p.state = Established) THEN
INCL(p.flags, DelAck)
ELSE
INCL(p.flags, AckNow)
END;
Reasm();
ELSE
EXCL(pf, Fin)
END;
IF Fin IN pf THEN
IF p.state < TimeWait THEN
INCL(p.flags, AckNow); INC(p.rcvnxt)
END;
CASE p.state OF
SynReceived, Established:
p.state := CloseWait
|FinWait1:
p.state := Closing
|FinWait2:
p.state := TimeWait; CancelTimers(p); p.timer[MSL2] := 2 * MSL;
|TimeWait:
p.timer[MSL2] := 2 * MSL
ELSE
END
END;
IF TraceProtocol THEN
TraceTCP("Input", p, buffer.data, empty^, buffer.ofs+hdrLen, 0, 0)
END;
IF (NeedOutput IN lf) OR (AckNow IN p.flags) THEN Output(p) END
END DoData;
PROCEDURE Step6(): BOOLEAN;
BEGIN
IF (Ack IN pf) & ((p.sndwl1 - seq < 0) OR ((p.sndwl1 = seq) & ((p.sndwl2 - ack < 0) OR
((p.sndwl2 = ack) & (win > p.sndwnd))))) THEN
IF (tlen = 0) & (p.sndwl2 = ack) & (win > p.sndwnd) THEN Machine.AtomicInc(NTCPRcvWinUpd) END;
p.sndwnd := win; p.sndwl1 := seq; p.sndwl2 := ack;
IF p.sndwnd > p.maxsndwnd THEN p.maxsndwnd := p.sndwnd END;
INCL(lf, NeedOutput)
END;
IF (Urg IN pf) & (urp # 0) & (p.state < TimeWait) THEN
Error(NIYOutOfBand, 0, p);
urp := 0; EXCL(pf, Urg); DoData; RETURN FALSE;
ELSE
IF p.rcvnxt - p.rcvup > 0 THEN p.rcvup := p.rcvnxt END
END;
RETURN TRUE
END Step6;
BEGIN
lf := {};
discard := 0;
tlen := buffer.len-hdrLen;
optLen := hdrLen - MinTCPHdrLen;
pf := SYSTEM.VAL(SET, LONG(ORD(buffer.data[buffer.ofs+13])));
IF optLen > 0 THEN
Machine.AtomicInc(NTCPRcvOptions);
IF ProcOptions THEN
IF DoRFC1323 THEN
IF ((optLen = 12) OR ((optLen > 12) & (buffer.data[buffer.ofs+MinTCPHdrLen+12] = 0X))) & ~(Syn IN pf) &
(buffer.data[buffer.ofs+MinTCPHdrLen] = 1X) &
(buffer.data[buffer.ofs+MinTCPHdrLen+1] = 1X) &
(buffer.data[buffer.ofs+MinTCPHdrLen+2] = 8X) &
(buffer.data[buffer.ofs+MinTCPHdrLen+3] = 0AX) THEN
INCL(lf, TSPresent);
tsval := Network.GetNet4(buffer.data, buffer.ofs+MinTCPHdrLen+4);
tsecr := Network.GetNet4(buffer.data, buffer.ofs+MinTCPHdrLen+8);
ELSE
INCL(lf, Options);
END;
ELSE
INCL(lf, Options);
END
END
END;
seq := Network.GetNet4(buffer.data, buffer.ofs+4);
ack := Network.GetNet4(buffer.data, buffer.ofs+8);
IF p = nilpcb THEN
IF packetDumpListener # NIL THEN packetDumpListener(p.fip, buffer) END;
GotoDropReset; RETURN
END;
IF p.state <= Closed THEN GotoDrop; RETURN END;
IF ~(Network.ChecksumTCP IN buffer.calcChecksum) THEN
reassembledLength := 0;
fragmentBuffer := buffer;
WHILE fragmentBuffer # NIL DO
INC(reassembledLength, fragmentBuffer.len);
fragmentBuffer := fragmentBuffer.nextFragment;
END;
pseudoHdrLen := p.int.WritePseudoHeader(pseudoHdr, p.fip, p.int.localAdr, IPTypeTCP, reassembledLength);
sum := IP.Checksum1(pseudoHdr, 0, pseudoHdrLen, 0);
IF buffer.nextFragment # NIL THEN
fragmentBuffer := buffer;
WHILE fragmentBuffer.nextFragment # NIL DO
sum := IP.Checksum1(fragmentBuffer.data, fragmentBuffer.ofs, fragmentBuffer.len, sum);
fragmentBuffer := fragmentBuffer.nextFragment;
END;
sum := IP.Checksum2(fragmentBuffer.data, fragmentBuffer.ofs, fragmentBuffer.len, sum);
ELSE
sum := IP.Checksum2(buffer.data, buffer.ofs, buffer.len, sum);
END;
IF sum # 0 THEN
Error(BadChecksum, 0, p);
GotoDrop;
RETURN;
END;
END;
win := Network.GetNet2(buffer.data, buffer.ofs+14);
urp := Network.GetNet2(buffer.data, buffer.ofs+18);
IF ~(Syn IN pf) THEN win := ASH(win, p.sndscale) END;
p.idle := 0; p.timer[Keep] := KeepIdle;
IF ProcOptions & (Options IN lf) & (p.state # Listen) THEN ProcessOptions END;
IF (p.state = Established) & (pf * {Syn,Fin,Rst,Urg,Ack} = {Ack}) &
(~DoRFC1323 OR ~(TSPresent IN lf) OR (tsval - p.tsrecent >= 0)) & (seq = p.rcvnxt) &
(win # 0) & (win = p.sndwnd) & (p.sndnxt = p.sndmax) THEN
IF DoRFC1323 & (TSPresent IN lf) & (seq - p.lastacksent <= 0) THEN
p.tsrecentage := timeSource.now;
p.tsrecent := tsval;
END;
IF tlen = 0 THEN
IF (ack - p.snduna > 0) & (ack - p.sndmax <= 0) & (p.sndcwnd >= p.sndwnd) & (p.dupacks < ReXmtThresh) THEN
Machine.AtomicInc(NTCPPredAck);
IF DoRFC1323 & (TSPresent IN lf) THEN XmitTimer(p, timeSource.now - tsecr + 1)
ELSIF (p.rtt # 0) & (ack - p.rtseq > 0) THEN XmitTimer(p, p.rtt)
ELSE
END;
acked := ack - p.snduna;
Machine.AtomicInc(NTCPAckPack); Machine.AtomicAdd(NTCPAckByte, acked);
SbDrop(p, acked);
p.snduna := ack;
IF ack = p.sndmax THEN p.timer[ReXmt] := 0
ELSIF p.timer[Persist] = 0 THEN
p.timer[ReXmt] := p.rxtcur;
ELSE
END;
IF p.sndcc # 0 THEN Output(p) END;
RETURN
END
ELSIF (ack = p.snduna) & (p.rcvreasm = NIL) & (tlen <= p.rcvspace) THEN
Machine.AtomicInc(NTCPPredData);
Machine.AtomicInc(NTCPRcvPackFast);
Machine.AtomicAdd(NTCPRcvByteFast, tlen);
Reasm;
INCL(p.flags, DelAck);
RETURN
ELSE
END
END;
p.rcvwnd := Strings.Max(Strings.Max(p.rcvspace, 0), p.rcvadv - p.rcvnxt);
CASE p.state OF
Listen:
IF ProcessListen() THEN Trim1 ELSE RETURN END
|SynSent:
IF ProcessSynSent() THEN Trim1 ELSE RETURN END
ELSE
IF DoRFC1323 & ~Paws() THEN RETURN END;
IF (p.rcvnxt - seq > 0) & ~Trim2(p.rcvnxt - seq) THEN RETURN END;
IF ((seq + tlen) - (p.rcvnxt + p.rcvwnd) > 0) & ~Trim3((seq + tlen) - (p.rcvnxt + p.rcvwnd)) THEN RETURN END;
IF DoRFC1323 & (TSPresent IN lf) & (seq - p.lastacksent <= 0) THEN RecordTS END;
IF (Rst IN pf) & ~ProcessRst() THEN RETURN END;
IF Syn IN pf THEN Drop(p, ConnectionReset); GotoDropReset; RETURN END;
IF ~(Ack IN pf) THEN GotoDrop; RETURN END;
IF ~ProcessAck() THEN RETURN END;
END;
IF ~Step6() THEN RETURN END;
DoData;
END ProcessInput;
PROCEDURE InitConnection(p: Connection);
VAR
buf: SendBuffer;
BEGIN
IF ~NewZeros THEN
CancelTimers(p);
p.fip := IP.NilAdr; p.fport := NilPort; p.rxtshift := 0; p.dupacks := 0;
p.snduna := 0; p.sndnxt := 0; p.sndup := 0; p.sndwl1 := 0; p.sndwl2 := 0; p.iss := 0;
p.sndwnd := 0; p.rcvwnd := 0; p.rcvnxt := 0; p.rcvup := 0; p.irs := 0; p.rcvadv := 0;
p.sndmax := 0; p.idle := 0; p.rtt := 0; p.rtseq := 0; p.maxsndwnd := 0;
p.sndscale := 0; p.rcvscale := 0; p.requestrscale := 0; p.requestedsscale := 0;
p.tsrecent := 0; p.tsrecentage := 0; p.lastacksent := 0; p.error := 0; p.acceptable := 0;
p.sndcc := 0; p.poolNext := NIL; p.parent := NIL; p.acceptNext := NIL
END;
p.maxseg := MSS; p.state := Closed;
IF DoRFC1323 THEN p.flags := {ReqScale,ReqTstmp} ELSE p.flags := {} END;
p.srtt := SRTTBase; p.rttvar := SRTTDflt * 4; p.rttmin := MinTime;
RangeSet(p.rxtcur, (SRTTBase DIV 4 + SRTTDflt * 4) DIV 2, MinTime, ReXmtMax);
p.sndcwnd := ASH(MaxWin, MaxWinShift);
p.sndssthresh := ASH(MaxWin, MaxWinShift);
p.sndspace := MaxSendSpace;
p.rcvspace := MaxRecvSpace;
p.rcvhiwat := MaxRecvSpace;
WHILE (p.requestrscale < MaxWinShift) & (ASH(MaxWin, p.requestrscale) < p.rcvhiwat) DO
INC(p.requestrscale)
END;
Machine.AtomicInc(NTCPNewBufs);
NEW(buf); NEW(buf.data, MSS * SegsPerBuf);
IF ~NewZeros THEN buf.ofs := 0; buf.len := 0; END;
buf.next := buf; p.sndhead := buf; p.sndtail := buf;
NEW(p.sndcontig, MSS);
p.rcvhead := NIL;
END InitConnection;
PROCEDURE UsrClosed(p: Connection);
BEGIN
CASE p.state OF
Closed, Listen, SynSent:
p.state := Closed; CloseConnection(p)
|SynReceived, Established:
p.state := FinWait1
|CloseWait:
p.state := LastAck
ELSE
END;
END UsrClosed;
PROCEDURE Expired(VAR t: LONGINT): BOOLEAN;
BEGIN
IF t # 0 THEN DEC(t); RETURN t = 0 ELSE RETURN FALSE END
END Expired;
PROCEDURE GetID(): LONGINT;
VAR id, time, date: LONGINT;
BEGIN
Clock.Get(time, date);
id := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, time) / SYSTEM.VAL(SET, date));
RETURN id;
END GetID;
PROCEDURE DisplayErrors*(context : Commands.Context);
VAR i: LONGINT;
BEGIN
FOR i := 1 TO NumErrors-1 DO
IF NTCPError[i] # 0 THEN
context.out.String("TCP: Error "); context.out.Int(i+MinError, 1);
context.out.String(" "); context.out.Int(NTCPError[i], 1); context.out.Ln;
END;
END;
END DisplayErrors;
PROCEDURE DiscardAll*;
BEGIN
pool.Finalize();
END DiscardAll;
PROCEDURE ToggleTrace*;
BEGIN
trace := ~trace;
KernelLog.Enter; KernelLog.String("TCP trace ");
IF trace THEN KernelLog.String("on") ELSE KernelLog.String("off") END;
KernelLog.Exit
END ToggleTrace;
PROCEDURE Init;
VAR i: LONGINT;
BEGIN
FOR i := 0 TO MaxRxtShift DO
IF i < 6 THEN backoff[i] := ASH(1, i) ELSE backoff[i] := 64 END;
INC(totbackoff, backoff[i])
END;
ASSERT(MaxRxtShift=12);
totbackoff := 0;
FOR i:=0 TO MaxRxtShift DO
INC(totbackoff, backoff[i]);
END;
outflags[Closed] := {Rst,Ack}; outflags[Listen] := {}; outflags[SynSent] := {Syn};
outflags[SynReceived] := {Syn,Ack}; outflags[Established] := {Ack};
outflags[CloseWait] := {Ack}; outflags[FinWait1] := {Fin,Ack}; outflags[Closing] := {Fin,Ack};
outflags[LastAck] := {Fin,Ack}; outflags[FinWait2] := {Ack}; outflags[TimeWait] := {Ack};
NEW(nilpcb);
nilpcb.lport := -1;
nilpcb.int := NIL;
IF ~NewZeros THEN nilpcb.rcvspace := 0; nilpcb.rcvscale := 0 END;
lastpcb := nilpcb;
NEW(empty, 1);
NEW(pool);
NEW(issSource, GetID());
NEW(timeSource)
END Init;
PROCEDURE Cleanup;
BEGIN
IP.RemoveReceiver(IPTypeTCP);
pool.Finalize();
timeSource.Finalize();
END Cleanup;
BEGIN
ASSERT(~DoRFC1323 OR (ProcOptions & GenOptions));
ASSERT(SYSTEM.VAL(LONGINT, {Fin}) = 1);
ASSERT((TimerPeriod MOD FastPeriod = 0) & (TimerPeriod MOD SlowPeriod = 0));
trace := FALSE;
Init();
IP.InstallReceiver(IPTypeTCP, Input);
Modules.InstallTermHandler(Cleanup);
END TCP.
(*
History:
08.11.2003 mvt Changed for new interface of IP and Network.
08.11.2003 mvt Fixed array position error in ProcessOptions().
09.11.2003 mvt Min()/Max() functions now in inline assembler.
10.11.2003 mvt Added InterfaceClosed detection in Connection.SlowTimer().
10.11.2003 mvt Added correct finalization of Connection, ConnectionPool, Timer and the module itself.
11.11.2003 mvt Completely changed receive buffer queueing, integrated Network buffers directly.
12.11.2003 mvt Completely changed ConnectionPool, now working with a hash table.
12.12.2003 mvt Bugfixed entire module by comparing it with the book.
12.12.2003 mvt Completed header prediction code according to the book.
12.12.2003 mvt Added support for RFC1323 (timestamp, PAWS).
12.12.2003 mvt Added support for window scaling (for windows >64KB).
14.02.2004 mvt Fixed reassembly bug in Reasm().
14.02.2004 mvt Fixed MSS option sending bug in Output().
28.02.2004 mvt Fixed early Fin sending bug in Output().
04.03.2004 rp Fixed & (p.dupacks < ReXmtThresh) in Step6 according to ftp://ftp.cs.arizona.edu/xkernel/papers/tcp_problems.ps
04.03.2004 rp Fixed XmitTimer according to ftp://ftp.cs.arizona.edu/xkernel/papers/tcp_problems.ps
02.05.2005 eb Supports IPv6 (WritePseudoHdr) and fragmented IP packets.
*)
(*
* Copyright (c) 1982, 1986, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*)
(* pjm:
Gigabit:
Alteon AceNIC / 3Com 3C985 / NetGear GA620 Gigabit Ethernet Adapter
http://sanjose.alteon.com/open.shtml
Linux Driver
http://home.cern.ch/~jes/gige/acenic.html
Packet Engines Hamachi Driver
http://www.nscl.msu.edu/~kasten/perf/hamachi/
A Connection has to react to external and internal events
1. User calls (external)
Connection.Open
Connection.Send
Connection.Receive
Connection.Available
Connection.Close
2. Timer.HandleTimeout (internal)
Connection.DelayedAck
Connection.SlowTimer
3. Packet arrival (external)
Connection.Input (from Input)
The Timer reacts to external and internal events
1. Timer.HandleTimeout (external)
2. Timer.GetISS (internal - only called from Connection.Open, which is only called externally)
The ConnectionPool reacts to external and internal events
1. Lookup (internal)
2. Enumerate (external)
3. Add (internal)
4. Remove (internal)
*)