-- 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.