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
7 unit to get various local system config
\r
10 - get IP addresses assigned to local interfaces.
\r
11 both IPv4 and IPv6, or one address family in isolation.
\r
12 works on both windows and linux.
\r
19 - mac OS X (probably works on freeBSD too)
\r
23 - localhost IPs (127.0.0.1, ::1) may be returned, the app must not expect them to be in or not in.
\r
24 (typically, they're returned on linux and not on windows)
\r
26 - normal behavior is to return all v6 IPs, including link local (fe80::).
\r
27 an app that doesn't want link local IPs has to filter them out.
\r
28 windows XP returns only one, global scope, v6 IP, due to shortcomings.
\r
32 - get system DNS servers
\r
34 - get system hostname (if not on windows, use freepascal's "unix")
\r
44 uses binipstuff,pgtypes;
\r
46 {$include lcoreconfig.inc}
\r
48 function getlocalips:tbiniplist;
\r
49 function getv4localips:tbiniplist;
\r
51 function getv6localips:tbiniplist;
\r
54 function getsystemdnsservers:tbiniplist;
\r
56 function have_ipv6_connectivity:boolean;
\r
58 function lcgethostname:ansistring;
\r
61 function getlocalipforip(const ip:tbinip):tbinip;
\r
65 v6_check_ip='2001:200::'; //a globally routeable v6 IP that is used in "get local IP for IP" etc, it should never actually be communicated with.
\r
72 baseunix,unix,sockets,sysutils;
\r
75 function getlocalips_internal(wantfamily:integer):tbiniplist;
\r
79 {$ifdef linux}SIOCGIFCONF=$8912;{$endif}
\r
80 {$ifdef bsd}{$ifdef cpu386}SIOCGIFCONF=$C0086924;{$endif}{$endif}
\r
82 {amd64: mac OS X: $C00C6924; freeBSD: $c0106924}
\r
84 tifconf=packed record
\r
89 tifrec=packed record
\r
90 ifr_ifrn:array [0..IF_NAMESIZE-1] of char;
\r
91 ifru_addr:TSockAddr;
\r
97 ifr,ifr2,ifrmax:^tifrec;
\r
98 lastlen,len:integer;
\r
100 ad:^TinetSockAddrV;
\r
102 result := biniplist_new;
\r
104 {must create a socket for this}
\r
105 s := fpsocket(AF_INET,SOCK_DGRAM,0);
\r
106 if (s < 0) then raise exception.create('getv4localips unable to create socket');
\r
108 fillchar(ifc,sizeof(ifc),0);
\r
113 len := 2*sizeof(tifrec);
\r
116 reallocmem(ifr,len);
\r
117 ifc.ifc_len := len;
\r
118 ifc.ifcu_rec := ifr;
\r
119 {get IP record list}
\r
120 if (fpioctl(s,SIOCGIFCONF,@ifc) < 0) then begin
\r
121 raise exception.create('getv4localips ioctl failed');
\r
123 if (lastlen = ifc.ifc_len) then break;
\r
124 lastlen := ifc.ifc_len;
\r
129 ifrmax := pointer(taddrint(ifr) + ifc.ifc_len);
\r
130 while (ifr2 < ifrmax) do begin
\r
131 lastlen := taddrint(ifrmax) - taddrint(ifr2);
\r
132 if (lastlen < sizeof(tifrec)) then break; {not enough left}
\r
134 ad := @ifr2.ifru_addr;
\r
137 len := ad.inaddr.len + IF_NAMESIZE;
\r
138 if (len < sizeof(tifrec)) then
\r
140 len := sizeof(tifrec);
\r
142 if (len < sizeof(tifrec)) then break; {not enough left}
\r
144 ip := inaddrvtobinip(ad^);
\r
145 if (ip.family <> 0) and ((ip.family = wantfamily) or (wantfamily = 0)) then biniplist_add(result,ip);
\r
146 inc(taddrint(ifr2),len);
\r
154 function getv6localips:tbiniplist;
\r
161 result := biniplist_new;
\r
163 assignfile(t,'/proc/net/if_inet6');
\r
164 {$i-}reset(t);{$i+}
\r
165 if ioresult <> 0 then begin
\r
166 {not on linux, try if this OS uses the other way to return v6 addresses}
\r
167 result := getlocalips_internal(AF_INET6);
\r
170 while not eof(t) do begin
\r
173 for a := 0 to 7 do begin
\r
174 if (s2 <> '') then s2 := s2 + ':';
\r
175 s2 := s2 + copy(s,(a shl 2)+1,4);
\r
178 if ip.family <> 0 then biniplist_add(result,ip);
\r
184 function getv4localips:tbiniplist;
\r
186 result := getlocalips_internal(AF_INET);
\r
189 function getlocalips:tbiniplist;
\r
191 result := getv4localips;
\r
193 biniplist_addlist(result,getv6localips);
\r
200 sysutils,windows,winsock,dnswin,registry;
\r
202 {the following code's purpose is to determine what IP windows would come from, to reach an IP
\r
203 it can be abused to find if there's any global v6 IPs on a local interface}
\r
205 SIO_ROUTING_INTERFACE_QUERY = $c8000014;
\r
206 type tWSAIoctl=function(s: TSocket; code:integer; const Buf; len: Integer; var output; outlen:integer; var outreturned: Integer; overlapped:pointer; completion: pointer): Integer; stdcall;
\r
208 function getlocalipforip(const ip:tbinip):tbinip;
\r
210 libraryhandle : hmodule;
\r
211 WSAIoctl:tWSAIoctl;
\r
214 inaddrv,inaddrv2:tinetsockaddrv;
\r
215 srcx:winsock.tsockaddr absolute inaddrv2;
\r
217 libraryhandle := LoadLibraryA('Ws2_32.dll');
\r
218 if (libraryhandle = 0) then raise exception.create('getlocalipforip: no winsock2');
\r
219 WSAIoctl := getprocaddress(libraryhandle,'WSAIoctl');
\r
220 handle := INVALID_SOCKET;
\r
222 if not assigned(WSAIoctl) then raise exception.create('getlocalipforip: no winsock2 WSAIoctl');
\r
224 makeinaddrv(ip,'0',inaddrv);
\r
225 handle := Socket(inaddrv.inaddr.family,SOCK_DGRAM,IPPROTO_UDP);
\r
226 if (handle = INVALID_SOCKET) then begin
\r
227 {this happens on XP without an IPv6 stack
\r
228 i can either fail with an exception, or with a "null result". an exception is annoying in the IDE}
\r
229 {fillchar(result,sizeof(result),0);
\r
231 raise exception.create('getlocalipforip: can''t create socket');
\r
233 if WSAIoctl(handle, SIO_ROUTING_INTERFACE_QUERY, inaddrv, sizeof(inaddrv), inaddrv2, sizeof(inaddrv2), a, nil, nil) <> 0
\r
234 then raise exception.create('getlocalipforip failed with error: '+inttostr(wsagetlasterror));
\r
235 result := inaddrvtobinip(inaddrv2);
\r
237 if (handle <> INVALID_SOCKET) then closesocket(handle);
\r
238 if (libraryhandle <> 0) then freelibrary(libraryhandle);
\r
243 function getv4localips:tbiniplist;
\r
245 templist:tbiniplist;
\r
249 result := biniplist_new;
\r
251 templist := getlocalips;
\r
252 for a := biniplist_getcount(templist)-1 downto 0 do begin
\r
253 biniptemp := biniplist_get(templist,a);
\r
254 if biniptemp.family = AF_INET then biniplist_add(result,biniptemp);
\r
259 function getv6localips:tbiniplist;
\r
261 templist:tbiniplist;
\r
265 result := biniplist_new;
\r
267 templist := getlocalips;
\r
268 for a := biniplist_getcount(templist)-1 downto 0 do begin
\r
269 biniptemp := biniplist_get(templist,a);
\r
270 if biniptemp.family = AF_INET6 then biniplist_add(result,biniptemp);
\r
275 function getlocalips:tbiniplist;
\r
279 usewindnstemp:boolean;
\r
282 result := winforwardlookuplist(lcgethostname,0,error);
\r
286 {windows XP doesn't add v6 IPs
\r
287 if we find no v6 IPs in the list, add one using a hack}
\r
288 for a := biniplist_getcount(result)-1 downto 0 do begin
\r
289 ip := biniplist_get(result,a);
\r
290 if ip.family = AF_INET6 then exit;
\r
294 ip := getlocalipforip(ipstrtobinf(v6_check_ip));
\r
295 if (ip.family = AF_INET6) then biniplist_add(result,ip);
\r
310 MAX_HOSTNAME_LEN = 132;
\r
311 MAX_DOMAIN_NAME_LEN = 132;
\r
312 MAX_SCOPE_ID_LEN = 260 ;
\r
313 MAX_ADAPTER_NAME_LENGTH = 260;
\r
314 MAX_ADAPTER_ADDRESS_LENGTH = 8;
\r
315 MAX_ADAPTER_DESCRIPTION_LENGTH = 132;
\r
316 ERROR_BUFFER_OVERFLOW = 111;
\r
317 MIB_IF_TYPE_ETHERNET = 6;
\r
318 MIB_IF_TYPE_TOKENRING = 9;
\r
319 MIB_IF_TYPE_FDDI = 15;
\r
320 MIB_IF_TYPE_PPP = 23;
\r
321 MIB_IF_TYPE_LOOPBACK = 24;
\r
322 MIB_IF_TYPE_SLIP = 28;
\r
326 tip_addr_string=packed record
\r
328 IpAddress : array[0..15] of ansichar;
\r
329 ipmask : array[0..15] of ansichar;
\r
332 pip_addr_string=^tip_addr_string;
\r
333 tFIXED_INFO=packed record
\r
334 HostName : array[0..MAX_HOSTNAME_LEN-1] of ansichar;
\r
335 DomainName : array[0..MAX_DOMAIN_NAME_LEN-1] of ansichar;
\r
336 currentdnsserver : pip_addr_string;
\r
337 dnsserverlist : tip_addr_string;
\r
338 nodetype : longint;
\r
339 ScopeId : array[0..MAX_SCOPE_ID_LEN + 4] of ansichar;
\r
340 enablerouting : longbool;
\r
341 enableproxy : longbool;
\r
342 enabledns : longbool;
\r
344 pFIXED_INFO=^tFIXED_INFO;
\r
347 iphlpapi : thandle;
\r
348 getnetworkparams : function(pFixedInfo : PFIXED_INFO;OutBufLen : plongint) : longint;stdcall;
\r
350 function callGetNetworkParams:pFIXED_INFO;
\r
352 fixed_info : pfixed_info;
\r
353 fixed_info_len : longint;
\r
356 if iphlpapi=0 then iphlpapi := loadlibrary('iphlpapi.dll');
\r
358 if not assigned(getnetworkparams) then getnetworkparams := getprocaddress(iphlpapi,'GetNetworkParams');
\r
359 if not assigned(getnetworkparams) then exit;
\r
360 fixed_info_len := 0;
\r
361 if GetNetworkParams(nil,@fixed_info_len)<>ERROR_BUFFER_OVERFLOW then exit;
\r
362 //fixed_info_len :=sizeof(tfixed_info);
\r
363 getmem(fixed_info,fixed_info_len);
\r
364 if GetNetworkParams(fixed_info,@fixed_info_len)<>0 then begin
\r
365 freemem(fixed_info);
\r
368 result := fixed_info;
\r
373 function getsystemdnsservers:tbiniplist;
\r
376 fixed_info : pfixed_info;
\r
377 currentdnsserver : pip_addr_string;
\r
379 nameserver,s:ansistring;
\r
390 result := biniplist_new;
\r
393 fixed_info := callgetnetworkparams;
\r
394 if fixed_info = nil then begin
\r
395 //2000 and up method not supported. use the 9x or NT 4 method.
\r
397 reg := TRegistry.Create();
\r
398 reg.RootKey := HKEY_LOCAL_MACHINE;
\r
400 if not reg.OpenKey('\System\CurrentControlSet\Services\VxD\MSTCP',false) then
\r
402 if not reg.OpenKey('\System\CurrentControlSet\Services\Tcpip\Parameters',false) then begin
\r
407 nameserver := reg.ReadString('NameServer');
\r
408 //DhcpNameServer is actually only set on NT
\r
409 if (nameserver = '') then nameserver := reg.ReadString('DhcpNameServer');
\r
413 //parse as comma separated list
\r
415 if (nameserver = '') then exit; //done
\r
416 a := pos(',',nameserver);
\r
417 if (a > 1) then begin
\r
418 s := copy(nameserver,1,a-1);
\r
419 nameserver := copy(nameserver,a+1,9999);
\r
425 ip := ipstrtobinf(s);
\r
426 if (ip.family <> 0) then biniplist_add(result,ip);
\r
430 currentdnsserver := @(fixed_info.dnsserverlist);
\r
431 while assigned(currentdnsserver) do begin
\r
432 ip := ipstrtobinf(currentdnsserver.IpAddress);
\r
433 if (ip.family <> 0) then biniplist_add(result,ip);
\r
434 currentdnsserver := currentdnsserver.next;
\r
436 freemem(fixed_info);
\r
439 assignfile(t,'/etc/resolv.conf');
\r
440 {$i-}reset(t);{$i+}
\r
441 if ioresult <> 0 then exit;
\r
443 while not eof(t) do begin
\r
445 if not (copy(s,1,10) = 'nameserver') then continue;
\r
446 s := copy(s,11,500);
\r
447 while s <> '' do begin
\r
448 if (s[1] = #32) or (s[1] = #9) then s := copy(s,2,500) else break;
\r
451 if a <> 0 then s := copy(s,1,a-1);
\r
453 if a <> 0 then s := copy(s,1,a-1);
\r
455 ip := ipstrtobinf(s);
\r
456 if (ip.family <> 0) then biniplist_add(result,ip);
\r
463 function have_ipv6_connectivity:boolean;
\r
468 ipmask_global,ipmask_6to4,ipmask_teredo:tbinip;
\r
470 function ip_is_suitable_v6:boolean;
\r
473 if (ip.family <> AF_INET6) then exit;
\r
474 if not comparebinipmask(ip,ipmask_global,3) then exit;
\r
475 if comparebinipmask(ip,ipmask_teredo,32) then exit;
\r
476 if comparebinipmask(ip,ipmask_6to4,16) then exit;
\r
483 ipstrtobin('2000::',ipmask_global);
\r
484 ipstrtobin('2001::',ipmask_teredo);
\r
485 ipstrtobin('2002::',ipmask_6to4);
\r
488 //better way on windows to check for ipv6 that works (returns no ipv6) if a v6 IP is assigned, but there is no connectivity
\r
490 ip := getlocalipforip(ipstrtobinf(v6_check_ip));
\r
491 if ip_is_suitable_v6 then result := true;
\r
496 l := getv6localips;
\r
497 if biniplist_getcount(l) = 0 then exit;
\r
499 {if there is any v6 IP which is globally routable and not 6to4 and not teredo, prefer v6}
\r
500 for a := biniplist_getcount(l)-1 downto 0 do begin
\r
501 ip := biniplist_get(l,a);
\r
502 if not ip_is_suitable_v6 then continue;
\r
510 function lcgethostname:ansistring;
\r
513 buf:array[0..255] of ansichar;
\r
517 fillchar(buf,sizeof(buf),0);
\r
518 i := winsock.gethostname(@buf,sizeof(buf));
\r
519 if (i = 0) then result := pansichar(@buf[0]);
\r
523 result := unix.gethostname;
\r