ArpaTCPStatesImpl.mesa
Copyright (C) 1984 by Xerox Corporation. All rights reserved. The following program was created in 1983 but has not been published within the meaning of the copyright law, is furnished under license, and may not be used, copied and/or disclosed except in accordance with the terms of said license.
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
Demers, September 7, 1988 2:35:10 pm PDT
Doug Terry, August 28, 1987 4:37:36 pm PDT
Hal Murray May 16, 1985 3:15:32 am PDT
John Larson, April 14, 1986 11:26:36 pm PST
DIRECTORY
Arpa USING [Address, NetNumber, nullAddress],
ArpaExtras USING [MyAddress],
ArpaIP USING [AllocBuffers, Buffers],
BasicTime USING [Now],
List USING [Nconc1, Remove],
ArpaTCP USING [maxArpanetSegmentSize, Error, Port, Reason, TCPInfo],
ArpaTCPLogging USING [PrintStateChange],
ArpaTCPOps USING [defaultReceiveWindow, defaultRTT, maxTCPDataLength, SetDataTimeout, TCPHandle, TCPHandleRec, tcpHdrByteLength, TCPHeaderP],
ArpaTCPStates,
ArpaTCPTransmit USING [SendFIN, SendReset, SendSYN];
ArpaTCPStatesImpl:
CEDAR
MONITOR
-- monitors the handleList
IMPORTS Arpa, ArpaExtras, ArpaIP, BasicTime, List, ArpaTCP, ArpaTCPLogging, ArpaTCPOps, ArpaTCPTransmit
EXPORTS ArpaTCPStates
~ BEGIN OPEN ArpaTCPOps, ArpaTCPStates;
handleList: LIST OF REF ANY; -- list of all open TCP connections
minPort: CARDINAL = 1025; -- To avoid well-known sockets and unix special sockets
nextLocalPort: CARDINAL ← minPort;
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
ArpaTCPTransmit.SendFIN[handle]; -- send fin and ack
ArpaTCPLogging.PrintStateChange[handle, finWait1];
handle.state ← finWait1;
handle.reason ← localClose;
};
closeWait => {
-- send fin after all preceeding sends
ArpaTCPTransmit.SendFIN[handle];
ArpaTCPLogging.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 ! ArpaTCP.Error => GO TO GiveUp];
SELECT handle.state
FROM
listen, synSent =>
CloseConnection[handle, localAbort];
synRcvd, established, finWait1, finWait2, closeWait => {
ArpaTCPTransmit.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: ArpaTCP.TCPInfo]
RETURNS [handle: TCPHandle] ~ {
Attempts to open a new TCP connection. Returns a TCPHandle to use or raises ArpaTCP.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 ArpaTCP.Error[localConflict];
IF tcpInfo.active
THEN
IF
NOT (tcpInfo.matchForeignPort
AND tcpInfo.matchForeignAddr)
THEN
RETURN WITH ERROR ArpaTCP.Error[unspecifiedRemoteEnd]
ELSE {
handle ← CreateHandle[tcpInfo];
handle.iss ← GetInitialSequenceNumber[];
handle.sndUna ← handle.iss;
ArpaTCPLogging.PrintStateChange[handle, synSent];
handle.state ← synSent;
ArpaTCPTransmit.SendSYN[handle]; -- send SYN
}
ELSE {
open listening connection
handle ← CreateHandle[tcpInfo];
ArpaTCPLogging.PrintStateChange[handle, listen];
handle.state ← listen;
};
handleList ← List.Nconc1[handleList, handle];
};
CloseConnection:
PUBLIC
ENTRY
PROC [handle: TCPHandle, reason: ArpaTCP.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;
ArpaTCPLogging.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 ArpaTCP.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 ArpaTCP.Error[IF handle = NIL THEN neverOpen ELSE handle.reason];
};
FindHandle:
PUBLIC
ENTRY
PROC [rcvdDatagram: ArpaIP.Buffers]
RETURNS [handle: TCPHandle] ~
TRUSTED {
Find matching handle for segment received from the net.
ENABLE UNWIND => NULL;
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@rcvdDatagram.body];
sourceAddr: Arpa.Address;
sourcePort: ArpaTCP.Port;
dstnPort: ArpaTCP.Port;
goodHandle: TCPHandle;
sourceAddr ← rcvdDatagram.hdr1.source;
sourcePort ← tcpHdrPtr^.sourcePort;
dstnPort ← tcpHdrPtr^.dstnPort;
goodHandle ← NIL;
FOR l:
LIST
OF
REF
ANY ← handleList, l.rest
WHILE l #
NIL
DO
h: ArpaTCPOps.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 [pool:
LIST
OF
REF
ANY]
RETURNS [handleListCopy:
LIST
OF
REF
ANY ←
NIL] ~ {
Make a copy of the handle list for the retransmit procedure to look at.
ENABLE UNWIND => NULL;
FOR from:
LIST
OF
REF
ANY ← handleList, from.rest
WHILE from #
NIL
DO
IF pool #
NIL
THEN {
temp: LIST OF REF ANY ← pool;
pool ← pool.rest;
temp.rest ← handleListCopy;
handleListCopy ← temp;
}
ELSE {
handleListCopy ← CONS[NIL, handleListCopy];
};
handleListCopy.first ← from.first;
ENDLOOP;
WHILE pool #
NIL
DO
next: LIST OF REF ANY ← pool.rest;
pool.first ← NIL; pool.rest ← NIL; pool ← next;
ENDLOOP
};
FindConflictingConn:
INTERNAL
PROC [info: ArpaTCP.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: ArpaTCP.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, minPort]; -- 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 ← Arpa.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;
SELECT
TRUE
FROM
info.maxSegmentSize = 0 => { -- default to most appropriate maxSegmentSize
This is a gross hack to get the Max Segment Size to be the proper Arpanet size if going out to the Arpanet. What we really should be doing is properly negotiating MSS using the MSS option. This should be fixed as soon as TCP options are implemented.
IF InLocalNet[info.foreignAddress] THEN handle.maxSegmentSize ← maxTCPDataLength
ELSE handle.maxSegmentSize ← ArpaTCP.maxArpanetSegmentSize;
};
info.maxSegmentSize IN[1..maxTCPDataLength] => handle.maxSegmentSize ← info.maxSegmentSize;
ENDCASE => handle.maxSegmentSize ← maxTCPDataLength;
handle.rcvWnd ← defaultReceiveWindow;
handle.fromNetQueue ← NIL;
handle.readyToReadQueue ← NIL;
handle.currentInputBuffer ← NIL;
handle.toNetQueue ← NIL;
handle.rexmitQueue ← NIL;
handle.currentOutputDatagram ← ArpaIP.AllocBuffers[1];
handle.currentOutputPtr ← tcpHdrByteLength;
handle.currentOutputLimit ← handle.currentOutputPtr+handle.maxSegmentSize;
handle.rtt ← ArpaTCPOps.defaultRTT;
ArpaTCPOps.SetDataTimeout[handle, info.timeout];
}; -- CreateHandle
InLocalNet:
PROC [remote: Arpa.Address]
RETURNS [
BOOL] =
BEGIN
myNetNumber: Arpa.Address ← Arpa.NetNumber[ArpaExtras.MyAddress[]];
hisNetNumber: Arpa.Address ← Arpa.NetNumber[remote];
IF hisNetNumber = [10,0,0,0] THEN RETURN[FALSE];
RETURN[myNetNumber = hisNetNumber];
END;
END.