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
76 function getlocalips_internal(wantfamily:integer):tbiniplist;
\r
82 tifconf=packed record
\r
87 tifrec=packed record
\r
88 ifr_ifrn:array [0..IF_NAMESIZE-1] of char;
\r
90 0: (ifru_addr: Tsockaddr);
\r
92 //tifrec is 40 bytes on 64 bits due to a union with one of the other data types
\r
93 1: (sizefor64: array[0..23] of byte);
\r
100 ifr,ifr2,ifrmax:^tifrec;
\r
101 lastlen,len:integer;
\r
103 ad:^TinetSockAddrV;
\r
105 result := biniplist_new;
\r
107 {must create a socket for this}
\r
108 s := fpsocket(AF_INET,SOCK_DGRAM,0);
\r
109 if (s < 0) then raise exception.create('getv4localips unable to create socket');
\r
111 fillchar(ifc,sizeof(ifc),0);
\r
116 len := 2*sizeof(tifrec);
\r
119 reallocmem(ifr,len);
\r
120 ifc.ifc_len := len;
\r
121 ifc.ifcu_rec := ifr;
\r
122 {get IP record list}
\r
123 if (fpioctl(s,SIOCGIFCONF,@ifc) < 0) then begin
\r
124 raise exception.create('getv4localips ioctl failed');
\r
126 if (lastlen = ifc.ifc_len) then break;
\r
127 lastlen := ifc.ifc_len;
\r
132 ifrmax := pointer(taddrint(ifr) + ifc.ifc_len);
\r
133 while (ifr2 < ifrmax) do begin
\r
134 lastlen := taddrint(ifrmax) - taddrint(ifr2);
\r
135 if (lastlen < sizeof(tifrec)) then break; {not enough left}
\r
137 ad := @ifr2.ifru_addr;
\r
139 len := sizeof(tifrec);
\r
141 if (len < sizeof(tifrec)) then break; {not enough left}
\r
143 ip := inaddrvtobinip(ad^);
\r
144 if (ip.family <> 0) and ((ip.family = wantfamily) or (wantfamily = 0)) then biniplist_add(result,ip);
\r
145 inc(taddrint(ifr2),len);
\r
157 pifaddrs = ^Tifaddrs;
\r
159 ifa_next: pifaddrs;
\r
160 ifa_name: pansichar;
\r
161 ifa_flags: cuint; // Interface flags (IFF_UP, IFF_BROADCAST, etc.)
\r
162 ifa_addr: Pinetsockaddrv;
\r
163 ifa_netmask: psockaddr;
\r
164 ifa_dstaddr: psockaddr; // union: Destination address (P-t-P) or broadcast address
\r
169 IFF_UP=1; //interface is administratively enabled
\r
171 function getifaddrs(var ifap: pifaddrs): cint; cdecl; external 'c' name 'getifaddrs';
\r
172 function freeifaddrs(ifap: pifaddrs): cint; cdecl; external 'c' name 'freeifaddrs';
\r
175 function getlocalips_internal(wantfamily:integer):tbiniplist;
\r
179 sa: PinetSockAddrV;
\r
181 result := biniplist_new;
\r
183 if getifaddrs(IfList) <> 0 then raise exception.create('getlocalips getifaddrs failed');
\r
186 while IfPtr <> nil do begin
\r
187 if ((IfPtr^.ifa_flags and IFF_UP) <> 0) then begin
\r
188 sa := IfPtr^.ifa_addr;
\r
189 //if (sa <> nil) then writeln(sa^.inaddr.len,' ',sa^.inaddr.family);
\r
191 if (sa <> nil) and (sa^.inaddr.family = wantfamily) then begin
\r
192 biniplist_add(result, inaddrvtobinip(sa^));
\r
195 IfPtr := IfPtr^.ifa_next;
\r
198 freeifaddrs(IfList);
\r
205 function getv6localips:tbiniplist;
\r
215 result := getlocalips_internal(AF_INET6);
\r
218 result := biniplist_new;
\r
220 assignfile(t,'/proc/net/if_inet6');
\r
221 {$i-}reset(t);{$i+}
\r
222 if ioresult <> 0 then begin
\r
223 {not on linux, try if this OS uses the other way to return v6 addresses}
\r
224 result := getlocalips_internal(AF_INET6);
\r
227 while not eof(t) do begin
\r
230 for a := 0 to 7 do begin
\r
231 if (s2 <> '') then s2 := s2 + ':';
\r
232 s2 := s2 + copy(s,(a shl 2)+1,4);
\r
235 if ip.family <> 0 then biniplist_add(result,ip);
\r
242 function getv4localips:tbiniplist;
\r
244 result := getlocalips_internal(AF_INET);
\r
247 function getlocalips:tbiniplist;
\r
249 result := getv4localips;
\r
251 biniplist_addlist(result,getv6localips);
\r
258 sysutils,windows,winsock,dnswin,registry;
\r
260 {the following code's purpose is to determine what IP windows would come from, to reach an IP
\r
261 it can be abused to find if there's any global v6 IPs on a local interface}
\r
263 SIO_ROUTING_INTERFACE_QUERY = $c8000014;
\r
264 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
266 function getlocalipforip(const ip:tbinip):tbinip;
\r
268 libraryhandle : hmodule;
\r
269 WSAIoctl:tWSAIoctl;
\r
272 inaddrv,inaddrv2:tinetsockaddrv;
\r
273 srcx:winsock.tsockaddr absolute inaddrv2;
\r
275 libraryhandle := LoadLibraryA('Ws2_32.dll');
\r
276 if (libraryhandle = 0) then raise exception.create('getlocalipforip: no winsock2');
\r
277 WSAIoctl := getprocaddress(libraryhandle,'WSAIoctl');
\r
278 handle := INVALID_SOCKET;
\r
280 if not assigned(WSAIoctl) then raise exception.create('getlocalipforip: no winsock2 WSAIoctl');
\r
282 makeinaddrv(ip,'0',inaddrv);
\r
283 handle := Socket(inaddrv.inaddr.family,SOCK_DGRAM,IPPROTO_UDP);
\r
284 if (handle = INVALID_SOCKET) then begin
\r
285 {this happens on XP without an IPv6 stack
\r
286 i can either fail with an exception, or with a "null result". an exception is annoying in the IDE}
\r
287 {fillchar(result,sizeof(result),0);
\r
289 raise exception.create('getlocalipforip: can''t create socket');
\r
291 if WSAIoctl(handle, SIO_ROUTING_INTERFACE_QUERY, inaddrv, sizeof(inaddrv), inaddrv2, sizeof(inaddrv2), a, nil, nil) <> 0
\r
292 then raise exception.create('getlocalipforip failed with error: '+inttostr(wsagetlasterror));
\r
293 result := inaddrvtobinip(inaddrv2);
\r
295 if (handle <> INVALID_SOCKET) then closesocket(handle);
\r
296 if (libraryhandle <> 0) then freelibrary(libraryhandle);
\r
301 function getv4localips:tbiniplist;
\r
303 templist:tbiniplist;
\r
307 result := biniplist_new;
\r
309 templist := getlocalips;
\r
310 for a := biniplist_getcount(templist)-1 downto 0 do begin
\r
311 biniptemp := biniplist_get(templist,a);
\r
312 if biniptemp.family = AF_INET then biniplist_add(result,biniptemp);
\r
317 function getv6localips:tbiniplist;
\r
319 templist:tbiniplist;
\r
323 result := biniplist_new;
\r
325 templist := getlocalips;
\r
326 for a := biniplist_getcount(templist)-1 downto 0 do begin
\r
327 biniptemp := biniplist_get(templist,a);
\r
328 if biniptemp.family = AF_INET6 then biniplist_add(result,biniptemp);
\r
333 function getlocalips:tbiniplist;
\r
337 usewindnstemp:boolean;
\r
340 result := winforwardlookuplist(lcgethostname,0,error);
\r
344 {windows XP doesn't add v6 IPs
\r
345 if we find no v6 IPs in the list, add one using a hack}
\r
346 for a := biniplist_getcount(result)-1 downto 0 do begin
\r
347 ip := biniplist_get(result,a);
\r
348 if ip.family = AF_INET6 then exit;
\r
352 ip := getlocalipforip(ipstrtobinf(v6_check_ip));
\r
353 if (ip.family = AF_INET6) then biniplist_add(result,ip);
\r
368 MAX_HOSTNAME_LEN = 132;
\r
369 MAX_DOMAIN_NAME_LEN = 132;
\r
370 MAX_SCOPE_ID_LEN = 260 ;
\r
371 MAX_ADAPTER_NAME_LENGTH = 260;
\r
372 MAX_ADAPTER_ADDRESS_LENGTH = 8;
\r
373 MAX_ADAPTER_DESCRIPTION_LENGTH = 132;
\r
374 ERROR_BUFFER_OVERFLOW = 111;
\r
375 MIB_IF_TYPE_ETHERNET = 6;
\r
376 MIB_IF_TYPE_TOKENRING = 9;
\r
377 MIB_IF_TYPE_FDDI = 15;
\r
378 MIB_IF_TYPE_PPP = 23;
\r
379 MIB_IF_TYPE_LOOPBACK = 24;
\r
380 MIB_IF_TYPE_SLIP = 28;
\r
384 tip_addr_string=packed record
\r
386 IpAddress : array[0..15] of ansichar;
\r
387 ipmask : array[0..15] of ansichar;
\r
390 pip_addr_string=^tip_addr_string;
\r
391 tFIXED_INFO=packed record
\r
392 HostName : array[0..MAX_HOSTNAME_LEN-1] of ansichar;
\r
393 DomainName : array[0..MAX_DOMAIN_NAME_LEN-1] of ansichar;
\r
394 currentdnsserver : pip_addr_string;
\r
395 dnsserverlist : tip_addr_string;
\r
396 nodetype : longint;
\r
397 ScopeId : array[0..MAX_SCOPE_ID_LEN + 4] of ansichar;
\r
398 enablerouting : longbool;
\r
399 enableproxy : longbool;
\r
400 enabledns : longbool;
\r
402 pFIXED_INFO=^tFIXED_INFO;
\r
405 iphlpapi : thandle;
\r
406 getnetworkparams : function(pFixedInfo : PFIXED_INFO;OutBufLen : plongint) : longint;stdcall;
\r
408 function callGetNetworkParams:pFIXED_INFO;
\r
410 fixed_info : pfixed_info;
\r
411 fixed_info_len : longint;
\r
414 if iphlpapi=0 then iphlpapi := loadlibrary('iphlpapi.dll');
\r
416 if not assigned(getnetworkparams) then getnetworkparams := getprocaddress(iphlpapi,'GetNetworkParams');
\r
417 if not assigned(getnetworkparams) then exit;
\r
418 fixed_info_len := 0;
\r
419 if GetNetworkParams(nil,@fixed_info_len)<>ERROR_BUFFER_OVERFLOW then exit;
\r
420 //fixed_info_len :=sizeof(tfixed_info);
\r
421 getmem(fixed_info,fixed_info_len);
\r
422 if GetNetworkParams(fixed_info,@fixed_info_len)<>0 then begin
\r
423 freemem(fixed_info);
\r
426 result := fixed_info;
\r
431 function getsystemdnsservers:tbiniplist;
\r
434 fixed_info : pfixed_info;
\r
435 currentdnsserver : pip_addr_string;
\r
437 nameserver,s:ansistring;
\r
448 result := biniplist_new;
\r
451 fixed_info := callgetnetworkparams;
\r
452 if fixed_info = nil then begin
\r
453 //2000 and up method not supported. use the 9x or NT 4 method.
\r
455 reg := TRegistry.Create();
\r
456 reg.RootKey := HKEY_LOCAL_MACHINE;
\r
458 if not reg.OpenKey('\System\CurrentControlSet\Services\VxD\MSTCP',false) then
\r
460 if not reg.OpenKey('\System\CurrentControlSet\Services\Tcpip\Parameters',false) then begin
\r
465 nameserver := reg.ReadString('NameServer');
\r
466 //DhcpNameServer is actually only set on NT
\r
467 if (nameserver = '') then nameserver := reg.ReadString('DhcpNameServer');
\r
471 //parse as comma separated list
\r
473 if (nameserver = '') then exit; //done
\r
474 a := pos(',',nameserver);
\r
475 if (a > 1) then begin
\r
476 s := copy(nameserver,1,a-1);
\r
477 nameserver := copy(nameserver,a+1,9999);
\r
483 ip := ipstrtobinf(s);
\r
484 if (ip.family <> 0) then biniplist_add(result,ip);
\r
488 currentdnsserver := @(fixed_info.dnsserverlist);
\r
489 while assigned(currentdnsserver) do begin
\r
490 ip := ipstrtobinf(currentdnsserver.IpAddress);
\r
491 if (ip.family <> 0) then biniplist_add(result,ip);
\r
492 currentdnsserver := currentdnsserver.next;
\r
494 freemem(fixed_info);
\r
497 assignfile(t,'/etc/resolv.conf');
\r
498 {$i-}reset(t);{$i+}
\r
499 if ioresult <> 0 then exit;
\r
501 while not eof(t) do begin
\r
503 if not (copy(s,1,10) = 'nameserver') then continue;
\r
504 s := copy(s,11,500);
\r
505 while s <> '' do begin
\r
506 if (s[1] = #32) or (s[1] = #9) then s := copy(s,2,500) else break;
\r
509 if a <> 0 then s := copy(s,1,a-1);
\r
511 if a <> 0 then s := copy(s,1,a-1);
\r
513 ip := ipstrtobinf(s);
\r
514 if (ip.family <> 0) then biniplist_add(result,ip);
\r
521 function have_ipv6_connectivity:boolean;
\r
526 ipmask_global,ipmask_6to4,ipmask_teredo:tbinip;
\r
528 function ip_is_suitable_v6:boolean;
\r
531 if (ip.family <> AF_INET6) then exit;
\r
532 if not comparebinipmask(ip,ipmask_global,3) then exit;
\r
533 if comparebinipmask(ip,ipmask_teredo,32) then exit;
\r
534 if comparebinipmask(ip,ipmask_6to4,16) then exit;
\r
541 ipstrtobin('2000::',ipmask_global);
\r
542 ipstrtobin('2001::',ipmask_teredo);
\r
543 ipstrtobin('2002::',ipmask_6to4);
\r
546 //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
548 ip := getlocalipforip(ipstrtobinf(v6_check_ip));
\r
549 if ip_is_suitable_v6 then result := true;
\r
554 l := getv6localips;
\r
555 if biniplist_getcount(l) = 0 then exit;
\r
557 {if there is any v6 IP which is globally routable and not 6to4 and not teredo, prefer v6}
\r
558 for a := biniplist_getcount(l)-1 downto 0 do begin
\r
559 ip := biniplist_get(l,a);
\r
560 if not ip_is_suitable_v6 then continue;
\r
568 function lcgethostname:ansistring;
\r
571 buf:array[0..255] of ansichar;
\r
575 fillchar(buf,sizeof(buf),0);
\r
576 i := winsock.gethostname(@buf,sizeof(buf));
\r
577 if (i = 0) then result := pansichar(@buf[0]);
\r
581 result := unix.gethostname;
\r