-- File: RouterImpl.mesa - last edit:
-- AOF 16-Dec-87 19:53:41
-- MI 1-Aug-86 15:22:18
-- SMA 23-May-86 10:25:42
-- Copyright (C) 1984, 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
--Function: The implementation module for the Pilot NS Router.
DIRECTORY
Buffer USING [ReturnBuffer],
ByteBlt USING [ByteBlt],
Checksums USING [SetChecksum, TestChecksum],
CommunicationInternal USING [PacketStreamsGo],
CommHeap USING [zone],
CommFlags USING [doStats, doStorms],
Driver USING [
ChangeNumberOfInputBuffers, GetDeviceChain, NetworkNonExistent,
Device, nilDevice, PutOnGlobalDoneQueue, GetNthDevice],
EchoServer USING [CreateServer, DeleteServer],
Environment USING [bytesPerWord],
HostNumbers USING [IsMulticastID],
IEEE8023 USING [EncapObject, Encapsulation],
Inline USING [LongCOPY, LowHalf],
NetworkStream USING [],
NSBuffer USING [
Body, CreditReceiveBuffer, GetBuffer, Buffer, Enqueue, ReturnBuffer],
NSConstants USING [
<<maxWellKnownSocket, >>routingInformationSocket, errorSocket],
NSConstantsExtras USING [maxWellKnownSocket],
NSTypes USING [
maxIDPBytesPerPacket, bytesPerIDPHeader, ErrorCode, maxIDPDataBytes],
Process USING [Pause, Yield],
Protocol1 USING [
MatrixRecord, FamilyUnit, DecapsulatorProc, EncapsulatorProc,
RegisterFamily, EvictFamily, EncapsulateAndTransmit, Family,
GetContext, Action, AddFamilyMember, RemoveFamilyMember,
SetMaximumBufferSize],
Router USING [PhysicalMedium, RoutersFunction],
RouterInternal USING [defaultRth],
RoutingTable USING [Handle, Object, NetworkContext, ContextObject],
Socket USING [SetPacketBytes, SwapSourceAndDestination],
SocketInternal USING [SocketHandle, SocketTable],
SpecialSystem USING [
GetProcessorID, NetworkNumber, HostNumber, SocketNumber, ProcessorID],
Stats USING [StatIncr],
System USING [
broadcastHostNumber, GetClockPulses, NetworkAddress, NetworkNumber,
nullNetworkNumber];
RouterImpl: MONITOR LOCKS socketRouterLock
IMPORTS
Buffer, NSBuffer, ByteBlt, Checksums, CommHeap, Driver, Inline, HostNumbers,
Process, Protocol1, RouterInternal, Socket, SpecialSystem, Stats, System,
CommunicationInternal, EchoServer
EXPORTS
Buffer, CommunicationInternal, NetworkStream, RoutingTable, Router,
RouterInternal, Socket, SocketInternal, System =
BEGIN
--EXPORTed TYPEs
Device: PUBLIC <<Buffer>> TYPE = Driver.Device;
NetworkNumber: PUBLIC <<System>> TYPE = SpecialSystem.NetworkNumber;
HostNumber: PUBLIC <<System>> TYPE = SpecialSystem.HostNumber;
ProcessorID: PUBLIC TYPE = SpecialSystem.ProcessorID;
SocketNumber: PUBLIC <<System>> TYPE = SpecialSystem.SocketNumber;
ChannelHandle: PUBLIC TYPE = SocketInternal.SocketHandle;
--Router.--NetworkNonExistent: PUBLIC ERROR = CODE;
--Router.--NoTableEntryForNet: PUBLIC ERROR = CODE;
--Socket.--DuplicateSocket: PUBLIC ERROR = CODE;
bpw: NATURAL = Environment.bytesPerWord;
socketRouterLock: PUBLIC <<RouterInternal>> MONITORLOCK;
socketTable: PUBLIC <<SocketInternal>> SocketInternal.SocketTable;
spareSocketID: SocketNumber;
useCount: INTEGER ← 0; --to detect multiple clients
lastWKS: SocketNumber = NSConstantsExtras.maxWellKnownSocket;
myProcID: ProcessorID; --processID of this machine
nsProtocolFamily: Protocol1.FamilyUnit ← [
name: ns, status: dead, spy: NIL, state: NIL,
maxBufferSize: NSTypes.maxIDPBytesPerPacket,
receive: LOOPHOLE[ReceivePacket], broadcast: LOOPHOLE[Broadcast],
stateChanged: NSStateChange];
rto: RoutingTable.Object;
checkIt: PUBLIC <<RouterInternal>> BOOLEAN ← TRUE;
routersFunction: PUBLIC <<RouterInternal>> Router.RoutersFunction;
startEnumeration: PUBLIC <<Router>> NetworkNumber; --set to start enumeration
endEnumeration: PUBLIC <<Router>> NetworkNumber; --implies end of enumeration
--PARAMETERS FOR KILLING PACKETS FOR DEBUGGING AND
--SWITCHES THAT DON'T CHANGE DURING EXECUTION UNLESS FOR DIAGNOSTICS
debugging: RECORD[
bolt: INTEGER ← 10, lightning: INTEGER ← 30,
stormy, driverLoopback: BOOLEAN ← FALSE,
allowedToSendErrorPackets: BOOLEAN ← TRUE] ← [];
PutOnGlobalDoneQueue: PROC[b: NSBuffer.Buffer] =
LOOPHOLE[Driver.PutOnGlobalDoneQueue];
<<
The drivers may have to be told what its network numbers are.
This is certainly true if this is the first machine running on a network.
Physical order is the location of the network on the network device chain.
>>
SetNetworkID: PUBLIC ENTRY PROC[
physicalOrder: CARDINAL,
medium: Router.PhysicalMedium,
newNetID: System.NetworkNumber]
RETURNS [oldNetID: System.NetworkNumber] =
BEGIN
ENABLE UNWIND => NULL;
context: RoutingTable.NetworkContext;
network: Driver.Device ← Driver.GetNthDevice[
physicalOrder, SELECT medium FROM
ethernet => ethernet, ethernetOne => ethernetOne, ENDCASE => phonenet !
Driver.NetworkNonExistent => GOTO noNet];
context ← Protocol1.GetContext[network, ns];
oldNetID ← context.netNumber; context.netNumber ← newNetID;
rto.stateChanged[context]; --see if routing table cares
EXITS noNet => ERROR NetworkNonExistent;
END;
GetNetworkID: PUBLIC ENTRY PROC[
physicalOrder: CARDINAL, medium: Router.PhysicalMedium]
RETURNS [System.NetworkNumber] =
BEGIN
ENABLE UNWIND => NULL;
context: RoutingTable.NetworkContext;
network: Driver.Device ← Driver.GetNthDevice[
physicalOrder, SELECT medium FROM
ethernet => ethernet, ethernetOne => ethernetOne, ENDCASE => phonenet];
context ← Protocol1.GetContext[network, ns];
RETURN[context.netNumber];
END;
<<
The following two routines know something of the level-0 machinery.
Consequently, they really shouldn't be housed in this module, but for
the sake of saving a module (aka global frame), they are. The question
then arises, "Is it better to look into level-1 from level-0 or into
level-0 from level-1?" If I decide what the answer is, I'll include it
here sometime. But for now....
>>
DecapsulateEthernet: Protocol1.DecapsulatorProc =
BEGIN
e: IEEE8023.Encapsulation = LOOPHOLE[b.linkLayer.blockPointer];
IF HostNumbers.IsMulticastID[LOOPHOLE[@e.ethernetSource]] THEN
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statPacketsDiscarded];
RETURN[orphan];
END;
IF e.ethernetType = ns THEN
BEGIN
bytes: CARDINAL = b.fo.driver.length;
body: NSBuffer.Body = LOOPHOLE[e + SIZE[ethernet IEEE8023.EncapObject]];
IF bytes < body.pktLength THEN RETURN[orphan]; --is packet valid?
b.linkLayer.stopIndexPlusOne ← SIZE[ethernet IEEE8023.EncapObject] * bpw;
b.highLayer ← [LOOPHOLE[body], 0, body.pktLength]; --Compute higher layer
RETURN[ns];
END
ELSE RETURN[vagrant];
END; --Decapsulate
EncapsulateEthernet: Protocol1.EncapsulatorProc =
BEGIN
e: IEEE8023.Encapsulation;
body: NSBuffer.Body = LOOPHOLE[b.highLayer.blockPointer];
e ← LOOPHOLE[body - SIZE[ethernet IEEE8023.EncapObject]];
e↑ ← [
ethernetDest: NARROW[immediate, LONG POINTER TO HostNumber]↑,
ethernetSource: myProcID, var: ethernet[ethernetType: ns]];
b.fo.driver.length ← body.pktLength +
(SIZE[ethernet IEEE8023.EncapObject] * bpw);
b.linkLayer ← [LOOPHOLE[e], 0, SIZE[ethernet IEEE8023.EncapObject] * bpw];
END; --Encapsulate
NSStateChange: 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];
modify => rto.stateChanged[context];
ENDCASE;
END; --StateChangedEthernet
Register: PUBLIC PROC[h: RoutingTable.Handle ← NIL] =
BEGIN
ExchangeObjects: ENTRY PROC =
BEGIN
IF h # NIL THEN rto ← h↑ --copy in new object
ELSE rto ← RouterInternal.defaultRth↑; --default to old
routersFunction ← rto.type;
startEnumeration ← rto.startEnumeration;
endEnumeration ← rto.endEnumeration;
END; --ExchangeObjects
nsProtocolFamily.status ← dead; --slow thing up a while
Process.Pause[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
nsProtocolFamily.status ← alive; --and let the good times role
END; --Register
EnumerateRoutingTable: PUBLIC PROC[previous: NetworkNumber, delay: CARDINAL]
RETURNS [net: NetworkNumber] = {net ← rto.enumerate[previous, delay]};
FillRoutingTable: PUBLIC PROC[maxDelay: CARDINAL] =
{rto.fillTable[maxDelay]}; --FillRoutingTable
GetDelayToNet: PUBLIC PROC[net: NetworkNumber] RETURNS [delay: CARDINAL] =
{delay ← rto.getDelay[net]};
GetRouterFunction: PUBLIC PROC [] RETURNS [Router.RoutersFunction] =
{RETURN[rto.type]}; --GetRouterFunction
FindDestinationRelativeNetID: PUBLIC PROC[net: NetworkNumber]
RETURNS [NetworkNumber] =
{RETURN[rto.findNetwork[net]]}; --FindDestinationRelativeNetID
FlushCacheEntry: PUBLIC PROC[net: System.NetworkNumber] =
{rto.flushCache[net]};
<<
This procedure assigns a temporary socket number in an System.NetworkAddress.
Some day the active socket numbers in use will be kept around, so that on wrap
around an unused number will be assigned. When the system element is
connected to more than one network, this procedure must return a list of
System.NetworkAddress.
>>
AssignAddress, AssignNetworkAddress: PUBLIC ENTRY PROC
RETURNS [localAddr: System.NetworkAddress] =
BEGIN
ReassignUniqueSocket[]; --get the unique socket
localAddr ← [
net: rto.findNetwork[System.nullNetworkNumber],
host: myProcID, socket: LOOPHOLE[spareSocketID]];
END; --AssignAddress
<<
This procedure is just like the previous one except that the network number
is relative to the destination network. That is, we pick that one of our
locally connected networks that is the best way to get to destNet, with
the hope that it is the best way to get from destNet to us.
>>
AssignDestinationRelativeAddress: PUBLIC ENTRY PROC[
destNet: NetworkNumber] RETURNS [localAddr: System.NetworkAddress] =
BEGIN
ReassignUniqueSocket[]; --get the unique socket
localAddr ← [
net: rto.findNetwork[destNet], host: myProcID,
socket: LOOPHOLE[spareSocketID]];
END; --AssignDestinationRelativeAddress
ReassignUniqueSocket: INTERNAL PROC =
BEGIN
--RETURNs from inner loop--DO
IF (spareSocketID ← [spareSocketID + 1]) = SocketNumber[0] THEN
spareSocketID ← [LOOPHOLE[lastWKS, CARDINAL] + 1];
FOR cH: ChannelHandle ← socketTable.first, cH.next UNTIL cH = NIL DO
socket: SocketNumber = cH.address.socket;
IF (spareSocketID = socket) THEN EXIT;
REPEAT FINISHED => RETURN; --that socket will work
ENDLOOP;
ENDLOOP;
END; --AssignUniqueSocket
AddSocket: PUBLIC ENTRY PROC[sH: ChannelHandle] =
BEGIN
FOR cH: ChannelHandle ← socketTable.first, cH.next UNTIL cH = NIL DO
IF (sH.address.socket = cH.address.socket) THEN
RETURN WITH ERROR DuplicateSocket;
ENDLOOP;
--add new socket to the head of the table
sH.next ← socketTable.first;
socketTable.first ← sH;
socketTable.length ← socketTable.length + 1;
--Is this the first "real" client? Should we tell the driver to open up?
SELECT TRUE FROM
(sH.pool.seal = listenPool) => NULL; --nope, he's a fake
(sH.pool.receive > 0) => Driver.ChangeNumberOfInputBuffers[TRUE];
ENDCASE;
END; --AddSocket
RemoveSocket: PUBLIC ENTRY PROC[sH: ChannelHandle] =
BEGIN
previousSH: ChannelHandle;
IF socketTable.first = sH THEN socketTable.first ← sH.next
ELSE
BEGIN
previousSH ← socketTable.first;
UNTIL previousSH = NIL DO
IF previousSH.next = sH THEN {previousSH.next ← sH.next; EXIT};
previousSH ← previousSH.next;
ENDLOOP;
END;
socketTable.length ← socketTable.length - 1;
--Is it time to "close" the drivers down again?
SELECT TRUE FROM
(sH.pool.seal = listenPool) => NULL; --that's a fake
(sH.pool.receive > 0) => Driver.ChangeNumberOfInputBuffers[FALSE];
ENDCASE;
END; --RemoveSocket
--DEBUGGING HOOKS
SetNSStormy: PUBLIC <<RouterInternal>> PROC[new: BOOLEAN] =
{debugging.stormy ← new};
SetNSDriverLoopback: PUBLIC <<RouterInternal>> PROC[new: BOOLEAN] =
{debugging.driverLoopback ← new};
--UTILITY ROUTINES
SetNSCheckit: PUBLIC <<RouterInternal>> PROC[new: BOOLEAN] = {checkIt ← new};
FindMyHostID: PUBLIC PROC RETURNS [HostNumber] = {RETURN[myProcID]};
<<
This procedure transmits a packet over a locally connected network.
The procedure assumes that all fields of the buffer have been filled
in appropriately, except the encapsulation and buffer length which depend
on the network. If the packet is destined for a local socket and we do
NOT want to do loopback at the driver or at the network, then it gets
looped back here.
Packets are no longer copied into system buffers. The caller owns the
buffer. The buffer is asynchronously sent and returned to the caller
process by the dispatcher using b.requeueProcedure.
Broadcast packets are NOT delivered to the source socket here, they are
delivered to the correct driver. The driver then has the responsibility to
get the packet onto the incoming packet queue. How that is done is driver
and hardware dependent. E.g., the Dolphin can hear Ethernet packets it has
sent, so the dolphin just broadcasts the pkt, and hears it at the same time.
The DLion can't do that, so it's Ethernet driver explicitly copies broadcast
pkts being sent, and puts them on the input queue.
>>
SendPacket: PUBLIC PROC[b: NSBuffer.Buffer] =
BEGIN
body: NSBuffer.Body = b.ns;
body.transportControl ← [trace: FALSE, filler: 0, hopCount: 0];
SELECT TRUE FROM
--debugging mode
(CommFlags.doStorms AND debugging.stormy AND PacketHit[]) =>
{PutOnGlobalDoneQueue[b]; Process.Yield[]};
--broadcasts and local w/loopback included here
((body.destination.host # myProcID) OR debugging.driverLoopback) =>
BEGIN
--If the destination net is inaccessible then we will have suffered
--the overhead of computing a checksum, but we don't expect inaccessible
--nets that often. Set the checksum if appropriate
IF checkIt THEN Checksums.SetChecksum[body] ELSE body.checksum ← 177777B;
[] ← rto.transmit[b];
END;
--(body.destination.host = myProcID) =>
--packet is for a local socket; broadcast packets are not delivered locally
(~DeliveredToLocalSocket[b, TRUE --copy this buffer--]) =>
BEGIN
b.fo.network ← Driver.nilDevice;
SendErrorPacket[b, noSocket, 0]; --back to local socket
IF CommFlags.doStats THEN Stats.StatIncr[statJunkNSForUsNoLocalSocket];
Process.Yield[];
END;
--we requeue here because we shorted the drivers and dispatcher.
ENDCASE => {PutOnGlobalDoneQueue[b]; Process.Yield[]};
END; --SendPacket
<<
This procedure is called by the Dispatcher when it sees a valid ns packet in
a system buffer. The procedure checks the checksum field and then routes
the packet to a local socket. If this is an internetwork router, then
the packets are forwarded over a suitable network. We now own this packet.
>>
ReceivePacket: PUBLIC PROC[b: NSBuffer.Buffer] =
BEGIN
SpecialCase: PROC RETURNS[BOOLEAN] = INLINE
BEGIN
OPEN c: NARROW[b.fo.context, RoutingTable.NetworkContext];
RETURN[
(c.netNumber = body.destination.net)
OR (body.destination.net = System.nullNetworkNumber)
OR (body.source.socket = NSConstants.routingInformationSocket)];
END; --SpecialCase
body: NSBuffer.Body = b.ns;
destHost: HostNumber ← body.destination.host;
<<
If to my host | broadcast to my net | boadcast to unknown net id
| broadcast routing info packet (from socket #1). Insures we see packets
when we don't yet know our net number (we think its zero).
We use broadcast packets if a) the pkt's dest. net is the net number of
the driver that the pkt came in on, or b) the pkt's dest. net is
System.nullNetworkNumber
If we use this broadcast packet, we do NOT forward it (we may have
previously forwarded it, and then the driver "heard" it, though)
>>
SELECT TRUE FROM
--is packet at least minimum length
(body.pktLength < NSTypes.bytesPerIDPHeader) =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statNSDiscarded];
NSBuffer.ReturnBuffer[b]; --done with this packet
END;
--if testing checksums, do they check
(checkIt AND ~Checksums.TestChecksum[body]) =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statReceivedBadNSChecksum];
NSBuffer.ReturnBuffer[b]; --done with this packet
END;
--debugging mode only - wipes out reception
(CommFlags.doStorms AND debugging.stormy AND PacketHit[]) =>
NSBuffer.ReturnBuffer[b]; --done with this packet
--packet directed to me
(destHost = myProcID) =>
BEGIN
--do I have a socket to deliver it to
IF ~DeliveredToLocalSocket[b, FALSE] THEN
BEGIN
SendErrorPacket[b, noSocket, 0];
IF CommFlags.doStats THEN Stats.StatIncr[statJunkNSForUsNoLocalSocket];
END;
END;
--is packet a broadcast to some well-known (even not so well known) socket
(HostNumbers.IsMulticastID[@destHost] AND SpecialCase[]) =>
BEGIN
IF ~DeliveredToLocalSocket[b, FALSE] THEN
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statJunkBroadcastNS];
NSBuffer.ReturnBuffer[b];
END;
END;
(rto.type = interNetworkRouting) =>
rto.forward[b]; --dispatcher returns b to system pool
ENDCASE =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statNSDiscarded];
NSBuffer.ReturnBuffer[b];
END;
END; --ReceivePacket
<<
This procedure attempts to hit a packet with a bolt of lightening, and
if it succeeds, then it returns true else false. The caller dispenses
with the buffer.
>>
PacketHit: --ENTRY-- PROC RETURNS [BOOLEAN] =
BEGIN
SELECT TRUE FROM
(~CommFlags.doStorms) => NULL;
((debugging.lightning ← debugging.lightning + 1) > debugging.bolt) =>
BEGIN
IF debugging.bolt > 100 THEN
BEGIN
randLong: LONG CARDINAL ← System.GetClockPulses[];
rand: CARDINAL ← Inline.LowHalf[randLong];
debugging.lightning ← -INTEGER[rand MOD 20B];
debugging.bolt ← 10;
IF CommFlags.doStats THEN Stats.StatIncr[statZappedP];
END;
RETURN[TRUE];
END;
(debugging.lightning < 0) =>
BEGIN
debugging.lightning ← 0; debugging.bolt ← debugging.bolt + 1;
IF CommFlags.doStats THEN Stats.StatIncr[statZappedP];
RETURN[TRUE];
END;
ENDCASE;
RETURN[FALSE];
END; --PacketHit
<<
This procedure finds a local socket object to deliver the packet to, and if
it succeeds, then it returns true else false. Caller retains ownership
of buffer if useCopy is true or return is false; caller loses buffer
ownership if NOT useCopy and return is true.
>>
DeliveredToLocalSocket: ENTRY PROC[b: NSBuffer.Buffer, useCopy: BOOLEAN]
RETURNS [BOOLEAN] =
BEGIN
OPEN c: NARROW[b.fo.context, RoutingTable.NetworkContext];
ENABLE UNWIND => NULL;
sH, prevSH: ChannelHandle;
body: NSBuffer.Body = b.ns;
<<
In case the sender of this buffer doesn't know his own network number,
try to help him along by reaching back into the input device's network
object and getting the network from there. It is also possible that we
don't know any more than the sender, and in fact, we may be the sender.
>>
SELECT TRUE FROM
(body.source.net # System.nullNetworkNumber) => NULL; --already set
(body.transportControl.hopCount # 0) => NULL; --too late to help
(LOOPHOLE[b.fo.network, Device] = Driver.nilDevice) => NULL; --we're sender
ENDCASE => body.source.net ← c.netNumber;
--find the correct socket for this packet
FOR sH ← (prevSH ← socketTable.first), sH.next UNTIL sH = NIL DO
SELECT TRUE FROM
(sH.address.socket # body.destination.socket) => {prevSH ← sH; LOOP};
(useCopy) => EnqueueCopyOfNewInput[sH, body];
(NSBuffer.CreditReceiveBuffer[sH.pool, b]) =>
BEGIN
b.fo.status ← goodCompletion;
WITH cH: sH SELECT FROM
normal =>
BEGIN
BROADCAST cH.newUserInput; --wake up the client
NSBuffer.Enqueue[@cH.queue, b]; --make sure he has the buffer
END;
listen=>
BEGIN
b.requeueData ← sH; --copy the socket handle into buffer
NOTIFY cH.arrival↑; --wake up the central process
NSBuffer.Enqueue[cH.queue, b]; --and make sure he has the buffer
END;
ENDCASE;
END;
ENDCASE =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statNSInputQueueOverflow];
NSBuffer.ReturnBuffer[b];
END;
--do some dynamic socket ordering before returning;
--rearange the list only if sH was not in the first
--two entries of the list.
IF prevSH # socketTable.first THEN
BEGIN
prevSH.next ← sH.next; --this removes sH from the list
sH.next ← socketTable.first;
socketTable.first ← sH; --this puts sH at head of list
END;
RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END; --DeliveredToLocalSocket
<<
This procedure enqueues a new input packet at the socket object.
The incoming buffer is from a local socket.
The caller of this routine retains ownership of this buffer. Since there
is a possibility of using more buffers than anticipated, we check to see
if there is a spare input buffer before trying to get a new buffer.
>>
EnqueueCopyOfNewInput: INTERNAL PROC[sH: ChannelHandle, body: NSBuffer.Body] =
BEGIN
getBuffer: NSBuffer.Buffer;
IF sH.pool.receiveInUse < sH.pool.receive THEN
BEGIN
getBuffer ← NSBuffer.GetBuffer[
sH.pool, receive, FALSE, nsProtocolFamily.maxBufferSize];
IF getBuffer = NIL THEN RETURN; --can't do it this time
getBuffer.fo.status ← goodCompletion;
Inline.LongCOPY[
from: @body.checksum, to: @getBuffer.ns.checksum,
nwords: (body.pktLength + 1) / bpw];
WITH cH: sH SELECT FROM
normal =>
BEGIN
BROADCAST cH.newUserInput; --wake up the client
NSBuffer.Enqueue[@cH.queue, getBuffer]; --make sure he has the buffer
END;
listen=>
BEGIN
getBuffer.requeueData ← sH; --copy the socket handle into buffer
NOTIFY cH.arrival↑; --wake up the central process
NSBuffer.Enqueue[cH.queue, getBuffer]; --and make sure he has buffer
END;
ENDCASE;
END
ELSE IF CommFlags.doStats THEN Stats.StatIncr[statNSInputQueueOverflow];
END; --EnqueueCopyOfNewInput
--This procedure causes a broadcast packet to be sent over all networks.
BroadcastThisPacket: PUBLIC <<RouterInternal>> PROC[b: NSBuffer.Buffer] =
BEGIN
network: Device;
body: NSBuffer.Body = b.ns;
b.fo.allNets ← TRUE; --this is where it gets turned on
b.fo.network ← network ← Driver.GetDeviceChain[];
IF network = NIL THEN
BEGIN
b.fo.status ← noRouteToNetwork;
PutOnGlobalDoneQueue[b];
IF CommFlags.doStats THEN Stats.StatIncr[statNSSentNowhere];
END
ELSE
BEGIN
b.fo.context ← Protocol1.GetContext[network, ns];
Broadcast[b];
IF CommFlags.doStats THEN Stats.StatIncr[statNSBroadcast];
END;
END; --BroadcastThisPacket
<<
This procedure causes a broadcast packet to be sent out over the right network.
This procedure is directly used by the Dispatcher when sending an allNets pkt
on to the next network. Among others, (I suppose).
>>
Broadcast: PROC[b: NSBuffer.Buffer] =
BEGIN
OPEN context: NARROW[b.fo.context, RoutingTable.NetworkContext];
body: NSBuffer.Body = b.ns;
network: Device = b.fo.network;
allHosts: HostNumber ← System.broadcastHostNumber;
--NB: Putting this on the GlobalDoneQueue causes the first driver in chain
--to be skipped, but progresses to the next in the chain.
SELECT TRUE FROM
(~network.alive) => PutOnGlobalDoneQueue[b]; --is the driver alive?
(b.fo.context = NIL) => PutOnGlobalDoneQueue[b]; --does it support family?
(b.fo.bypassZeroNet) AND (context.netNumber = System.nullNetworkNumber) =>
PutOnGlobalDoneQueue[b]; --we're skipping null nets and it is
ENDCASE =>
BEGIN
--assumes caller has set both destination and sourse socket fields
b.fo.status ← goodCompletion; --rash assumption
body.transportControl ← [FALSE, 0, 0]; --just starting
body.destination.host ← allHosts; body.source.host ← myProcID;
body.destination.net ← body.source.net ← context.netNumber;
IF ~checkIt THEN body.checksum ← 177777B ELSE Checksums.SetChecksum[body];
Protocol1.EncapsulateAndTransmit[LOOPHOLE[b], @allHosts];
IF CommFlags.doStats THEN Stats.StatIncr[statBroadcastRequest];
END;
END; --SendBroadcastPacketToCorrectNet
--This procedure generates and sends an error packet.
SendErrorPacket: PUBLIC PROC[
offendingPkt: NSBuffer.Buffer,
errCode: NSTypes.ErrorCode, errParm: CARDINAL] =
BEGIN
body: NSBuffer.Body = offendingPkt.ns;
<<
"aborted" is a status that ULP should RETRY. Other status is probably
treated with a more fatal attitude. This means that we won't be able
to slow down SPP's retransmitter in cases when the local machine is
congested. Do we care?
>>
offendingPkt.fo.status ← SELECT errCode FROM
noSocket => invalidDestAddr,
unspecified => aborted,
badChecksum => aborted,
inconsistent => aborted,
congestionWarning => aborted,
congestionDiscard => aborted,
unspecifiedInRoute => aborted,
noError => aborted,
resourceLimits => rejected,
listenerReject => rejected,
tooBig => rejected,
connectionLimit => rejected,
invalidPacketType => protocolViolation,
protocolViolation => protocolViolation,
cantGetThere => noRouteToNetwork,
excessHops => noRouteToNetwork,
ENDCASE => aborted; --least fatal of the bunch
<<
offendingPkt.requeueProcedure may be == Buffer.ReturnBuffer or
it may really be a client requeue procedure; we'll never know.
>>
SELECT TRUE FROM
--don't send errors back to multicast
(HostNumbers.IsMulticastID[@body.destination.host]) =>
PutOnGlobalDoneQueue[offendingPkt];
--don't error errors
(body.packetType = error) => PutOnGlobalDoneQueue[offendingPkt];
--if there is a client requeue, then it's a local packet
(offendingPkt.requeueProcedure # LOOPHOLE[Buffer.ReturnBuffer]) =>
PutOnGlobalDoneQueue[offendingPkt];
--might be inhibited by clients
(debugging.allowedToSendErrorPackets) => --can we send errors now?
BEGIN
<<
This only adheres the the protocol externally. Internally, we just
send the errorType and errorParameter, none of the offending packet.
This gets us around the problem with rippling the error packet into
itself when the packet may be a short buffer.
>>
moved: CARDINAL ← IF body.source.host = myProcID THEN 0
ELSE ByteBlt.ByteBlt[
[LOOPHOLE[@body.errorBody], 0, NSTypes.maxIDPDataBytes - 4],
[LOOPHOLE[body], 0, body.pktLength], move];
Socket.SetPacketBytes[offendingPkt, moved + 4];
Socket.SwapSourceAndDestination[offendingPkt];
--IF not from socket, then from router. Any MLs here?
IF errCode ~IN[listenerReject..protocolViolation] THEN
body.source ← [
net: rto.findNetwork[body.destination.net],
host: myProcID, socket: NSConstants.errorSocket];
body.packetType ← error;
body.errorType ← errCode;
body.errorParameter ← errParm;
SendPacket[offendingPkt];
IF CommFlags.doStats THEN Stats.StatIncr[statNSErrorPacketsSent];
END;
ENDCASE => PutOnGlobalDoneQueue[offendingPkt]; --can't send errors now
END; --SendErrorPacket
<<
This procedure starts XNS. The Communication software is written
with the idea of eventually turning the code on and off during execution,
and so we should get the latest values of netID and hostID when being
turned back on.
>>
NSPackageMake: PUBLIC PROC[] RETURNS[Protocol1.Family] =
BEGIN
AlreadyStarted: ENTRY PROC[] RETURNS[BOOLEAN] = INLINE
{RETURN[(useCount ← useCount + 1) > 1]};
IF ~AlreadyStarted[] THEN
BEGIN
NSRouterOn[]; --XNS protocol support
CommunicationInternal.PacketStreamsGo[]; --miscellaneous
EchoServer.CreateServer[]; --default echo server
END;
RETURN[@nsProtocolFamily];
END;
NSPackageDestroy: PUBLIC PROC =
BEGIN
Users: ENTRY PROC RETURNS[BOOLEAN] = INLINE
{RETURN[(useCount = 0) OR ((useCount ← useCount - 1) > 0)]};
IF Users[] THEN RETURN; --either inactive or too many users
EchoServer.DeleteServer[]; --default echo server
NSRouterOff[]; --shut the router off
END;
NSRouterOn: PUBLIC PROC =
BEGIN
driver: Device;
matrix: Protocol1.MatrixRecord;
NSRouterActivate[];
rto.stop ← NIL; rto.addNetwork ← NIL; --don't call those just yet
Protocol1.RegisterFamily[@nsProtocolFamily]; --make us known
matrix ← [
family: @nsProtocolFamily, context: NIL,
encapsulator: EncapsulateEthernet,
decapsulator: DecapsulateEthernet];
FOR driver ← Driver.GetDeviceChain[], driver.next UNTIL driver = NIL DO
IF driver.device # ethernet THEN LOOP;
matrix.context ← CommHeap.zone.NEW[RoutingTable.ContextObject ← [
netNumber: System.nullNetworkNumber, network: driver, stats: NIL]];
Protocol1.AddFamilyMember[driver, @matrix];
Protocol1.SetMaximumBufferSize[
driver, @nsProtocolFamily, NSTypes.maxIDPBytesPerPacket];
ENDLOOP;
Register[]; --register default table impl
END; --NSRouterOn
NSRouterActivate: ENTRY PROC = INLINE
BEGIN
myProcID ← SpecialSystem.GetProcessorID[];
UNTIL LOOPHOLE[spareSocketID, CARDINAL] > LOOPHOLE[lastWKS, CARDINAL] DO
spareSocketID ← Inline.LowHalf[System.GetClockPulses[]];
ENDLOOP;
socketTable ← [length: 0, first: NIL];
END; --NSRouterActivate
NSRouterOff: PUBLIC PROC =
BEGIN
driver: Device;
context: RoutingTable.NetworkContext;
nsProtocolFamily.status ← dead; --just pretending
IF rto.stop # NIL THEN rto.stop[]; --convey plans to router
FOR driver ← Driver.GetDeviceChain[], driver.next UNTIL driver = NIL DO
IF driver.device # ethernet THEN LOOP; --questionable logic
context ← Protocol1.GetContext[driver, ns]; --find the context
Protocol1.RemoveFamilyMember[driver, @nsProtocolFamily];
CommHeap.zone.FREE[@context]; --free the dude up
ENDLOOP;
Protocol1.EvictFamily[ns]; --goin' south for the winter
END; --NSRouterOff
--Send error packets in normal manner
EnableNSErrorPackets: PUBLIC <<RouterInternal>> PROC =
{debugging.allowedToSendErrorPackets ← TRUE};
--Suppress all error packets (probably used in debugging)
DisableNSErrorPackets:PUBLIC <<RouterInternal>> PROC =
{debugging.allowedToSendErrorPackets ← FALSE};
--mainline code
END. --RouterImpl module.
LOG
17-May-84 10:05:24 AOF Post Klamath
5-Mar-85 17:57:17 AOF Treat broadcast as special case of multicast
4-Apr-85 14:22:34 AOF Monitor deadlock trying to stop XNS
25-Apr-85 9:53:57 AOF Remove LOOPHOLEs around .TransferStatus
3-May-85 11:01:31 AOF Error packet to use overLap ← move
23-Aug-85 8:12:35 AOF Delete context object when shutting off router
5-Nov-85 9:15:30 AOF Removing more LOOPHOLEs
10-Dec-85 14:21:30 AOF Restructuring listeners
18-Dec-85 12:17:18 AOF Opening/closing driver's input queues
18-Apr-86 10:15:26 AOF Condition PacketHit to with CommFlags.doStorms
22-May-86 18:07:33 SMA No more Buffer dependencies and added NetworkNonExistent.
6-Jun-86 17:42:15 AOF More twiddles in encal/decap routines
17-Jun-86 19:39:41 AOF Setting MaximumBufferSize for the family
1-Aug-86 15:21:46 MI Changed degree of driver.length from word to byte.
18-Aug-86 16:05:39 AOF Changed some (most) NSBuffer.Buffer => LP TO .BufferBody
15-Oct-86 15:57:36 AOF Better translation of error codes in SendErrorPacket
26-Oct-86 18:52:23 AOF Tracking maxBufferSize when getting copies
16-Nov-86 15:54:10 AOF More Error packet stuff
21-Dec-86 13:25:54 AOF (Re)EXPORT the variable "checkIt"
17-Apr-87 18:36:58 AOF Fix BroadcastToAllNets
5-May-87 13:19:06 AOF Move default control setting in SendPacket
18-Jul-87 10:52:37 AOF Moving from EtherMAC to IEEE8023
28-Jul-87 9:13:16 AOF AR#11506 (Can't * to null network - James L.)
30-Aug-87 10:50:45 AOF Better instrumentation
30-Aug-87 12:15:15 AOF Broadcast wasn't setting '* as destination
15-Oct-87 12:40:14 AOF RemoveSocket was increasing buffers too