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 mswindows}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
110 {the maximum number of RR of a kind of purely an extra sanity check and could be omitted.
\r
111 before, i set it to 20, but valid replies can have more. dnscore only does udp requests,
\r
112 and ordinary DNS, so up to 512 bytes. the maximum number of A records that fits seems to be 29}
\r
114 retryafter=300000; //microseconds must be less than one second;
\r
115 timeoutlag=1000000000; // penalty value to be treated as lag in the event of a timeout (microseconds)
\r
117 dvar=array[0..0] of byte;
\r
119 tdnspacket=packed record
\r
122 rrcount:array[0..3] of word;
\r
123 payload:array[0..511-12] of byte;
\r
130 recursioncount:integer;
\r
131 queryname:ansistring;
\r
133 parsepacket:boolean;
\r
134 resultstr:ansistring;
\r
136 resultlist:tbiniplist;
\r
137 resultaction:integer;
\r
138 numrr1:array[0..3] of integer;
\r
141 sendpacketlen:integer;
\r
142 sendpacket:tdnspacket;
\r
143 recvpacketlen:integer;
\r
144 recvpacket:tdnspacket;
\r
145 forwardfamily:integer;
\r
149 requesttypehi:byte;
\r
154 data:array[0..511] of byte;
\r
157 trrpointer=packed record
\r
164 //commenting out functions from interface that do not have documented semantics
\r
165 //and probablly should not be called from outside this unit, reenable them
\r
166 //if you must but please document them at the same time --plugwash
\r
168 //function buildrequest(const name:string;var packet:tdnspacket;requesttype:word):integer;
\r
170 //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
171 function makereversename(const binip:tbinip):ansistring;
\r
173 procedure setstate_request_init(const name:ansistring;var state:tdnsstate);
\r
175 //set up state for a foward lookup. A family value of AF_INET6 will give only
\r
176 //ipv6 results. Any other value will give only ipv4 results
\r
177 procedure setstate_forward(const name:ansistring;var state:tdnsstate;family:integer);
\r
179 procedure setstate_reverse(const binip:tbinip;var state:tdnsstate);
\r
180 procedure setstate_failure(var state:tdnsstate);
\r
181 //procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);
\r
183 //for custom raw lookups such as TXT, as desired by the user
\r
184 procedure setstate_custom(const name:ansistring; requesttype:integer; var state:tdnsstate);
\r
186 procedure state_process(var state:tdnsstate);
\r
188 //function decodename(const packet:tdnspacket;len,start,recursion:integer;var numread:integer):string;
\r
190 procedure populatednsserverlist;
\r
191 procedure cleardnsservercache;
\r
194 dnsserverlist : tbiniplist;
\r
195 dnsserverlag:tlist;
\r
196 // currentdnsserverno : integer;
\r
199 //getcurrentsystemnameserver returns the nameserver the app should use and sets
\r
200 //id to the id of that nameserver. id should later be used to report how laggy
\r
201 //the servers response was and if it was timed out.
\r
202 function getcurrentsystemnameserver(var id:integer) :ansistring;
\r
203 function getcurrentsystemnameserverbin(var id:integer) :tbinip;
\r
204 procedure reportlag(id:integer;lag:integer); //lag should be in microseconds and should be -1 to report a timeout
\r
207 // unixnameservercache:string;
\r
212 procedure initpreferredmode;
\r
215 preferredmodeinited:boolean;
\r
220 failurereason:ansistring;
\r
222 function getquerytype(s:ansistring):integer;
\r
232 function getquerytype(s:ansistring):integer;
\r
236 if (s = 'A') then result := querytype_a else
\r
237 if (s = 'CNAME') then result := querytype_cname else
\r
238 if (s = 'AAAA') then result := querytype_aaaa else
\r
239 if (s = 'PTR') then result := querytype_ptr else
\r
240 if (s = 'NS') then result := querytype_ns else
\r
241 if (s = 'MX') then result := querytype_mx else
\r
242 if (s = 'A6') then result := querytype_a6 else
\r
243 if (s = 'TXT') then result := querytype_txt else
\r
244 if (s = 'SOA') then result := querytype_soa else
\r
245 if (s = 'SPF') then result := querytype_spf;
\r
248 function buildrequest(const name:ansistring;var packet:tdnspacket;requesttype:word):integer;
\r
252 arr:array[0..sizeof(packet)-1] of byte absolute packet;
\r
254 { writeln('buildrequest: name: ',name);}
\r
256 fillchar(packet,sizeof(packet),0);
\r
257 packet.id := randominteger($10000);
\r
259 packet.flags := htons($0100);
\r
260 packet.rrcount[0] := htons($0001);
\r
263 s := copy(name,1,maxnamelength);
\r
264 if s = '' then exit;
\r
265 if s[length(s)] <> '.' then s := s + '.';
\r
268 if (s = '.') then begin
\r
269 packet.payload[0] := 0;
\r
272 for a := 1 to length(s) do begin
\r
273 if s[a] = '.' then begin
\r
274 if b > maxnamefieldlen then exit;
\r
275 if (b = 0) then exit;
\r
276 packet.payload[a-b-1] := b;
\r
279 packet.payload[a] := byte(s[a]);
\r
283 if b > maxnamefieldlen then exit;
\r
284 packet.payload[length(s)-b] := b;
\r
285 result := length(s) + 12+5;
\r
288 arr[result-1] := 1;
\r
289 arr[result-3] := requesttype and $ff;
\r
290 arr[result-4] := requesttype shr 8;
\r
293 function makereversename(const binip:tbinip):ansistring;
\r
299 if binip.family = AF_INET then begin
\r
300 b := htonl(binip.ip);
\r
301 for a := 0 to 3 do begin
\r
302 name := name + inttostr(b shr (a shl 3) and $ff)+'.';
\r
304 name := name + 'in-addr.arpa';
\r
307 if binip.family = AF_INET6 then begin
\r
308 for a := 15 downto 0 do begin
\r
309 b := binip.ip6.u6_addr8[a];
\r
310 name := name + hexchars[b and $f]+'.'+hexchars[b shr 4]+'.';
\r
312 name := name + 'ip6.arpa';
\r
322 decodes DNS format name to a string. does not includes the root dot.
\r
323 doesnt read beyond len.
\r
324 empty result + non null failurereason: failure
\r
325 empty result + null failurereason: internal use
\r
327 function decodename(const packet:tdnspacket;len,start,recursion:integer;var numread:integer):ansistring;
\r
329 arr:array[0..sizeof(packet)-1] of byte absolute packet;
\r
335 if (start+numread < 0) or (start+numread >= len) then begin
\r
337 failurereason := 'decoding name: got out of range1';
\r
340 b := arr[start+numread];
\r
341 if b >= $c0 then begin
\r
342 {recursive sub call}
\r
343 if recursion > 10 then begin
\r
345 failurereason := 'decoding name: max recursion';
\r
348 if ((start+numread+1) >= len) then begin
\r
350 failurereason := 'decoding name: got out of range3';
\r
353 a := ((b shl 8) or arr[start+numread+1]) and $3fff;
\r
354 s := decodename(packet,len,a,recursion+1,a);
\r
355 if (s = '') and (failurereason <> '') then begin
\r
359 if result <> '' then result := result + '.';
\r
360 result := result + s;
\r
363 end else if b < 64 then begin
\r
364 if (numread <> 0) and (b <> 0) then result := result + '.';
\r
365 for a := start+numread+1 to start+numread+b do begin
\r
366 if (a >= len) then begin
\r
368 failurereason := 'decoding name: got out of range2';
\r
371 result := result + ansichar(arr[a]);
\r
375 if b = 0 then begin
\r
376 if (result = '') and (recursion = 0) then result := '.';
\r
377 exit; {reached end of name}
\r
380 failurereason := 'decoding name: read invalid char';
\r
387 {==============================================================================}
\r
389 function getrawfromrr(const rrp:trrpointer;len:integer):ansistring;
\r
391 setlength(result,htons(trr(rrp.p^).datalen));
\r
392 uniquestring(result);
\r
393 move(trr(rrp.p^).data,result[1],length(result));
\r
397 function getipfromrr(const rrp:trrpointer;len:integer):tbinip;
\r
399 fillchar(result,sizeof(result),0);
\r
400 case trr(rrp.p^).requesttype of
\r
402 if htons(trr(rrp.p^).datalen) <> 4 then exit;
\r
403 move(trr(rrp.p^).data,result.ip,4);
\r
404 result.family :=AF_INET;
\r
407 querytype_aaaa: begin
\r
408 if htons(trr(rrp.p^).datalen) <> 16 then exit;
\r
409 result.family := AF_INET6;
\r
410 move(trr(rrp.p^).data,result.ip6,16);
\r
418 procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);
\r
422 state.resultaction := action_done;
\r
423 state.resultstr := '';
\r
424 case trr(rrp.p^).requesttype of
\r
425 querytype_a{$ifdef ipv6},querytype_aaaa{$endif}: begin
\r
426 state.resultbin := getipfromrr(rrp,len);
\r
428 querytype_txt:begin
\r
429 {TXT returns a raw string}
\r
430 state.resultstr := copy(getrawfromrr(rrp,len),2,9999);
\r
431 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
434 {MX is a name after a 16 bits word}
\r
435 state.resultstr := decodename(state.recvpacket,state.recvpacketlen,taddrint(rrp.p)-taddrint(@state.recvpacket)+12,0,a);
\r
436 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
439 {other reply types (PTR, MX) return a hostname}
\r
440 state.resultstr := decodename(state.recvpacket,state.recvpacketlen,taddrint(rrp.p)-taddrint(@state.recvpacket)+10,0,a);
\r
441 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
445 procedure setstate_request_init(const name:ansistring;var state:tdnsstate);
\r
447 {destroy things properly}
\r
448 state.resultstr := '';
\r
449 state.queryname := '';
\r
450 state.rrdata := '';
\r
451 fillchar(state,sizeof(state),0);
\r
452 state.queryname := name;
\r
453 state.parsepacket := false;
\r
456 procedure setstate_forward(const name:ansistring;var state:tdnsstate;family:integer);
\r
458 setstate_request_init(name,state);
\r
459 state.forwardfamily := family;
\r
461 if family = AF_INET6 then state.requesttype := querytype_aaaa else
\r
463 state.requesttype := querytype_a;
\r
466 procedure setstate_reverse(const binip:tbinip;var state:tdnsstate);
\r
468 setstate_request_init(makereversename(binip),state);
\r
469 state.requesttype := querytype_ptr;
\r
472 procedure setstate_custom(const name:ansistring; requesttype:integer; var state:tdnsstate);
\r
474 setstate_request_init(name,state);
\r
475 state.requesttype := requesttype;
\r
479 procedure setstate_failure(var state:tdnsstate);
\r
481 state.resultstr := '';
\r
482 fillchar(state.resultbin,sizeof(state.resultbin),0);
\r
483 state.resultaction := action_done;
\r
486 procedure state_process(var state:tdnsstate);
\r
492 rrptemp:^trrpointer;
\r
494 if state.parsepacket then begin
\r
495 if state.recvpacketlen < 12 then begin
\r
496 failurereason := 'Undersized packet';
\r
497 state.resultaction := action_ignore;
\r
500 if state.id <> state.recvpacket.id then begin
\r
501 failurereason := 'ID mismatch';
\r
502 state.resultaction := action_ignore;
\r
506 for a := 0 to 3 do begin
\r
507 state.numrr1[a] := htons(state.recvpacket.rrcount[a]);
\r
508 if state.numrr1[a] > maxrrofakind then begin
\r
509 failurereason := 'exceeded maximum RR of a kind';
\r
512 inc(state.numrr2,state.numrr1[a]);
\r
515 setlength(state.rrdata,state.numrr2*sizeof(trrpointer));
\r
517 {- put all replies into a list}
\r
521 for a := 0 to state.numrr1[0]-1 do begin
\r
522 if (ofs < 12) or (ofs > state.recvpacketlen-4) then goto failure;
\r
523 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
524 rrptemp.p := @state.recvpacket.payload[ofs-12];
\r
525 rrptemp.ofs := ofs;
\r
526 decodename(state.recvpacket,state.recvpacketlen,ofs,0,b);
\r
527 rrptemp.len := b + 4;
\r
528 inc(ofs,rrptemp.len);
\r
531 for a := state.numrr1[0] to state.numrr2-1 do begin
\r
532 if (ofs < 12) or (ofs > state.recvpacketlen-12) then goto failure;
\r
533 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
534 if decodename(state.recvpacket,state.recvpacketlen,ofs,0,b) = '' then goto failure;
\r
535 rrtemp := @state.recvpacket.payload[ofs-12+b]; {rrtemp points to values and result, after initial name}
\r
536 rrptemp.p := rrtemp;
\r
537 rrptemp.ofs := ofs; {ofs is start of RR before initial name from start of packet}
\r
538 rrptemp.namelen := b;
\r
539 b := htons(rrtemp.datalen);
\r
540 rrptemp.len := b + 10 + rrptemp.namelen;
\r
541 inc(ofs,rrptemp.len);
\r
543 if (ofs <> state.recvpacketlen) then begin
\r
544 failurereason := 'ofs <> state.packetlen';
\r
548 {if we requested A or AAAA build a list of all replies}
\r
549 if (state.requesttype = querytype_a) or (state.requesttype = querytype_aaaa) then begin
\r
550 state.resultlist := biniplist_new;
\r
551 for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin
\r
552 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
553 rrtemp := rrptemp.p;
\r
555 if rrtemp.requesttype = state.requesttype then begin
\r
556 biniplist_add(state.resultlist,getipfromrr(rrptemp^,b));
\r
561 {- check for items of the requested type in answer section, if so return success first}
\r
562 for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin
\r
563 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
564 rrtemp := rrptemp.p;
\r
566 if rrtemp.requesttype = state.requesttype then begin
\r
567 setstate_return(rrptemp^,b,state);
\r
572 {if no items of correct type found, follow first cname in answer section}
\r
573 for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin
\r
574 rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];
\r
575 rrtemp := rrptemp.p;
\r
577 if rrtemp.requesttype = querytype_cname then begin
\r
578 state.queryname := decodename(state.recvpacket,state.recvpacketlen,rrptemp.ofs+12,0,b);
\r
583 {no cnames found, no items of correct type found}
\r
584 if state.forwardfamily <> 0 then goto failure;
\r
588 {here it needs recursed lookup}
\r
589 {if needing to follow a cname, change state to do so}
\r
590 inc(state.recursioncount);
\r
591 if state.recursioncount > maxrecursion then goto failure;
\r
594 {here, a name needs to be resolved}
\r
595 if state.queryname = '' then begin
\r
596 failurereason := 'empty query name';
\r
600 {do /ets/hosts lookup here}
\r
601 state.sendpacketlen := buildrequest(state.queryname,state.sendpacket,state.requesttype);
\r
602 if state.sendpacketlen = 0 then begin
\r
603 failurereason := 'building request packet failed';
\r
606 state.id := state.sendpacket.id;
\r
607 state.resultaction := action_sendquery;
\r
611 setstate_failure(state);
\r
615 procedure populatednsserverlist;
\r
619 if assigned(dnsserverlag) then begin
\r
620 dnsserverlag.clear;
\r
622 dnsserverlag := tlist.Create;
\r
625 dnsserverlist := getsystemdnsservers;
\r
626 for a := biniplist_getcount(dnsserverlist)-1 downto 0 do dnsserverlag.Add(nil);
\r
629 procedure cleardnsservercache;
\r
631 if assigned(dnsserverlag) then begin
\r
632 dnsserverlag.destroy;
\r
633 dnsserverlag := nil;
\r
634 dnsserverlist := '';
\r
638 function getcurrentsystemnameserverbin(var id:integer):tbinip;
\r
642 {override the name server choice here, instead of overriding it whereever it's called
\r
643 setting ID to -1 causes it to be ignored in reportlag}
\r
644 if (overridednsserver <> '') then begin
\r
645 result := ipstrtobinf(overridednsserver);
\r
646 if result.family <> 0 then begin
\r
652 if not assigned(dnsserverlag) then populatednsserverlist;
\r
653 if dnsserverlag.count=0 then raise exception.create('no dns servers availible');
\r
655 if dnsserverlag.count >1 then begin
\r
656 for counter := dnsserverlag.count-1 downto 1 do begin
\r
657 if taddrint(dnsserverlag[counter]) < taddrint(dnsserverlag[id]) then id := counter;
\r
660 result := biniplist_get(dnsserverlist,id);
\r
663 function getcurrentsystemnameserver(var id:integer):ansistring;
\r
665 result := ipbintostr(getcurrentsystemnameserverbin(id));
\r
668 procedure reportlag(id:integer;lag:integer); //lag should be in microseconds and should be -1 to report a timeout
\r
673 if (id < 0) or (id >= dnsserverlag.count) then exit;
\r
674 if lag = -1 then lag := timeoutlag;
\r
675 for counter := 0 to dnsserverlag.count-1 do begin
\r
676 temp := taddrint(dnsserverlag[counter]) *15;
\r
677 if counter=id then temp := temp + lag;
\r
678 dnsserverlag[counter] := tobject(temp div 16);
\r
686 procedure initpreferredmode;
\r
691 ipmask_global,ipmask_6to4,ipmask_teredo:tbinip;
\r
694 if preferredmodeinited then exit;
\r
695 if useaf <> useaf_default then exit;
\r
696 l := getv6localips;
\r
697 if biniplist_getcount(l) = 0 then exit;
\r
698 useaf := useaf_preferv4;
\r
699 ipstrtobin('2000::',ipmask_global);
\r
700 ipstrtobin('2001::',ipmask_teredo);
\r
701 ipstrtobin('2002::',ipmask_6to4);
\r
702 {if there is any v6 IP which is globally routable and not 6to4 and not teredo, prefer v6}
\r
703 for a := biniplist_getcount(l)-1 downto 0 do begin
\r
704 ip := biniplist_get(l,a);
\r
705 if not comparebinipmask(ip,ipmask_global,3) then continue;
\r
706 if comparebinipmask(ip,ipmask_teredo,32) then continue;
\r
707 if comparebinipmask(ip,ipmask_6to4,16) then continue;
\r
708 useaf := useaf_preferv6;
\r
709 preferredmodeinited := true;
\r
717 { quick and dirty description of dns packet structure to aid writing and
\r
718 understanding of parser code, refer to appropriate RFCs for proper specs
\r
719 - all words are network order
\r
721 www.google.com A request:
\r
723 0, 2: random transaction ID
\r
724 2, 2: flags: only the "recursion desired" bit set. (bit 8 of word)
\r
726 6, 2: answer RR's: 0.
\r
727 8, 2: authority RR's: 0.
\r
728 10, 2: additional RR's: 0.
\r
731 #03 "www" #06 "google" #03 "com" #00
\r
732 size-4, 2: type: host address (1)
\r
733 size-2, 2: class: inet (1)
\r
737 0,2: random transaction ID
\r
738 2,2: flags: set: response (bit 15), recursion desired (8), recursion available (7)
\r
740 6,4: answer RR's: 2
\r
741 8,4: authority RR's: 9
\r
742 10,4: additional RR's: 9
\r
747 0,2 "c0 0c" "name: www.google.com"
\r
748 2,2 "00 05" "type: cname for an alias"
\r
749 4,2 "00 01" "class: inet"
\r
751 10,2: data length "00 17" (23)
\r
752 12: the cname name (www.google.akadns.net)
\r
755 2,2 "00 01" host address
\r
758 10,2: data length (4)
\r
760 authority - 9 records
\r
761 additional - 9 records
\r
767 4,2: class: inet (0001)
\r
769 10,2: data size (16)
\r
772 ptr request: query type 000c
\r
774 name compression: word "cxxx" in the name, xxx points to offset in the packet}
\r