TCPStatesImpl.mesa
Last Edited by: Nichols, September 1, 1983 4:23 pm
Last Edited by: Taft, January 4, 1984 10:11 am
Last Edited by: HGM, March 27, 1984 3:23:42 am PST
Hal Murray May 16, 1985 3:15:32 am PDT
John Larson, April 14, 1986 11:26:36 pm PST
DIRECTORY
BasicTime USING [Now],
IPDefs USING [Datagram, DatagramRec, DByte, Address, nullAddress],
List USING [Append, Nconc1, Remove],
Process USING [DisableTimeout, MsecToTicks, SetTimeout],
TCP USING [Error, maxTimeout, Reason, TCPInfo],
TCPLogging USING [PrintStateChange],
TCPOps USING [defaultReceiveWindow, maxTCPDataLength, TCPHandle, TCPHandleRec, tcpHdrByteLength, TCPHeaderP],
TCPStates,
TCPTransmit USING [SendFIN, SendReset, SendSYN];
TCPStatesImpl:
CEDAR
MONITOR
-- monitors the handleList
IMPORTS BasicTime, List, Process, TCP, TCPLogging, TCPOps, TCPTransmit
EXPORTS TCPStates
~ BEGIN OPEN TCPOps, TCPStates;
handleList: LIST OF REF ANY; -- list of all open TCP connections
nextLocalPort: CARDINAL ← 256; -- avoid well-known sockets
lastSequenceNumber: INT ← LOOPHOLE[BasicTime.Now[]]; -- used to general initial sequence numbers
Close:
PUBLIC
PROC [handle: TCPHandle] ~ {
Close the TCP connection; returns an error if the connection does not exist or is already closing. Sends a FIN to the remote TCP. Note that the user may receive more data from the remote TCP after calling Close; when the connection is closed, the user will receive a message in which the data is a pointer to a ControlInfo record which contains a pointer to the TCB and the reason for closing. This is the last message that the user receives on the connection; on receipt, the user can delete all connection specific information.
ValidHandle[handle];
SELECT handle.state
FROM
listen, synSent => CloseConnection[handle, localClose];
synRcvd, established => {
-- send fin after all preceeding sends
TCPTransmit.SendFIN[handle]; -- send fin and ack
TCPLogging.PrintStateChange[handle, finWait1];
handle.state ← finWait1;
handle.reason ← localClose;
};
closeWait => {
-- send fin after all preceeding sends
TCPTransmit.SendFIN[handle];
TCPLogging.PrintStateChange[handle, lastAck];
handle.state ← lastAck;
};
closing, lastAck, timeWait =>
-- connection is closing
NULL; -- so what?
ENDCASE;
}; -- Close
Abort:
PUBLIC
PROC [handle: TCPHandle] ~ {
Abort the connection. This routine sends a TCP Reset if necessary; it calls CloseConnection to dispose of all the connection specific storage and to send a message to the user. The data in this message is a pointer to a ControlInfo record containing a pointer to the TCB and the reason the connection was closed (UserAbort). On receipt of this message, the user can dispose of all connection specific information.
ValidHandle[handle ! TCP.Error => GO TO GiveUp];
SELECT handle.state
FROM
listen, synSent =>
CloseConnection[handle, localAbort];
synRcvd, established, finWait1, finWait2, closeWait => {
TCPTransmit.SendReset[handle, handle.localPort, handle.foreignPort, handle.foreignAddr, handle.sndNxt, handle.rcvNxt];
CloseConnection[handle, localAbort];
};
closing, lastAck, timeWait =>
CloseConnection[handle, localAbort];
ENDCASE;
}; -- Abort
GetInitialSequenceNumber:
PUBLIC
PROC
RETURNS [
INT] = {
Generate an initial send sequence number for a connection.
lastSequenceNumber ← lastSequenceNumber*1037 + 12345;
RETURN [ABS[lastSequenceNumber]];
};
Open:
PUBLIC
ENTRY
PROC [tcpInfo:
TCP.TCPInfo]
RETURNS [handle: TCPHandle] ~ {
Attempts to open a new TCP connection. Returns a TCPHandle to use or raises TCP.OpenFailed if the Open request conflicts with an existing request or is a duplicate Open request on the same connection. The connection is open either in the LISTEN or SYNSENT state. In the LISTEN state, wait for a SYN from the remote TCP; in the SYNSENT state, send a SYN to initiate the connection. If an Open call is done to open a connection in the LISTEN state, then another Open call can be done to send the SYN and put the connection in the SYNSENT state.
ENABLE UNWIND => NULL;
IF FindConflictingConn[tcpInfo] #
NIL
THEN
RETURN WITH ERROR TCP.Error[localConflict];
IF tcpInfo.active
THEN
IF
NOT (tcpInfo.matchForeignPort
AND tcpInfo.matchForeignAddr)
THEN
RETURN WITH ERROR TCP.Error[unspecifiedRemoteEnd]
ELSE {
handle ← CreateHandle[tcpInfo];
handle.iss ← GetInitialSequenceNumber[];
handle.sndUna ← handle.iss;
TCPLogging.PrintStateChange[handle, synSent];
handle.state ← synSent;
TCPTransmit.SendSYN[handle]; -- send SYN
}
ELSE {
open listening connection
handle ← CreateHandle[tcpInfo];
TCPLogging.PrintStateChange[handle, listen];
handle.state ← listen;
};
handleList ← List.Nconc1[handleList, handle];
};
CloseConnection:
PUBLIC
ENTRY
PROC [handle: TCPHandle, reason:
TCP.Reason] = {
Clear off the transmit and retransmit queues and remove this handle form the list.
ENABLE UNWIND => NULL;
Kill[handle.toNetQueue]; handle.toNetQueue ← NIL;
Kill[handle.rexmitQueue]; handle.rexmitQueue ← NIL;
TCPLogging.PrintStateChange[handle, closed];
handle.state ← closed;
handle.reason ← reason;
handleList ← List.Remove[handle, handleList];
BROADCAST handle.windowAvailable; -- make sure everyone finds out.
BROADCAST handle.urgentAvailable;
BROADCAST handle.dataAvailable;
BROADCAST handle.notListening;
}; -- CloseConnection
Kill:
PROC [list:
LIST
OF
REF
ANY] = {
DO
temp: LIST OF REF ANY ← list;
IF temp = NIL THEN RETURN;
list ← list.rest;
temp.first ← NIL;
temp.rest ← NIL;
ENDLOOP; };
ValidHandle:
PUBLIC
ENTRY
PROC [handle: TCPHandle] ~ {
Check if handle given as argument by user is valid. Called by Abort, Close, and Send. Raises TCP.StreamClosing if bad handle, else it just returns.
ENABLE UNWIND => NULL;
FOR l:
LIST
OF
REF
ANY ← handleList, l.rest
WHILE l #
NIL
DO
IF l.first = handle
THEN
RETURN;
ENDLOOP;
RETURN WITH ERROR TCP.Error[IF handle = NIL THEN neverOpen ELSE handle.reason];
};
FindHandle:
PUBLIC
ENTRY
PROC [rcvdDatagram: IPDefs.Datagram]
RETURNS [handle: TCPHandle] ~
TRUSTED {
Find matching handle for segment received from the net.
ENABLE UNWIND => NULL;
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@rcvdDatagram.data];
sourceAddr: IPDefs.Address;
sourcePort: IPDefs.DByte;
dstnPort: IPDefs.DByte;
goodHandle: TCPHandle;
sourceAddr ← rcvdDatagram.inHdr.source;
sourcePort ← tcpHdrPtr^.sourcePort;
dstnPort ← tcpHdrPtr^.dstnPort;
goodHandle ← NIL;
FOR l:
LIST
OF
REF
ANY ← handleList, l.rest
WHILE l #
NIL
DO
h: TCPOps.TCPHandle ← NARROW[l.first];
IF h.localPort = dstnPort
AND (h.foreignAddr = sourceAddr
OR
NOT h.matchForeignAddr)
AND (h.foreignPort = sourcePort
OR
NOT h.matchForeignPort)
THEN
IF h.matchForeignAddr
AND h.matchForeignPort
THEN
RETURN [h] -- found exact match, return TCB
ELSE
-- match TCB with foreign address or port not specified
IF h.matchForeignAddr
THEN
-- address match is best
goodHandle ← h
ELSE
IF h.matchForeignPort
AND goodHandle =
NIL
THEN
goodHandle ← h
ELSE
IF goodHandle =
NIL
THEN
goodHandle ← h;
ENDLOOP;
RETURN [goodHandle] -- best match found
}; -- FindTCB
CopyHandleList:
PUBLIC
ENTRY
PROC
RETURNS [handleListCopy:
LIST
OF
REF
ANY] ~ {
Make a copy of the handle list for the retransmit procedure to look at.
ENABLE UNWIND => NULL;
RETURN [List.Append[handleList, NIL]];
};
FindConflictingConn:
INTERNAL
PROC [info:
TCP.TCPInfo]
RETURNS [conflictingHandle: TCPHandle] = {
Checks for conflicting handles; i.e. handles which have the same local and foreign ports; if such a handle exists, then return a pointer to it; used in user open call to determine if user connection request conflicts with existing connection.
FOR q:
LIST
OF
REF
ANY ← handleList, q.rest
WHILE q #
NIL
DO
thisHandle: TCPHandle ← NARROW[q.first];
IF thisHandle.localPort = info.localPort
AND info.matchLocalPort
AND thisHandle.foreignAddr = info.foreignAddress
AND info.matchForeignAddr
AND thisHandle.foreignPort = info.foreignPort
AND info.matchForeignPort
THEN
RETURN [thisHandle];
ENDLOOP;
RETURN [NIL]; -- no conflict
}; -- FindConflictingConn
CreateHandle:
PROC [info:
TCP.TCPInfo]
RETURNS [handle: TCPHandle] = {
handle ← NEW[TCPHandleRec];
IF info.matchLocalPort
THEN
handle.localPort ← info.localPort
ELSE
DO
-- assign local port automatically and ensure it doesn't match any existing one
handle.localPort ← nextLocalPort;
nextLocalPort ← MAX[nextLocalPort+1, 256]; -- avoid well-known sockets
FOR q:
LIST
OF
REF
ANY ← handleList, q.rest
WHILE q#
NIL
DO
IF NARROW[q.first, TCPHandle].localPort=handle.localPort THEN EXIT; -- try again
REPEAT
FINISHED => GOTO ok;
ENDLOOP;
ENDLOOP;
IF info.matchForeignAddr
THEN
handle.foreignAddr ← info.foreignAddress
ELSE
handle.foreignAddr ← IPDefs.nullAddress;
IF info.matchForeignPort
THEN
handle.foreignPort ← info.foreignPort
ELSE
handle.foreignPort ← 0;
handle.matchForeignAddr ← info.matchForeignAddr;
handle.matchForeignPort ← info.matchForeignPort;
handle.state ← closed;
handle.reason ← neverOpen;
handle.active ← info.active;
handle.maxSegmentSize ← maxTCPDataLength;
handle.rcvWnd ← defaultReceiveWindow;
handle.fromNetQueue ← NIL;
handle.readyToReadQueue ← NIL;
handle.currentInputBuffer ← NIL;
handle.toNetQueue ← NIL;
handle.rexmitQueue ← NIL;
handle.currentOutputDatagram ← NEW[IPDefs.DatagramRec];
handle.currentOutputPtr ← tcpHdrByteLength;
handle.currentOutputLimit ← handle.currentOutputPtr+handle.maxSegmentSize;
handle.dataTimeout ← MIN[info.timeout, TCP.maxTimeout];
TRUSTED {
IF handle.dataTimeout>=0
THEN {
Process.SetTimeout[@handle.dataAvailable, Process.MsecToTicks[MIN[handle.dataTimeout, 10000]]];
Process.SetTimeout[@handle.windowAvailable, Process.MsecToTicks[MIN[handle.dataTimeout, 10000]]]}
ELSE Process.DisableTimeout[@handle.dataAvailable];
};
}; -- CreateHandle
END.