ArpaTCPTransmitImpl.mesa
Copyright (C) 1983, 1985 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, August 25, 1983 4:37 pm
Last Edited by: Taft, January 4, 1984 12:01 pm
Last Edited by: HGM, April 18, 1985 11:07:00 pm PST
Demers, September 7, 1988 1:51:50 pm PDT
Carl Hauser, April 22, 1988 4:51:51 pm PDT
Doug Terry, April 27, 1988 5:09:46 pm PDT
Hal Murray June 3, 1985 10:05:37 pm PDT
John Larson, April 14, 1986 11:30:03 pm PST
DIRECTORY
Arpa USING [Address],
ArpaIP USING [AllocBuffers, Buffers, ChecksumProc, Error, FreeBuffers, GetUserBytes, SetUserBytes, Send],
List USING [Nconc1],
ArpaTCP USING [Error, Port],
ArpaTCPLogging USING [PrintTCPPacket],
ArpaTCPOps USING [Buffer, Flip, Flop, InitialTimeoutFromHandle, maxRTT, minRTT, pktsRexmitted, pktsSent, repacketizing, RexmitIntervalFromHandle, RexmitTimeoutFromHandle, sendBufferLength, SetTimeout, TCPChecksum, TCPControlSet, TCPHandle, tcpHdrByteLength, tcpHdrWordLength, TCPHeaderP, tcpIPHandle, TCPSendBuffer],
ArpaTCPTransmit,
BasicTime USING [GetClockPulses, PulsesToMicroseconds];
ArpaTCPTransmitImpl:
CEDAR
PROGRAM
IMPORTS ArpaIP, List, ArpaTCP, ArpaTCPLogging, ArpaTCPOps, BasicTime
EXPORTS ArpaTCPTransmit =
BEGIN
OPEN ArpaTCPOps, ArpaTCPTransmit;
TCPSend:
PROC [data: ArpaIP.Buffers, sourcePort, dstnPort: ArpaTCP.Port, dstn: Arpa.Address] ~ {
Fill in the header and miscellaneous stuff in the datagram and send it. ArpaIP fills in the IP header and uses a callback to compute the TCP checksum afterwards.
TRUSTED {
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@data.body];
tcpHdrPtr.sourcePort ← sourcePort;
tcpHdrPtr.dstnPort ← dstnPort;
tcpHdrPtr.unused ← 0;
tcpHdrPtr.dataWordOffset ← tcpHdrWordLength;
tcpHdrPtr.checksum ← TCPChecksum[data]; -- computed later via TCPChecksumCallback
};
pktsSent ← pktsSent + 1;
[] ← ArpaIP.Send[ArpaTCPOps.tcpIPHandle, data, dstn, TCPChecksumCallback ! ArpaIP.Error => CONTINUE];
};
TCPChecksumCallback: ArpaIP.ChecksumProc ~
TRUSTED {
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@b.body];
tcpHdrPtr.checksum ← TCPChecksum[b];
};
SendSegmentToNet:
PROC [handle: TCPHandle, sendDatagram: ArpaIP.Buffers, dataSegment:
BOOL] ~
TRUSTED {
Procedure to send data, control or ack only segments to network. The dataSegment argument should be set if there is control or data in the segment which should be acked, i.e. if it is not an ack only segment. If the dataSegment argument is set, the packet is put on the rexmit queue for the connection.
bodyBytes: CARDINAL ← ArpaIP.GetUserBytes[sendDatagram].bodyBytes - tcpHdrByteLength;
tcpHdrPtr: TCPHeaderP; -- TCP header to send
tcpSendBufferPtr: REF TCPSendBuffer; -- information about sent segment
common code for sending data or ack only segments
tcpHdrPtr ← LOOPHOLE[@sendDatagram.body];
tcpHdrPtr.seqNumber ← Flop[handle.sndNxt]; -- set sequence number
tcpHdrPtr.window ← handle.rcvWnd; -- send latest window
handle.zeroRcvWnd ← handle.rcvWnd = 0;
IF ~(handle.state = listen
OR handle.state = synSent)
THEN {
-- if in synced state
tcpHdrPtr.ack ← TRUE; -- send ack
tcpHdrPtr.ackNumber ← Flop[handle.rcvNxt]; }
ELSE {
tcpHdrPtr.ack ← FALSE;
tcpHdrPtr.ackNumber ← Flop[0]; };
update next seq number to send
handle.sndNxt ← handle.sndNxt + bodyBytes;
IF tcpHdrPtr.syn
THEN
-- adjust seq number for SYN and FIN
handle.sndNxt ← handle.sndNxt + 1;
IF tcpHdrPtr.fin
THEN {
handle.sndNxt ← handle.sndNxt + 1;
handle.finSequence ← Flip[tcpHdrPtr.seqNumber]; };
IF handle.sndUrgent
THEN {
tcpHdrPtr.urg ← TRUE;
tcpHdrPtr.urgentPtr ← handle.sndUp - handle.sndNxt; }
ELSE {
tcpHdrPtr.urg ← FALSE;
tcpHdrPtr.urgentPtr ← 0; };
TCPSend[sendDatagram, handle.localPort, handle.foreignPort, handle.foreignAddr];
ArpaTCPLogging.PrintTCPPacket[handle, sendDatagram, toNet];
IF dataSegment
THEN {
-- if didn't send ack alone
then put entry on rexmit queue
dataSegment may lie. Check it out. -- CHH
IF bodyBytes#0
OR tcpHdrPtr.fin
OR tcpHdrPtr.syn
THEN {
tcpSendBufferPtr ← NEW[TCPSendBuffer];
tcpSendBufferPtr.dataByteCount ← bodyBytes;
tcpSendBufferPtr.datagram ← sendDatagram;
IF (tcpSendBufferPtr.xmitTime ← BasicTime.GetClockPulses[]) = 0 THEN tcpSendBufferPtr.xmitTime ← 1;
tcpSendBufferPtr.rexmitTime ← SetTimeout[RexmitIntervalFromHandle[handle]];
tcpSendBufferPtr.timeoutTime ←
IF tcpHdrPtr.syn
THEN SetTimeout[InitialTimeoutFromHandle[handle]]
ELSE SetTimeout[RexmitTimeoutFromHandle[handle]];
handle.rexmitQueue ← List.Nconc1[handle.rexmitQueue, tcpSendBufferPtr]; };
};
}; -- SendSegmentToNet
TryToSend:
PUBLIC
PROC [handle: TCPHandle] = {
Called to send as much data as possible from the ToNet queue. Checks window to determine if data can be sent; if data can not be sent, sends an ack only segment. Calls SendSegmentToNet to actually send the data.
sendDatagram: ArpaIP.Buffers; -- datagram to send
sendData: BOOL; -- set if sending data, not ack only
IF handle.state = listen
OR handle.state = synSent
THEN
-- if in unsynced state
RETURN WITH ERROR ArpaTCP.Error[handle.reason]; -- Trying to send data in unsynced state
IF handle.sndWnd = 0
THEN
-- in synced state, if send window is zero
IF handle.rexmitQueue = NIL THEN sendData ← TRUE -- if no pkts sent, send pkt to probe window
ELSE sendData ← FALSE -- if pkts sent and unacked, send ack
ELSE
IF handle.sndNxt >= handle.sndUna + handle.sndWnd THEN sendData ← FALSE -- if non-zero window is full, send ack
ELSE sendData ← TRUE; -- else send data
IF
-- if can send data, but no data to send
sendData AND handle.toNetQueue = NIL THEN
sendData ← FALSE; -- just send an ack
IF
NOT sendData
THEN
TRUSTED {
-- if sending only an ack
allocate new buffer for ack and fill in header
tcpHdrPtr: TCPHeaderP; -- TCP header to send
sendDatagram ← ArpaIP.AllocBuffers[1];
tcpHdrPtr ← LOOPHOLE[@sendDatagram.body];
tcpHdrPtr.psh ← FALSE;
tcpHdrPtr.syn ← FALSE;
tcpHdrPtr.fin ← FALSE;
tcpHdrPtr.rst ← FALSE;
IF handle.sndUrgent
THEN {
tcpHdrPtr.urg ← TRUE;
tcpHdrPtr.urgentPtr ← handle.sndUp - handle.sndNxt; }
ELSE {
tcpHdrPtr.urg ← FALSE;
tcpHdrPtr.urgentPtr ← 0; };
ArpaIP.SetUserBytes[sendDatagram, tcpHdrByteLength+0];
SendSegmentToNet[handle, sendDatagram, FALSE]; -- send to net
ArpaIP.FreeBuffers[sendDatagram];
RETURN; -- finished trying to send
};
DO
-- send data until send window is full or no more data to send
IF handle.toNetQueue = NIL THEN RETURN; -- out of data
sendDatagram ← NARROW[handle.toNetQueue.first];
TRUSTED{sendDatagram ← LOOPHOLE[handle.toNetQueue.first]};
handle.toNetQueue ← handle.toNetQueue.rest;
SendSegmentToNet[handle, sendDatagram, TRUE]; -- send segment to net
IF handle.sndNxt >= handle.sndUna + handle.sndWnd THEN EXIT
ENDLOOP
}; -- TryToSend
TryToSendData:
PUBLIC
PROC [handle: TCPHandle] = {
Called to determine if data can be sent; if so, it calls TryToSend. This routine is called when you want to send a segment if and only if data exists; thus it is called when an empty ack packet has been received and more data may be sent or when the user supplies more data to send via the TCPSend routine. TryToSend can't be called directly because it always generates an ack and we don't need acks for the acks.
sendData: BOOL;
IF handle.state = listen
OR handle.state = synSent
THEN
-- if in unsynced state
RETURN WITH ERROR ArpaTCP.Error[handle.reason]; -- Trying to send data in unsynced state
IF handle.sndWnd = 0
THEN
-- if send window is zero
IF handle.rexmitQueue = NIL THEN sendData ← TRUE -- if no pkts sent, send pkt to probe window
ELSE sendData ← FALSE -- if pkts sent and unacked, send ack
ELSE
IF handle.sndNxt >= handle.sndUna + handle.sndWnd THEN sendData ← FALSE -- if non-zero window is full, send ack
ELSE sendData ← TRUE; -- else send data
IF sendData
AND handle.toNetQueue #
NIL
THEN
-- if allowed to send data and data to send
TryToSend[handle] -- then send it
}; -- TryToSendData
SendSegment:
PUBLIC
PROC [handle: TCPHandle, sendDatagram: Buffer, dataByteCount:
INT, ctl: TCPControlSet] ~
TRUSTED {
This function is called when there is data from the user to send. It fills in the TCP header, queues the packet on the TCB ToNet queue and if the connection is in a state in which data can be sent, it calls TryToSendData to try to send the data. The caller must ensure that the packet fits in the current TCP send window.
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@sendDatagram.body];
IF repacketizing
THEN {
-- if repacketizing data then copy data to TCP buffer
FOR i:
INT
IN [tcpHdrPtr.dataWordOffset*4..tcpHdrPtr.dataWordOffset*4+dataByteCount)
DO
handle.sendBuffer[handle.fillSlot] ← sendDatagram.body.bytes[i];
handle.fillSlot ← (handle.fillSlot + 1) MOD sendBufferLength;
ENDLOOP;
};
IF ctl.urg
THEN {
-- fill in urgent control and pointer
tcpHdrPtr.urg ← TRUE;
tcpHdrPtr.urgentPtr ← dataByteCount
}
byte following end of packet
ELSE {
tcpHdrPtr.urg ← FALSE;
tcpHdrPtr.urgentPtr ← 0;
};
tcpHdrPtr.psh ← ctl.psh;
tcpHdrPtr.ack ← FALSE;
tcpHdrPtr.rst ← FALSE;
tcpHdrPtr.syn ← FALSE;
tcpHdrPtr.fin ← FALSE;
tcpHdrPtr.checksum ← 0;
ArpaIP.SetUserBytes[sendDatagram, tcpHdrByteLength+dataByteCount]; -- set data length
handle.nBytesToSend ← handle.nBytesToSend + dataByteCount;
handle.toNetQueue ← List.Nconc1[handle.toNetQueue, sendDatagram]; -- put buffer on queue to send
IF handle.state = established
OR handle.state = closeWait
THEN
TryToSendData[handle]; -- send it if window permits
}; -- SendSegment
SendSYN:
PUBLIC
PROC [handle: TCPHandle] =
TRUSTED {
This procedure allocates a segment to send a SYN. It calls SendSegmentToNet to send the SYN segment.
sendDatagram: ArpaIP.Buffers ← ArpaIP.AllocBuffers[1];
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@sendDatagram.body];
tcpHdrPtr.urg ← FALSE;
tcpHdrPtr.urgentPtr ← 0;
tcpHdrPtr.psh ← FALSE;
tcpHdrPtr.rst ← FALSE;
tcpHdrPtr.syn ← FALSE;
tcpHdrPtr.fin ← FALSE;
tcpHdrPtr.ack ← FALSE;
ArpaIP.SetUserBytes[sendDatagram, tcpHdrByteLength+0]; -- set data length
tcpHdrPtr.syn ← TRUE;
tcpHdrPtr.seqNumber ← Flop[handle.iss];
handle.sndNxt ← handle.iss;
SendSegmentToNet[handle, sendDatagram, TRUE]; };
SendFIN:
PUBLIC
PROC [handle: TCPHandle] =
TRUSTED {
This procedure allocates a segment to send a FIN, queues it on the end of the ToNet queue and then calls TryToSend to send any data on the ToNet queue and the FIN.
sendDatagram: ArpaIP.Buffers ← ArpaIP.AllocBuffers[1];
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@sendDatagram.body];
tcpHdrPtr.urg ← FALSE;
tcpHdrPtr.urgentPtr ← 0;
tcpHdrPtr.psh ← FALSE;
tcpHdrPtr.rst ← FALSE;
tcpHdrPtr.syn ← FALSE;
tcpHdrPtr.fin ← TRUE;
tcpHdrPtr.ack ← FALSE;
ArpaIP.SetUserBytes[sendDatagram, tcpHdrByteLength+0];
handle.toNetQueue ← List.Nconc1[handle.toNetQueue, sendDatagram];
TryToSend[handle] -- try to send data and fin on to net queue
}; -- SendFIN
SendReset:
PUBLIC
PROC [handle: TCPHandle, sourcePort, dstnPort: ArpaTCP.Port, Dstn: Arpa.Address, seq, ack:
INT] =
TRUSTED {
This procedure is called to send a reset; it sends directly to the network by calling the TCPSend routine; it does not use the TryToSend or SendSegmentToNet routines because resets can be sent when no TCB exists.
sendDatagram: ArpaIP.Buffers; -- segment to send
tcpHdrPtr: TCPHeaderP; -- header to send
sendDatagram ← ArpaIP.AllocBuffers[1];
ArpaIP.SetUserBytes[sendDatagram, tcpHdrByteLength+0];
sendDatagram.inHdr.destination ← Dstn;
tcpHdrPtr ← LOOPHOLE[@sendDatagram.body];
tcpHdrPtr.sourcePort ← sourcePort;
tcpHdrPtr.dstnPort ← dstnPort;
tcpHdrPtr.seqNumber ← Flop[seq];
tcpHdrPtr.ackNumber ← Flop[ack];
tcpHdrPtr.urg ← FALSE;
tcpHdrPtr.psh ← FALSE;
tcpHdrPtr.syn ← FALSE;
tcpHdrPtr.fin ← FALSE;
tcpHdrPtr.rst ← TRUE;
IF ack # 0 THEN tcpHdrPtr.ack ← TRUE
ELSE tcpHdrPtr.ack ← FALSE;
tcpHdrPtr.window ← 0;
tcpHdrPtr.checksum ← 0;
tcpHdrPtr.urgentPtr ← 0;
TCPSend[sendDatagram, sourcePort, dstnPort, Dstn];
ArpaTCPLogging.PrintTCPPacket[handle, sendDatagram, toNet];
ArpaIP.FreeBuffers[sendDatagram];
}; -- SendReset
RemoveAckedSegments:
PUBLIC
PROC [handle: TCPHandle] ~
TRUSTED {
IF handle.sndUna >= handle.sndUp
THEN {
handle.sndUrgent ← FALSE;
handle.sndUp ← 0; };
WHILE handle.rexmitQueue #
NIL
DO
tcpSendBufferPtr: REF TCPSendBuffer ← NARROW[handle.rexmitQueue.first];
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@tcpSendBufferPtr.datagram.body];
lastSeqNumber: INT ← Flip[tcpHdrPtr.seqNumber] + tcpSendBufferPtr.dataByteCount - 1;
IF tcpHdrPtr.syn THEN lastSeqNumber ← lastSeqNumber + 1;
IF tcpHdrPtr.fin THEN lastSeqNumber ← lastSeqNumber + 1;
IF handle.sndUna > lastSeqNumber
THEN {
beNiceToGC: LIST OF REF ANY ← handle.rexmitQueue;
If not resent (so this is a valid sample) then update round-trip-time estimate in handle.rtt ...
IF tcpSendBufferPtr.xmitTime # 0
THEN {
delta: CARD;
sampleRTT: INT;
delta ← BasicTime.GetClockPulses[] - tcpSendBufferPtr.xmitTime;
sampleRTT ← BasicTime.PulsesToMicroseconds[delta] / 1000;
sampleRTT ← MIN[MAX[minRTT, sampleRTT], maxRTT];
handle.rtt ← (7*handle.rtt + sampleRTT)/8;
};
ArpaIP.FreeBuffers[tcpSendBufferPtr.datagram];
handle.rexmitQueue ← handle.rexmitQueue.rest;
beNiceToGC.first ← NIL;
beNiceToGC.rest ← NIL; }
ELSE RETURN; -- segment at head of queue not acked
ENDLOOP;
}; -- RemoveAckedSegments
RepacketizeandRexmit:
PUBLIC
PROC [handle: TCPHandle, tcpSendBufferPtr:
REF TCPSendBuffer] ~
TRUSTED {
This procedure is called to repacketize and retransmit data on the retransmit queue. It is called from CheckRexmitQueues when repacketizing is enabled.
sendDatagram: ArpaIP.Buffers;
tcpHdrPtr: TCPHeaderP;
i, j: INT;
bytesToSend: INT;
bytesRexmitted: INT;
tcpSendBufferPtr.xmitTime ← 0;
tcpSendBufferPtr.rexmitTime ← SetTimeout[RexmitIntervalFromHandle[handle]];
IF handle.state = synSent
OR handle.state = synRcvd
THEN {
if syn unacked, just rexmit users's packet
sendDatagram ← tcpSendBufferPtr.datagram;
pktsRexmitted ← pktsRexmitted + 1;
handle.rexmits ← handle.rexmits + 1;
tcpHdrPtr ← LOOPHOLE[@sendDatagram.body];
IF
NOT (handle.state = listen
OR handle.state = synSent)
THEN
IF
NOT tcpHdrPtr.ack
OR Flip[tcpHdrPtr.ackNumber] # handle.rcvNxt
THEN {
tcpHdrPtr.ack ← TRUE;
tcpHdrPtr.ackNumber ← Flop[handle.rcvNxt]; };
TCPSend[sendDatagram, handle.localPort, handle.foreignPort, handle.foreignAddr];
ArpaTCPLogging.PrintTCPPacket[handle, sendDatagram, rexmitToNet];
RETURN };
IF handle.nBytesToSend = 0
AND
NOT (
SELECT handle.state
FROM
finWait1, closing, lastAck => TRUE
ENDCASE => FALSE) THEN
RETURN; -- if no data or fin to send, then exit
bytesToSend ← handle.nBytesToSend;
j ← handle.sendSlot;
bytesRexmitted ← 0;
sendDatagram ← ArpaIP.AllocBuffers[1];
DO
-- allocate buffers to send and fill them
i ← 0;
fill send buffer until all bytes are sent or send buffer is full
WHILE i # bytesToSend
AND i # handle.maxSegmentSize
DO
sendDatagram.body.bytes[i+tcpHdrByteLength] ← handle.sendBuffer[j];
j ← (j + 1) MOD sendBufferLength;
i ← i + 1
ENDLOOP;
bytesToSend ← bytesToSend - i;
tcpHdrPtr ← LOOPHOLE[@sendDatagram.body];
tcpHdrPtr.urg ← FALSE;
tcpHdrPtr.urgentPtr ← 0;
tcpHdrPtr.psh ← FALSE;
tcpHdrPtr.rst ← FALSE;
tcpHdrPtr.syn ← FALSE;
IF handle.sndUna + i = handle.finSequence THEN tcpHdrPtr.fin ← TRUE
ELSE tcpHdrPtr.fin ← FALSE;
tcpHdrPtr.checksum ← 0;
ArpaIP.SetUserBytes[sendDatagram, tcpHdrByteLength+i]; -- set data length
tcpHdrPtr.seqNumber ← Flop[handle.sndUna + bytesRexmitted];
bytesRexmitted ← bytesRexmitted + i; -- to calculate sequence numbers
tcpHdrPtr.window ← handle.rcvWnd; -- send latest window
handle.zeroRcvWnd ← handle.rcvWnd = 0;
tcpHdrPtr.ack ← TRUE;
tcpHdrPtr.ackNumber ← Flop[handle.rcvNxt];
send the segment to the internet
pktsRexmitted ← pktsRexmitted + 1;
handle.rexmits ← handle.rexmits + 1;
TCPSend[sendDatagram, handle.localPort, handle.foreignPort, handle.foreignAddr];
ArpaTCPLogging.PrintTCPPacket[handle, sendDatagram, rexmitToNet];
IF bytesToSend = 0 THEN EXIT
ENDLOOP; -- end filling send buffers
ArpaIP.FreeBuffers[sendDatagram];
}; -- RepacketizeandRexmit
Rexmit:
PUBLIC
PROC [handle: TCPHandle, tcpSendBufferPtr:
REF TCPSendBuffer] ~
TRUSTED {
This procedure is called to retransmit a segment on the retransmit queue when Repacketizing is not enabled. It is called from CheckRexmitQueues.
sendDatagram: ArpaIP.Buffers;
tcpHdrPtr: TCPHeaderP;
tcpSendBufferPtr.xmitTime ← 0;
tcpSendBufferPtr.rexmitTime ← SetTimeout[RexmitIntervalFromHandle[handle]];
sendDatagram ← tcpSendBufferPtr.datagram;
pktsRexmitted ← pktsRexmitted + 1;
handle.rexmits ← handle.rexmits + 1;
tcpHdrPtr ← LOOPHOLE[@sendDatagram.body];
IF
NOT (handle.state = listen
OR handle.state = synSent)
THEN
-- update ack field
IF
NOT tcpHdrPtr.ack
OR Flip[tcpHdrPtr.ackNumber] # handle.rcvNxt
THEN {
tcpHdrPtr.ack ← TRUE;
tcpHdrPtr.ackNumber ← Flop[handle.rcvNxt]; };
TCPSend[sendDatagram, handle.localPort, handle.foreignPort, handle.foreignAddr];
ArpaTCPLogging.PrintTCPPacket[handle, sendDatagram, rexmitToNet]; };
END.