-- File: NameResImpl.mesa - last edit: -- JAV 26-Aug-87 11:43:20 -- AOF 1-Apr-87 12:39:11 -- Copyright (C) 1987 by Xerox Corporation. All rights reserved. -- This software was produced by the University of Southern California (USC) -- Information Sciences Institute (ISI). -- USC-ISI does not assume any responsibility for the correctness, -- performance, or use of this sotfware. -- Distribution of this software is limited by agreement between USC-ISI -- and the XEROX Corporation. DIRECTORY ArpaAddressTranslation USING [ AddressTableEntry, AddressTableLength, AppendINetAddress, AppendINetAddressAndGrow, AppendProtocol, DebugProc, defaultTimeout, HostTableEntry, HostTableLength, INetAddr, INetAddress, MailboxTableEntry, mailboxTableLength, NamesPerEntry, nullAddressDesc, nullHostDesc, nullINetAddress, nullMailboxDesc, nullServerDesc, PortType, QClass, QType, QueryHeader, ResponseType, ServerTableEntry, serverTableLength, ServerType], ArpaBuffer USING [Body, Buffer, ReturnBuffer], ArpaPort USING [ AssignPort, Create, Delete, GetPacket, GetSendBuffer, Handle, PutPacket, SetIPLengths, SetUDPLength, SetWaitTime, Timeout, UDPHeaderBytes], ArpaPortInternal USING [ AddrMatch, BuildMasks, GetArpaAddr, GetDomainNameServer], ArpaRouter USING [InternetAddress, Port, unknownInternetAddress], Environment USING [Byte], Format USING [Number, StringProc], Heap USING [systemZone], Inline USING [BITSHIFT, DBITSHIFT, HighByte, LowByte], Process USING [Detach], String USING [ AppendChar, AppendDecimal, AppendString, AppendStringAndGrow, CopyToNewString, Empty, Equivalent, EquivalentSubString, FreeString, MakeString, Replace, SubStringDescriptor], System USING [ AdjustGreenwichMeanTime, GetGreenwichMeanTime, GreenwichMeanTime, SecondsSinceEpoch]; NameResImpl: MONITOR IMPORTS ArpaAddressTranslation, ArpaBuffer, ArpaPort, ArpaPortInternal, ArpaRouter, Format, Heap, Inline, Process, String, System EXPORTS ArpaAddressTranslation = BEGIN OPEN ArpaAddressTranslation; MaxQuerySize: CARDINAL = 512; maxTries: CARDINAL = 4; maxReferrals: CARDINAL = 4; defaultNameServer: INetAddr ¬ nullINetAddress; myINetAddress: ArpaRouter.InternetAddress ¬ LOOPHOLE[nullINetAddress]; --cache tables hostTable: PUBLIC ARRAY [0..HostTableLength) OF HostTableEntry ¬ ALL[nullHostDesc]; serverTable: PUBLIC ARRAY [0..serverTableLength) OF ServerTableEntry ¬ ALL[nullServerDesc]; addressTable: PUBLIC ARRAY [0..AddressTableLength) OF AddressTableEntry ¬ ALL[nullAddressDesc]; mailboxTable: PUBLIC ARRAY [0..mailboxTableLength) OF MailboxTableEntry ¬ ALL[nullMailboxDesc]; NoAnswer: PUBLIC SIGNAL = CODE; ErrorInReply: PUBLIC SIGNAL = CODE; requestCount: CARDINAL ¬ 0; -- used for making up unique id UDPBuffer: TYPE = LONG POINTER TO UDPBufferObject; UDPBufferObject: TYPE = MACHINE DEPENDENT RECORD [ source(0:0..15): PortType ¬ null, dest(1:0..15): PortType ¬ null, length(2:0..15): [0..177777B] ¬ 0, checksum(3:0..15): [0..177777B] ¬ 0, data(4): PACKED ARRAY [0..0) OF Environment.Byte ¬ NULL ]; --****************************************************************************-- GetWord: PROCEDURE [data: LONG POINTER TO PACKED ARRAY [0..0) OF Environment.Byte, index: LONG POINTER TO CARDINAL] RETURNS [i: CARDINAL] = BEGIN i ¬ Inline.BITSHIFT[data[index­],8] + data[index­+1]; index­ ¬ index­ + 2; END; -- GetWord GetLong: PROCEDURE [data: LONG POINTER TO PACKED ARRAY [0..0) OF Environment.Byte, index: LONG POINTER TO CARDINAL] RETURNS [i: LONG UNSPECIFIED] = BEGIN i ¬ Inline.DBITSHIFT[data[index­], 24] + Inline.DBITSHIFT[data[index­+1],16] + Inline.DBITSHIFT[data[index­+2],8] + data[index­+3]; index­ ¬ index­ + 4; END; -- GetLong BuildRequest: PROCEDURE [domain: LONG STRING, type: QType, dest: INetAddr, destPort: PortType, b: ArpaBuffer.Buffer] = BEGIN -- UDP Data bufferBody: ArpaBuffer.Body = b.arpa; { OPEN ip: bufferBody.ipHeader, udp: bufferBody.user; hPtr: LONG POINTER TO QueryHeader ¬ LOOPHOLE[@udp.bytes]; index, countIndex, count: CARDINAL ¬ 0; udp.destinationPort ¬ LOOPHOLE[destPort]; ip.destination ¬ LOOPHOLE[dest]; ip.protocol ¬ userDatagram; ip.service ¬ LOOPHOLE[0]; ip.identification ¬ 0; ip.lifetime ¬ 15B; -- make up request hPtr­ ¬ [requestCount, query, query, FALSE, FALSE, FALSE, FALSE, 0, okay, 1, 0, 0, 0]; index ¬ 2 * SIZE[QueryHeader]; countIndex ¬ index; -- fill in count later FOR i:CARDINAL IN [0..domain.length) DO IF domain.text[i] = '. THEN { udp.bytes[countIndex] ¬ count; countIndex ¬ index ¬ index + 1; count ¬ 0} ELSE { index ¬ index + 1; udp.bytes[index] ¬ LOOPHOLE[domain.text[i]]; count ¬ count + 1}; ENDLOOP; IF count # 0 THEN { udp.bytes[countIndex] ¬ count; index ¬ index + 1; udp.bytes[index] ¬ 0; index ¬ index + 1} ELSE IF udp.bytes[index-1] # 0 THEN index ¬ index + 1; udp.bytes[index] ¬ Inline.HighByte[type]; udp.bytes[index+1] ¬ Inline.LowByte[type]; udp.bytes[index+2] ¬ Inline.HighByte[QClass.internet]; udp.bytes[index+3] ¬ Inline.LowByte[QClass.internet]; index ¬ index + 4; ArpaPort.SetUDPLength[bufferBody, index]; --Sets the udp length. ArpaPort.SetIPLengths[bufferBody, 0, index + ArpaPort.UDPHeaderBytes]; }; END; -- BuildRequest RemoteRequest: PROCEDURE [domain: LONG STRING, type: QType, server: ServerType, udpH: ArpaPort.Handle, debugProc: DebugProc ¬ NIL] RETURNS [b: ArpaBuffer.Buffer ¬ NIL] = BEGIN IF debugProc # NIL THEN BEGIN tempString: LONG STRING ¬ [80]; String.AppendString[tempString, " Sending "L]; String.AppendString[tempString, SELECT type FROM address => "ADDRESS"L, mb => "MAILBOX"L, ns => "NAME SERVER"L, ptr => "POINTER"L, wks => "WELL KNOWN SERVICE"L, ENDCASE => "UNKNOWN"L]; String.AppendString[tempString, " query to "L]; AppendINetAddress[tempString, server.host]; String.AppendString[tempString, ", port = "L]; String.AppendDecimal[tempString, LOOPHOLE[server.port]]; debugProc[tempString]; END; -- send request FOR i: CARDINAL IN [0..maxTries) WHILE b = NIL DO sendBuf: ArpaBuffer.Buffer ¬ ArpaPort.GetSendBuffer[udpH]; BEGIN ENABLE UNWIND => IF sendBuf # NIL THEN ArpaBuffer.ReturnBuffer[sendBuf]; IF debugProc # NIL THEN debugProc["."L]; BuildRequest[domain, type, server.host, server.port, sendBuf]; ArpaPort.PutPacket[udpH, sendBuf]; sendBuf ¬ NIL; b ¬ ArpaPort.GetPacket[udpH ! ArpaPort.Timeout => LOOP]; END; ENDLOOP; requestCount ¬ requestCount + 1; IF debugProc # NIL THEN debugProc["\n"L]; IF b = NIL THEN SIGNAL NoAnswer; END; --RemoteRequest --****************************************************************************-- UnpackName: PROCEDURE [p: UDPBuffer, i: CARDINAL, s: LONG POINTER TO LONG STRING] RETURNS [newIndex: CARDINAL] = BEGIN UNTIL p.data[i] = 0 OR p.data[i] >= 300B OR i >= MaxQuerySize DO FOR j: CARDINAL IN [i+1..i+p.data[i]] DO s^.text[s^.length] ¬ LOOPHOLE[p.data[j]]; s^.length ¬ s^.length + 1; ENDLOOP; i ¬ i + p.data[i] + 1; s^.text[s^.length] ¬ '. ; s^.length ¬ s^.length + 1; ENDLOOP; IF i < MaxQuerySize THEN IF p.data[i] >= 300B THEN BEGIN tempIndex: CARDINAL; tempIndex ¬ GetWord[@p.data, @i]; tempIndex ¬ tempIndex - 140000B; IF (s^.maxlength - s^.length) > p.data[tempIndex] THEN tempIndex ¬ UnpackName[p, tempIndex, s]; END ELSE BEGIN i ¬ i + 1; -- 1 byte for the 0 (end of name) IF s^.length =0 THEN { s^.text[s^.length] ¬ '.; s^.length ¬ s^.length + 1 }; END; RETURN[i]; END; -- UnpackName UpdateTime: PROCEDURE [timePtr: LONG POINTER TO System.GreenwichMeanTime, newTime: System.GreenwichMeanTime] = BEGIN IF timePtr^ = 0 OR System.SecondsSinceEpoch[timePtr^] < System.SecondsSinceEpoch[newTime] THEN timePtr^ ¬ newTime; END; -- UpdateTime AddStringEntry: PROCEDURE [arrayPtr: LONG POINTER TO ARRAY [0..NamesPerEntry) OF LONG STRING, countPtr: LONG POINTER TO CARDINAL, str: LONG STRING] = BEGIN IF countPtr^ < NamesPerEntry THEN BEGIN FOR i: CARDINAL IN [0..countPtr^) DO IF String.Equivalent[str, arrayPtr[i]] THEN EXIT; REPEAT FINISHED => BEGIN -- no matching entry arrayPtr[countPtr^] ¬ String.CopyToNewString[str, Heap.systemZone]; countPtr^ ¬ countPtr^ + 1; END; ENDLOOP; END; END; -- AddStringEntry AddAddressEntry: PROCEDURE [arrayPtr: LONG POINTER TO ARRAY [0..NamesPerEntry) OF INetAddr, countPtr: LONG POINTER TO CARDINAL, addr: INetAddr] = BEGIN IF countPtr^ < NamesPerEntry THEN BEGIN FOR i: CARDINAL IN [0..countPtr^) DO IF addr = arrayPtr[i] THEN EXIT; REPEAT FINISHED => BEGIN -- no matching entry arrayPtr[countPtr^] ¬ addr; countPtr^ ¬ countPtr^ + 1; END; ENDLOOP; END; END; -- AddAddressEntry ProcessReply: PROCEDURE [b: ArpaBuffer.Buffer, domain: LONG STRING, currentTime: System.GreenwichMeanTime, debugProc: DebugProc ¬ NIL] RETURNS [result: ResponseType] = BEGIN OPEN udp: b.arpa.user; pUDP: UDPBuffer ¬ LOOPHOLE[@udp]; hPtr: LONG POINTER TO QueryHeader ¬ LOOPHOLE[@udp.data]; -- check rcode and counts in header IF hPtr.qr = response AND hPtr.rcode = okay THEN BEGIN ENABLE UNWIND => ArpaBuffer.ReturnBuffer[b]; domainAddress: INetAddr; queryType: QType; out: LONG STRING ¬ [120]; index: CARDINAL ¬ 2 * SIZE[QueryHeader]; -- check the query in the response FOR i:CARDINAL IN [0..hPtr.queryCount) DO index ¬ UnpackName[pUDP, index, @out]; IF NOT String.Equivalent[domain, out] THEN SIGNAL ErrorInReply; queryType ¬ LOOPHOLE[GetWord[@pUDP.data, @index]]; IF queryType = ptr THEN BEGIN temp: INetAddr; out.length ¬ out.length -9; --cut off .IN-ADDR. temp ¬ INetAddress[out]; domainAddress.d ¬ temp.a; domainAddress.c ¬ temp.b; domainAddress.b ¬ temp.c; domainAddress.a ¬ temp.d; END; index ¬ index + 2; -- 2 bytes for the class ENDLOOP; FOR i:CARDINAL IN [0..(hPtr.answerCount+hPtr.nsCount+hPtr.arCount)) DO rdlen: CARDINAL ¬ 0; type: QType; ttl: System.GreenwichMeanTime ¬ [0]; time: LONG CARDINAL; out.length ¬ 0; index ¬ UnpackName[pUDP, index, @out]; type ¬ LOOPHOLE[GetWord[@pUDP.data, @index]]; index ¬ index + 2; -- 2 bytes for the class time ¬ GetLong[@pUDP.data, @index]; ttl ¬ System.AdjustGreenwichMeanTime[currentTime, MIN[17777777777B, time]]; rdlen ¬ GetWord[@pUDP.data, @index]; SELECT type FROM ns => -- reply to ns queryType, or referral BEGIN server: LONG POINTER TO ServerTableEntry ¬ InitServerEntry[out, currentTime]; out.length ¬ 0; index ¬ UnpackName[pUDP, index, @out]; IF debugProc # NIL AND queryType # ns THEN BEGIN debugProc[" Received referral to "L]; debugProc[out]; debugProc["\n"]; END; AddStringEntry[@server.host, @server.hostCount, out]; UpdateTime[@server.time, ttl]; END; cName => BEGIN fullName: LONG STRING ¬ [120]; index ¬ UnpackName[pUDP, index, @fullName]; SELECT queryType FROM address, wks => BEGIN host: LONG POINTER TO HostTableEntry ¬ InitHostEntry[@fullName, currentTime]; AddStringEntry[@host.name, @host.nameCount, out]; UpdateTime[@host.time, ttl]; END; mb => BEGIN mailbox: LONG POINTER TO MailboxTableEntry ¬ InitMailboxEntry[@fullName, currentTime]; AddStringEntry[@mailbox.name, @mailbox.nameCount, out]; UpdateTime[@mailbox.time, ttl]; END; ENDCASE; END; mb => BEGIN mailbox: LONG POINTER TO MailboxTableEntry ¬ InitMailboxEntry[@out, currentTime]; out.length ¬ 0; index ¬ UnpackName[pUDP, index, @out]; String.Replace[@mailbox.host, out, Heap.systemZone]; UpdateTime[@mailbox.time, ttl]; END; address => BEGIN addr: LONG POINTER TO AddressTableEntry; host: LONG POINTER TO HostTableEntry ¬ InitHostEntry[@out, currentTime]; address: INetAddr ¬ [pUDP.data[index], pUDP.data[index+1], pUDP.data[index+2], pUDP.data[index+3]]; index ¬ index + 4; -- 4 bytes for the internet address AddAddressEntry[@host.addr, @host.addrCount, address]; UpdateTime[@host.time, ttl]; addr ¬ InitAddressEntry[address, currentTime]; FOR i: CARDINAL IN [0..host.nameCount) DO AddStringEntry[@addr.name, @addr.nameCount, host.name[i]]; ENDLOOP; UpdateTime[@addr.time, ttl]; END; ptr => IF String.Equivalent[domain, out] THEN BEGIN addr: LONG POINTER TO AddressTableEntry ¬ InitAddressEntry[domainAddress, currentTime]; out.length ¬ 0; index ¬ UnpackName[pUDP, index, @out]; AddStringEntry[@addr.name, @addr.nameCount, out]; UpdateTime[@addr.time, ttl]; END; wks => BEGIN addr: LONG POINTER TO AddressTableEntry; host: LONG POINTER TO HostTableEntry ¬ InitHostEntry[@out, currentTime]; address: INetAddr ¬ [pUDP.data[index], pUDP.data[index+1], pUDP.data[index+2], pUDP.data[index+3]]; AddAddressEntry[@host.addr, @host.addrCount, address]; UpdateTime[@host.time, ttl]; addr ¬ InitAddressEntry[address, currentTime]; FOR i: CARDINAL IN [0..host.nameCount) DO AddStringEntry[@addr.name, @addr.nameCount, host.name[i]]; ENDLOOP; UpdateTime[@addr.time, ttl]; IF debugProc # NIL THEN BEGIN stringProc: Format.StringProc = {String.AppendString[out, s]}; out.length ¬ 0; String.AppendString[out, " For: "L]; AppendINetAddress[out, address]; String.AppendString[out, " ("L]; AppendProtocol[out, LOOPHOLE[pUDP.data[index+4]]]; String.AppendString[out, "), WKS bitmap: "L]; debugProc[out]; FOR i: CARDINAL IN [index+5..index+rdlen) DO IF ((i-index+5) MOD 10) = 0 THEN BEGIN debugProc["\n"]; debugProc[" "L]; -- 24 blanks END; out.length ¬ 0; Format.Number[stringProc, pUDP.data[i], [8,TRUE,TRUE,3]]; String.AppendString[out,"B "L]; debugProc[out]; ENDLOOP; debugProc["\n"]; END; index ¬ index + rdlen; END; ENDCASE => index ¬ index + rdlen; ENDLOOP; END; ArpaBuffer.ReturnBuffer[b]; RETURN[hPtr.rcode]; END; --ProcessReply --****************************************************************************-- ClearHostEntry: PROCEDURE [host: LONG POINTER TO HostTableEntry] = BEGIN IF host # NIL THEN BEGIN FOR n: CARDINAL IN [0..host.nameCount) DO IF host.name[n] # NIL THEN String.FreeString[Heap.systemZone, host.name[n]]; ENDLOOP; host^ ¬ nullHostDesc; END; END; -- ClearHostEntry ClearMailboxEntry: PROCEDURE [desc: LONG POINTER TO MailboxTableEntry] = BEGIN IF desc # NIL THEN BEGIN FOR n: CARDINAL IN [0..desc.nameCount) DO IF desc.name[n] # NIL THEN String.FreeString[Heap.systemZone, desc.name[n]]; ENDLOOP; String.FreeString[Heap.systemZone, desc.host]; desc^ ¬ nullMailboxDesc; END; END; -- ClearMailboxEntry ClearServerEntry: PROCEDURE [server: LONG POINTER TO ServerTableEntry] = BEGIN IF server # NIL THEN BEGIN String.FreeString[Heap.systemZone, server.domain]; FOR i: CARDINAL IN [0..server.hostCount) DO IF server.host[i] # NIL THEN String.FreeString[Heap.systemZone, server.host[i]]; ENDLOOP; server^ ¬ nullServerDesc; END; END; -- ClearServerEntry ClearAddressEntry: PROCEDURE [address: LONG POINTER TO AddressTableEntry] = BEGIN IF address # NIL THEN BEGIN FOR i: CARDINAL IN [0..address.nameCount) DO IF address.name[i] # NIL THEN String.FreeString[Heap.systemZone, address.name[i]]; ENDLOOP; address^ ¬ nullAddressDesc; END; END; -- ClearAddressEntry --****************************************************************************-- FindHostEntry: PROCEDURE [domain: LONG POINTER TO LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO HostTableEntry ¬ NIL] = BEGIN FOR i: CARDINAL IN [0..HostTableLength) DO FOR j: CARDINAL IN [0..hostTable[i].nameCount) DO IF String.Equivalent[domain^,hostTable[i].name[j]] THEN IF hostTable[i].time # 0 AND (System.SecondsSinceEpoch[hostTable[i].time] >= System.SecondsSinceEpoch[currentTime]) THEN RETURN[@hostTable[i]] ELSE EXIT; ENDLOOP; ENDLOOP; END; -- FindHostEntry FindMailboxEntry: PROCEDURE [domain: LONG POINTER TO LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO MailboxTableEntry ¬ NIL] = BEGIN FOR i: CARDINAL IN [0..mailboxTableLength) DO FOR j: CARDINAL IN [0..mailboxTable[i].nameCount) DO IF String.Equivalent[domain^,mailboxTable[i].name[j]] THEN IF mailboxTable[i].time # 0 AND (System.SecondsSinceEpoch[mailboxTable[i].time] >= System.SecondsSinceEpoch[currentTime]) THEN RETURN[@mailboxTable[i]] ELSE EXIT; ENDLOOP; ENDLOOP; END; -- FindMailboxEntry FindAddress: PROCEDURE [address: INetAddr, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO AddressTableEntry ¬ NIL] = BEGIN FOR i: CARDINAL IN [0..AddressTableLength) DO IF addressTable[i].addr = address AND addressTable[i].time # 0 AND System.SecondsSinceEpoch[addressTable[i].time] >= System.SecondsSinceEpoch[currentTime] THEN RETURN[@addressTable[i]]; ENDLOOP; END; -- FindAddress MatchingServer: PROCEDURE [domain: LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO ServerTableEntry ¬ NIL] = BEGIN FOR i: CARDINAL IN [0..serverTableLength) DO IF String.Equivalent[serverTable[i].domain, domain] AND serverTable[i].time # 0 AND System.SecondsSinceEpoch[serverTable[i].time] >= System.SecondsSinceEpoch[currentTime] THEN RETURN[@serverTable[i]]; ENDLOOP; END; -- MatchingServer BestServer: PROCEDURE [domain: LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO ServerTableEntry ¬ NIL] = -- find the server from the cache (serverTable) for the longest domain name -- that matches the domain in question (Domain) BEGIN bestIndex, strLength: CARDINAL ¬ 0; FOR i: CARDINAL IN [0..serverTableLength) DO IF serverTable[i].domain # NIL AND serverTable[i].domain.length > strLength AND NOT (serverTable[i].time = 0 OR System.SecondsSinceEpoch[serverTable[i].time] < System.SecondsSinceEpoch[currentTime]) THEN IF (serverTable[i].domain.length < domain.length AND domain.text[domain.length-serverTable[i].domain.length-1] = '.) OR serverTable[i].domain.length = domain.length THEN BEGIN domainSubStr: String.SubStringDescriptor ¬ [serverTable[i].domain, 0, serverTable[i].domain.length]; tail: String.SubStringDescriptor ¬ [domain, (domain.length - serverTable[i].domain.length), serverTable[i].domain.length]; IF String.EquivalentSubString[@tail, @domainSubStr] THEN BEGIN bestIndex ¬ i; strLength ¬ serverTable[i].domain.length; END; END; ENDLOOP; IF strLength # 0 THEN desc ¬ @serverTable[bestIndex]; END; -- BestServer --****************************************************************************-- InitMailboxEntry: PROCEDURE [domain: LONG POINTER TO LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO MailboxTableEntry ¬ NIL] = BEGIN IF (desc ¬ FindMailboxEntry[domain, currentTime]) # NIL THEN RETURN; FOR j: CARDINAL IN [0..mailboxTableLength) DO IF mailboxTable[j].time = 0 OR System.SecondsSinceEpoch[mailboxTable[j].time] < System.SecondsSinceEpoch[currentTime] THEN BEGIN -- found an empty entry desc ¬ @mailboxTable[j]; ClearMailboxEntry[desc]; EXIT; END; REPEAT FINISHED => BEGIN -- clear out the whole mailboxTable desc ¬ @mailboxTable[0]; FOR k: CARDINAL IN [0..mailboxTableLength) DO ClearMailboxEntry[@mailboxTable[k]]; ENDLOOP; END; ENDLOOP; -- fill in name desc.name[0] ¬ String.CopyToNewString[domain^, Heap.systemZone]; desc.nameCount ¬ 1; END; -- InitMailboxEntry InitHostEntry: PROCEDURE [domain: LONG POINTER TO LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO HostTableEntry ¬ NIL] = BEGIN IF (desc ¬ FindHostEntry[domain, currentTime]) # NIL THEN RETURN; FOR j: CARDINAL IN [0..HostTableLength) DO IF hostTable[j].time = 0 OR System.SecondsSinceEpoch[hostTable[j].time] < System.SecondsSinceEpoch[currentTime] THEN BEGIN -- found an empty entry desc ¬ @hostTable[j]; ClearHostEntry[desc]; EXIT; END; REPEAT FINISHED => BEGIN -- clear out the whole hostTable desc ¬ @hostTable[0]; FOR k: CARDINAL IN [0..HostTableLength) DO ClearHostEntry[@hostTable[k]]; ENDLOOP; END; ENDLOOP; -- fill in name desc.name[0] ¬ String.CopyToNewString[domain^, Heap.systemZone]; desc.nameCount ¬ 1; END; --InitHostEntry InitAddressEntry: PROCEDURE [address: INetAddr, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO AddressTableEntry ¬ NIL] = BEGIN -- find matching domain IF (desc ¬ FindAddress[address, currentTime]) # NIL THEN RETURN[desc]; -- find unused entry FOR i: CARDINAL IN [0..serverTableLength) DO IF addressTable[i].time = 0 OR System.SecondsSinceEpoch[addressTable[i].time] < System.SecondsSinceEpoch[currentTime] THEN BEGIN -- found an empty entry ClearAddressEntry[@addressTable[i]]; addressTable[i].addr ¬ address; RETURN[@addressTable[i]]; END; REPEAT FINISHED => BEGIN -- clear out the whole serverTable FOR j: CARDINAL IN [0..AddressTableLength) DO ClearAddressEntry[@addressTable[j]]; ENDLOOP; addressTable[0].addr ¬ address; RETURN[@addressTable[0]]; END; ENDLOOP; END; --InitAddressEntry InitServerEntry: PROCEDURE [domain: LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO ServerTableEntry ¬ NIL] = BEGIN -- find matching domain IF (desc ¬ MatchingServer[domain, currentTime]) # NIL THEN RETURN[desc]; -- find unused entry FOR i: CARDINAL IN [0..serverTableLength) DO IF serverTable[i].time = 0 OR System.SecondsSinceEpoch[serverTable[i].time] < System.SecondsSinceEpoch[currentTime] THEN BEGIN -- found an empty entry ClearServerEntry[@serverTable[i]]; serverTable[i].domain ¬ String.CopyToNewString[domain, Heap.systemZone]; RETURN[@serverTable[i]]; END; REPEAT FINISHED => BEGIN -- clear out the whole serverTable FOR j: CARDINAL IN [0..serverTableLength) DO ClearServerEntry[@serverTable[j]]; ENDLOOP; serverTable[0].domain ¬ String.CopyToNewString[domain, Heap.systemZone]; RETURN[@serverTable[0]]; END; ENDLOOP; END; --InitServerEntry --****************************************************************************-- Resolve: PROCEDURE [LookupEntry: PROCEDURE RETURNS [done: BOOLEAN], SendQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN], domain: LONG STRING, server: ServerType ¬ [nullINetAddress, domainNS], debugProc: DebugProc ¬ NIL, currentTime: System.GreenwichMeanTime] = BEGIN done: BOOLEAN ¬ FALSE; tries: CARDINAL ¬ 0; TryServerFromCache: PROCEDURE = BEGIN serverHost: LONG POINTER TO HostTableEntry ¬ NIL; previousServer, bestServer: LONG POINTER TO ServerTableEntry ¬ NIL; WHILE NOT done DO IF (bestServer ¬ BestServer[domain, currentTime]) = NIL OR (previousServer = bestServer) THEN RETURN ELSE FOR i: CARDINAL IN [0..bestServer.hostCount) WHILE NOT done DO IF (serverHost ¬ FindHostEntry[@bestServer.host[i], currentTime]) # NIL AND (tries ¬ tries + 1) <= maxReferrals THEN BEGIN IF debugProc # NIL THEN debugProc[" Cache:"L]; done ¬ SendQuery[[SelectLocalAddress[serverHost­], domainNS] ! NoAnswer => CONTINUE]; END; ENDLOOP; previousServer ¬ bestServer; ENDLOOP; END; -- TryServerFromCache TryServerFromParam: PROCEDURE = BEGIN IF server.host # nullINetAddress AND (tries ¬ tries + 1) <= maxReferrals THEN BEGIN IF debugProc # NIL THEN debugProc[" Param:"L]; done ¬ SendQuery[server]; END; END; -- TryServerFromParam TryServerFromInit: PROCEDURE = BEGIN IF defaultNameServer = nullINetAddress THEN defaultNameServer ¬ LOOPHOLE[ArpaPortInternal.GetDomainNameServer[]]; IF defaultNameServer # nullINetAddress AND (tries ¬ tries + 1) <= maxReferrals THEN BEGIN IF debugProc # NIL THEN debugProc[" Init:"L]; done ¬ SendQuery[[defaultNameServer, domainNS] ! NoAnswer => CONTINUE]; END; END; -- TryServerFromInit IF LookupEntry[] THEN RETURN; -- is information already in cache TryServerFromParam[]; -- if server was specified, try it IF done OR tries >= maxReferrals THEN RETURN; IF domain # NIL THEN -- try any applicable servers from cache BEGIN TryServerFromCache[]; IF done OR tries >= maxReferrals THEN RETURN; END; TryServerFromInit[]; -- try default servers IF done OR tries >= maxReferrals THEN RETURN; IF domain # NIL THEN TryServerFromCache[]; -- in case default server made referral END; --Resolve --****************************************************************************-- SelectLocalAddress: PROCEDURE [host: HostTableEntry] RETURNS [address: INetAddr ¬ nullINetAddress] = BEGIN IF myINetAddress = LOOPHOLE[nullINetAddress] THEN myINetAddress ¬ ArpaPortInternal.GetArpaAddr[]; IF host.addrCount # 0 THEN BEGIN FOR i: CARDINAL IN [0..host.addrCount) DO mask: ArpaRouter.InternetAddress ¬ ArpaPortInternal.BuildMasks[myINetAddress].netMask; IF ArpaPortInternal.AddrMatch[mask, myINetAddress, LOOPHOLE[host.addr[i]]] THEN RETURN[host.addr[i]]; ENDLOOP; RETURN[host.addr[0]]; END; END; --SelectLocalAddress INetAddressOrName: PUBLIC PROCEDURE [s: LONG STRING] RETURNS [address: INetAddr ¬ nullINetAddress] = BEGIN Later: PROCEDURE [domain: LONG STRING] ={ [] ¬ SelectLocalAddress[ResolveHostName[domain]]}; IF ~String.Empty[s] THEN BEGIN address ¬ INetAddress[s]; IF ArpaPortInternal.GetDomainNameServer[] # ArpaRouter.unknownInternetAddress THEN { IF (address ¬ INetAddress[s]) = nullINetAddress THEN address ¬ SelectLocalAddress[ResolveHostName[domain: s<<, time: 2>>]]; IF address = nullINetAddress THEN Process.Detach[FORK Later[s]]}; END; END; -- INetAddressOrName ResolveHostName: PUBLIC PROCEDURE [domain: LONG STRING, WKSFlag: BOOLEAN ¬ FALSE, useCache: BOOLEAN ¬ TRUE, server: ServerType ¬ [nullINetAddress, domainNS], time: CARDINAL ¬ defaultTimeout, debugProc: DebugProc ¬ NIL] RETURNS [desc: HostTableEntry ¬ nullHostDesc] = BEGIN domainStr: LONG STRING ¬ String.CopyToNewString[domain, Heap.systemZone, 1]; currentTime: System.GreenwichMeanTime ¬ System.GetGreenwichMeanTime[]; LookupHost: ENTRY PROCEDURE RETURNS [done: BOOLEAN ¬ FALSE] = BEGIN hostPtr: LONG POINTER TO HostTableEntry ¬ FindHostEntry[@domainStr, currentTime]; IF done ¬ (hostPtr # NIL) THEN IF (done ¬ useCache) THEN desc ¬ hostPtr^ ELSE ClearHostEntry[hostPtr] END; -- LookupHost SendHostQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN ¬ FALSE] = BEGIN port: ArpaRouter.Port ¬ ArpaPort.AssignPort[]; udpHandle: ArpaPort.Handle ¬ ArpaPort.Create[port, 1, 1, normal]; b: ArpaBuffer.Buffer ¬ NIL; UpdateTables: ENTRY PROCEDURE = BEGIN hostPtr: LONG POINTER TO HostTableEntry; done ¬ (ProcessReply[b, domainStr, currentTime, debugProc] = nameError); IF NOT done THEN IF (done ¬ ((hostPtr ¬ FindHostEntry[@domainStr, currentTime]) # NIL)) THEN desc ¬ hostPtr^; END; -- UpdateTables BEGIN ENABLE UNWIND => {IF b # NIL THEN ArpaBuffer.ReturnBuffer[b]; ArpaPort.Delete[udpHandle]}; ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)]; b ¬ RemoteRequest[domainStr, IF WKSFlag THEN wks ELSE address, server, udpHandle, debugProc]; UpdateTables[]; END; ArpaPort.Delete[udpHandle]; END; -- SendHostQuery -- main IF domainStr.text[domainStr.length-1] # '. THEN String.AppendChar[domainStr, '.]; Resolve[LookupHost, SendHostQuery, domainStr, server, debugProc, currentTime]; String.FreeString[Heap.systemZone, domainStr]; END; -- ResolveHostName --****************************************************************************-- ResolveMailboxName: PUBLIC PROCEDURE [domain: LONG STRING, useCache: BOOLEAN ¬ TRUE, server: ServerType ¬ [nullINetAddress, domainNS], time: CARDINAL ¬ defaultTimeout, debugProc: DebugProc ¬ NIL] RETURNS [desc: MailboxTableEntry ¬ nullMailboxDesc] = BEGIN domainStr: LONG STRING ¬ String.CopyToNewString[domain, Heap.systemZone, 1]; currentTime: System.GreenwichMeanTime ¬ System.GetGreenwichMeanTime[]; LookupMailbox: ENTRY PROCEDURE RETURNS [done: BOOLEAN ¬ FALSE] = BEGIN mbPtr: LONG POINTER TO MailboxTableEntry ¬ FindMailboxEntry[@domainStr, currentTime]; IF done ¬ (mbPtr # NIL) THEN IF (done ¬ useCache) THEN desc ¬ mbPtr^ ELSE ClearMailboxEntry[mbPtr] END; -- LookupMailbox SendMailboxQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN ¬ FALSE] = BEGIN port: ArpaRouter.Port ¬ ArpaPort.AssignPort[]; udpHandle: ArpaPort.Handle ¬ ArpaPort.Create[port, 1, 1, normal]; b: ArpaBuffer.Buffer ¬ NIL; UpdateTables: ENTRY PROCEDURE = BEGIN mbPtr: LONG POINTER TO MailboxTableEntry; done ¬ (ProcessReply[b, domainStr, currentTime, debugProc] = nameError); IF NOT done THEN IF (done ¬ ((mbPtr ¬ FindMailboxEntry[@domainStr, currentTime]) # NIL)) THEN desc ¬ mbPtr^; END; -- UpdateTables BEGIN ENABLE UNWIND => {IF b # NIL THEN ArpaBuffer.ReturnBuffer[b]; ArpaPort.Delete[udpHandle]}; ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)]; b ¬ RemoteRequest[domainStr, mb, server, udpHandle, debugProc]; UpdateTables[]; END; ArpaPort.Delete[udpHandle]; END; -- SendMailboxQuery -- main IF domainStr.text[domainStr.length-1] # '. THEN String.AppendChar[domainStr, '.]; Resolve[LookupMailbox, SendMailboxQuery, domainStr, server, debugProc, currentTime]; String.FreeString[Heap.systemZone, domainStr]; END; -- ResolveMailboxName --****************************************************************************-- ClearServerCache: PUBLIC ENTRY PROCEDURE = BEGIN FOR i: CARDINAL IN [0..serverTableLength) DO ClearServerEntry[@serverTable[i]]; ENDLOOP; END; -- ClearServerCache ResolveServerName: PUBLIC PROCEDURE [domain: LONG STRING, useCache: BOOLEAN ¬ TRUE, server: ServerType ¬ [nullINetAddress, domainNS], time: CARDINAL ¬ defaultTimeout, debugProc: DebugProc ¬ NIL] RETURNS [desc: ServerTableEntry ¬ nullServerDesc] = BEGIN domainStr: LONG STRING ¬ String.CopyToNewString[domain, Heap.systemZone, 1]; currentTime: System.GreenwichMeanTime ¬ System.GetGreenwichMeanTime[]; LookupServer: ENTRY PROCEDURE RETURNS [done: BOOLEAN ¬ FALSE] = BEGIN serverPtr: LONG POINTER TO ServerTableEntry ¬ MatchingServer[domainStr, currentTime]; IF done ¬ (serverPtr # NIL) THEN IF (done ¬ useCache) THEN desc ¬ serverPtr^ ELSE ClearServerEntry[serverPtr] END; -- LookupServer SendServerQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN ¬ FALSE] = BEGIN port: ArpaRouter.Port ¬ ArpaPort.AssignPort[]; udpHandle: ArpaPort.Handle ¬ ArpaPort.Create[port, 1, 1, normal]; b: ArpaBuffer.Buffer ¬ NIL; UpdateTables: ENTRY PROCEDURE = BEGIN serverPtr: LONG POINTER TO ServerTableEntry; done ¬ (ProcessReply[b, domainStr, currentTime, debugProc] = nameError); IF NOT done THEN IF (done ¬ ((serverPtr ¬ MatchingServer[domainStr, currentTime]) # NIL)) THEN desc ¬ serverPtr^; END; -- UpdateTables BEGIN ENABLE UNWIND => {IF b # NIL THEN ArpaBuffer.ReturnBuffer[b]; ArpaPort.Delete[udpHandle]}; ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)]; b ¬ RemoteRequest[domainStr, ns, server, udpHandle, debugProc]; UpdateTables[]; END; ArpaPort.Delete[udpHandle]; END; -- SendServerQuery -- main IF domainStr.text[domainStr.length-1] # '. THEN String.AppendChar[domainStr, '.]; Resolve[LookupServer, SendServerQuery, domainStr, server, debugProc, currentTime]; String.FreeString[Heap.systemZone, domainStr]; END; --ResolveServerName --****************************************************************************-- AppendNameOrINetAddress: PUBLIC PROCEDURE [to: LONG STRING, address: INetAddr] = BEGIN currentTime: System.GreenwichMeanTime ¬ System.GetGreenwichMeanTime[]; addrPtr: LONG POINTER TO AddressTableEntry ¬ FindAddress[address, currentTime]; Later: PROCEDURE = { [] ¬ ResolveHostAddress[host: address, time: 2*defaultTimeout] }; IF addrPtr = NIL THEN BEGIN Process.Detach[FORK Later]; AppendINetAddress[to, address]; END ELSE String.AppendString[to: to, from: addrPtr^.name[0]]; END; -- AppendNameOrINetAddress AppendNameOrINetAddressAndGrow: PUBLIC PROCEDURE [to: LONG POINTER TO LONG STRING, address: INetAddr, z: UNCOUNTED ZONE] = BEGIN currentTime: System.GreenwichMeanTime ¬ System.GetGreenwichMeanTime[]; addrPtr: LONG POINTER TO AddressTableEntry ¬ FindAddress[address, currentTime]; Later: PROCEDURE = { [] ¬ ResolveHostAddress[host: address, time: 2*defaultTimeout] }; IF addrPtr = NIL THEN BEGIN Process.Detach[FORK Later]; AppendINetAddressAndGrow[to, address, z]; END ELSE String.AppendStringAndGrow[to, addrPtr^.name[0], z]; END; -- AppendNameOrINetAddress ResolveHostAddress: PUBLIC PROCEDURE [host: INetAddr, useCache: BOOLEAN ¬ TRUE, server: ServerType ¬ [nullINetAddress, domainNS], time: CARDINAL ¬ defaultTimeout, debugProc: DebugProc ¬ NIL] RETURNS [desc: AddressTableEntry ¬ nullAddressDesc] = BEGIN currentTime: System.GreenwichMeanTime ¬ System.GetGreenwichMeanTime[]; LookupAddress: ENTRY PROCEDURE RETURNS [done: BOOLEAN ¬ FALSE] = BEGIN addrPtr: LONG POINTER TO AddressTableEntry ¬ FindAddress[host, currentTime]; IF done ¬ (addrPtr # NIL) THEN IF (done ¬ useCache) THEN desc ¬ addrPtr^ ELSE ClearAddressEntry[addrPtr]; END; -- LookupAddress SendInAddrQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN ¬ FALSE] = BEGIN temp: LONG STRING ¬ [40]; port: ArpaRouter.Port ¬ ArpaPort.AssignPort[]; udpHandle: ArpaPort.Handle ¬ ArpaPort.Create[port, 1, 1, normal]; b: ArpaBuffer.Buffer ¬ NIL; UpdateTables: ENTRY PROCEDURE = BEGIN addrPtr: LONG POINTER TO AddressTableEntry; done ¬ (ProcessReply[b, temp, currentTime, debugProc] # okay); IF NOT done THEN IF (done ¬ ((addrPtr ¬ FindAddress[host, currentTime]) # NIL)) THEN desc ¬ addrPtr^; END; -- UpdateTables BEGIN ENABLE UNWIND => {IF b # NIL THEN ArpaBuffer.ReturnBuffer[b]; ArpaPort.Delete[udpHandle]}; -- build string to pass to RemoteRequest String.AppendDecimal[temp, host.d]; String.AppendChar[temp, '.]; String.AppendDecimal[temp, host.c]; String.AppendChar[temp, '.]; String.AppendDecimal[temp, host.b]; String.AppendChar[temp, '.]; String.AppendDecimal[temp, host.a]; String.AppendString[temp, ".IN-ADDR.ARPA."L]; ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)]; b ¬ RemoteRequest[temp, ptr, server, udpHandle, debugProc]; UpdateTables[]; END; ArpaPort.Delete[udpHandle]; END; -- SendInAddrQuery -- main Resolve[LookupAddress, SendInAddrQuery, NIL, server, debugProc, currentTime]; END; -- ResolveHostAddress --****************************************************************************-- MakePointerQuery: PUBLIC PROCEDURE [pointer: LONG STRING, server: ServerType ¬ [nullINetAddress, domainNS], time: CARDINAL ¬ defaultTimeout, debugProc: DebugProc ¬ NIL, z: UNCOUNTED ZONE] RETURNS [s: LONG STRING ¬ NIL] = BEGIN currentTime: System.GreenwichMeanTime ¬ System.GetGreenwichMeanTime[]; strPointer: LONG STRING ¬ String.CopyToNewString[pointer, z, 1]; port: ArpaRouter.Port ¬ ArpaPort.AssignPort[]; udpHandle: ArpaPort.Handle ¬ ArpaPort.Create[port, 1, 1, normal]; b: ArpaBuffer.Buffer; BEGIN ENABLE UNWIND => { String.FreeString[z, strPointer]; IF b # NIL THEN ArpaBuffer.ReturnBuffer[b]; ArpaPort.Delete[udpHandle]}; IF strPointer.text[strPointer.length-1] # '. THEN String.AppendChar[strPointer, '.]; ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)]; b ¬ RemoteRequest[strPointer, ptr, server, udpHandle, debugProc]; BEGIN OPEN udp: b.arpa.user; pUDP: UDPBuffer ¬ LOOPHOLE[@udp]; hPtr: LONG POINTER TO QueryHeader ¬ LOOPHOLE[@udp.data]; -- check rcode and counts in header IF hPtr.qr = response AND hPtr.rcode = okay THEN BEGIN ENABLE UNWIND => ArpaBuffer.ReturnBuffer[b]; temp: LONG STRING ¬ [120]; index: CARDINAL ¬ 2 * SIZE[QueryHeader]; -- check the query in the response FOR i:CARDINAL IN [0..hPtr.queryCount) DO index ¬ UnpackName[pUDP, index, @temp]; IF NOT String.Equivalent[strPointer, temp] THEN SIGNAL ErrorInReply; index ¬ index + 4; -- 2 bytes for the type, 2 bytes for the class ENDLOOP; s ¬ String.MakeString[z, 40]; FOR i:CARDINAL IN [0..(hPtr.answerCount+hPtr.nsCount+hPtr.arCount)) DO type: QType; time: LONG CARDINAL; temp.length ¬ 0; index ¬ UnpackName[pUDP, index, @temp]; type ¬ LOOPHOLE[GetWord[@pUDP.data, @index]]; index ¬ index + 2; -- 2 bytes for the class time ¬ GetLong[@pUDP.data, @index]; index ¬ index + 2; --2 bytes for the rdlength SELECT type FROM ns => -- referral BEGIN server: LONG POINTER TO ServerTableEntry ¬ InitServerEntry[temp, currentTime]; ttl: System.GreenwichMeanTime ¬ System.AdjustGreenwichMeanTime[currentTime, MIN[17777777777B, time]]; temp.length ¬ 0; index ¬ UnpackName[pUDP, index, @temp]; IF debugProc # NIL THEN BEGIN debugProc[" Received referral to "L]; debugProc[temp]; debugProc["\n"]; END; AddStringEntry[@server.host, @server.hostCount, temp]; UpdateTime[@server.time, ttl]; END; ptr => BEGIN temp.length ¬ 0; index ¬ UnpackName[pUDP, index, @temp]; String.AppendStringAndGrow[@s, temp, z]; String.AppendStringAndGrow[@s, ", "L, z]; END ENDCASE => SIGNAL ErrorInReply; ENDLOOP; IF s.length > 2 THEN s.length ¬ s.length -2; END; END; -- OPEN END; -- ENABLE ArpaBuffer.ReturnBuffer[b]; ArpaPort.Delete[udpHandle]; String.FreeString[z, strPointer]; END; -- MakePointerQuery END.