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 success 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
57 {$ifdef fpc}{$mode delphi}{$endif}
\r
59 {$include lcoreconfig.inc}
\r
63 uses binipstuff,classes,pgtypes,lcorernd;
\r
65 var usewindns : boolean = {$ifdef win32}true{$else}false{$endif};
\r
66 {hint to users of this unit that they should use windows dns instead.
\r
67 May be disabled by applications if desired. (e.g. if setting a custom
\r
70 note: this unit will not be able to self populate it's dns server list on
\r
71 older versions of windows.}
\r
80 hint to users of this unit to how to deal with connecting to hostnames regarding ipv4 or ipv6 usage
\r
81 can be set by apps as desired
\r
83 var useaf:integer = useaf_default;
\r
86 (temporarily) use a different nameserver, regardless of the dnsserverlist
\r
88 var overridednsserver:ansistring;
\r
93 //note: when using action_ignore the dnscore code *must* preserve the contents of state.sendpacket to allow for retries
\r
94 //note: action_ignore must not be used in response to the original request but there is no valid reason for doing this anyway
\r
111 retryafter=300000; //microseconds must be less than one second;
\r
112 timeoutlag=1000000000; // penalty value to be treated as lag in the event of a timeout (microseconds)
\r
114 dvar=array[0..0] of byte;
\r
116 tdnspacket=packed record
\r
119 rrcount:array[0..3] of word;
\r
120 payload:array[0..511-12] of byte;
\r
127 recursioncount:integer;
\r
128 queryname:ansistring;
\r
130 parsepacket:boolean;
\r
131 resultstr:ansistring;
\r
133 resultlist:tbiniplist;
\r
134 resultaction:integer;
\r
135 numrr1:array[0..3] of integer;
\r
138 sendpacketlen:integer;
\r
139 sendpacket:tdnspacket;
\r
140 recvpacketlen:integer;
\r
141 recvpacket:tdnspacket;
\r
142 forwardfamily:integer;
\r
146 requesttypehi:byte;
\r
151 data:array[0..511] of byte;
\r
154 trrpointer=packed record
\r
161 //commenting out functions from interface that do not have documented semantics
\r
162 //and probablly should not be called from outside this unit, reenable them
\r
163 //if you must but please document them at the same time --plugwash
\r
165 //function buildrequest(const name:string;var packet:tdnspacket;requesttype:word):integer;
\r
167 //returns the DNS name used to reverse look up an IP, such as 4.3.2.1.in-addr.arpa for 1.2.3.4
\r
168 function makereversename(const binip:tbinip):ansistring;
\r
170 procedure setstate_request_init(const name:ansistring;var state:tdnsstate);
\r
172 //set up state for a foward lookup. A family value of AF_INET6 will give only
\r
173 //ipv6 results. Any other value will give only ipv4 results
\r
174 procedure setstate_forward(const name:ansistring;var state:tdnsstate;family:integer);
\r
176 procedure setstate_reverse(const binip:tbinip;var state:tdnsstate);
\r
177 procedure setstate_failure(var state:tdnsstate);
\r
178 //procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);
\r
180 //for custom raw lookups such as TXT, as desired by the user
\r
181 procedure setstate_custom(const name:ansistring; requesttype:integer; var state:tdnsstate);
\r
183 procedure state_process(var state:tdnsstate);
\r
185 //function decodename(const packet:tdnspacket;len,start,recursion:integer;var numread:integer):string;
\r
187 procedure populatednsserverlist;
\r
188 procedure cleardnsservercache;
\r
191 dnsserverlist : tbiniplist;
\r
192 dnsserverlag:tlist;
\r
193 // currentdnsserverno : integer;
\r
196 //getcurrentsystemnameserver returns the nameserver the app should use and sets
\r
197 //id to the id of that nameserver. id should later be used to report how laggy
\r
198 //the servers response was and if it was timed out.
\r
199 function getcurrentsystemnameserver(var id:integer) :ansistring;
\r
200 function getcurrentsystemnameserverbin(var id:integer) :tbinip;
\r
201 procedure reportlag(id:integer;lag:integer); //lag should be in microseconds and should be -1 to report a timeout
\r
204 // unixnameservercache:string;
\r
209 procedure initpreferredmode;
\r
212 preferredmodeinited:boolean;
\r
217 failurereason:ansistring;
\r
219 function getquerytype(s:ansistring):integer;
\r
229 function getquerytype(s:ansistring):integer;
\r
233 if (s = 'A') then result := querytype_a else
\r
234 if (s = 'CNAME') then result := querytype_cname else
\r
235 if (s = 'AAAA') then result := querytype_aaaa else
\r
236 if (s = 'PTR') then result := querytype_ptr else
\r
237 if (s = 'NS') then result := querytype_ns else
\r
238 if (s = 'MX') then result := querytype_mx else
\r
239 if (s = 'A6') then result := querytype_a6 else
\r
240 if (s = 'TXT') then result := querytype_txt else
\r
241 if (s = 'SOA') then result := querytype_soa else
\r
242 if (s = 'SPF') then result := querytype_spf;
\r
245 function buildrequest(const name:ansistring;var packet:tdnspacket;requesttype:word):integer;
\r
249 arr:array[0..sizeof(packet)-1] of byte absolute packet;
\r
251 { writeln('buildrequest: name: ',name);}
\r
253 fillchar(packet,sizeof(packet),0);
\r
254 packet.id := randominteger($10000);
\r
256 packet.flags := htons($0100);
\r
257 packet.rrcount[0] := htons($0001);
\r
260 s := copy(name,1,maxnamelength);
\r
261 if s = '' then exit;
\r
262 if s[length(s)] <> '.' then s := s + '.';
\r
265 if (s = '.') then begin
\r
266 packet.payload[0] := 0;
\r
269 for a := 1 to length(s) do begin
\r
270 if s[a] = '.' then begin
\r
271 if b > maxnamefieldlen then exit;
\r
272 if (b = 0) then exit;
\r
273 packet.payload[a-b-1] := b;
\r
276 packet.payload[a] := byte(s[a]);
\r
280 if b > maxnamefieldlen then exit;
\r
281 packet.payload[length(s)-b] := b;
\r
282 result := length(s) + 12+5;
\r
285 arr[result-1] := 1;
\r
286 arr[result-3] := requesttype and $ff;
\r
287 arr[result-4] := requesttype shr 8;
\r
290 function makereversename(const binip:tbinip):ansistring;
\r
296 if binip.family = AF_INET then begin
\r
297 b := htonl(binip.ip);
\r
298 for a := 0 to 3 do begin
\r
299 name := name + inttostr(b shr (a shl 3) and $ff)+'.';
\r
301 name := name + 'in-addr.arpa';
\r
304 if binip.family = AF_INET6 then begin
\r
305 for a := 15 downto 0 do begin
\r
306 b := binip.ip6.u6_addr8[a];
\r
307 name := name + hexchars[b and $f]+'.'+hexchars[b shr 4]+'.';
\r
309 name := name + 'ip6.arpa';
\r
319 decodes DNS format name to a string. does not includes the root dot.
\r
320 doesnt read beyond len.
\r
321 empty result + non null failurereason: failure
\r
322 empty result + null failurereason: internal use
\r
324 function decodename(const packet:tdnspacket;len,start,recursion:integer;var numread:integer):ansistring;
\r
326 arr:array[0..sizeof(packet)-1] of byte absolute packet;
\r
332 if (start+numread < 0) or (start+numread >= len) then begin
\r
334 failurereason := 'decoding name: got out of range1';
\r
337 b := arr[start+numread];
\r
338 if b >= $c0 then begin
\r
339 {recursive sub call}
\r
340 if recursion > 10 then begin
\r
342 failurereason := 'decoding name: max recursion';
\r
345 if ((start+numread+1) >= len) then begin
\r
347 failurereason := 'decoding name: got out of range3';
\r
350 a := ((b shl 8) or arr[start+numread+1]) and $3fff;
\r
351 s := decodename(packet,len,a,recursion+1,a);
\r
352 if (s = '') and (failurereason <> '') then begin
\r
356 if result <> '' then result := result + '.';
\r
357 result := result + s;
\r
360 end else if b < 64 then begin
\r
361 if (numread <> 0) and (b <> 0) then result := result + '.';
\r
362 for a := start+numread+1 to start+numread+b do begin
\r
363 if (a >= len) then begin
\r
365 failurereason := 'decoding name: got out of range2';
\r
368 result := result + ansichar(arr[a]);
\r
372 if b = 0 then begin
\r
373 if (result = '') and (recursion = 0) then result := '.';
\r
374 exit; {reached end of name}
\r
377 failurereason := 'decoding name: read invalid char';
\r
384 {==============================================================================}
\r
386 function getrawfromrr(const rrp:trrpointer;len:integer):ansistring;
\r
388 setlength(result,htons(trr(rrp.p^).datalen));
\r
389 uniquestring(result);
\r
390 move(trr(rrp.p^).data,result[1],length(result));
\r
394 function getipfromrr(const rrp:trrpointer;len:integer):tbinip;
\r
396 fillchar(result,sizeof(result),0);
\r
397 case trr(rrp.p^).requesttype of
\r
399 if htons(trr(rrp.p^).datalen) <> 4 then exit;
\r
400 move(trr(rrp.p^).data,result.ip,4);
\r
401 result.family :=AF_INET;
\r
404 querytype_aaaa: begin
\r
405 if htons(trr(rrp.p^).datalen) <> 16 then exit;
\r
406 result.family := AF_INET6;
\r
407 move(trr(rrp.p^).data,result.ip6,16);
\r
415 procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);
\r
419 state.resultaction := action_done;
\r
420 state.resultstr := '';
\r
421 case trr(rrp.p^).requesttype of
\r
422 querytype_a{$ifdef ipv6},querytype_aaaa{$endif}: begin
\r
423 state.resultbin := getipfromrr(rrp,len);
\r
425 querytype_txt:begin
\r
426 {TXT returns a raw string}
\r
427 state.resultstr := copy(getrawfromrr(rrp,len),2,9999);
\r
428 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
431 {MX is a name after a 16 bits word}
\r
432 state.resultstr := decodename(state.recvpacket,state.recvpacketlen,taddrint(rrp.p)-taddrint(@state.recvpacket)+12,0,a);
\r
433 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
436 {other reply types (PTR, MX) return a hostname}
\r
437 state.resultstr := decodename(state.recvpacket,state.recvpacketlen,taddrint(rrp.p)-taddrint(@state.recvpacket)+10,0,a);
\r
438 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
442 procedure setstate_request_init(const name:ansistring;var state:tdnsstate);
\r
444 {destroy things properly}
\r
445 state.resultstr := '';
\r
446 state.queryname := '';
\r
447 state.rrdata := '';
\r
448 fillchar(state,sizeof(state),0);
\r
449 state.queryname := name;
\r
450 state.parsepacket := false;
\r
453 procedure setstate_forward(const name:ansistring;var state:tdnsstate;family:integer);
\r
455 setstate_request_init(name,state);
\r
456 state.forwardfamily := family;
\r
458 if family = AF_INET6 then state.requesttype := querytype_aaaa else
\r
460 state.requesttype := querytype_a;
\r
463 procedure setstate_reverse(const binip:tbinip;var state:tdnsstate);
\r
465 setstate_request_init(makereversename(binip),state);
\r
466 state.requesttype := querytype_ptr;
\r
469 procedure setstate_custom(const name:ansistring; requesttype:integer; var state:tdnsstate);
\r
471 setstate_request_init(name,state);
\r
472 state.requesttype := requesttype;
\r
476 procedure setstate_failure(var state:tdnsstate);
\r
478 state.resultstr := '';
\r
479 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
480 state.resultaction := action_done;
\r
483 procedure state_process(var state:tdnsstate);
\r
489 rrptemp:^trrpointer;
\r
491 if state.parsepacket then begin
\r
492 if state.recvpacketlen < 12 then begin
\r
493 failurereason := 'Undersized packet';
\r
494 state.resultaction := action_ignore;
\r
497 if state.id <> state.recvpacket.id then begin
\r
498 failurereason := 'ID mismatch';
\r
499 state.resultaction := action_ignore;
\r
503 for a := 0 to 3 do begin
\r
504 state.numrr1[a] := htons(state.recvpacket.rrcount[a]);
\r
505 if state.numrr1[a] > maxrrofakind then goto failure;
\r
506 inc(state.numrr2,state.numrr1[a]);
\r
509 setlength(state.rrdata,state.numrr2*sizeof(trrpointer));
\r
511 {- put all replies into a list}
\r
515 for a := 0 to state.numrr1[0]-1 do begin
\r
516 if (ofs < 12) or (ofs > state.recvpacketlen-4) then goto failure;
\r
517 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
518 rrptemp.p := @state.recvpacket.payload[ofs-12];
\r
519 rrptemp.ofs := ofs;
\r
520 decodename(state.recvpacket,state.recvpacketlen,ofs,0,b);
\r
521 rrptemp.len := b + 4;
\r
522 inc(ofs,rrptemp.len);
\r
525 for a := state.numrr1[0] to state.numrr2-1 do begin
\r
526 if (ofs < 12) or (ofs > state.recvpacketlen-12) then goto failure;
\r
527 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
528 if decodename(state.recvpacket,state.recvpacketlen,ofs,0,b) = '' then goto failure;
\r
529 rrtemp := @state.recvpacket.payload[ofs-12+b]; {rrtemp points to values and result, after initial name}
\r
530 rrptemp.p := rrtemp;
\r
531 rrptemp.ofs := ofs; {ofs is start of RR before initial name from start of packet}
\r
532 rrptemp.namelen := b;
\r
533 b := htons(rrtemp.datalen);
\r
534 rrptemp.len := b + 10 + rrptemp.namelen;
\r
535 inc(ofs,rrptemp.len);
\r
537 if (ofs <> state.recvpacketlen) then begin
\r
538 failurereason := 'ofs <> state.packetlen';
\r
542 {if we requested A or AAAA build a list of all replies}
\r
543 if (state.requesttype = querytype_a) or (state.requesttype = querytype_aaaa) then begin
\r
544 state.resultlist := biniplist_new;
\r
545 for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin
\r
546 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
547 rrtemp := rrptemp.p;
\r
549 if rrtemp.requesttype = state.requesttype then begin
\r
550 biniplist_add(state.resultlist,getipfromrr(rrptemp^,b));
\r
555 {- check for items of the requested type in answer section, if so return success first}
\r
556 for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin
\r
557 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
558 rrtemp := rrptemp.p;
\r
560 if rrtemp.requesttype = state.requesttype then begin
\r
561 setstate_return(rrptemp^,b,state);
\r
566 {if no items of correct type found, follow first cname in answer section}
\r
567 for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin
\r
568 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
569 rrtemp := rrptemp.p;
\r
571 if rrtemp.requesttype = querytype_cname then begin
\r
572 state.queryname := decodename(state.recvpacket,state.recvpacketlen,rrptemp.ofs+12,0,b);
\r
577 {no cnames found, no items of correct type found}
\r
578 if state.forwardfamily <> 0 then goto failure;
\r
582 {here it needs recursed lookup}
\r
583 {if needing to follow a cname, change state to do so}
\r
584 inc(state.recursioncount);
\r
585 if state.recursioncount > maxrecursion then goto failure;
\r
588 {here, a name needs to be resolved}
\r
589 if state.queryname = '' then begin
\r
590 failurereason := 'empty query name';
\r
594 {do /ets/hosts lookup here}
\r
595 state.sendpacketlen := buildrequest(state.queryname,state.sendpacket,state.requesttype);
\r
596 if state.sendpacketlen = 0 then begin
\r
597 failurereason := 'building request packet failed';
\r
600 state.id := state.sendpacket.id;
\r
601 state.resultaction := action_sendquery;
\r
605 setstate_failure(state);
\r
609 procedure populatednsserverlist;
\r
613 if assigned(dnsserverlag) then begin
\r
614 dnsserverlag.clear;
\r
616 dnsserverlag := tlist.Create;
\r
619 dnsserverlist := getsystemdnsservers;
\r
620 for a := biniplist_getcount(dnsserverlist)-1 downto 0 do dnsserverlag.Add(nil);
\r
623 procedure cleardnsservercache;
\r
625 if assigned(dnsserverlag) then begin
\r
626 dnsserverlag.destroy;
\r
627 dnsserverlag := nil;
\r
628 dnsserverlist := '';
\r
632 function getcurrentsystemnameserverbin(var id:integer):tbinip;
\r
636 {override the name server choice here, instead of overriding it whereever it's called
\r
637 setting ID to -1 causes it to be ignored in reportlag}
\r
638 if (overridednsserver <> '') then begin
\r
639 result := ipstrtobinf(overridednsserver);
\r
640 if result.family <> 0 then begin
\r
646 if not assigned(dnsserverlag) then populatednsserverlist;
\r
647 if dnsserverlag.count=0 then raise exception.create('no dns servers availible');
\r
649 if dnsserverlag.count >1 then begin
\r
650 for counter := dnsserverlag.count-1 downto 1 do begin
\r
651 if taddrint(dnsserverlag[counter]) < taddrint(dnsserverlag[id]) then id := counter;
\r
654 result := biniplist_get(dnsserverlist,id);
\r
657 function getcurrentsystemnameserver(var id:integer):ansistring;
\r
659 result := ipbintostr(getcurrentsystemnameserverbin(id));
\r
662 procedure reportlag(id:integer;lag:integer); //lag should be in microseconds and should be -1 to report a timeout
\r
667 if (id < 0) or (id >= dnsserverlag.count) then exit;
\r
668 if lag = -1 then lag := timeoutlag;
\r
669 for counter := 0 to dnsserverlag.count-1 do begin
\r
670 temp := taddrint(dnsserverlag[counter]) *15;
\r
671 if counter=id then temp := temp + lag;
\r
672 dnsserverlag[counter] := tobject(temp div 16);
\r
680 procedure initpreferredmode;
\r
685 ipmask_global,ipmask_6to4,ipmask_teredo:tbinip;
\r
688 if preferredmodeinited then exit;
\r
689 if useaf <> useaf_default then exit;
\r
690 l := getv6localips;
\r
691 if biniplist_getcount(l) = 0 then exit;
\r
692 useaf := useaf_preferv4;
\r
693 ipstrtobin('2000::',ipmask_global);
\r
694 ipstrtobin('2001::',ipmask_teredo);
\r
695 ipstrtobin('2002::',ipmask_6to4);
\r
696 {if there is any v6 IP which is globally routable and not 6to4 and not teredo, prefer v6}
\r
697 for a := biniplist_getcount(l)-1 downto 0 do begin
\r
698 ip := biniplist_get(l,a);
\r
699 if not comparebinipmask(ip,ipmask_global,3) then continue;
\r
700 if comparebinipmask(ip,ipmask_teredo,32) then continue;
\r
701 if comparebinipmask(ip,ipmask_6to4,16) then continue;
\r
702 useaf := useaf_preferv6;
\r
703 preferredmodeinited := true;
\r
711 { quick and dirty description of dns packet structure to aid writing and
\r
712 understanding of parser code, refer to appropriate RFCs for proper specs
\r
713 - all words are network order
\r
715 www.google.com A request:
\r
717 0, 2: random transaction ID
\r
718 2, 2: flags: only the "recursion desired" bit set. (bit 8 of word)
\r
720 6, 2: answer RR's: 0.
\r
721 8, 2: authority RR's: 0.
\r
722 10, 2: additional RR's: 0.
\r
725 #03 "www" #06 "google" #03 "com" #00
\r
726 size-4, 2: type: host address (1)
\r
727 size-2, 2: class: inet (1)
\r
731 0,2: random transaction ID
\r
732 2,2: flags: set: response (bit 15), recursion desired (8), recursion available (7)
\r
734 6,4: answer RR's: 2
\r
735 8,4: authority RR's: 9
\r
736 10,4: additional RR's: 9
\r
741 0,2 "c0 0c" "name: www.google.com"
\r
742 2,2 "00 05" "type: cname for an alias"
\r
743 4,2 "00 01" "class: inet"
\r
745 10,2: data length "00 17" (23)
\r
746 12: the cname name (www.google.akadns.net)
\r
749 2,2 "00 01" host address
\r
752 10,2: data length (4)
\r
754 authority - 9 records
\r
755 additional - 9 records
\r
761 4,2: class: inet (0001)
\r
763 10,2: data size (16)
\r
766 ptr request: query type 000c
\r
768 name compression: word "cxxx" in the name, xxx points to offset in the packet}
\r