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
6 //FIXME: this code only ever seems to use one dns server for a request and does
\r
7 //not seem to have any form of retry code.
\r
15 {$include lcoreconfig.inc}
\r
18 {$ifdef winasyncdns}
\r
22 classes,binipstuff,dnscore,btime,lcorernd;
\r
25 numsock=1{$ifdef ipv6}+1{$endif};
\r
29 //after completion or cancelation a dnswinasync may be reused
\r
30 tdnsasync=class(tcomponent)
\r
33 //made a load of stuff private that does not appear to be part of the main
\r
34 //public interface. If you make any of it public again please consider the
\r
35 //consequences when using windows dns. --plugwash.
\r
36 sockets: array[0..numsock-1] of tlsocket;
\r
38 states: array[0..numsock-1] of tdnsstate;
\r
40 destinations: array[0..numsock-1] of tbinip;
\r
42 dnsserverids : array[0..numsock-1] of integer;
\r
44 {$ifdef winasyncdns}
\r
45 dwas : tdnswinasync;
\r
48 numsockused : integer;
\r
49 fresultlist : tbiniplist;
\r
50 requestaf : integer;
\r
51 procedure asyncprocess(socketno:integer);
\r
52 procedure receivehandler(sender:tobject;error:word);
\r
53 function sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;
\r
54 {$ifdef winasyncdns}
\r
55 procedure winrequestdone(sender:tobject;error:word);
\r
59 onrequestdone:tsocketevent;
\r
61 //addr and port allow the application to specify a dns server specifically
\r
62 //for this dnsasync object. This is not a recommended mode of operation
\r
63 //because it limits the app to one dns server but is kept for compatibility
\r
65 addr,port:ansistring;
\r
67 overrideaf : integer;
\r
69 procedure cancel;//cancel an outstanding dns request
\r
70 function dnsresult:ansistring; //get result of dnslookup as a string
\r
71 procedure dnsresultbin(var binip:tbinip); //get result of dnslookup as a tbinip
\r
72 property dnsresultlist : tbiniplist read fresultlist;
\r
73 procedure forwardlookup(const name:ansistring); //start forward lookup,
\r
75 procedure reverselookup(const binip:tbinip); //start reverse lookup
\r
76 procedure customlookup(const name:ansistring;querytype:integer); //start custom type lookup
\r
78 constructor create(aowner:tcomponent); override;
\r
79 destructor destroy; override;
\r
83 //function that the app can use to know whether the builtin or system resolver is being used
\r
84 function willusebuiltindns_async:boolean;
\r
91 function willusebuiltindns_async:boolean;
\r
94 {$ifdef winasyncdns}if usewindns and (overridednsserver = '') and not (hostsfile_disabled or hostsfile_onlylocalhost) then result := false;{$endif}
\r
98 constructor tdnsasync.create;
\r
100 inherited create(aowner);
\r
101 dnsserverids[0] := -1;
\r
102 sockets[0] := twsocket.create(self);
\r
103 sockets[0].tag := 0;
\r
105 dnsserverids[1] := -1;
\r
106 sockets[1] := twsocket.Create(self);
\r
107 sockets[1].tag := 1;
\r
111 destructor tdnsasync.destroy;
\r
113 socketno : integer;
\r
115 for socketno := 0 to numsock -1 do begin
\r
116 if assigned(sockets[socketno]) then begin
\r
117 if dnsserverids[socketno] >= 0 then begin
\r
118 reportlag(dnsserverids[socketno],-1);
\r
119 dnsserverids[socketno] := -1;
\r
121 sockets[socketno].release;
\r
122 setstate_request_init('',states[socketno]);
\r
126 {$ifdef winasyncdns}
\r
127 if assigned(dwas) then begin
\r
136 procedure tdnsasync.receivehandler(sender:tobject;error:word);
\r
138 socketno : integer;
\r
139 Src : TInetSockAddrV;
\r
142 fromport:ansistring;
\r
144 socketno := tlsocket(sender).tag;
\r
145 //writeln('got a reply on socket number ',socketno);
\r
146 fillchar(states[socketno].recvpacket,sizeof(states[socketno].recvpacket),0);
\r
148 SrcLen := SizeOf(Src);
\r
149 states[socketno].recvpacketlen := twsocket(sender).ReceiveFrom(@(states[socketno].recvpacket), SizeOf(states[socketno].recvpacket), Src, SrcLen);
\r
151 fromip := inaddrvtobinip(Src);
\r
152 fromport := inttostr(htons(src.InAddr.port));
\r
154 if ((not comparebinip(fromip,destinations[socketno])) or (fromport <> port)) then begin
\r
155 // writeln('dnsasync received from wrong IP:port ',ipbintostr(fromip),'#',fromport,', expected ',ipbintostr(destinations[socketno]),'#',port);
\r
159 states[socketno].parsepacket := true;
\r
160 if states[socketno].resultaction <> action_done then begin
\r
161 //we ignore packets that come after we are done
\r
162 if dnsserverids[socketno] >= 0 then begin
\r
163 reportlag(dnsserverids[socketno],trunc((wintimefloat-startts)*1000000));
\r
164 dnsserverids[socketno] := -1;
\r
166 { writeln('received reply');}
\r
168 asyncprocess(socketno);
\r
169 //writeln('processed it');
\r
171 //writeln('ignored it because request is done');
\r
175 function tdnsasync.sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;
\r
177 destination : tbinip;
\r
178 inaddr : tinetsockaddrv;
\r
179 trytolisten:integer;
\r
181 { writeln('sendquery ',decodename(state.packet,state.packetlen,12,0,a),' ',state.requesttype);}
\r
182 //writeln('trying to send query on socket number ',socketno);
\r
184 if len = 0 then exit; {no packet}
\r
185 if sockets[socketno].state <> wsconnected then begin
\r
186 startts := wintimefloat;
\r
187 if port = '' then port := '53';
\r
188 sockets[socketno].Proto := 'udp';
\r
189 sockets[socketno].ondataavailable := receivehandler;
\r
191 {we are going to bind on a random local port for the DNS request, against the kaminsky attack
\r
192 there is a small chance that we're trying to bind on an already used port, so retry a few times}
\r
193 for trytolisten := 3 downto 0 do begin
\r
195 sockets[socketno].port := inttostr(1024 + randominteger(65536 - 1024));
\r
196 sockets[socketno].listen;
\r
198 {writeln('failed to listen ',sockets[socketno].localport,' ',trytolisten);}
\r
199 if (trytolisten = 0) then begin
\r
207 if addr <> '' then begin
\r
208 dnsserverids[socketno] := -1;
\r
209 destination := ipstrtobinf(addr);
\r
211 destination := getcurrentsystemnameserverbin(dnsserverids[socketno]);
\r
213 destinations[socketno] := destination;
\r
215 {$ifdef ipv6}{$ifdef mswindows}
\r
216 if destinations[socketno].family = AF_INET6 then if (requestaf = useaf_default) then requestaf := useaf_preferv6;
\r
219 makeinaddrv(destinations[socketno],port,inaddr);
\r
220 sockets[socketno].sendto(inaddr,sizeof(inaddr), @packet,len);
\r
226 procedure tdnsasync.asyncprocess(socketno:integer);
\r
228 state_process(states[socketno]);
\r
229 case states[socketno].resultaction of
\r
230 action_ignore: begin {do nothing} end;
\r
233 if (numsockused = 1) or (states[socketno xor 1].resultaction=action_done) then
\r
234 //if using two sockets we need to wait until both sockets are in the done
\r
235 //state before firing the event
\r
238 fresultlist := biniplist_new;
\r
239 if (numsockused = 1) then begin
\r
240 //writeln('processing for one state');
\r
241 biniplist_addlist(fresultlist,states[0].resultlist);
\r
243 end else if (requestaf = useaf_preferv6) then begin
\r
244 //writeln('processing for two states, ipv6 preference');
\r
245 //writeln('merging lists '+biniplist_tostr(states[1].resultlist)+' and '+biniplist_tostr(states[0].resultlist));
\r
246 biniplist_addlist(fresultlist,states[1].resultlist);
\r
247 biniplist_addlist(fresultlist,states[0].resultlist);
\r
249 //writeln('processing for two states, ipv4 preference');
\r
250 biniplist_addlist(fresultlist,states[0].resultlist);
\r
251 biniplist_addlist(fresultlist,states[1].resultlist);
\r
254 //writeln(biniplist_tostr(fresultlist));
\r
255 onrequestdone(self,0);
\r
258 action_sendquery:begin
\r
259 sendquery(socketno,states[socketno].sendpacket,states[socketno].sendpacketlen);
\r
264 procedure tdnsasync.forwardlookup;
\r
268 willusewindns:boolean;
\r
270 ipstrtobin(name,bip);
\r
272 if bip.family <> 0 then begin
\r
273 // it was an IP address
\r
274 fresultlist := biniplist_new;
\r
275 biniplist_add(fresultlist,bip);
\r
276 onrequestdone(self,0);
\r
280 willusewindns := false;
\r
281 {$ifdef winasyncdns}
\r
282 if usewindns and (addr = '') and (overridednsserver = '') and not (hostsfile_disabled or hostsfile_onlylocalhost) then willusewindns := true;
\r
285 if overrideaf = useaf_default then begin
\r
287 if not willusewindns then initpreferredmode;
\r
289 requestaf := useaf;
\r
291 requestaf := overrideaf;
\r
294 {$ifdef winasyncdns}
\r
295 if willusewindns then begin
\r
296 dwas := tdnswinasync.create;
\r
297 dwas.onrequestdone := winrequestdone;
\r
299 dwas.forwardlookup(name);
\r
305 if (((overridednsserver = '') and (addr = '')) or hostsfile_alsocustomserver) and (not hostsfile_disabled) then begin
\r
306 //try a hosts file lookup
\r
307 fresultlist := hostsfile_forwardlookuplist(name);
\r
308 if (biniplist_getcount(fresultlist) > 0) then begin
\r
309 onrequestdone(self,0);
\r
315 fresultlist := biniplist_new;
\r
316 if (requestaf <> useaf_v6) then begin
\r
317 setstate_forward(name,states[numsockused],af_inet);
\r
322 if (requestaf <> useaf_v4) then begin
\r
323 setstate_forward(name,states[numsockused],af_inet6);
\r
328 for i := 0 to numsockused-1 do begin
\r
333 procedure tdnsasync.reverselookup;
\r
335 {$ifdef winasyncdns}
\r
336 if usewindns and (addr = '') and (overridednsserver = '') and not (hostsfile_disabled or hostsfile_onlylocalhost) then begin
\r
337 dwas := tdnswinasync.create;
\r
338 dwas.onrequestdone := winrequestdone;
\r
339 dwas.reverselookup(binip);
\r
344 if (((overridednsserver = '') and (addr = '')) or hostsfile_alsocustomserver) and (not hostsfile_disabled) then begin
\r
345 //try a hosts file lookup
\r
346 states[0].resultstr := hostsfile_reverselookup(binip);
\r
347 if (states[0].resultstr <> '') then begin
\r
348 onrequestdone(self,0);
\r
353 setstate_reverse(binip,states[0]);
\r
358 procedure tdnsasync.customlookup;
\r
360 setstate_custom(name,querytype,states[0]);
\r
365 function tdnsasync.dnsresult;
\r
367 if states[0].resultstr <> '' then result := states[0].resultstr else begin
\r
368 result := ipbintostr(biniplist_get(fresultlist,0));
\r
372 procedure tdnsasync.dnsresultbin(var binip:tbinip);
\r
374 binip := biniplist_get(fresultlist,0);
\r
377 procedure tdnsasync.cancel;
\r
379 socketno : integer;
\r
381 {$ifdef winasyncdns}
\r
382 if assigned(dwas) then begin
\r
388 for socketno := 0 to numsock-1 do begin
\r
389 reportlag(dnsserverids[socketno],-1);
\r
390 dnsserverids[socketno] := -1;
\r
392 sockets[socketno].close;
\r
396 for socketno := 0 to numsock-1 do begin
\r
397 setstate_failure(states[socketno]);
\r
400 fresultlist := biniplist_new;
\r
401 onrequestdone(self,0);
\r
404 {$ifdef winasyncdns}
\r
405 procedure tdnsasync.winrequestdone(sender:tobject;error:word);
\r
408 if dwas.reverse then begin
\r
409 states[0].resultstr := dwas.name;
\r
413 if (requestaf = useaf_preferv4) then begin
\r
414 {prefer mode: sort the IP's}
\r
415 fresultlist := biniplist_new;
\r
416 addipsoffamily(fresultlist,dwas.iplist,af_inet);
\r
417 addipsoffamily(fresultlist,dwas.iplist,af_inet6);
\r
419 end else if (requestaf = useaf_preferv6) then begin
\r
420 {prefer mode: sort the IP's}
\r
421 fresultlist := biniplist_new;
\r
422 addipsoffamily(fresultlist,dwas.iplist,af_inet6);
\r
423 addipsoffamily(fresultlist,dwas.iplist,af_inet);
\r
428 fresultlist := dwas.iplist;
\r
433 onrequestdone(self,error);
\r