1 { Copyright (C) 2005 Bas Steendijk and Peter Green
\r
2 For conditions of distribution and use, see copyright notice in zlib_license.txt
\r
3 which is included in the package
\r
4 ----------------------------------------------------------------------------- }
\r
8 code wanting to use this dns system should act as follows (note: app
\r
9 developers will probablly want to use dnsasync or dnssync or write a similar
\r
10 wrapper unit of thier own).
\r
12 for normal lookups call setstate_forward or setstate_reverse to set up the
\r
13 state, for more obscure lookups use setstate_request_init and fill in other
\r
14 relavent state manually.
\r
16 call state_process which will do processing on the information in the state
\r
17 and return an action
\r
18 action_ignore means that dnscore wants the code that calls it to go
\r
19 back to waiting for packets
\r
20 action_sendpacket means that dnscore wants the code that calls it to send
\r
21 the packet in sendpacket/sendpacketlen and then start (or go back to) listening
\r
23 action_done means the request has completed (either suceeded or failed)
\r
25 callers should resend the last packet they tried to send if they have not
\r
26 been asked to send a new packet for more than some timeout value they choose.
\r
28 when a packet is received the application should put the packet in
\r
29 recvbuf/recvbuflen , set state.parsepacket and call state_process again
\r
31 once the app gets action_done it can determine sucess or failure in the
\r
34 on failure state.resultstr will be an empty string and state.resultbin will
\r
35 be zeroed out (easilly detected by the fact that it will have a family of 0)
\r
37 on success for a A or AAAA lookup state.resultstr will be an empty string
\r
38 and state.resultbin will contain the result (note: AAAA lookups require IPV6
\r
41 if an A lookup fails and the code is built with ipv6 enabled then the code
\r
42 will return any AAAA records with the same name. The reverse does not apply
\r
43 so if an application preffers IPV6 but wants IPV4 results as well it must
\r
44 check them seperately.
\r
46 on success for any other type of lookup state.resultstr will be an empty
\r
48 note the state contains ansistrings, setstate_init with a null name parameter
\r
49 can be used to clean theese up if required.
\r
51 callers may use setstate_failure to mark the state as failed themseleves
\r
52 before passing it on to other code, for example this may be done in the event
\r
59 {$ifdef fpc}{$mode delphi}{$endif}
\r
67 uses binipstuff,classes,pgtypes;
\r
69 var usewindns : boolean = {$ifdef win32}true{$else}false{$endif};
\r
70 //hint to users of this unit that they should use windows dns instead.
\r
71 //May be disabled by applications if desired. (e.g. if setting a custom
\r
74 //note: this unit will not be able to self populate it's dns server list on
\r
75 //older versions of windows.
\r
80 //note: when using action_ignore the dnscore code *must* preserve the contents of state.sendpacket to allow for retries
\r
81 //note: action_ignore must not be used in response to the original request but there is no valid reason for doing this anyway
\r
96 retryafter=300000; //microseconds must be less than one second;
\r
97 timeoutlag=1000000000; // penalty value to be treated as lag in the event of a timeout (microseconds)
\r
99 dvar=array[0..0] of byte;
\r
101 tdnspacket=packed record
\r
104 rrcount:array[0..3] of word;
\r
105 payload:array[0..511-12] of byte;
\r
112 recursioncount:integer;
\r
115 parsepacket:boolean;
\r
118 resultaction:integer;
\r
119 numrr1:array[0..3] of integer;
\r
122 sendpacketlen:integer;
\r
123 sendpacket:tdnspacket;
\r
124 recvpacketlen:integer;
\r
125 recvpacket:tdnspacket;
\r
126 forwardfamily:integer;
\r
130 requesttypehi:byte;
\r
135 data:array[0..511] of byte;
\r
138 trrpointer=packed record
\r
145 //commenting out functions from interface that do not have documented semantics
\r
146 //and probablly should not be called from outside this unit, reenable them
\r
147 //if you must but please document them at the same time --plugwash
\r
149 //function buildrequest(const name:string;var packet:tdnspacket;requesttype:word):integer;
\r
150 //function makereversename(const binip:tbinip):string;
\r
152 procedure setstate_request_init(const name:string;var state:tdnsstate);
\r
154 //set up state for a foward lookup. A family value of AF_INET6 will give only
\r
155 //ipv6 results. Any other value will give ipv4 results in preference and ipv6
\r
156 //results if ipv4 results are not available;
\r
157 procedure setstate_forward(const name:string;var state:tdnsstate;family:integer);
\r
159 procedure setstate_reverse(const binip:tbinip;var state:tdnsstate);
\r
160 procedure setstate_failure(var state:tdnsstate);
\r
161 //procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);
\r
164 procedure state_process(var state:tdnsstate);
\r
166 //function decodename(const packet:tdnspacket;len,start,recursion:integer;var numread:integer):string;
\r
168 //presumablly this is exported to allow more secure random functions
\r
169 //to be substituted?
\r
170 var randomfunction:function:integer;
\r
173 procedure populatednsserverlist;
\r
174 procedure cleardnsservercache;
\r
177 dnsserverlist : tstringlist;
\r
178 // currentdnsserverno : integer;
\r
180 function getcurrentsystemnameserver(var id:integer) :string;
\r
183 // unixnameservercache:string;
\r
187 procedure reportlag(id:integer;lag:integer); //lag should be in microseconds and should be -1 to report a timeout
\r
189 failurereason:string;
\r
200 function buildrequest(const name:string;var packet:tdnspacket;requesttype:word):integer;
\r
204 arr:array[0..sizeof(packet)-1] of byte absolute packet;
\r
206 { writeln('buildrequest: name: ',name);}
\r
208 fillchar(packet,sizeof(packet),0);
\r
209 if assigned(randomfunction) then packet.id := (randomfunction and $ffff) else packet.id := random(65536);
\r
210 packet.flags := htons($0100);
\r
211 packet.rrcount[0] := htons($0001);
\r
214 s := copy(name,1,maxnamelength);
\r
215 if s = '' then exit;
\r
216 if s[length(s)] <> '.' then s := s + '.';
\r
219 if (s = '.') then begin
\r
220 packet.payload[0] := 0;
\r
223 for a := 1 to length(s) do begin
\r
224 if s[a] = '.' then begin
\r
225 if b > maxnamefieldlen then exit;
\r
226 if (b = 0) then exit;
\r
227 packet.payload[a-b-1] := b;
\r
230 packet.payload[a] := byte(s[a]);
\r
234 if b > maxnamefieldlen then exit;
\r
235 packet.payload[length(s)-b] := b;
\r
236 result := length(s) + 12+5;
\r
239 arr[result-1] := 1;
\r
240 arr[result-3] := requesttype and $ff;
\r
241 arr[result-4] := requesttype shr 8;
\r
244 function makereversename(const binip:tbinip):string;
\r
250 if binip.family = AF_INET then begin
\r
251 b := htonl(binip.ip);
\r
252 for a := 0 to 3 do begin
\r
253 name := name + inttostr(b shr (a shl 3) and $ff)+'.';
\r
255 name := name + 'in-addr.arpa';
\r
258 if binip.family = AF_INET6 then begin
\r
259 for a := 15 downto 0 do begin
\r
260 b := binip.ip6.u6_addr8[a];
\r
261 name := name + hexchars[b and $f]+'.'+hexchars[b shr 4]+'.';
\r
263 name := name + 'ip6.arpa';
\r
273 decodes DNS format name to a string. does not includes the root dot.
\r
274 doesnt read beyond len.
\r
275 empty result + non null failurereason: failure
\r
276 empty result + null failurereason: internal use
\r
278 function decodename(const packet:tdnspacket;len,start,recursion:integer;var numread:integer):string;
\r
280 arr:array[0..sizeof(packet)-1] of byte absolute packet;
\r
286 if (start+numread < 0) or (start+numread >= len) then begin
\r
288 failurereason := 'decoding name: got out of range1';
\r
291 b := arr[start+numread];
\r
292 if b >= $c0 then begin
\r
293 {recursive sub call}
\r
294 if recursion > 10 then begin
\r
296 failurereason := 'decoding name: max recursion';
\r
299 if ((start+numread+1) >= len) then begin
\r
301 failurereason := 'decoding name: got out of range3';
\r
304 a := ((b shl 8) or arr[start+numread+1]) and $3fff;
\r
305 s := decodename(packet,len,a,recursion+1,a);
\r
306 if (s = '') and (failurereason <> '') then begin
\r
310 if result <> '' then result := result + '.';
\r
311 result := result + s;
\r
314 end else if b < 64 then begin
\r
315 if (numread <> 0) and (b <> 0) then result := result + '.';
\r
316 for a := start+numread+1 to start+numread+b do begin
\r
317 if (a >= len) then begin
\r
319 failurereason := 'decoding name: got out of range2';
\r
322 result := result + char(arr[a]);
\r
326 if b = 0 then begin
\r
327 if (result = '') and (recursion = 0) then result := '.';
\r
328 exit; {reached end of name}
\r
331 failurereason := 'decoding name: read invalid char';
\r
338 {==============================================================================}
\r
340 procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);
\r
344 state.resultaction := action_done;
\r
345 state.resultstr := '';
\r
346 case trr(rrp.p^).requesttype of
\r
348 if htons(trr(rrp.p^).datalen) <> 4 then exit;
\r
349 move(trr(rrp.p^).data,state.resultbin.ip,4);
\r
350 state.resultbin.family :=AF_INET;
\r
353 querytype_aaaa: begin
\r
354 if htons(trr(rrp.p^).datalen) <> 16 then exit;
\r
355 state.resultbin.family := AF_INET6;
\r
356 move(trr(rrp.p^).data,state.resultbin.ip6,16);
\r
360 {other reply types (PTR, MX) return a hostname}
\r
361 state.resultstr := decodename(state.recvpacket,state.recvpacketlen,taddrint(rrp.p)-taddrint(@state.recvpacket)+10,0,a);
\r
362 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
366 procedure setstate_request_init(const name:string;var state:tdnsstate);
\r
368 {destroy things properly}
\r
369 state.resultstr := '';
\r
370 state.queryname := '';
\r
371 state.rrdata := '';
\r
372 fillchar(state,sizeof(state),0);
\r
373 state.queryname := name;
\r
374 state.parsepacket := false;
\r
377 procedure setstate_forward(const name:string;var state:tdnsstate;family:integer);
\r
379 setstate_request_init(name,state);
\r
380 state.forwardfamily := family;
\r
382 if family = AF_INET6 then state.requesttype := querytype_aaaa else
\r
384 state.requesttype := querytype_a;
\r
387 procedure setstate_reverse(const binip:tbinip;var state:tdnsstate);
\r
389 setstate_request_init(makereversename(binip),state);
\r
390 state.requesttype := querytype_ptr;
\r
393 procedure setstate_failure(var state:tdnsstate);
\r
395 state.resultstr := '';
\r
396 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
397 state.resultaction := action_done;
\r
400 procedure state_process(var state:tdnsstate);
\r
406 rrptemp:^trrpointer;
\r
408 if state.parsepacket then begin
\r
409 if state.recvpacketlen < 12 then begin
\r
410 failurereason := 'Undersized packet';
\r
411 state.resultaction := action_ignore;
\r
414 if state.id <> state.recvpacket.id then begin
\r
415 failurereason := 'ID mismatch';
\r
416 state.resultaction := action_ignore;
\r
420 for a := 0 to 3 do begin
\r
421 state.numrr1[a] := htons(state.recvpacket.rrcount[a]);
\r
422 if state.numrr1[a] > maxrrofakind then goto failure;
\r
423 inc(state.numrr2,state.numrr1[a]);
\r
426 setlength(state.rrdata,state.numrr2*sizeof(trrpointer));
\r
428 {- put all replies into a list}
\r
432 for a := 0 to state.numrr1[0]-1 do begin
\r
433 if (ofs < 12) or (ofs > state.recvpacketlen-4) then goto failure;
\r
434 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
435 rrptemp.p := @state.recvpacket.payload[ofs-12];
\r
436 rrptemp.ofs := ofs;
\r
437 decodename(state.recvpacket,state.recvpacketlen,ofs,0,b);
\r
438 rrptemp.len := b + 4;
\r
439 inc(ofs,rrptemp.len);
\r
442 for a := state.numrr1[0] to state.numrr2-1 do begin
\r
443 if (ofs < 12) or (ofs > state.recvpacketlen-12) then goto failure;
\r
444 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
445 if decodename(state.recvpacket,state.recvpacketlen,ofs,0,b) = '' then goto failure;
\r
446 rrtemp := @state.recvpacket.payload[ofs-12+b]; {rrtemp points to values and result, after initial name}
\r
447 rrptemp.p := rrtemp;
\r
448 rrptemp.ofs := ofs; {ofs is start of RR before initial name from start of packet}
\r
449 rrptemp.namelen := b;
\r
450 b := htons(rrtemp.datalen);
\r
451 rrptemp.len := b + 10 + rrptemp.namelen;
\r
452 inc(ofs,rrptemp.len);
\r
454 if (ofs <> state.recvpacketlen) then begin
\r
455 failurereason := 'ofs <> state.packetlen';
\r
459 {- check for items of the requested type in answer section, if so return success first}
\r
460 for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin
\r
461 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
462 rrtemp := rrptemp.p;
\r
464 if rrtemp.requesttype = state.requesttype then begin
\r
465 setstate_return(rrptemp^,b,state);
\r
470 {if no items of correct type found, follow first cname in answer section}
\r
471 for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin
\r
472 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
473 rrtemp := rrptemp.p;
\r
475 if rrtemp.requesttype = querytype_cname then begin
\r
476 state.queryname := decodename(state.recvpacket,state.recvpacketlen,rrptemp.ofs+12,0,b);
\r
481 {no cnames found, no items of correct type found}
\r
482 if state.forwardfamily <> 0 then goto failure;
\r
484 if (state.requesttype = querytype_a) then begin
\r
485 {v6 only: in case of forward, look for AAAA in alternative section}
\r
486 for a := state.numrr1[0]+state.numrr1[1]+state.numrr1[2] to (state.numrr2-1) do begin
\r
487 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
488 rrtemp := rrptemp.p;
\r
490 if rrtemp.requesttype = querytype_aaaa then begin
\r
491 setstate_return(rrptemp^,b,state);
\r
495 {no AAAA's found in alternative, do a recursive lookup for them}
\r
496 state.requesttype := querytype_aaaa;
\r
502 {here it needs recursed lookup}
\r
503 {if needing to follow a cname, change state to do so}
\r
504 inc(state.recursioncount);
\r
505 if state.recursioncount > maxrecursion then goto failure;
\r
508 {here, a name needs to be resolved}
\r
509 if state.queryname = '' then begin
\r
510 failurereason := 'empty query name';
\r
514 {do /ets/hosts lookup here}
\r
515 state.sendpacketlen := buildrequest(state.queryname,state.sendpacket,state.requesttype);
\r
516 if state.sendpacketlen = 0 then begin
\r
517 failurereason := 'building request packet failed';
\r
520 state.id := state.sendpacket.id;
\r
521 state.resultaction := action_sendquery;
\r
525 setstate_failure(state);
\r
529 MAX_HOSTNAME_LEN = 132;
\r
530 MAX_DOMAIN_NAME_LEN = 132;
\r
531 MAX_SCOPE_ID_LEN = 260 ;
\r
532 MAX_ADAPTER_NAME_LENGTH = 260;
\r
533 MAX_ADAPTER_ADDRESS_LENGTH = 8;
\r
534 MAX_ADAPTER_DESCRIPTION_LENGTH = 132;
\r
535 ERROR_BUFFER_OVERFLOW = 111;
\r
536 MIB_IF_TYPE_ETHERNET = 6;
\r
537 MIB_IF_TYPE_TOKENRING = 9;
\r
538 MIB_IF_TYPE_FDDI = 15;
\r
539 MIB_IF_TYPE_PPP = 23;
\r
540 MIB_IF_TYPE_LOOPBACK = 24;
\r
541 MIB_IF_TYPE_SLIP = 28;
\r
545 tip_addr_string=packed record
\r
547 IpAddress : array[0..15] of char;
\r
548 ipmask : array[0..15] of char;
\r
551 pip_addr_string=^tip_addr_string;
\r
552 tFIXED_INFO=packed record
\r
553 HostName : array[0..MAX_HOSTNAME_LEN-1] of char;
\r
554 DomainName : array[0..MAX_DOMAIN_NAME_LEN-1] of char;
\r
555 currentdnsserver : pip_addr_string;
\r
556 dnsserverlist : tip_addr_string;
\r
557 nodetype : longint;
\r
558 ScopeId : array[0..MAX_SCOPE_ID_LEN + 4] of char;
\r
559 enablerouting : longbool;
\r
560 enableproxy : longbool;
\r
561 enabledns : longbool;
\r
563 pFIXED_INFO=^tFIXED_INFO;
\r
566 iphlpapi : thandle;
\r
567 getnetworkparams : function(pFixedInfo : PFIXED_INFO;OutBufLen : plongint) : longint;stdcall;
\r
569 procedure populatednsserverlist;
\r
572 fixed_info : pfixed_info;
\r
573 fixed_info_len : longint;
\r
574 currentdnsserver : pip_addr_string;
\r
582 if assigned(dnsserverlist) then begin
\r
583 dnsserverlist.clear;
\r
585 dnsserverlist := tstringlist.Create;
\r
588 if iphlpapi=0 then iphlpapi := loadlibrary('iphlpapi.dll');
\r
589 if not assigned(getnetworkparams) then @getnetworkparams := getprocaddress(iphlpapi,'GetNetworkParams');
\r
590 fixed_info_len := 0;
\r
591 if GetNetworkParams(nil,@fixed_info_len)<>ERROR_BUFFER_OVERFLOW then exit;
\r
592 //fixed_info_len :=sizeof(tfixed_info);
\r
593 getmem(fixed_info,fixed_info_len);
\r
594 if GetNetworkParams(fixed_info,@fixed_info_len)<>0 then begin
\r
595 freemem(fixed_info);
\r
598 currentdnsserver := @(fixed_info.dnsserverlist);
\r
599 while assigned(currentdnsserver) do begin
\r
600 dnsserverlist.Add(currentdnsserver.IpAddress);
\r
601 currentdnsserver := currentdnsserver.next;
\r
603 freemem(fixed_info);
\r
606 assignfile(t,'/etc/resolv.conf');
\r
607 {$i-}reset(t);{$i+}
\r
608 if ioresult <> 0 then exit;
\r
610 while not eof(t) do begin
\r
612 if not (copy(s,1,10) = 'nameserver') then continue;
\r
613 s := copy(s,11,500);
\r
614 while s <> '' do begin
\r
615 if (s[1] = #32) or (s[1] = #9) then s := copy(s,2,500) else break;
\r
618 if a <> 0 then s := copy(s,1,a-1);
\r
620 if a <> 0 then s := copy(s,1,a-1);
\r
622 //if result <> '' then break;
\r
623 dnsserverlist.Add(s);
\r
629 procedure cleardnsservercache;
\r
631 if assigned(dnsserverlist) then begin
\r
632 dnsserverlist.destroy;
\r
633 dnsserverlist := nil;
\r
637 function getcurrentsystemnameserver(var id:integer):string;
\r
642 if not assigned(dnsserverlist) then populatednsserverlist;
\r
643 if dnsserverlist.count=0 then raise exception.create('no dns servers availible');
\r
645 if dnsserverlist.count >1 then begin
\r
647 for counter := 1 to dnsserverlist.count-1 do begin
\r
648 if taddrint(dnsserverlist.objects[counter]) < taddrint(dnsserverlist.objects[id]) then id := counter;
\r
651 result := dnsserverlist[id]
\r
654 procedure reportlag(id:integer;lag:integer); //lag should be in microseconds and should be -1 to report a timeout
\r
659 if (id < 0) or (id >= dnsserverlist.count) then exit;
\r
660 if lag = -1 then lag := timeoutlag;
\r
661 for counter := 0 to dnsserverlist.count-1 do begin
\r
662 temp := taddrint(dnsserverlist.objects[counter]) *15;
\r
663 if counter=id then temp := temp + lag;
\r
664 dnsserverlist.objects[counter] := tobject(temp div 16);
\r
669 { quick and dirty description of dns packet structure to aid writing and
\r
670 understanding of parser code, refer to appropriate RFCs for proper specs
\r
671 - all words are network order
\r
673 www.google.com A request:
\r
675 0, 2: random transaction ID
\r
676 2, 2: flags: only the "recursion desired" bit set. (bit 8 of word)
\r
678 6, 2: answer RR's: 0.
\r
679 8, 2: authority RR's: 0.
\r
680 10, 2: additional RR's: 0.
\r
683 #03 "www" #06 "google" #03 "com" #00
\r
684 size-4, 2: type: host address (1)
\r
685 size-2, 2: class: inet (1)
\r
689 0,2: random transaction ID
\r
690 2,2: flags: set: response (bit 15), recursion desired (8), recursion available (7)
\r
692 6,4: answer RR's: 2
\r
693 8,4: authority RR's: 9
\r
694 10,4: additional RR's: 9
\r
699 0,2 "c0 0c" "name: www.google.com"
\r
700 2,2 "00 05" "type: cname for an alias"
\r
701 4,2 "00 01" "class: inet"
\r
703 10,2: data length "00 17" (23)
\r
704 12: the cname name (www.google.akadns.net)
\r
707 2,2 "00 01" host address
\r
710 10,2: data length (4)
\r
712 authority - 9 records
\r
713 additional - 9 records
\r
719 4,2: class: inet (0001)
\r
721 10,2: data size (16)
\r
724 ptr request: query type 000c
\r
726 name compression: word "cxxx" in the name, xxx points to offset in the packet}
\r