-- 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 [ <>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 <> TYPE = Driver.Device; NetworkNumber: PUBLIC <> TYPE = SpecialSystem.NetworkNumber; HostNumber: PUBLIC <> TYPE = SpecialSystem.HostNumber; ProcessorID: PUBLIC TYPE = SpecialSystem.ProcessorID; SocketNumber: PUBLIC <> 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 <> MONITORLOCK; socketTable: PUBLIC <> 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 <> BOOLEAN ¬ TRUE; routersFunction: PUBLIC <> Router.RoutersFunction; startEnumeration: PUBLIC <> NetworkNumber; --set to start enumeration endEnumeration: PUBLIC <> 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 <> PROC[new: BOOLEAN] = {debugging.stormy ¬ new}; SetNSDriverLoopback: PUBLIC <> PROC[new: BOOLEAN] = {debugging.driverLoopback ¬ new}; --UTILITY ROUTINES SetNSCheckit: PUBLIC <> 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 <> 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 <> PROC = {debugging.allowedToSendErrorPackets ¬ TRUE}; --Suppress all error packets (probably used in debugging) DisableNSErrorPackets:PUBLIC <> 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