01bc2ffa71a55f993b4a6b1fd483db0aa8e917de
[lcore.git] / dnsasync.pas
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
5 \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
8 \r
9 unit dnsasync;\r
10 \r
11 interface\r
12 \r
13 uses\r
14   {$ifdef mswindows}\r
15     dnswin,\r
16   {$endif}\r
17   lsocket,lcore,\r
18   classes,binipstuff,dnscore,btime,lcorernd;\r
19 \r
20 {$include lcoreconfig.inc}\r
21 \r
22 const\r
23   numsock=1{$ifdef ipv6}+1{$endif};\r
24 \r
25 type\r
26 \r
27   //after completion or cancelation a dnswinasync may be reused\r
28   tdnsasync=class(tcomponent)\r
29 \r
30   private\r
31     //made a load of stuff private that does not appear to be part of the main\r
32     //public interface. If you make any of it public again please consider the\r
33     //consequences when using windows dns. --plugwash.\r
34     sockets: array[0..numsock-1] of tlsocket;\r
35 \r
36     states: array[0..numsock-1] of tdnsstate;\r
37 \r
38     destinations: array[0..numsock-1] of tbinip;\r
39 \r
40     dnsserverids : array[0..numsock-1] of integer;\r
41     startts:double;\r
42     {$ifdef mswindows}\r
43       dwas : tdnswinasync;\r
44     {$endif}\r
45 \r
46     numsockused : integer;\r
47     fresultlist : tbiniplist;\r
48     requestaf : integer;\r
49     procedure asyncprocess(socketno:integer);\r
50     procedure receivehandler(sender:tobject;error:word);\r
51     function sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;\r
52     {$ifdef mswindows}\r
53       procedure winrequestdone(sender:tobject;error:word);\r
54     {$endif}\r
55 \r
56   public\r
57     onrequestdone:tsocketevent;\r
58 \r
59     //addr and port allow the application to specify a dns server specifically\r
60     //for this dnsasync object. This is not a reccomended mode of operation\r
61     //because it limits the app to one dns server but is kept for compatibility\r
62     //and special uses.\r
63     addr,port:ansistring;\r
64 \r
65     overrideaf : integer;\r
66 \r
67     procedure cancel;//cancel an outstanding dns request\r
68     function dnsresult:ansistring; //get result of dnslookup as a string\r
69     procedure dnsresultbin(var binip:tbinip); //get result of dnslookup as a tbinip\r
70     property dnsresultlist : tbiniplist read fresultlist;\r
71     procedure forwardlookup(const name:ansistring); //start forward lookup,\r
72                                                 //preffering ipv4\r
73     procedure reverselookup(const binip:tbinip); //start reverse lookup\r
74     procedure customlookup(const name:ansistring;querytype:integer); //start custom type lookup\r
75 \r
76     constructor create(aowner:tcomponent); override;\r
77     destructor destroy; override;\r
78 \r
79   end;\r
80 \r
81 implementation\r
82 \r
83 uses sysutils;\r
84 \r
85 constructor tdnsasync.create;\r
86 begin\r
87   inherited create(aowner);\r
88   dnsserverids[0] := -1;\r
89   sockets[0] := twsocket.create(self);\r
90   sockets[0].tag := 0;\r
91   {$ifdef ipv6}\r
92     dnsserverids[1] := -1;\r
93     sockets[1] := twsocket.Create(self);\r
94     sockets[1].tag := 1;\r
95   {$endif}\r
96 end;\r
97 \r
98 destructor tdnsasync.destroy;\r
99 var\r
100   socketno : integer;\r
101 begin\r
102   for socketno := 0 to numsock -1 do begin\r
103     if assigned(sockets[socketno]) then begin\r
104       if dnsserverids[socketno] >= 0 then begin\r
105         reportlag(dnsserverids[socketno],-1);\r
106         dnsserverids[socketno] := -1;\r
107       end;\r
108       sockets[socketno].release;\r
109       setstate_request_init('',states[socketno]);\r
110     end;\r
111   end;\r
112 \r
113   {$ifdef mswindows}\r
114   if assigned(dwas) then begin\r
115     dwas.release;\r
116     dwas := nil;\r
117   end;\r
118   {$endif}\r
119 \r
120   inherited destroy;\r
121 end;\r
122 \r
123 procedure tdnsasync.receivehandler(sender:tobject;error:word);\r
124 var\r
125   socketno : integer;\r
126   Src    : TInetSockAddrV;\r
127   SrcLen : Integer;\r
128   fromip:tbinip;\r
129   fromport:ansistring;\r
130 begin\r
131   socketno := tlsocket(sender).tag;\r
132   //writeln('got a reply on socket number ',socketno);\r
133   fillchar(states[socketno].recvpacket,sizeof(states[socketno].recvpacket),0);\r
134 \r
135   SrcLen := SizeOf(Src);\r
136   states[socketno].recvpacketlen := twsocket(sender).ReceiveFrom(@(states[socketno].recvpacket), SizeOf(states[socketno].recvpacket), Src, SrcLen);\r
137 \r
138   fromip := inaddrvtobinip(Src);\r
139   fromport := inttostr(htons(src.InAddr.port));\r
140 \r
141   if ((not comparebinip(fromip,destinations[socketno])) or (fromport <> port)) then begin\r
142    // writeln('dnsasync received from wrong IP:port ',ipbintostr(fromip),'#',fromport,', expected ',ipbintostr(destinations[socketno]),'#',port);\r
143     exit;\r
144   end;\r
145 \r
146   states[socketno].parsepacket := true;\r
147   if states[socketno].resultaction <> action_done then begin\r
148     //we ignore packets that come after we are done\r
149     if dnsserverids[socketno] >= 0 then begin\r
150       reportlag(dnsserverids[socketno],trunc((unixtimefloat-startts)*1000000));\r
151       dnsserverids[socketno] := -1;\r
152     end;\r
153   {  writeln('received reply');}\r
154 \r
155     asyncprocess(socketno);\r
156     //writeln('processed it');\r
157   end else begin\r
158     //writeln('ignored it because request is done');\r
159   end;\r
160 end;\r
161 \r
162 function tdnsasync.sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;\r
163 var\r
164   destination : tbinip;\r
165   inaddr : tinetsockaddrv;\r
166   trytolisten:integer;\r
167 begin\r
168 {  writeln('sendquery ',decodename(state.packet,state.packetlen,12,0,a),' ',state.requesttype);}\r
169   //writeln('trying to send query on socket number ',socketno);\r
170   result := false;\r
171   if len = 0 then exit; {no packet}\r
172   if sockets[socketno].state <> wsconnected then begin\r
173     startts := unixtimefloat;\r
174     if port = '' then port := '53';\r
175     sockets[socketno].Proto := 'udp';\r
176     sockets[socketno].ondataavailable := receivehandler;\r
177 \r
178     {we are going to bind on a random local port for the DNS request, against the kaminsky attack\r
179     there is a small chance that we're trying to bind on an already used port, so retry a few times}\r
180     for trytolisten := 3 downto 0 do begin\r
181       try\r
182         sockets[socketno].port := inttostr(1024 + randominteger(65536 - 1024));\r
183         sockets[socketno].listen;\r
184       except\r
185         {writeln('failed to listen ',sockets[socketno].localport,' ',trytolisten);}\r
186         if (trytolisten = 0) then begin\r
187           result := false;\r
188           exit;\r
189         end;\r
190       end;\r
191     end;\r
192 \r
193   end;\r
194   if addr <> '' then begin\r
195     dnsserverids[socketno] := -1;\r
196     destination := ipstrtobinf(addr);\r
197   end else begin\r
198     destination := getcurrentsystemnameserverbin(dnsserverids[socketno]);\r
199   end;\r
200   destinations[socketno] := destination;\r
201 \r
202   {$ifdef ipv6}{$ifdef mswindows}\r
203   if destinations[socketno].family = AF_INET6 then if (requestaf = useaf_default) then requestaf := useaf_preferv6;\r
204   {$endif}{$endif}\r
205 \r
206   makeinaddrv(destinations[socketno],port,inaddr);\r
207   sockets[socketno].sendto(inaddr,sizeof(inaddr), @packet,len);\r
208   result := true;\r
209 \r
210 \r
211 end;\r
212 \r
213 procedure tdnsasync.asyncprocess(socketno:integer);\r
214 begin\r
215   state_process(states[socketno]);\r
216   case states[socketno].resultaction of\r
217     action_ignore: begin {do nothing} end;\r
218     action_done: begin\r
219       {$ifdef ipv6}\r
220       if (numsockused = 1) or (states[socketno xor 1].resultaction=action_done) then\r
221       //if using two sockets we need to wait until both sockets are in the done\r
222       //state before firing the event\r
223       {$endif}\r
224       begin\r
225         fresultlist := biniplist_new;\r
226         if (numsockused = 1) then begin\r
227           //writeln('processing for one state');\r
228           biniplist_addlist(fresultlist,states[0].resultlist);\r
229         {$ifdef ipv6}\r
230         end else if (requestaf = useaf_preferv6) then begin\r
231           //writeln('processing for two states, ipv6 preference');\r
232           //writeln('merging lists '+biniplist_tostr(states[1].resultlist)+' and '+biniplist_tostr(states[0].resultlist));\r
233           biniplist_addlist(fresultlist,states[1].resultlist);\r
234           biniplist_addlist(fresultlist,states[0].resultlist);\r
235         end else begin\r
236           //writeln('processing for two states, ipv4 preference');\r
237           biniplist_addlist(fresultlist,states[0].resultlist);\r
238           biniplist_addlist(fresultlist,states[1].resultlist);\r
239         {$endif}\r
240         end;\r
241         //writeln(biniplist_tostr(fresultlist));\r
242         onrequestdone(self,0);\r
243       end;\r
244     end;\r
245     action_sendquery:begin\r
246       sendquery(socketno,states[socketno].sendpacket,states[socketno].sendpacketlen);\r
247     end;\r
248   end;\r
249 end;\r
250 \r
251 procedure tdnsasync.forwardlookup;\r
252 var\r
253   bip : tbinip;\r
254   i : integer;\r
255 begin\r
256   ipstrtobin(name,bip);\r
257 \r
258   if bip.family <> 0 then begin\r
259     // it was an IP address\r
260     fresultlist := biniplist_new;\r
261     biniplist_add(fresultlist,bip);\r
262     onrequestdone(self,0);\r
263     exit;\r
264   end;\r
265 \r
266   if overrideaf = useaf_default then begin\r
267     {$ifdef ipv6}\r
268       {$ifdef mswindows}if not (usewindns and (addr = '')) then{$endif}\r
269       initpreferredmode;\r
270     {$endif}\r
271     requestaf := useaf;\r
272   end else begin\r
273     requestaf := overrideaf;\r
274   end;\r
275 \r
276   {$ifdef mswindows}\r
277     if usewindns and (addr = '') then begin\r
278       dwas := tdnswinasync.create;\r
279       dwas.onrequestdone := winrequestdone;\r
280 \r
281       dwas.forwardlookup(name);\r
282 \r
283       exit;\r
284     end;\r
285   {$endif}\r
286 \r
287   numsockused := 0;\r
288   fresultlist := biniplist_new;\r
289   if (requestaf <> useaf_v6) then begin\r
290     setstate_forward(name,states[numsockused],af_inet);\r
291     inc(numsockused);\r
292   end;\r
293 \r
294   {$ifdef ipv6}\r
295     if (requestaf <> useaf_v4) then begin\r
296       setstate_forward(name,states[numsockused],af_inet6);\r
297       inc(numsockused);\r
298     end;\r
299   {$endif}\r
300 \r
301   for i := 0 to numsockused-1 do begin\r
302     asyncprocess(i);\r
303   end;\r
304 end;\r
305 \r
306 procedure tdnsasync.reverselookup;\r
307 begin\r
308   {$ifdef mswindows}\r
309     if usewindns and (addr = '') then begin\r
310       dwas := tdnswinasync.create;\r
311       dwas.onrequestdone := winrequestdone;\r
312       dwas.reverselookup(binip);\r
313       exit;\r
314     end;\r
315   {$endif}\r
316 \r
317   setstate_reverse(binip,states[0]);\r
318   numsockused := 1;\r
319   asyncprocess(0);\r
320 end;\r
321 \r
322 procedure tdnsasync.customlookup;\r
323 begin\r
324   setstate_custom(name,querytype,states[0]);\r
325   numsockused := 1;\r
326   asyncprocess(0);\r
327 end;\r
328 \r
329 function tdnsasync.dnsresult;\r
330 begin\r
331   if states[0].resultstr <> '' then result := states[0].resultstr else begin\r
332     result := ipbintostr(biniplist_get(fresultlist,0));\r
333   end;\r
334 end;\r
335 \r
336 procedure tdnsasync.dnsresultbin(var binip:tbinip);\r
337 begin\r
338   binip := biniplist_get(fresultlist,0);\r
339 end;\r
340 \r
341 procedure tdnsasync.cancel;\r
342 var\r
343   socketno : integer;\r
344 begin\r
345   {$ifdef mswindows}\r
346     if assigned(dwas) then begin\r
347       dwas.release;\r
348       dwas := nil;\r
349     end else\r
350   {$endif}\r
351   begin\r
352     for socketno := 0 to numsock-1 do begin\r
353       reportlag(dnsserverids[socketno],-1);\r
354       dnsserverids[socketno] := -1;\r
355 \r
356       sockets[socketno].close;\r
357     end;\r
358 \r
359   end;\r
360   for socketno := 0 to numsock-1 do begin\r
361     setstate_failure(states[socketno]);\r
362 \r
363   end;\r
364   fresultlist := biniplist_new;\r
365   onrequestdone(self,0);\r
366 end;\r
367 \r
368 {$ifdef mswindows}\r
369   procedure tdnsasync.winrequestdone(sender:tobject;error:word);\r
370  \r
371   begin\r
372     if dwas.reverse then begin\r
373       states[0].resultstr := dwas.name;\r
374     end else begin \r
375 \r
376       {$ifdef ipv6}\r
377       if (requestaf = useaf_preferv4) then begin\r
378         {prefer mode: sort the IP's}\r
379         fresultlist := biniplist_new;\r
380         addipsoffamily(fresultlist,dwas.iplist,af_inet);\r
381         addipsoffamily(fresultlist,dwas.iplist,af_inet6);\r
382 \r
383       end else if (requestaf = useaf_preferv6) then begin\r
384         {prefer mode: sort the IP's}\r
385         fresultlist := biniplist_new;\r
386         addipsoffamily(fresultlist,dwas.iplist,af_inet6);\r
387         addipsoffamily(fresultlist,dwas.iplist,af_inet);\r
388         \r
389       end else\r
390       {$endif}\r
391       begin\r
392         fresultlist := dwas.iplist;\r
393       end;\r
394 \r
395     end;\r
396     dwas.release;\r
397     onrequestdone(self,error);\r
398   end;\r
399 {$endif}\r
400 end.\r