-- File: ArpaRouterImpl.mesa - last edit:
-- JAV 10-Dec-87 12:49:33
-- AOF 26-May-87 11:07:23
-- SMA 27-Jun-86 13:40:56
-- Copyright (C) 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
DIRECTORY
ArpaBuffer USING [
Body, Buffer, Enqueue, ExtractFromQueue, DataBytesPerRawBuffer,
From, QueueObject, QueueCleanup, QueueInitialize, ReturnBuffer, To],
ArpaFlags USING [doStats],
ArpaInit USING [GetArpaInitInfo],
ArpaPortInternal USING [AddrMatch, AddrMismatch, Checksum, DestType,
InitUniquePort, QueueForClient],
ArpaPort USING [minIPHeaderBytes, SwapSourceAndDestination],
Arpa10MBit USING [Capsulators, CreateCapsulators],
ArpaRouter USING [unknownInternetAddress],
ArpaRoutingTable USING [ContextObject, defaultRth, Handle, InfoReply,
NetworkContext, Object, ProbeGateway, Redirect, RoutersFunction],
ArpaStats USING [Incr],
ArpaTypes USING [Cardinal32, Icmp, IcmpType, InternetAddress, Port, Timestamp],
Buffer USING [],
ByteBlt USING [ByteBlt],
CommHeap USING [zone],
CommunicationInternal USING [CommPackageGo],
Driver USING [GetDeviceChain, Device, nilDevice, PutOnGlobalDoneQueue],
Environment USING [Byte, bytesPerWord],
IEEE8023 USING [EncapObject, maxDataBytesPerEthernetPacket],
Inline USING [DBITAND, UDDivMod],
Mopcodes USING [zEXCH],
Process USING [Abort, EnableAborts, Pause, priorityNormal, SecondsToTicks,
SetPriority, SetTimeout, Yield],
ProcessorFace USING [mp, SetMP],
Protocol1 USING [
Action, AddFamilyMember, EncapsulateAndTransmit, EvictFamily, Family, FamilyUnit,
MatrixRecord, RegisterFamily, SetMaximumBufferSize],
System USING [GetClockPulses, GetGreenwichMeanTime, PulsesToMicroseconds];
ArpaRouterImpl: MONITOR
IMPORTS
ArpaBuffer, ArpaInit, ArpaPort, ArpaPortInternal, Arpa10MBit, ArpaRouter,
ArpaRoutingTable, ArpaStats, ByteBlt, CommHeap, CommunicationInternal,
Driver, Inline, Process, ProcessorFace, Protocol1, System
EXPORTS ArpaPortInternal, ArpaPort, ArpaRouter, ArpaRoutingTable, Buffer =
BEGIN --IP implementation.
Port: PUBLIC --ArpaRouter-- TYPE = ArpaTypes.Port;
InternetAddress: PUBLIC --ArpaRouter-- TYPE = ArpaTypes.InternetAddress;
Device: PUBLIC --Buffer-- TYPE = Driver.Device;
driverLoopback: BOOLEAN ← FALSE;
bpw: NATURAL = Environment.bytesPerWord;
routersFunction: ArpaRoutingTable.RoutersFunction;
unknownInternetAddress: PUBLIC <<ArpaRouter>> InternetAddress ← [0, 0];
--Start up and addresses for the primary ethernet.
findAddrMP: CARDINAL = 982; --set in MP when searching for my address
myArpaAddr, myBroadcastAddr, myGWAddr, mySubnetMask, myDomainNameServer,
myNullAddr: InternetAddress ← unknownInternetAddress;
rto: PUBLIC ArpaRoutingTable.Object; --the routing object
arpaProtocolFamily: PUBLIC Protocol1.FamilyUnit ← [
name: <<Protocol1.ProtocolFamily>> arpa,
state: <<LONG POINTER>> NIL,
status: <<Protocol1.FamilyStatus>> dead,
maxBufferSize: <<CARDINAL>> IEEE8023.maxDataBytesPerEthernetPacket, --1500
receive: <<PROC[Buffer.Buffer]>> LOOPHOLE[ReceivePacket],
broadcast: <<PROC[Buffer.Buffer]>> LOOPHOLE[Broadcast],
stateChanged: <<PROC[
Buffer.Device, LONG POINTER, Protocol1.Action]>> StateChanged,
spy: <<PROC[
Buffer.Buffer, Buffer.Function] RETURNS [Buffer.Buffer]>> NIL];
useCount: CARDINAL ← 0;
--IP reassembly.
ListEntry: TYPE = LONG ORDERED POINTER TO ListObject;
ListObject: TYPE = RECORD [next: ListEntry, first, last: CARDINAL];
fragQueue: ArpaBuffer.QueueObject; --queue of packets being reassembled.
fragTimeout: CARDINAL = 15; --in seconds
fragTimer: CONDITION;
fragWatcher: PROCESS ← NIL;
--addressing classes
classA: InternetAddress = [0B, 0B];
classB: InternetAddress = [100000B, 0B];
classC: InternetAddress = [140000B, 0B];
classAMask: InternetAddress = [100000B, 0B];
classBMask: InternetAddress = [140000B, 0B];
classCMask: InternetAddress = [160000B, 0B];
ArpaPackageMake: PUBLIC PROC =
BEGIN
AlreadyStarted: ENTRY PROC RETURNS[BOOLEAN] = INLINE
{RETURN[(useCount ← useCount + 1) > 1]};
driver: Device;
matrix: Protocol1.MatrixRecord;
oldMp: CARDINAL = ProcessorFace.mp;
IF AlreadyStarted[] THEN RETURN;
CommunicationInternal.CommPackageGo[]; --we need the basics
driverLoopback ← TRUE;
ArpaPortInternal.InitUniquePort[];
<<
--This should eventually be done by something like RARP.
This proc should eventually broadcast an ICMP address mask reply and
Receive should parse ICMP address mask replies. But for now (and until we
know if any gateways actually support ICMP address mask replies), I used
our favorite host table hack in GetArpaAddr...
>>
ProcessorFace.SetMP[findAddrMP];
<<UNTIL myArpaAddr # ArpaRouter.unknownInternetAddress>> DO
[myArpaAddr, myGWAddr, mySubnetMask, myDomainNameServer] ←
ArpaInit.GetArpaInitInfo[];
IF myArpaAddr # unknownInternetAddress THEN EXIT;
Process.Pause[Process.SecondsToTicks[10]];
ENDLOOP;
ProcessorFace.SetMP[oldMp]; --back to original value
myBroadcastAddr ← SELECT TRUE FROM
Inline.DBITAND[LOOPHOLE[myArpaAddr], LOOPHOLE[classAMask]] =
LOOPHOLE[classA, LONG UNSPECIFIED] => [77777B, 177777B],
Inline.DBITAND[LOOPHOLE[myArpaAddr], LOOPHOLE[classBMask]] =
LOOPHOLE[classB, LONG UNSPECIFIED] => [137777B, 177777B],
Inline.DBITAND[LOOPHOLE[myArpaAddr], LOOPHOLE[classCMask]] =
LOOPHOLE[classC, LONG UNSPECIFIED] => [157777B, 177777B],
ENDCASE => unknownInternetAddress;
myNullAddr ← SELECT TRUE FROM
Inline.DBITAND[LOOPHOLE[myArpaAddr], LOOPHOLE[classAMask]] =
LOOPHOLE[classA, LONG UNSPECIFIED] => classA,
Inline.DBITAND[LOOPHOLE[myArpaAddr], LOOPHOLE[classBMask]] =
LOOPHOLE[classB, LONG UNSPECIFIED] => classB,
Inline.DBITAND[LOOPHOLE[myArpaAddr], LOOPHOLE[classCMask]] =
LOOPHOLE[classC, LONG UNSPECIFIED] => classC,
ENDCASE => ArpaRouter.unknownInternetAddress;
BEGIN
OPEN c: LOOPHOLE[matrix.context, ArpaRoutingTable.NetworkContext];
Protocol1.RegisterFamily[@arpaProtocolFamily]; --make us known
matrix.family ← @arpaProtocolFamily; --this is the same for all drivers
FOR driver ← Driver.GetDeviceChain[], driver.next UNTIL driver = NIL DO
IF (driver.device # ethernet) OR ~driver.alive THEN LOOP;
matrix.context ← CommHeap.zone.NEW[ArpaRoutingTable.ContextObject];
c.network ← driver;
c.stats ← NIL;
c.protocol ← Arpa10MBit.CreateCapsulators[driver];
[matrix.decapsulator, matrix.encapsulator] ← Arpa10MBit.Capsulators[];
--after the call to ProbeGateway, c.net will either be set, or will be null
--with the correct class bits turned on.
c.net ← ArpaRouter.unknownInternetAddress;
c.host ← myArpaAddr;
[c.hostMask, c.netMask] ← BuildMasks[c.host];
IF (mySubnetMask ← GetSubnetMask[]) # ArpaRouter.unknownInternetAddress THEN
c.netMask ← mySubnetMask; --are subnets in use?
Protocol1.AddFamilyMember[driver, @matrix];
Protocol1.SetMaximumBufferSize[driver, @arpaProtocolFamily, IEEE8023.maxDataBytesPerEthernetPacket];
ENDLOOP;
Register[]; --register default table impl
ArpaBuffer.QueueInitialize[@fragQueue];
Process.SetTimeout[@fragTimer, Process.SecondsToTicks[7]];
Process.EnableAborts[@fragTimer];
END; --OPEN context
ArpaRoutingTable.ProbeGateway[]; --for our own net number.
END; --ArpaPackageMake
ArpaPackageDestroy: PUBLIC PROC =
BEGIN
StillInUse: ENTRY PROC RETURNS[BOOLEAN] =
{RETURN [(useCount ← useCount - 1) # 0]};
IF StillInUse[] THEN RETURN;
Protocol1.EvictFamily[arpa]; --stop dispatcher's access
IF (fragWatcher # NIL) THEN
BEGIN
Process.Abort[fragWatcher];
JOIN fragWatcher;
END;
ArpaBuffer.QueueCleanup[@fragQueue];
IF rto.stop # NIL THEN rto.stop[];
END; --ArpaPackageDestroy
Broadcast: PROC[b: ArpaBuffer.Buffer] =
BEGIN
network: Device = b.fo.network;
body: ArpaBuffer.Body = b.arpa;
IF ~network.alive THEN
{Driver.PutOnGlobalDoneQueue[ArpaBuffer.To[b]]; RETURN};
body.ipHeader.source ← myArpaAddr;
body.ipHeader.destination ← myBroadcastAddr;
body.ipHeader.checksum ← 0;
body.ipHeader.checksum ← ArpaPortInternal.Checksum[
0, body.ipHeader.ihl * bpw, @body.ipHeader];
b.fo.status ← goodCompletion;
Protocol1.EncapsulateAndTransmit[ArpaBuffer.To[b], @myBroadcastAddr];
IF ArpaFlags.doStats THEN ArpaStats.Incr[broadcastsSent];
END; --Broadcast
BuildMasks: PUBLIC PROC [addr: InternetAddress]
RETURNS [hostMask, netMask: InternetAddress] =
BEGIN
SELECT TRUE FROM
(Inline.DBITAND[LOOPHOLE[addr], LOOPHOLE[classAMask]] =
LOOPHOLE[classA, LONG UNSPECIFIED]) =>
{hostMask ← [100377B, 177777B];
netMask ← [177400B, 0B]};
(Inline.DBITAND[LOOPHOLE[addr], LOOPHOLE[classBMask]] =
LOOPHOLE[classB, LONG UNSPECIFIED]) =>
{hostMask ← [140000B, 177777B];
netMask ← [177777B, 0B]};
(Inline.DBITAND[LOOPHOLE[addr], LOOPHOLE[classCMask]] =
LOOPHOLE[classC, LONG UNSPECIFIED]) =>
{hostMask ← [160000B, 377B];
netMask ← [177777B, 177400B]};
ENDCASE => --unimplemented extended addressing or unrecognizable.
hostMask ← netMask ← [0B, 0B];
END; --BuildMasks
GetArpaAddr, GetAddress: PUBLIC <<ArpaPortInternal, ArpaRouter>> ENTRY PROC
RETURNS [ArpaTypes.InternetAddress] = {RETURN[myArpaAddr]};
GetGatewayAddr: PUBLIC <<ArpaPortInternal>> ENTRY PROC
RETURNS [addr: ArpaTypes.InternetAddress] = {RETURN[myGWAddr]};
SetGatewayAddr: PUBLIC <<ArpaPortInternal>> ENTRY PROC
[addr: ArpaTypes.InternetAddress] = {myGWAddr ← addr};
GetDomainNameServer: PUBLIC <<ArpaPortInternal>> ENTRY PROC
RETURNS [addr: ArpaTypes.InternetAddress] = {RETURN[myDomainNameServer]};
SetDomainNameServer: PUBLIC <<ArpaPortInternal>> ENTRY PROC
[addr: ArpaTypes.InternetAddress] = {myDomainNameServer ← addr};
GetSubnetMask: PUBLIC PROC RETURNS[InternetAddress] = {RETURN[mySubnetMask]};
GetMyBroadcastAddr: PUBLIC <<ArpaPortInternal>> PROC[]
RETURNS[InternetAddress] = {RETURN[myBroadcastAddr]}; --GetMyBroadcastAddr
GetMyNullAddr: PUBLIC <<ArpaPortInternal>> PROC[] RETURNS[InternetAddress] =
{RETURN[myNullAddr]}; --GetMyNullAddr
GetIPLengths: PUBLIC PROC [body: ArpaBuffer.Body]
RETURNS [optionsLen, dataLen: CARDINAL] =
BEGIN
dataLen ← body.ipHeader.length - body.ipHeader.ihl*4;
optionsLen ← body.ipHeader.ihl*4 - ArpaPort.minIPHeaderBytes - dataLen;
END; --GetIPLengths
ProcessICMPPacket: PROC [b: ArpaBuffer.Buffer]
RETURNS [keeper: BOOLEAN ← FALSE] =
BEGIN
OPEN c: LOOPHOLE[b.fo.context, ArpaRoutingTable.NetworkContext];
body: ArpaBuffer.Body = b.arpa;
hmsk: InternetAddress = c.hostMask;
bytes: CARDINAL ← body.ipHeader.length - (body.ipHeader.ihl*4);
words: CARDINAL ← (bytes + bpw - 1) / bpw;
PTag: TYPE = LONG POINTER TO Tag;
Tag: TYPE = ARRAY[0..2) OF ArpaTypes.IcmpType;
IF ArpaFlags.doStats THEN ArpaStats.Incr[
SELECT body.icmp.type FROM
unreachable => icmpUnreachable,
timeExceeded => icmpTimeExceeded,
paramProblem => icmpParam,
quench => icmpQuench,
redirect => icmpRedirect,
infoRequest => icmpInfoReq,
infoReply => icmpInfoRep,
echo => icmpEcho,
echoReply => icmpEchoRep,
timestamp => icmpTimestamp,
timestampReply => icmpTimestampRep,
ENDCASE => icmpUnknown];
--Check the checksum.
IF (bytes MOD 2) # 0 THEN body.ipBytes[bytes] ← 0; --zero the pad byte.
IF ArpaPortInternal.Checksum[0, words, @body.icmp] # 0 THEN
BEGIN
IF ArpaFlags.doStats THEN ArpaStats.Incr[icmpChecksum];
ArpaBuffer.ReturnBuffer[b]; RETURN;
END;
WITH icmp: body.icmp SELECT FROM
--for the routing table implementation.
redirect =>
{ArpaRoutingTable.Redirect[b]; ArpaBuffer.ReturnBuffer[b]};
infoReply =>
{ArpaRoutingTable.InfoReply[b]; ArpaBuffer.ReturnBuffer[b]};
--echo and timestamp can be taken care of right here.
echo =>
BEGIN
<<
Need to modify the IcmpType from 'echo' to 'echoReply'. That's no
small feat considering that it's also the variant tag field.
>>
tag: Tag = ALL[ArpaTypes.IcmpType[echoReply]];
LOOPHOLE[@body.icmp.body, PTag]↑[0] ← tag[0];
ArpaPort.SwapSourceAndDestination[b];
icmp.checksum ← 0; --first you set it to zero
icmp.checksum ← ArpaPortInternal.Checksum[0, words, @body.icmp];
SendPacket[b,
IF ArpaPortInternal.AddrMatch[hmsk, body.ipHeader.source,
body.ipHeader.destination] THEN local ELSE remote];
END;
timestamp =>
BEGIN
<<
Need to modify the IcmpType from 'timestamp' to 'timestampReply'.
That's no small feat considering that it's also the variant tag field.
>>
CardToTime: PROC[LONG CARDINAL] RETURNS[ArpaTypes.Cardinal32] =
MACHINE CODE {Mopcodes.zEXCH};
tag: Tag = ALL[ArpaTypes.IcmpType[timestampReply]];
secondsInDay: LONG CARDINAL = LONG[24 --hr-- * 60 --min--] * 60 --sec--;
now: LONG CARDINAL ← System.GetGreenwichMeanTime[]; --that's current
now ← now MOD secondsInDay; --so that would have been midnight
icmp.receive ← icmp.transmit ← CardToTime[now * 1000]; --in msecs
LOOPHOLE[@body.icmp.body, PTag]↑[0] ← tag[0];
ArpaPort.SwapSourceAndDestination[b]; --get ready to send packet back
icmp.checksum ← 0; --first you set it to zero
icmp.checksum ← ArpaPortInternal.Checksum[0, words, @body.icmp];
SendPacket[b, IF ArpaPortInternal.AddrMatch[hmsk,
body.ipHeader.source, body.ipHeader.destination]
THEN local ELSE remote];
END;
--These are turned over to the client to handle
unreachable, timeExceeded, paramProblem, quench, echoReply => keeper ← TRUE;
--infoRequest, timestampReply, and unknowns just get ignored right here
ENDCASE => ArpaBuffer.ReturnBuffer[b];
END; --ProcessICMPPacket
ReceivePacket: PROC[b: ArpaBuffer.Buffer] =
BEGIN
OPEN
ArpaPortInternal, --for AddrMismatch and Checksum.
c: LOOPHOLE[b.fo.context, ArpaRoutingTable.NetworkContext];
body: ArpaBuffer.Body = b.arpa;
hmsk: InternetAddress = c.hostMask;
CleanFragList: ENTRY PROC = INLINE {NOTIFY fragTimer};
CleanFragList[]; --get rid of possible old fragments
IF ArpaFlags.doStats THEN ArpaStats.Incr[ipRecvd];
SELECT TRUE FROM
(rto.type # gateway AND
AddrMismatch[hmsk, c.host, b.arpa.ipHeader.destination] AND
AddrMismatch[hmsk, myBroadcastAddr, b.arpa.ipHeader.destination] AND
AddrMismatch[hmsk, myNullAddr, b.arpa.ipHeader.destination]) => -- this is in violation fo protocol for 4.2Bsd broadcast
{IF ArpaFlags.doStats THEN ArpaStats.Incr[badDest];
ArpaBuffer.ReturnBuffer[b]}; --not for us.
(Checksum[0, body.ipHeader.ihl * bpw, body] # 0) =>
{IF ArpaFlags.doStats THEN ArpaStats.Incr[badChecksum];
ArpaBuffer.ReturnBuffer[b]}; --bad checksum in header.
(body.ipHeader.lifetime = 0) =>
{IF ArpaFlags.doStats THEN ArpaStats.Incr[tooOld];
ArpaBuffer.ReturnBuffer[b]}; --been around too long.
((body.ipHeader.fragment # 0) OR body.ipHeader.flags.moreFragment) AND
((b ← Reassemble[b]) = NIL) => NULL; --Reassemble will return b.
(rto.type = gateway) AND --packet to forward.
(AddrMismatch[hmsk, myBroadcastAddr, b.arpa.ipHeader.destination]) AND
(AddrMismatch[hmsk, myNullAddr, b.arpa.ipHeader.destination]) AND -- don't forward broadcasts!!!
(AddrMismatch[c.hostMask, c.host, body.ipHeader.destination]) =>
{IF ArpaFlags.doStats THEN ArpaStats.Incr[forwarded];
rto.forward[b]};
(body.ipHeader.protocol = icmp) AND (~ProcessICMPPacket[b]) =>
--icmp, maybe to client.
NULL; --ProcessICMPPacket will turn around or return the buffer.
(~ArpaPortInternal.QueueForClient[b: b, copy: FALSE]) => --to client
ArpaBuffer.ReturnBuffer[b];
ENDCASE;
END; --ReceivePacket
Reassemble: ENTRY PROC[frag: ArpaBuffer.Buffer]
RETURNS [b: ArpaBuffer.Buffer] =
BEGIN
<<
Takes an IP fragment and puts it into the reassembly buffer. If this is
the first fragment of a packet to arrive, its buffer will be used as the
reassembly buffer. The reassembly is managed via hole descriptors and the
algorthms in RFC 815. The only difference is that the header is also
managed with hole descriptors, hence all the additions of hdrLen in the
calculations.
>>
body: ArpaBuffer.Body ← frag.arpa;
IF fragWatcher = NIL THEN fragWatcher ← FORK FragWatcher[];
IF ArpaFlags.doStats THEN ArpaStats.Incr[fragRcvd];
IF (b ← FindReassembly[body]) = NIL THEN
BEGIN
listHead: ListEntry;
--fragment of a new packet - this buffer will be used to reassemble into.
ArpaBuffer.Enqueue[@fragQueue, frag];
IF body.ipHeader.fragment = 0 THEN --this has the header we want.
BEGIN
listHead ← LOOPHOLE[body + (body.ipHeader.length + 1)/2];
listHead↑ ← [NIL, body.ipHeader.length, LAST[CARDINAL]];
END
ELSE
BEGIN --we just want the data from any subsequent ones.
listHead ← LOOPHOLE[body];
listHead↑ ← [NIL, 0, body.ipHeader.fragment * 8 - 1];
listHead.next ← LOOPHOLE[body + (body.ipHeader.fragment * 8 + 1)/2];
listHead.next↑ ← [NIL, body.ipHeader.length, LAST[CARDINAL]]
END;
<<
requeueData is used to hold the timeout for assembling this buffer.
This is truly slimy, but it means we can use the queue package and not
have to carry another state object around for the timeout info.
>>
frag.requeueData ← MAX[body.ipHeader.lifetime, fragTimeout];
frag.fo.driver.iocb ← LOOPHOLE[listHead];
END
ELSE
BEGIN
b ← AddFrag[frag, b];
ArpaBuffer.ReturnBuffer[frag];
END;
END; --Reassemble
FindReassembly: INTERNAL PROC [tIp: ArpaBuffer.Body]
RETURNS [b: ArpaBuffer.Buffer] =
BEGIN
--searches list of reassembly buffers for the one containing this fragment.
FOR b ← ArpaBuffer.From[fragQueue.first], ArpaBuffer.From[b.fo.next]
UNTIL b = NIL DO
bIp: ArpaBuffer.Body = b.arpa;
SELECT TRUE FROM
(bIp.ipHeader.source # tIp.ipHeader.source) => NULL;
(bIp.ipHeader.destination # tIp.ipHeader.destination) => NULL;
(bIp.ipHeader.identification # tIp.ipHeader.identification) => NULL;
(bIp.ipHeader.protocol # tIp.ipHeader.protocol) => NULL;
ENDCASE => EXIT; --got it!
ENDLOOP;
END; --FindReassembly
FragWatcher: ENTRY PROC =
BEGIN
elapsedSeconds: LONG CARDINAL;
b, newB: ArpaBuffer.Buffer ← NIL;
Process.SetPriority[Process.priorityNormal];
DO --until aborted
ENABLE ABORTED => EXIT;
WAIT fragTimer;
IF fragQueue.length > 0 THEN
FOR b ← ArpaBuffer.From[fragQueue.first], newB UNTIL newB = NIL DO
newB ← ArpaBuffer.From[b.fo.next]; --in case we delete b
<<
b.fo.time is in Pulses.
b.requeueData is in seconds.
uSecs * 1D6 is a real small time!
This is going to be messy.
>>
[elapsedSeconds, ] ← Inline.UDDivMod[
(System.PulsesToMicroseconds[
[System.GetClockPulses[] - b.fo.time]]), 1D6];
IF elapsedSeconds > b.requeueData THEN
BEGIN
b ← ArpaBuffer.ExtractFromQueue[@fragQueue, b];
ArpaBuffer.ReturnBuffer[b];
--**send ICMP time exceeded.
END;
ENDLOOP;
ENDLOOP;
END; --FragWatcher
Ripple: INTERNAL PROC [b: ArpaBuffer.Buffer, offset: CARDINAL]
RETURNS [success: BOOLEAN ← TRUE] =
BEGIN
<<
ripples the IP data in the buffer by offset to make room for a
"larger than anticipated" header when reassembling. The hole descriptors
also have to be adjusted. The actual entries will be moved along with the
data.
This code is bogus! There's something wrong with the computation of
'length'. It can't be a long pointer plus a SIZE that gives a length.
I suspect that the offset has to be subtracted from the beginning of
some buffer, and I suspect that it's 'b'. The following is the original
code just commented out. Also, note the comparison of the length times
something that's obvoiously bytes to "DataWordsPerRawBuffer[]".
e, next: ListEntry;
length: LONG CARDINAL;
FOR e ← LOOPHOLE[b.driver.iocb], next UNTIL next = NIL DO
next ← e.next; --save link, since we are going add the offset to it.
e.first ← e.first + offset;
e.last ← e.last + offset;
e.next ← e.next + offset;
ENDLOOP;
--e points to last hole descriptor, which is last piece of valuable info.
length ← LOOPHOLE[e + SIZE[ListObject]];
length ← LOOPHOLE[(@b.bufferBody + length * 2) - @b.bufferBody];
IF length + SIZE[DriverTypes.Encapsulation] * 2 >
Buffer.DataWordsPerRawBuffer[] THEN success ← FALSE;
[] ← ByteBlt.ByteBlt[
to: [LOOPHOLE[@b.bufferBody + offset], 0, Inline.LowHalf[length]],
from: [LOOPHOLE[@b.bufferBody], 0, Inline.LowHalf[length]]];
>>
END; --Ripple
AddFrag: INTERNAL PROC [frag, b: ArpaBuffer.Buffer]
RETURNS [completed: ArpaBuffer.Buffer ← NIL] =
BEGIN OPEN ip: frag.arpa.ipHeader;
<<
Pieces the fragment into the specified reassembly buffer. If this
fragment completes the reassembly, the reassembly buffer will be returned;
if it is not completed, NIL will be returned. The hole descripter resides
in the hole itself, which we know to be at least 8 bytes, and the
descriptors are linked, with the head being in the iocb field of the fixed
overhead. This field is not used at this point, and we need someplace to
put a long pointer. Slimy, but it works!
>>
offset: CARDINAL ← 0; --difference in header from default size.
hdrLen, dataLen: CARDINAL;
first, last: CARDINAL ← 0; --fragment offsets.
listHead: ListEntry ← LOOPHOLE[b.fo.driver.iocb];
hole, prev: ListEntry;
use: BOOLEAN;
AddHole: PROC [f, l: CARDINAL] =
BEGIN
p, e, previous: ListEntry;
--this is where the hole descriptor will be stored.
p ← LOOPHOLE[b.arpa + (first + 1)/2 + (hdrLen + 1)/2];
previous ← NIL;
FOR e ← listHead, e.next UNTIL e = NIL DO
IF (f + hdrLen < e.first) THEN EXIT;
previous ← e;
ENDLOOP;
IF previous = NIL THEN listHead ← p
ELSE previous.next ← p;
p↑ ← [e, f + hdrLen, l + hdrLen];
END; --AddHole
<<
Either we know the header length, or we will guess at the default. Ripple
will take care of us if we guess wrong. If this is the first fragment,
hdrLen will be updated by the code that copies the header.
>>
hdrLen ← IF listHead.first = 0 THEN ArpaPort.minIPHeaderBytes
ELSE b.arpa.ipHeader.ihl * 4;
dataLen ← ip.length - ip.ihl * 4;
first ← ip.fragment * 8;
last ← first + ip.length - (ip.ihl * 4) - 1;
SELECT TRUE FROM
<<
Can only reassemble to size of our buffers. Again, Ripple will take
care of the case where we guessed wrong at the header size.
>>
last > (ArpaBuffer.DataBytesPerRawBuffer[frag] - hdrLen -
(SIZE[ethernet IEEE8023.EncapObject] * bpw)) =>
{IF ArpaFlags.doStats THEN ArpaStats.Incr[reassemblyTooBig]; RETURN};
(ip.fragment = 0) => --first fragment processing (need to save header).
SELECT TRUE FROM
--already have header, let's ignore header in this fragment.
(listHead.first > 0) => NULL;
--first to arrive, plenty of hdr room.
(listHead.last = LAST[CARDINAL]),
ip.ihl = 5 => --reassembly buffer not empty, but header fits.
BEGIN
hdrLen ← ip.ihl * 4;
IF hdrLen > arpaProtocolFamily.maxBufferSize THEN --sanity check.
{IF ArpaFlags.doStats THEN ArpaStats.Incr[reassemblyTooBig];
RETURN};
[] ← ByteBlt.ByteBlt[to: [LOOPHOLE[b.arpa], 0, hdrLen],
from: [LOOPHOLE[frag.arpa], 0, hdrLen]];
listHead.first ← hdrLen; --update the hole descriptor
END;
ENDCASE => --oops, header was greater than the size we allowed for.
BEGIN
hdrLen ← ip.ihl * 4;
IF hdrLen > arpaProtocolFamily.maxBufferSize THEN --sanity check.
{IF ArpaFlags.doStats THEN ArpaStats.Incr[reassemblyTooBig];
RETURN};
offset ← hdrLen - ArpaPort.minIPHeaderBytes;
IF ~Ripple[b, offset] THEN
{IF ArpaFlags.doStats THEN ArpaStats.Incr[reassemblyTooBig];
ArpaBuffer.ReturnBuffer[frag]};
[] ← ByteBlt.ByteBlt[to: [LOOPHOLE[b.arpa], 0, hdrLen],
from: [LOOPHOLE[frag.arpa], 0, hdrLen]];
listHead.first ← hdrLen; --update the hole descriptor
END;
ENDCASE;
IF ((last + 1) MOD 8) # 0 AND ip.flags.moreFragment THEN
--next frag won't be 32 aligned, but if last who cares.
{IF ArpaFlags.doStats THEN ArpaStats.Incr[fragAlign];
RETURN};
use ← FALSE; --is this fragment usable at all (overlaps a hole?).
--find out where this fragment goes.
prev ← NIL;
FOR hole ← listHead, hole.next UNTIL hole = NIL DO
IF (first + hdrLen > hole.last) THEN {prev ← hole; LOOP};
IF (last + hdrLen < hole.first) THEN {prev ← hole; LOOP};
use ← TRUE;
--remove current entry from list.
IF prev = NIL THEN listHead ← hole.next --first entry
ELSE prev.next ← hole.next;
--new hole descriptors?
IF first + hdrLen > hole.first THEN
AddHole[hole.first, ip.fragment * 8 - 1];
IF (last + hdrLen < hole.last) AND (ip.flags.moreFragment) THEN
AddHole[last + 1 , hole.last];
ENDLOOP;
IF use THEN
BEGIN
[] ← ByteBlt.ByteBlt[
to: [LOOPHOLE[
b.arpa + (hdrLen + first)/2], 0, ip.length - ip.ihl * 4],
from: [LOOPHOLE[
@frag.arpa.ipBytes], 0, ip.length - ip.ihl * 4]];
<< length will not be correct (i.e., will not include header), until just
before we return the assembled buffer to the client! >>
IF ~ip.flags.moreFragment THEN b.arpa.ipHeader.length ← last + 1;
IF listHead = NIL THEN
BEGIN --add in the header length.
b.arpa.ipHeader.length ← b.arpa.ipHeader.length + b.arpa.ipHeader.ihl * 4;
RETURN[ArpaBuffer.ExtractFromQueue[@fragQueue, b]];
END;
END; --use
b.fo.driver.iocb ← LOOPHOLE[listHead]; --restore
END; --AddFrag
Register: PUBLIC PROC[h: ArpaRoutingTable.Handle ← NIL] =
BEGIN
ExchangeObjects: ENTRY PROC =
BEGIN
IF h # NIL THEN rto ← h↑ --copy in new object
ELSE rto ← ArpaRoutingTable.defaultRth↑; --default to old
routersFunction ← rto.type;
END; --ExchangeObjects
arpaProtocolFamily.status ← dead; --slow things up a while
Process.Pause[Process.SecondsToTicks[1]]; --wait for traffic to clear
IF rto.stop # NIL THEN rto.stop[]; --stop current
ExchangeObjects[]; --reregister
IF rto.start # NIL THEN rto.start[]; --start new
arpaProtocolFamily.status ← alive; --and let the good times role
END; --Register
SendPacket: PUBLIC PROC[
b: ArpaBuffer.Buffer, destType: ArpaPortInternal.DestType] =
BEGIN
body: ArpaBuffer.Body = b.arpa;
body.ipHeader.version ← vers4;
body.ipHeader.fragment ← 0;
body.ipHeader.flags.moreFragment ← FALSE;
SELECT TRUE FROM
--broadcasts and local w/loopback included here
driverLoopback, (destType = remote) =>
BEGIN
body.ipHeader.checksum ← 0;
body.ipHeader.checksum ← ArpaPortInternal.Checksum[
0, body.ipHeader.ihl*2, @body.ipHeader];
IF ArpaFlags.doStats THEN ArpaStats.Incr[ipSent];
[] ← rto.transmit[b];
END;
--sending to myself in non-loopback mode
(~ArpaPortInternal.QueueForClient[b: b, copy: TRUE]) =>
{b.fo.network ← Driver.nilDevice;
--**Send some icmp packet here?
IF ArpaFlags.doStats THEN ArpaStats.Incr[noLocalPort];
Process.Yield[]};
ENDCASE => --we copied it for the receiver, now requeue it for the sender.
{Driver.PutOnGlobalDoneQueue[ArpaBuffer.To[b]]; Process.Yield[]};
END; --SendPacket
SetIPLengths: PUBLIC PROC[
body: ArpaBuffer.Body, optionsLen, dataLen: CARDINAL] =
BEGIN
body.ipHeader.ihl ← (optionsLen + ArpaPort.minIPHeaderBytes+3)/4;
body.ipHeader.length ← body.ipHeader.ihl*4 + dataLen;
END; --SetIPLengths
SetOptions: PUBLIC PROC =
BEGIN
END; --SetOptions
StateChanged: PROC[
driver: Device, context: LONG POINTER, why: Protocol1.Action] =
BEGIN
SELECT why FROM
add => IF rto.addNetwork # NIL THEN rto.addNetwork[context];
remove => rto.removeNetwork[context]; --tell routing table to stop
modify => rto.stateChanged[context]; --see if routing table cares
ENDCASE;
END; --StateChanged
END.... --ArpaRouterImpl module.
LOG
25-Jun-84 13:33:44 AOF Created file.
8-Feb-85 15:51:44 SMA Development.
13-Jul-85 13:52:31 SMA Added stats.
18-Oct-85 12:15:36 SMA Move queueing code to ArpaPortImpl - now has only IP. 8-Jan-86 10:40:03 SMA ICMP support.
10-Mar-86 13:24:25 SMA ICMP checksums.
21-Jun-86 13:13:07 SMA IP reassembly.
24-Sep-86 16:42:50 JAV Fixed IP reassembly.
9-Mar-87 20:30:15 AOF Funston buffer management
24-Mar-87 18:39:06 AOF Just tweaking
15-Jun-87 11:14:13 JAV Added SetGatewayAddr & SetDomainNameServer