removed use of global vars in dnssync, should be reentrant safe
[lcore.git] / dnssync.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 unit dnssync;\r
6 {$ifdef fpc}\r
7   {$mode delphi}\r
8 {$endif}\r
9 \r
10 {$include lcoreconfig.inc}\r
11 \r
12 interface\r
13   uses\r
14     dnscore,\r
15     binipstuff,\r
16     {$ifdef win32}\r
17       winsock,\r
18       windows,\r
19     {$else}\r
20       {$ifdef VER1_0}\r
21         linux,\r
22       {$else}\r
23         baseunix,unix,unixutil,\r
24       {$endif}\r
25       sockets,\r
26       fd_utils,\r
27     {$endif}\r
28     lcorernd,\r
29     sysutils,\r
30     ltimevalstuff;\r
31 \r
32 //convert a name to an IP\r
33 //will return v4 or v6 depending on what seems favorable, or manual preference setting\r
34 //on error the binip will have a family of 0 (other fiels are also currently\r
35 //zeroed out but may be used for further error information in future)\r
36 //timeout is in miliseconds, it is ignored when using windows dns\r
37 function forwardlookup(name:ansistring;timeout:integer):tbinip;\r
38 \r
39 //convert a name to a list of all IP's returned\r
40 //this returns both v4 and v6 IP's, or possibly only v4 or v6, depending on settings\r
41 //on error, returns an empty list\r
42 function forwardlookuplist(name:ansistring;timeout:integer):tbiniplist;\r
43 \r
44 \r
45 //convert an IP to a name, on error a null string will be returned, other\r
46 //details as above\r
47 function reverselookup(ip:tbinip;timeout:integer):ansistring;\r
48 \r
49 \r
50 \r
51 const\r
52   tswrap=$4000;\r
53   tsmask=tswrap-1;\r
54 \r
55   numsock=1{$ifdef ipv6}+1{$endif};\r
56   defaulttimeout=10000;\r
57   const mintimeout=16;\r
58 \r
59   toport='53';\r
60 \r
61 implementation\r
62 \r
63 {$ifdef win32}\r
64   uses dnswin;\r
65 {$endif}\r
66 \r
67 \r
68 {$ifndef win32}\r
69 {$define syncdnscore}\r
70 {$endif}\r
71 \r
72 {$i unixstuff.inc}\r
73 \r
74 \r
75 {$ifdef syncdnscore}\r
76 \r
77 \r
78 {$ifdef win32}\r
79   const\r
80     winsocket = 'wsock32.dll';\r
81   function sendto(s: TSocket; const Buf; len, flags: Integer; var addrto: TinetSockAddrV; tolen: Integer): Integer; stdcall; external    winsocket name 'sendto';\r
82   function bind(s: TSocket; var addr: TinetSockAddrV; namelen: Integer): Longbool; stdcall; external    winsocket name 'bind';\r
83   type\r
84     fdset=tfdset;\r
85 {$endif}\r
86 \r
87 \r
88 function getts:integer;\r
89 {$ifdef win32}\r
90 begin\r
91   result := GetTickCount and tsmask;\r
92 {$else}\r
93 var\r
94   temp:ttimeval;\r
95 begin\r
96   gettimeofday(temp);\r
97   result := ((temp.tv_usec div 1000) + (temp.tv_sec * 1000)) and tsmask;\r
98 {$endif}\r
99 end;\r
100 \r
101 \r
102 type tdnsstatearr=array[0..numsock-1] of tdnsstate;\r
103 \r
104 procedure resolveloop(timeout:integer;var state:tdnsstatearr;numsockused:integer);\r
105 var\r
106   selectresult   : integer;\r
107   fds            : fdset;\r
108 \r
109   endtime      : longint;\r
110   starttime    : longint;\r
111   wrapmode     : boolean;\r
112   currenttime  : integer;\r
113 \r
114   lag            : ttimeval;\r
115   selecttimeout  : ttimeval;\r
116   socknum:integer;\r
117   needprocessing:array[0..numsock-1] of boolean;\r
118   finished:array[0..numsock-1] of boolean;\r
119   a,b:integer;\r
120 \r
121   Src    : TInetSockAddrV;\r
122   Srcx   : {$ifdef win32}sockaddr_in{$else}TInetSockAddrV{$endif} absolute Src;\r
123   SrcLen : Integer;\r
124   fromip:tbinip;\r
125   fromport:ansistring;\r
126 \r
127   fd:array[0..numsock-1] of integer;\r
128   toaddr:array[0..numsock-1] of tbinip;\r
129   id:integer;\r
130   sendquerytime:array[0..numsock-1] of integer;\r
131 \r
132 \r
133 procedure setupsocket;\r
134 var\r
135   inAddrtemp : TInetSockAddrV;\r
136   biniptemp:tbinip;\r
137   a:integer;\r
138 begin\r
139   biniptemp := getcurrentsystemnameserverbin(id);\r
140   //must get the DNS server here so we know to init v4 or v6\r
141 \r
142   if biniptemp.family = AF_INET6 then biniptemp := ipstrtobinf('::') else biniptemp := ipstrtobinf('0.0.0.0');\r
143 \r
144 \r
145   for a := 0 to numsockused-1 do begin\r
146     makeinaddrv(biniptemp,inttostr( 1024 + randominteger(65536 - 1024) ),inaddrtemp);\r
147 \r
148     fd[a] := Socket(biniptemp.family,SOCK_DGRAM,0);\r
149 \r
150     If {$ifndef win32}Not{$endif} Bind(fd[a],inAddrtemp,inaddrsize(inaddrtemp)) Then begin\r
151       {$ifdef win32}\r
152         raise Exception.create('unable to bind '+inttostr(WSAGetLastError));\r
153       {$else}\r
154         raise Exception.create('unable to bind '+inttostr(socketError));\r
155       {$endif}\r
156     end;\r
157   end;\r
158 end;\r
159 \r
160 procedure cleanupsockets;\r
161 var\r
162   a:integer;\r
163 begin\r
164   for a := 0 to numsockused-1 do closesocket(fd[a]);\r
165 end;\r
166 \r
167 function sendquery(socknum:integer;const packet:tdnspacket;len:integer):boolean;\r
168 var\r
169   ip       : tbinip;\r
170   port       : ansistring;\r
171   inaddr     : TInetSockAddrV;\r
172 begin\r
173 {  writeln('sendquery ',decodename(state.packet,state.packetlen,12,0,a),' ',state.requesttype);}\r
174   result := false;\r
175   if len = 0 then exit; {no packet}\r
176 \r
177   ip := getcurrentsystemnameserverbin(id);\r
178 \r
179   {$ifdef ipv6}{$ifdef win32}\r
180   if toaddr[socknum].family = AF_INET6 then if (useaf = 0) then useaf := useaf_preferv6;\r
181   {$endif}{$endif}\r
182 \r
183   port := toport;\r
184   toaddr[socknum] := ip;\r
185   makeinaddrv(toaddr[socknum],port,inaddr);\r
186 \r
187   sendto(fd[socknum],packet,len,0,inaddr,inaddrsize(inaddr));\r
188   sendquerytime[socknum] := getts;\r
189   result := true;\r
190 end;\r
191 \r
192 begin\r
193   if timeout < mintimeout then timeout := defaulttimeout;\r
194 \r
195   starttime := getts;\r
196   endtime := starttime + timeout;\r
197   if (endtime and tswrap)=0 then begin\r
198     wrapmode := false;\r
199   end else begin\r
200     wrapmode := true;\r
201   end;\r
202   endtime := endtime and tsmask;\r
203 \r
204   setupsocket;\r
205 \r
206 \r
207   for socknum := 0 to numsockused-1 do begin\r
208     needprocessing[socknum] := true;\r
209     finished[socknum] := false;\r
210   end;\r
211 \r
212   repeat\r
213     for socknum := numsockused-1 downto 0 do if needprocessing[socknum] then begin\r
214       state_process(state[socknum]);\r
215       case state[socknum].resultaction of\r
216         action_ignore: begin\r
217           {do nothing}\r
218         end;\r
219         action_done: begin\r
220           finished[socknum] := true;\r
221           //exit if all resolvers are finished\r
222           b := 0;\r
223           for a := 0 to numsockused-1 do begin\r
224             if finished[a] then inc(b);\r
225           end;\r
226           if (b = numsockused) then begin\r
227             cleanupsockets;\r
228             exit;\r
229           end;\r
230           //onrequestdone(self,0);\r
231         end;\r
232         action_sendquery:begin\r
233 {        writeln('send query');}\r
234           sendquery(socknum,state[socknum].sendpacket,state[socknum].sendpacketlen);\r
235         end;\r
236       end;\r
237       needprocessing[socknum] := false;\r
238     end;\r
239 \r
240     currenttime := getts;\r
241     msectotimeval(selecttimeout, (endtime-currenttime) and tsmask);\r
242 \r
243     fd_zero(fds);\r
244     for socknum := numsockused-1 downto 0 do if not finished[socknum] then fd_set(fd[socknum],fds);\r
245     if (selecttimeout.tv_sec > 0) or (selecttimeout.tv_usec > retryafter) then begin\r
246       selecttimeout.tv_sec := 0;\r
247       selecttimeout.tv_usec := retryafter;\r
248     end;\r
249     //find the highest of the used fd's\r
250     b := 0;\r
251     for socknum := numsockused-1 downto 0 do if fd[socknum] > b then b := fd[socknum];\r
252     selectresult := select(b+1,@fds,nil,nil,@selecttimeout);\r
253     if selectresult > 0 then begin\r
254       currenttime := getts;\r
255       for socknum := numsockused-1 downto 0 do if fd_isset(fd[socknum],fds) then begin\r
256   {      writeln('selectresult>0');}\r
257         //why the hell are we zeroing out the packet buffer before reading into it?! --plugwash\r
258 \r
259         fillchar(state[socknum].recvpacket,sizeof(state[socknum].recvpacket),0);\r
260         msectotimeval(lag,(currenttime-sendquerytime[socknum]) and tsmask);\r
261 \r
262         reportlag(id,(lag.tv_sec*1000000)+lag.tv_usec);\r
263 \r
264         SrcLen := SizeOf(Src);\r
265         state[socknum].recvpacketlen := recvfrom(fd[socknum],state[socknum].recvpacket, SizeOf(state[socknum].recvpacket),0,Srcx,SrcLen);\r
266 \r
267         if (state[socknum].recvpacketlen > 0) then begin\r
268           fromip := inaddrvtobinip(Src);\r
269           fromport := inttostr(htons(src.InAddr.port));\r
270           if ((not comparebinip(toaddr[socknum],fromip)) or (fromport <> toport)) then begin\r
271 //            writeln('dnssync received from wrong IP:port ',ipbintostr(fromip),'#',fromport);\r
272             state[socknum].recvpacketlen := 0;\r
273           end else begin\r
274             state[socknum].parsepacket := true;\r
275             needprocessing[socknum] := true;\r
276           end;\r
277         end;\r
278       end;\r
279     end;\r
280     if selectresult < 0 then exit;\r
281     if selectresult = 0 then begin\r
282 \r
283       currenttime := getts;\r
284 \r
285       reportlag(id,-1);\r
286       if (currenttime >= endtime) and ((not wrapmode) or (currenttime < starttime)) then begin\r
287         cleanupsockets;\r
288         exit;\r
289       end else begin\r
290         //resend\r
291         for socknum := numsockused-1 downto 0 do begin\r
292           sendquery(socknum,state[socknum].sendpacket,state[socknum].sendpacketlen);\r
293         end;\r
294       end;\r
295     end;\r
296   until false;\r
297 end;\r
298 {$endif}\r
299 \r
300 \r
301 \r
302 function forwardlookuplist(name:ansistring;timeout:integer):tbiniplist;\r
303 var\r
304   dummy : integer;\r
305   a:integer;\r
306   biniptemp:tbinip;\r
307   l:tbiniplist;\r
308 \r
309   numsockused:integer;\r
310   state:tdnsstatearr;\r
311 \r
312 begin\r
313   ipstrtobin(name,biniptemp);\r
314   if biniptemp.family <> 0 then begin\r
315     result := biniplist_new;\r
316     biniplist_add(result,biniptemp);\r
317     exit; //it was an IP address, no need for dns\r
318   end;\r
319 \r
320   {$ifdef win32}\r
321   if usewindns then begin\r
322     if (useaf = useaf_v4) then a := af_inet else if (useaf = useaf_v6) then a := af_inet6 else a := 0;\r
323     result := winforwardlookuplist(name,a,dummy);\r
324     {$ifdef ipv6}\r
325     if (useaf = useaf_preferv4) then begin\r
326       {prefer mode: sort the IP's}\r
327       l := biniplist_new;\r
328       addipsoffamily(l,result,af_inet);\r
329       addipsoffamily(l,result,af_inet6);\r
330       result := l;\r
331     end;\r
332     if (useaf = useaf_preferv6) then begin\r
333       {prefer mode: sort the IP's}\r
334       l := biniplist_new;\r
335       addipsoffamily(l,result,af_inet6);\r
336       addipsoffamily(l,result,af_inet);\r
337       result := l;\r
338     end;\r
339     {$endif}\r
340   end else\r
341   {$endif}\r
342   begin\r
343   {$ifdef syncdnscore}\r
344     {$ifdef ipv6}initpreferredmode;{$endif}\r
345 \r
346     numsockused := 0;\r
347 \r
348     result := biniplist_new;\r
349     if (useaf <> useaf_v6) then begin\r
350       setstate_forward(name,state[numsockused],af_inet);\r
351       inc(numsockused);\r
352     end;\r
353     {$ifdef ipv6}\r
354     if (useaf <> useaf_v4) then begin\r
355       setstate_forward(name,state[numsockused],af_inet6);\r
356       inc(numsockused);\r
357     end;\r
358     {$endif}\r
359 \r
360     resolveloop(timeout,state,numsockused);\r
361 \r
362     if (numsockused = 1) then begin\r
363       biniplist_addlist(result,state[0].resultlist);\r
364     {$ifdef ipv6}\r
365     end else if (useaf = useaf_preferv6) then begin\r
366       biniplist_addlist(result,state[1].resultlist);\r
367       biniplist_addlist(result,state[0].resultlist);\r
368     end else begin\r
369       biniplist_addlist(result,state[0].resultlist);\r
370       biniplist_addlist(result,state[1].resultlist);\r
371     {$endif}\r
372     end;\r
373     {$endif}\r
374   end;\r
375 end;\r
376 \r
377 function forwardlookup(name:ansistring;timeout:integer):tbinip;\r
378 var\r
379   listtemp:tbiniplist;\r
380 begin\r
381   listtemp := forwardlookuplist(name,timeout);\r
382   result := biniplist_get(listtemp,0);\r
383 end;\r
384 \r
385 function reverselookup(ip:tbinip;timeout:integer):ansistring;\r
386 var\r
387   dummy : integer;\r
388   numsockused:integer;\r
389   state:tdnsstatearr;\r
390 begin\r
391   {$ifdef win32}\r
392     if usewindns then begin\r
393       result := winreverselookup(ip,dummy);\r
394       exit;\r
395     end;\r
396   {$endif}\r
397   {$ifdef syncdnscore}\r
398   setstate_reverse(ip,state[0]);\r
399   numsockused := 1;\r
400   resolveloop(timeout,state,numsockused);\r
401   result := state[0].resultstr;\r
402   {$endif}\r
403 end;\r
404 \r
405 {$ifdef win32}\r
406   var\r
407     wsadata : twsadata;\r
408 \r
409   initialization\r
410     WSAStartUp($2,wsadata);\r
411   finalization\r
412     WSACleanUp;\r
413 {$endif}\r
414 end.\r
415 \r
416 \r