DIRECTORY
Arpa USING [Address, NetNumber, nullAddress],
ArpaExtras USING [broadcastAddress, IsMyAddress, IsSpecificNet, NetAndSubnetNumber, NetAndSubnetNumberWithMask],
ArpaRIPBuf USING [arpaAddressFamily, Buffer, hdrBytes, maxBytes, maxTuples, NumTuplesFromUserBytes, UserBytesFromNumTuples],
ArpaRouter USING [Hops, maxHops, RoutingTableEntry, unreachable],
ArpaRouterPrivate USING [EventProc, EventReason, TakeThis],
ArpaTranslation USING [GetSubnetMask],
ArpaUDP USING [AllocBuffers, Create, dontWait, FreeBuffers, Get, GetSource, GetUserBytes, Handle, Port, Send, SetGetTimeout, SetUserBytes],
ArpaUDPBuf USING [Buffer],
Basics USING [Card32FromF, FFromCard32, HFromCard16, HWORD],
BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses, PulsesToMicroseconds],
Booting USING [RegisterProcs, RollbackProc],
CardTab USING [Create, EachPairAction, Fetch, Insert, Key, Pairs, Ref],
CommDriver USING [GetNetworkChain, InsertReceiveProc, Network]
;
ArpaRouterImpl:
CEDAR
MONITOR
LOCKS routingEntry USING routingEntry: RoutingEntry
IMPORTS Arpa, ArpaExtras, ArpaRIPBuf, ArpaRouterPrivate, ArpaTranslation, ArpaUDP, Basics, BasicTime, Booting, CardTab, CommDriver
EXPORTS ArpaRouter, ArpaRouterPrivate
~ {
HWORD: TYPE ~ Basics.HWORD;
Address: TYPE ~ Arpa.Address;
Port: TYPE ~ ArpaUDP.Port;
unknownAddress: Address ~ Arpa.nullAddress;
Network: TYPE ~ CommDriver.Network;
Hops: TYPE ~ ArpaRouter.Hops;
maxHops: Hops ~ ArpaRouter.maxHops;
unreachable: Hops ~ ArpaRouter.unreachable;
Parameters
ripVersion: BYTE ~ 1;
ripPort: Port ~ Basics.HFromCard16[520];
ripArpaAddressFamily: HWORD ~ ArpaRIPBuf.arpaAddressFamily;
Timeouts
Pulses: TYPE ~ BasicTime.Pulses;
Msecs: TYPE ~ CARD;
PulsesSince:
PROC [then: Pulses]
RETURNS[Pulses] ~
INLINE {
RETURN [BasicTime.GetClockPulses[] - then] };
sweepSeconds: CARDINAL ~ 15;
sweepPulses: Pulses ~ BasicTime.MicrosecondsToPulses[ LONG[sweepSeconds] * 1000000 ];
suspectSweeps: CARDINAL ← 6;
invalidSweeps: CARDINAL ← 12;
Routing Table
Routing Entries are kept in a hash table, keyed by the net you're trying to reach.
INVARIANT: routingEntry.hops = unreachable IFF routingEntry.network = NIL.
RoutingTable: TYPE ~ CardTab.Ref;
initialTableSize: NAT ← 311;
RoutingEntry: TYPE ~ REF RoutingEntryObject;
RoutingEntryObject:
TYPE ~
MONITORED
RECORD [
hops: Hops ← unreachable, -- hops to the desired net
immediate: Address ← unknownAddress, -- first router on route
network: Network ← NIL, -- directly connected net to first router
sweepsSinceRefresh: CARDINAL ← 0];
routingTable: RoutingTable ← CardTab.Create[initialTableSize];
Table of RoutingEntry, updated by ProcessResponse or Redirect.
defaultRoutingEntry: RoutingEntry ←
NEW[RoutingEntryObject];
Points at default gateway:
If some gateway is broadcasting a route to [0, 0, 0, 0] it points there with hops = 1.
If we're receiving any RIP packets, it points at some active gateway with hops = maxhops.
KeyFromAddress:
PROC [address: Address]
RETURNS [CardTab.Key] ~
INLINE {
RETURN [ LOOPHOLE[address] ] };
AddressFromKey:
PROC [key: CardTab.Key]
RETURNS [Address] ~
INLINE {
RETURN [ LOOPHOLE[key] ] };
LookupEntry:
PROC [address: Address]
RETURNS [val:
REF] ~
INLINE {
Lookup entry. Return NIL is no entry exists.
[val~val] ← CardTab.Fetch[routingTable, KeyFromAddress[address]] };
GetEntry:
PROC [address: Address]
RETURNS [val:
REF] ~ {
Lookup entry, or create a new one if none exists. Never return NIL.
key: CardTab.Key ~ KeyFromAddress[address];
new: REF ← NIL;
DO
[val~val] ← CardTab.Fetch[routingTable, key];
IF val # NIL THEN RETURN;
IF new = NIL THEN new ← NEW[RoutingEntryObject];
val ← new;
IF CardTab.Insert[routingTable, key, val] THEN RETURN;
ENDLOOP;
};
Routing Daemon
responsesSent: INT ← 0;
ProcessRequest:
PROC [b: ArpaRIPBuf.Buffer, userBytes:
CARDINAL] ~ {
Reply to b^ (which must be a routing information request packet of length userBytes). For a simple router (not gateway) this is done only if the b^ was directed at this host (rather than broadcast).
sendBuf: ArpaUDPBuf.Buffer ← NIL; -- the send buffer.
iSend: CARDINAL; -- index into sb.body.tuples
nReq: CARDINAL ← 0; -- number of request tuples in b, or 0 if b = NIL
Send:
PROC ~ {
Send sendBuf^ to destination computed from b.
srb: ArpaRIPBuf.Buffer;
TRUSTED { srb ← LOOPHOLE[sendBuf] };
srb.hdr3 ← [command~response, version~ripVersion, filler~[0, 0]];
ArpaUDP.SetUserBytes[sendBuf, ArpaRIPBuf.UserBytesFromNumTuples[iSend]];
{ destAddress: Address; destPort: Port;
TRUSTED { [destAddress, destPort] ← ArpaUDP.GetSource[LOOPHOLE[b]] };
ArpaUDP.Send[b~sendBuf, address~destAddress, port~destPort];
};
ArpaUDP.FreeBuffers[sendBuf]; sendBuf ← NIL;
};
AddToSendBuf: CardTab.EachPairAction
-- [key, val] RETURNS [quit]-- ~ {
Add info from routingEntry to response packet in sendBuf^, sending the old response packet and allocating a new one if necessary.
IF (sendBuf #
NIL)
AND (iSend >= ArpaRIPBuf.maxTuples)
THEN {
Send[]; sendBuf ← NIL };
IF sendBuf =
NIL THEN {
sendBuf ← ArpaUDP.AllocBuffers[h~routingSocketHandle];
iSend ← 0 };
AddToSendBufInner[routingEntry~NARROW[val], net~AddressFromKey[key]];
iSend ← iSend + 1;
RETURN [FALSE] };
AddToSendBufInner:
ENTRY
PROC [routingEntry: RoutingEntry, net: Address] ~
--INLINE-- {
TRUSTED {
srb: ArpaRIPBuf.Buffer ~ LOOPHOLE[sendBuf];
srb.body.tuples[iSend] ← [addressFamily~ripArpaAddressFamily, filler1~[0,0], address~net, filler2~[[0,0], [0,0]], filler3~[[0,0], [0,0]], delay~Basics.FFromCard32[routingEntry.hops]];
};
};
IF b = NIL THEN ERROR;
IF NOT ArpaExtras.IsMyAddress[b.hdr1.dest] THEN RETURN; -- Simple router responds only to requests directed at it.
nReq ← ArpaRIPBuf.NumTuplesFromUserBytes[userBytes];
IF (nReq = 1)
AND (b.body.tuples[0].addressFamily = [0, 0])
AND (Basics.Card32FromF[b.body.tuples[0].delay] > ArpaRouter.maxHops)
THEN {
Dump the entire routing table.
[] ← CardTab.Pairs[routingTable, AddToSendBuf];
}
ELSE {
Reply with routing table entries for requested nets only. Responses are in the same order as the requests ...
FOR iReq:
CARDINAL
IN [0 .. nReq)
DO
adr: Address;
routingEntry: RoutingEntry;
IF b.body.tuples[iReq].addressFamily # ripArpaAddressFamily THEN LOOP;
adr ← b.body.tuples[iReq].address;
routingEntry ← NARROW[ LookupEntry[adr] ];
IF routingEntry # NIL THEN
[] ← AddToSendBuf[KeyFromAddress[adr], routingEntry];
ENDLOOP;
};
IF sendBuf # NIL THEN { Send[]; sendBuf ← NIL };
responsesSent ← responsesSent.SUCC;
};
totalTuples: CARD ← 0;
bogusTuples: CARD ← 0;
ProcessResponse:
PROC [b: ArpaRIPBuf.Buffer, userBytes:
CARDINAL] ~ {
Update the routing table using information from b^ (which must be a routing information response packet of length userBytes).
network: Network ← NARROW[b.ovh.network];
immediate: Address ← b.hdr1.source;
tupleAddress: Address;
hops: CARD;
UpdateEntryFromTuple:
ENTRY
PROC [routingEntry: RoutingEntry] ~ {
IF (routingEntry.network = network)
AND (routingEntry.immediate = immediate)
THEN {
Update is from owner of entry, we will accept it.
IF Monitoring[] AND (routingEntry.hops # hops) THEN PostChange[tupleAddress, routingEntry, hops, immediate];
}
ELSE {
Update is not from owner of entry, we may want to reject it.
Update less desirable than the current entry is rejected.
IF routingEntry.sweepsSinceRefresh < suspectSweeps
THEN {
IF routingEntry.hops < hops THEN RETURN;
IF (routingEntry.hops = hops) AND (routingEntry.network # NIL) AND (routingEntry.network.speed >= network.speed) THEN RETURN;
};
Update making an (unowned) entry unreachable is rejected if it comes from a gateway on a different net from the old route. That's just a heuristic.
IF (hops >= unreachable) AND (network # routingEntry.network) THEN RETURN;
IF Monitoring[] THEN PostChange[tupleAddress, routingEntry, hops, immediate];
routingEntry.network ← network;
routingEntry.immediate ← immediate;
};
routingEntry.hops ← hops;
routingEntry.sweepsSinceRefresh ← 0;
IF hops >= unreachable
THEN {
routingEntry.network ← NIL;
routingEntry.immediate ← unknownAddress };
};
UpdateDefaultEntryFromTuple:
ENTRY
PROC [routingEntry: RoutingEntry] ~ {
IF (routingEntry.immediate # immediate) AND (routingEntry.hops < maxHops) AND (routingEntry.sweepsSinceRefresh < suspectSweeps) THEN RETURN;
IF Monitoring[] THEN PostChange[unknownAddress, routingEntry, 1, immediate];
routingEntry.network ← network;
routingEntry.immediate ← immediate;
routingEntry.hops ← 1; -- Kludge!
routingEntry.sweepsSinceRefresh ← 0;
};
UpdateDefaultEntryNotFromTuple:
ENTRY
PROC [routingEntry: RoutingEntry] ~ {
IF routingEntry.hops < unreachable THEN RETURN;
IF Monitoring[] THEN PostChange[unknownAddress, routingEntry, maxHops, immediate];
routingEntry.network ← network;
routingEntry.immediate ← immediate;
routingEntry.hops ← maxHops; -- Kludge!
routingEntry.sweepsSinceRefresh ← 0;
};
FOR i:
CARDINAL
IN [0 .. ArpaRIPBuf.NumTuplesFromUserBytes[userBytes])
DO
totalTuples ← totalTuples.SUCC;
tupleAddress ← b.body.tuples[i].address;
hops ← Basics.Card32FromF[b.body.tuples[i].delay];
SELECT
TRUE
FROM
ArpaExtras.IsSpecificNet[tupleAddress] => UpdateEntryFromTuple[NARROW[GetEntry[tupleAddress]]];
tupleAddress = unknownAddress => UpdateDefaultEntryFromTuple[defaultRoutingEntry];
ENDCASE => bogusTuples ← bogusTuples.SUCC;
ENDLOOP;
UpdateDefaultEntryNotFromTuple[defaultRoutingEntry];
};
bogusRedirects: CARD ← 0;
Redirect:
PUBLIC
PROC [dest: Address, network: Network, immediate: Address] ~ {
RedirectInner:
ENTRY
PROC [routingEntry: RoutingEntry] ~ {
hops: Hops ~ IF routingEntry.hops <= maxHops THEN routingEntry.hops ELSE maxHops;
IF Monitoring[] THEN PostChange[dest, routingEntry, hops, immediate, icmp];
routingEntry.network ← network;
routingEntry.immediate ← immediate;
routingEntry.hops ← hops;
routingEntry.sweepsSinceRefresh ← 0;
};
IF NOT ArpaExtras.IsSpecificNet[dest]
THEN { bogusRedirects ← bogusRedirects.SUCC; RETURN };
RedirectInner[NARROW[GetEntry[dest]]];
};
ConnectNets:
PROC [] ~ {
Ensure that there's a valid 0-hops routing entry for each connected network / subnet for which the network number has already been filled in.
network: Network;
ConnectNetInner:
ENTRY PROC [routingEntry: RoutingEntry] ~
--INLINE-- {
routingEntry.hops ← 0;
routingEntry.network ← network;
routingEntry.sweepsSinceRefresh ← 0;
};
FOR network ← CommDriver.GetNetworkChain[], network.next
WHILE network #
NIL
DO
netAddress: Address;
IF network.arpa.host = unknownAddress THEN LOOP;
netAddress ← ArpaExtras.NetAndSubnetNumberWithMask[network.arpa.host, ArpaTranslation.GetSubnetMask[network]];
ConnectNetInner[NARROW[GetEntry[netAddress]]];
ENDLOOP;
};
Sweep:
PROC ~ {
Throw away (i.e. mark as unreachable) any routing entries that are too old to be credible.
CheckEntry: CardTab.EachPairAction
-- [key, val] RETURNS [quit]-- ~ {
CheckEntryInner[NARROW[val]];
RETURN [FALSE] };
CheckEntryInner:
ENTRY
PROC [routingEntry: RoutingEntry] ~ {
IF routingEntry.sweepsSinceRefresh >= invalidSweeps
THEN routingEntry.hops ← unreachable;
routingEntry.sweepsSinceRefresh ← routingEntry.sweepsSinceRefresh.SUCC;
};
[] ← CardTab.Pairs[routingTable, CheckEntry];
CheckEntryInner[defaultRoutingEntry];
};
packetsReceived: INT ← 0;
requestsReceived: INT ← 0;
responsesReceived: INT ← 0;
badLength: INT ← 0;
badProtocol: INT ← 0;
badCommand: INT ← 0;
sweeps: CARD ← 0;
minSweepPulses: Pulses ← sweepPulses;
lastSweep: Pulses ← BasicTime.GetClockPulses[];
Daemon:
PROC ~ {
DO
sinceSweep: Pulses ~ PulsesSince[lastSweep];
b: ArpaUDPBuf.Buffer;
getTimeout: Msecs ~
IF sinceSweep >= sweepPulses
THEN ArpaUDP.dontWait
ELSE (1 + (BasicTime.PulsesToMicroseconds[sweepPulses - sinceSweep] / 1000));
ArpaUDP.SetGetTimeout[routingSocketHandle, getTimeout];
IF (b ← ArpaUDP.Get[routingSocketHandle]) #
NIL
THEN
BEGIN
Receive and process a routing info packet.
userBytes: CARDINAL ~ ArpaUDP.GetUserBytes[b];
rB: ArpaRIPBuf.Buffer;
packetsReceived ← packetsReceived.SUCC;
IF (userBytes < ArpaRIPBuf.hdrBytes)
OR (userBytes > ArpaRIPBuf.maxBytes)
THEN {
badLength ← badLength.SUCC;
GOTO Next };
TRUSTED { rB ← LOOPHOLE[b] };
IF rB.hdr3.version = 0
THEN {
badProtocol ← badProtocol.SUCC;
GOTO Next };
SELECT rB.hdr3.command
FROM
request => {
requestsReceived ← requestsReceived.SUCC;
ProcessRequest[rB, userBytes] };
response => {
responsesReceived ← responsesReceived.SUCC;
ProcessResponse[rB, userBytes] };
ENDCASE => {
badCommand ← badCommand.SUCC;
};
GOTO Next;
EXITS
Next => IF b # NIL THEN ArpaUDP.FreeBuffers[b];
END
ELSE BEGIN
Sweep the table.
minSweepPulses ← MIN[minSweepPulses, PulsesSince[lastSweep]];
ConnectNets[];
Sweep[];
sweeps ← sweeps.SUCC;
lastSweep ← BasicTime.GetClockPulses[];
END;
ENDLOOP;
};
Requesting Tables From the Gateways
PokeGateways:
PROC ~ {
b: ArpaUDPBuf.Buffer;
rB: ArpaRIPBuf.Buffer;
b ← ArpaUDP.AllocBuffers[h~routingSocketHandle];
TRUSTED { rB ← LOOPHOLE[b] };
rB.hdr3 ← [command~request, version~ripVersion, filler~[0, 0]];
rB.body.tuples[0] ← [addressFamily~[0, 0], filler1~[0,0], address~unknownAddress, filler2~[[0,0], [0,0]], filler3~[[0,0], [0,0]], delay~Basics.FFromCard32[ArpaRouter.unreachable]];
ArpaUDP.SetUserBytes [b~b, bytes~ArpaRIPBuf.UserBytesFromNumTuples[1]];
ArpaUDP.Send[b~b, address~ArpaExtras.broadcastAddress, port~ripPort];
ArpaUDP.FreeBuffers[b];
};
Routing a Packet
Route:
PUBLIC
PROC [him: Arpa.Address]
RETURNS [network: Network, immediate: Address] ~ {
entry: REF;
RouteInner:
ENTRY
PROC [routingEntry: RoutingEntry] ~
--INLINE-- {
network ← routingEntry.network;
immediate ← IF routingEntry.hops = 0 THEN him ELSE routingEntry.immediate;
};
IF (entry ← LookupEntry[him]) # NIL
THEN { RouteInner[NARROW[entry]]; RETURN };
IF (entry ← LookupEntry[ArpaExtras.NetAndSubnetNumber[him]]) # NIL
THEN { RouteInner[NARROW[entry]]; RETURN };
IF (entry ← LookupEntry[Arpa.NetNumber[him]]) # NIL
THEN { RouteInner[NARROW[entry]]; RETURN };
RouteInner[defaultRoutingEntry];
};
Rollback
Rollback: Booting.RollbackProc = {
SmashEntry: CardTab.EachPairAction
-- [key, val] RETURNS [quit]-- ~ {
SmashEntryInner[NARROW[val]];
RETURN [FALSE] };
SmashEntryInner:
ENTRY
PROC [routingEntry: RoutingEntry] ~
--INLINE-- {
routingEntry.sweepsSinceRefresh ← invalidSweeps.PRED;
};
defaultRoutingEntry ← NEW[RoutingEntryObject];
[] ← CardTab.Pairs[routingTable, SmashEntry];
PokeGateways[];
};
Event Monitoring (Exported to ArpaRouterPrivate)
eventLock: RoutingEntry ← NEW[RoutingEntryObject];
EventProc: TYPE ~ ArpaRouterPrivate.EventProc;
EventProcList: TYPE ~ REF EventProcObject;
EventProcObject:
TYPE ~
RECORD [
next: EventProcList ← NIL,
proc: EventProc];
eventProcList: EventProcList ← NIL;
SetEventProc:
PUBLIC
PROC [proc: EventProc] ~ {
SetEventProcInner:
ENTRY
PROC [routingEntry: RoutingEntry] ~
--INLINE-- {
eventProcList ← NEW[ EventProcObject ← [next~eventProcList, proc~proc] ] };
SetEventProcInner[eventLock] };
ClearEventProc:
PUBLIC
PROC [proc: EventProc] ~ {
p, prev: EventProcList;
ClearEventProcInner:
ENTRY
PROC [routingEntry: RoutingEntry] ~
--INLINE-- {
p ← eventProcList; prev ← NIL;
WHILE (p # NIL) AND (p.proc # proc) DO prev ← p; p ← p.next ENDLOOP;
IF p #
NIL
THEN {
IF prev = NIL THEN eventProcList ← p.next ELSE prev.next ← p.next };
};
ClearEventProcInner[eventLock] };
Monitoring: PROC RETURNS [BOOL] ~ INLINE { RETURN [eventProcList # NIL] };
PostChange:
PROC [key: Address, old: RoutingEntry, newHops: Hops, newImmediate: Address, why: ArpaRouterPrivate.EventReason ← rip] ~ {
PostChangeInner:
ENTRY
PROC [routingEntry: RoutingEntry ← eventLock] ~ {
FOR pH: EventProcList ← eventProcList, pH.next
WHILE pH #
NIL
DO
(pH.proc)[
key,
[ hops~old.hops,
immediate~old.immediate,
time~(old.sweepsSinceRefresh*sweepSeconds)],
[ hops~newHops,
immediate~newImmediate,
time~0],
why
];
ENDLOOP;
};
PostChangeInner[];
};
Client Interface (Exported to ArpaRouter)
Fast:
PUBLIC
PROC [address: Address]
RETURNS [yes:
BOOL] ~ {
routingEntry: RoutingEntry ~ NARROW[LookupEntry[address]];
IF (routingEntry = NIL) OR (routingEntry.network = NIL) THEN RETURN[FALSE];
SELECT routingEntry.network.type
FROM
ethernet => RETURN[TRUE];
ethernetOne => RETURN[TRUE];
ENDCASE => RETURN[FALSE];
};
GetHops:
PUBLIC PROC [address: Address]
RETURNS [hops: Hops] ~ {
entry: REF;
GetHopsInner: ENTRY PROC [routingEntry: RoutingEntry]
RETURNS [Hops] ~ { RETURN [routingEntry.hops] };
IF (entry ← LookupEntry[address]) # NIL
THEN RETURN[GetHopsInner[NARROW[entry]]];
IF (entry ← LookupEntry[ArpaExtras.NetAndSubnetNumber[address]]) # NIL
THEN RETURN[GetHopsInner[NARROW[entry]]];
IF (entry ← LookupEntry[Arpa.NetNumber[address]]) # NIL
THEN RETURN[GetHopsInner[NARROW[entry]]];
RETURN [unreachable]; -- don't use default route to determine hops
};
RTEFromRE:
PROC [routingEntry: RoutingEntry]
RETURNS [ArpaRouter.RoutingTableEntry] ~
INLINE {
RETURN [[hops~routingEntry.hops,
immediate~routingEntry.immediate,
time~(routingEntry.sweepsSinceRefresh*sweepSeconds)]];
};
GetRouting:
PUBLIC
PROC [address: Address]
RETURNS [rte: ArpaRouter.RoutingTableEntry] ~ {
entry: REF;
GetRoutingInner:
ENTRY
PROC [routingEntry: RoutingEntry]
RETURNS [ArpaRouter.RoutingTableEntry] ~ {
RETURN [RTEFromRE[routingEntry]];
};
IF (entry ← LookupEntry[address]) # NIL
THEN RETURN [GetRoutingInner[NARROW[entry]]];
IF (entry ← LookupEntry[ArpaExtras.NetAndSubnetNumber[address]]) # NIL
THEN RETURN [GetRoutingInner[NARROW[entry]]];
IF (entry ← LookupEntry[Arpa.NetNumber[address]]) # NIL
THEN RETURN [GetRoutingInner[NARROW[entry]]];
RETURN [GetRoutingInner[defaultRoutingEntry]];
};
Enumerate:
PUBLIC PROC [
low: Hops ← 0, high: Hops ← unreachable,
proc: PROC [Address, ArpaRouter.RoutingTableEntry]] ~ {
EachPair: CardTab.EachPairAction
-- [key, val] RETURNS [quit] -- ~ {
rte: ArpaRouter.RoutingTableEntry;
EachPairInner:
ENTRY
PROC [routingEntry: RoutingEntry]
RETURNS [inRange:
BOOL] ~
--INLINE-- {
IF (inRange ← ((routingEntry.hops >= low)
AND (routingEntry.hops <= high)))
THEN {
rte ← RTEFromRE[routingEntry];
};
};
IF EachPairInner[NARROW[val]].inRange THEN proc[ AddressFromKey[key], rte ];
};
[] ← CardTab.Pairs[routingTable, EachPair];
};
Initialization
daemonProcess: PROCESS;
routingSocketHandle: ArpaUDP.Handle;
Init:
PROC ~ {
Install recv proc for each network on chain.
CommDriver.InsertReceiveProc[network~NIL, type~arpa, proc~ArpaRouterPrivate.TakeThis];
Register rollback proc.
Booting.RegisterProcs[r: Rollback];
Start routing daemon.
routingSocketHandle ← ArpaUDP.Create[localPort~ripPort];
daemonProcess ← FORK Daemon[];
ConnectNets[];
PokeGateways[];
};
Init[];
}.