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
92 0: (ifru_addr: Tsockaddr);
\r
94 //tifrec is 40 bytes on 64 bits due to a union with one of the other data types
\r
95 1: (sizefor64: array[0..23] of byte);
\r
102 ifr,ifr2,ifrmax:^tifrec;
\r
103 lastlen,len:integer;
\r
105 ad:^TinetSockAddrV;
\r
107 result := biniplist_new;
\r
109 {must create a socket for this}
\r
110 s := fpsocket(AF_INET,SOCK_DGRAM,0);
\r
111 if (s < 0) then raise exception.create('getv4localips unable to create socket');
\r
113 fillchar(ifc,sizeof(ifc),0);
\r
118 len := 2*sizeof(tifrec);
\r
121 reallocmem(ifr,len);
\r
122 ifc.ifc_len := len;
\r
123 ifc.ifcu_rec := ifr;
\r
124 {get IP record list}
\r
125 if (fpioctl(s,SIOCGIFCONF,@ifc) < 0) then begin
\r
126 raise exception.create('getv4localips ioctl failed');
\r
128 if (lastlen = ifc.ifc_len) then break;
\r
129 lastlen := ifc.ifc_len;
\r
134 ifrmax := pointer(taddrint(ifr) + ifc.ifc_len);
\r
135 while (ifr2 < ifrmax) do begin
\r
136 lastlen := taddrint(ifrmax) - taddrint(ifr2);
\r
137 if (lastlen < sizeof(tifrec)) then break; {not enough left}
\r
139 ad := @ifr2.ifru_addr;
\r
142 len := ad.inaddr.len + IF_NAMESIZE;
\r
143 if (len < sizeof(tifrec)) then
\r
145 len := sizeof(tifrec);
\r
147 if (len < sizeof(tifrec)) then break; {not enough left}
\r
149 ip := inaddrvtobinip(ad^);
\r
150 if (ip.family <> 0) and ((ip.family = wantfamily) or (wantfamily = 0)) then biniplist_add(result,ip);
\r
151 inc(taddrint(ifr2),len);
\r
159 function getv6localips:tbiniplist;
\r
166 result := biniplist_new;
\r
168 assignfile(t,'/proc/net/if_inet6');
\r
169 {$i-}reset(t);{$i+}
\r
170 if ioresult <> 0 then begin
\r
171 {not on linux, try if this OS uses the other way to return v6 addresses}
\r
172 result := getlocalips_internal(AF_INET6);
\r
175 while not eof(t) do begin
\r
178 for a := 0 to 7 do begin
\r
179 if (s2 <> '') then s2 := s2 + ':';
\r
180 s2 := s2 + copy(s,(a shl 2)+1,4);
\r
183 if ip.family <> 0 then biniplist_add(result,ip);
\r
189 function getv4localips:tbiniplist;
\r
191 result := getlocalips_internal(AF_INET);
\r
194 function getlocalips:tbiniplist;
\r
196 result := getv4localips;
\r
198 biniplist_addlist(result,getv6localips);
\r
205 sysutils,windows,winsock,dnswin,registry;
\r
207 {the following code's purpose is to determine what IP windows would come from, to reach an IP
\r
208 it can be abused to find if there's any global v6 IPs on a local interface}
\r
210 SIO_ROUTING_INTERFACE_QUERY = $c8000014;
\r
211 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
213 function getlocalipforip(const ip:tbinip):tbinip;
\r
215 libraryhandle : hmodule;
\r
216 WSAIoctl:tWSAIoctl;
\r
219 inaddrv,inaddrv2:tinetsockaddrv;
\r
220 srcx:winsock.tsockaddr absolute inaddrv2;
\r
222 libraryhandle := LoadLibraryA('Ws2_32.dll');
\r
223 if (libraryhandle = 0) then raise exception.create('getlocalipforip: no winsock2');
\r
224 WSAIoctl := getprocaddress(libraryhandle,'WSAIoctl');
\r
225 handle := INVALID_SOCKET;
\r
227 if not assigned(WSAIoctl) then raise exception.create('getlocalipforip: no winsock2 WSAIoctl');
\r
229 makeinaddrv(ip,'0',inaddrv);
\r
230 handle := Socket(inaddrv.inaddr.family,SOCK_DGRAM,IPPROTO_UDP);
\r
231 if (handle = INVALID_SOCKET) then begin
\r
232 {this happens on XP without an IPv6 stack
\r
233 i can either fail with an exception, or with a "null result". an exception is annoying in the IDE}
\r
234 {fillchar(result,sizeof(result),0);
\r
236 raise exception.create('getlocalipforip: can''t create socket');
\r
238 if WSAIoctl(handle, SIO_ROUTING_INTERFACE_QUERY, inaddrv, sizeof(inaddrv), inaddrv2, sizeof(inaddrv2), a, nil, nil) <> 0
\r
239 then raise exception.create('getlocalipforip failed with error: '+inttostr(wsagetlasterror));
\r
240 result := inaddrvtobinip(inaddrv2);
\r
242 if (handle <> INVALID_SOCKET) then closesocket(handle);
\r
243 if (libraryhandle <> 0) then freelibrary(libraryhandle);
\r
248 function getv4localips:tbiniplist;
\r
250 templist:tbiniplist;
\r
254 result := biniplist_new;
\r
256 templist := getlocalips;
\r
257 for a := biniplist_getcount(templist)-1 downto 0 do begin
\r
258 biniptemp := biniplist_get(templist,a);
\r
259 if biniptemp.family = AF_INET then biniplist_add(result,biniptemp);
\r
264 function getv6localips:tbiniplist;
\r
266 templist:tbiniplist;
\r
270 result := biniplist_new;
\r
272 templist := getlocalips;
\r
273 for a := biniplist_getcount(templist)-1 downto 0 do begin
\r
274 biniptemp := biniplist_get(templist,a);
\r
275 if biniptemp.family = AF_INET6 then biniplist_add(result,biniptemp);
\r
280 function getlocalips:tbiniplist;
\r
284 usewindnstemp:boolean;
\r
287 result := winforwardlookuplist(lcgethostname,0,error);
\r
291 {windows XP doesn't add v6 IPs
\r
292 if we find no v6 IPs in the list, add one using a hack}
\r
293 for a := biniplist_getcount(result)-1 downto 0 do begin
\r
294 ip := biniplist_get(result,a);
\r
295 if ip.family = AF_INET6 then exit;
\r
299 ip := getlocalipforip(ipstrtobinf(v6_check_ip));
\r
300 if (ip.family = AF_INET6) then biniplist_add(result,ip);
\r
315 MAX_HOSTNAME_LEN = 132;
\r
316 MAX_DOMAIN_NAME_LEN = 132;
\r
317 MAX_SCOPE_ID_LEN = 260 ;
\r
318 MAX_ADAPTER_NAME_LENGTH = 260;
\r
319 MAX_ADAPTER_ADDRESS_LENGTH = 8;
\r
320 MAX_ADAPTER_DESCRIPTION_LENGTH = 132;
\r
321 ERROR_BUFFER_OVERFLOW = 111;
\r
322 MIB_IF_TYPE_ETHERNET = 6;
\r
323 MIB_IF_TYPE_TOKENRING = 9;
\r
324 MIB_IF_TYPE_FDDI = 15;
\r
325 MIB_IF_TYPE_PPP = 23;
\r
326 MIB_IF_TYPE_LOOPBACK = 24;
\r
327 MIB_IF_TYPE_SLIP = 28;
\r
331 tip_addr_string=packed record
\r
333 IpAddress : array[0..15] of ansichar;
\r
334 ipmask : array[0..15] of ansichar;
\r
337 pip_addr_string=^tip_addr_string;
\r
338 tFIXED_INFO=packed record
\r
339 HostName : array[0..MAX_HOSTNAME_LEN-1] of ansichar;
\r
340 DomainName : array[0..MAX_DOMAIN_NAME_LEN-1] of ansichar;
\r
341 currentdnsserver : pip_addr_string;
\r
342 dnsserverlist : tip_addr_string;
\r
343 nodetype : longint;
\r
344 ScopeId : array[0..MAX_SCOPE_ID_LEN + 4] of ansichar;
\r
345 enablerouting : longbool;
\r
346 enableproxy : longbool;
\r
347 enabledns : longbool;
\r
349 pFIXED_INFO=^tFIXED_INFO;
\r
352 iphlpapi : thandle;
\r
353 getnetworkparams : function(pFixedInfo : PFIXED_INFO;OutBufLen : plongint) : longint;stdcall;
\r
355 function callGetNetworkParams:pFIXED_INFO;
\r
357 fixed_info : pfixed_info;
\r
358 fixed_info_len : longint;
\r
361 if iphlpapi=0 then iphlpapi := loadlibrary('iphlpapi.dll');
\r
363 if not assigned(getnetworkparams) then getnetworkparams := getprocaddress(iphlpapi,'GetNetworkParams');
\r
364 if not assigned(getnetworkparams) then exit;
\r
365 fixed_info_len := 0;
\r
366 if GetNetworkParams(nil,@fixed_info_len)<>ERROR_BUFFER_OVERFLOW then exit;
\r
367 //fixed_info_len :=sizeof(tfixed_info);
\r
368 getmem(fixed_info,fixed_info_len);
\r
369 if GetNetworkParams(fixed_info,@fixed_info_len)<>0 then begin
\r
370 freemem(fixed_info);
\r
373 result := fixed_info;
\r
378 function getsystemdnsservers:tbiniplist;
\r
381 fixed_info : pfixed_info;
\r
382 currentdnsserver : pip_addr_string;
\r
384 nameserver,s:ansistring;
\r
395 result := biniplist_new;
\r
398 fixed_info := callgetnetworkparams;
\r
399 if fixed_info = nil then begin
\r
400 //2000 and up method not supported. use the 9x or NT 4 method.
\r
402 reg := TRegistry.Create();
\r
403 reg.RootKey := HKEY_LOCAL_MACHINE;
\r
405 if not reg.OpenKey('\System\CurrentControlSet\Services\VxD\MSTCP',false) then
\r
407 if not reg.OpenKey('\System\CurrentControlSet\Services\Tcpip\Parameters',false) then begin
\r
412 nameserver := reg.ReadString('NameServer');
\r
413 //DhcpNameServer is actually only set on NT
\r
414 if (nameserver = '') then nameserver := reg.ReadString('DhcpNameServer');
\r
418 //parse as comma separated list
\r
420 if (nameserver = '') then exit; //done
\r
421 a := pos(',',nameserver);
\r
422 if (a > 1) then begin
\r
423 s := copy(nameserver,1,a-1);
\r
424 nameserver := copy(nameserver,a+1,9999);
\r
430 ip := ipstrtobinf(s);
\r
431 if (ip.family <> 0) then biniplist_add(result,ip);
\r
435 currentdnsserver := @(fixed_info.dnsserverlist);
\r
436 while assigned(currentdnsserver) do begin
\r
437 ip := ipstrtobinf(currentdnsserver.IpAddress);
\r
438 if (ip.family <> 0) then biniplist_add(result,ip);
\r
439 currentdnsserver := currentdnsserver.next;
\r
441 freemem(fixed_info);
\r
444 assignfile(t,'/etc/resolv.conf');
\r
445 {$i-}reset(t);{$i+}
\r
446 if ioresult <> 0 then exit;
\r
448 while not eof(t) do begin
\r
450 if not (copy(s,1,10) = 'nameserver') then continue;
\r
451 s := copy(s,11,500);
\r
452 while s <> '' do begin
\r
453 if (s[1] = #32) or (s[1] = #9) then s := copy(s,2,500) else break;
\r
456 if a <> 0 then s := copy(s,1,a-1);
\r
458 if a <> 0 then s := copy(s,1,a-1);
\r
460 ip := ipstrtobinf(s);
\r
461 if (ip.family <> 0) then biniplist_add(result,ip);
\r
468 function have_ipv6_connectivity:boolean;
\r
473 ipmask_global,ipmask_6to4,ipmask_teredo:tbinip;
\r
475 function ip_is_suitable_v6:boolean;
\r
478 if (ip.family <> AF_INET6) then exit;
\r
479 if not comparebinipmask(ip,ipmask_global,3) then exit;
\r
480 if comparebinipmask(ip,ipmask_teredo,32) then exit;
\r
481 if comparebinipmask(ip,ipmask_6to4,16) then exit;
\r
488 ipstrtobin('2000::',ipmask_global);
\r
489 ipstrtobin('2001::',ipmask_teredo);
\r
490 ipstrtobin('2002::',ipmask_6to4);
\r
493 //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
495 ip := getlocalipforip(ipstrtobinf(v6_check_ip));
\r
496 if ip_is_suitable_v6 then result := true;
\r
501 l := getv6localips;
\r
502 if biniplist_getcount(l) = 0 then exit;
\r
504 {if there is any v6 IP which is globally routable and not 6to4 and not teredo, prefer v6}
\r
505 for a := biniplist_getcount(l)-1 downto 0 do begin
\r
506 ip := biniplist_get(l,a);
\r
507 if not ip_is_suitable_v6 then continue;
\r
515 function lcgethostname:ansistring;
\r
518 buf:array[0..255] of ansichar;
\r
522 fillchar(buf,sizeof(buf),0);
\r
523 i := winsock.gethostname(@buf,sizeof(buf));
\r
524 if (i = 0) then result := pansichar(@buf[0]);
\r
528 result := unix.gethostname;
\r