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
59 function gethostname:ansistring;
\r
60 function getlocalipforip(const ip:tbinip):tbinip;
\r
64 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
71 baseunix,sockets,sysutils;
\r
74 function getlocalips_internal(wantfamily:integer):tbiniplist;
\r
78 {$ifdef linux}SIOCGIFCONF=$8912;{$endif}
\r
79 {$ifdef bsd}{$ifdef cpu386}SIOCGIFCONF=$C0086924;{$endif}{$endif}
\r
81 {amd64: mac OS X: $C00C6924; freeBSD: $c0106924}
\r
83 tifconf=packed record
\r
88 tifrec=packed record
\r
89 ifr_ifrn:array [0..IF_NAMESIZE-1] of char;
\r
90 ifru_addr:TSockAddr;
\r
96 ifr,ifr2,ifrmax:^tifrec;
\r
97 lastlen,len:integer;
\r
101 result := biniplist_new;
\r
103 {must create a socket for this}
\r
104 s := fpsocket(AF_INET,SOCK_DGRAM,0);
\r
105 if (s < 0) then raise exception.create('getv4localips unable to create socket');
\r
107 fillchar(ifc,sizeof(ifc),0);
\r
112 len := 2*sizeof(tifrec);
\r
115 reallocmem(ifr,len);
\r
116 ifc.ifc_len := len;
\r
117 ifc.ifcu_rec := ifr;
\r
118 {get IP record list}
\r
119 if (fpioctl(s,SIOCGIFCONF,@ifc) < 0) then begin
\r
120 raise exception.create('getv4localips ioctl failed');
\r
122 if (lastlen = ifc.ifc_len) then break;
\r
123 lastlen := ifc.ifc_len;
\r
128 ifrmax := pointer(taddrint(ifr) + ifc.ifc_len);
\r
129 while (ifr2 < ifrmax) do begin
\r
130 lastlen := taddrint(ifrmax) - taddrint(ifr2);
\r
131 if (lastlen < sizeof(tifrec)) then break; {not enough left}
\r
133 ad := @ifr2.ifru_addr;
\r
136 len := ad.inaddr.len + IF_NAMESIZE;
\r
137 if (len < sizeof(tifrec)) then
\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
153 function getv6localips:tbiniplist;
\r
160 result := biniplist_new;
\r
162 assignfile(t,'/proc/net/if_inet6');
\r
163 {$i-}reset(t);{$i+}
\r
164 if ioresult <> 0 then begin
\r
165 {not on linux, try if this OS uses the other way to return v6 addresses}
\r
166 result := getlocalips_internal(AF_INET6);
\r
169 while not eof(t) do begin
\r
172 for a := 0 to 7 do begin
\r
173 if (s2 <> '') then s2 := s2 + ':';
\r
174 s2 := s2 + copy(s,(a shl 2)+1,4);
\r
177 if ip.family <> 0 then biniplist_add(result,ip);
\r
183 function getv4localips:tbiniplist;
\r
185 result := getlocalips_internal(AF_INET);
\r
188 function getlocalips:tbiniplist;
\r
190 result := getv4localips;
\r
192 biniplist_addlist(result,getv6localips);
\r
199 sysutils,windows,winsock,dnswin;
\r
201 {the following code's purpose is to determine what IP windows would come from, to reach an IP
\r
202 it can be abused to find if there's any global v6 IPs on a local interface}
\r
204 SIO_ROUTING_INTERFACE_QUERY = $c8000014;
\r
205 function WSAIoctl(s: TSocket; code:integer; const Buf; len: Integer; var output; outlen:integer; var outreturned: Integer; overlapped:pointer; completion: pointer): Integer; stdcall; external 'ws2_32.dll' name 'WSAIoctl';
\r
207 function getlocalipforip(const ip:tbinip):tbinip;
\r
211 inaddrv,inaddrv2:tinetsockaddrv;
\r
212 srcx:winsock.tsockaddr absolute inaddrv2;
\r
214 makeinaddrv(ip,'0',inaddrv);
\r
215 handle := Socket(inaddrv.inaddr.family,SOCK_DGRAM,IPPROTO_UDP);
\r
216 if (handle < 0) then begin
\r
217 {this happens on XP without an IPv6 stack
\r
218 i can either fail with an exception, or with a "null result". an exception is annoying in the IDE}
\r
219 {fillchar(result,sizeof(result),0);
\r
221 raise exception.create('getlocalipforip: can''t create socket');
\r
223 if WSAIoctl(handle, SIO_ROUTING_INTERFACE_QUERY, inaddrv, sizeof(inaddrv), inaddrv2, sizeof(inaddrv2), a, nil, nil) <> 0
\r
224 then raise exception.create('getlocalipforip failed with error: '+inttostr(wsagetlasterror));
\r
225 result := inaddrvtobinip(inaddrv2);
\r
226 closesocket(handle);
\r
230 function getv4localips:tbiniplist;
\r
232 templist:tbiniplist;
\r
236 result := biniplist_new;
\r
238 templist := getlocalips;
\r
239 for a := biniplist_getcount(templist)-1 downto 0 do begin
\r
240 biniptemp := biniplist_get(templist,a);
\r
241 if biniptemp.family = AF_INET then biniplist_add(result,biniptemp);
\r
246 function getv6localips:tbiniplist;
\r
248 templist:tbiniplist;
\r
252 result := biniplist_new;
\r
254 templist := getlocalips;
\r
255 for a := biniplist_getcount(templist)-1 downto 0 do begin
\r
256 biniptemp := biniplist_get(templist,a);
\r
257 if biniptemp.family = AF_INET6 then biniplist_add(result,biniptemp);
\r
262 function getlocalips:tbiniplist;
\r
266 usewindnstemp:boolean;
\r
269 result := winforwardlookuplist('',0,error);
\r
273 {windows XP doesn't add v6 IPs
\r
274 if we find no v6 IPs in the list, add one using a hack}
\r
275 for a := biniplist_getcount(result)-1 downto 0 do begin
\r
276 ip := biniplist_get(result,a);
\r
277 if ip.family = AF_INET6 then exit;
\r
281 ip := getlocalipforip(ipstrtobinf(v6_check_ip));
\r
282 if (ip.family = AF_INET6) then biniplist_add(result,ip);
\r
297 MAX_HOSTNAME_LEN = 132;
\r
298 MAX_DOMAIN_NAME_LEN = 132;
\r
299 MAX_SCOPE_ID_LEN = 260 ;
\r
300 MAX_ADAPTER_NAME_LENGTH = 260;
\r
301 MAX_ADAPTER_ADDRESS_LENGTH = 8;
\r
302 MAX_ADAPTER_DESCRIPTION_LENGTH = 132;
\r
303 ERROR_BUFFER_OVERFLOW = 111;
\r
304 MIB_IF_TYPE_ETHERNET = 6;
\r
305 MIB_IF_TYPE_TOKENRING = 9;
\r
306 MIB_IF_TYPE_FDDI = 15;
\r
307 MIB_IF_TYPE_PPP = 23;
\r
308 MIB_IF_TYPE_LOOPBACK = 24;
\r
309 MIB_IF_TYPE_SLIP = 28;
\r
313 tip_addr_string=packed record
\r
315 IpAddress : array[0..15] of ansichar;
\r
316 ipmask : array[0..15] of ansichar;
\r
319 pip_addr_string=^tip_addr_string;
\r
320 tFIXED_INFO=packed record
\r
321 HostName : array[0..MAX_HOSTNAME_LEN-1] of ansichar;
\r
322 DomainName : array[0..MAX_DOMAIN_NAME_LEN-1] of ansichar;
\r
323 currentdnsserver : pip_addr_string;
\r
324 dnsserverlist : tip_addr_string;
\r
325 nodetype : longint;
\r
326 ScopeId : array[0..MAX_SCOPE_ID_LEN + 4] of ansichar;
\r
327 enablerouting : longbool;
\r
328 enableproxy : longbool;
\r
329 enabledns : longbool;
\r
331 pFIXED_INFO=^tFIXED_INFO;
\r
334 iphlpapi : thandle;
\r
335 getnetworkparams : function(pFixedInfo : PFIXED_INFO;OutBufLen : plongint) : longint;stdcall;
\r
337 function callGetNetworkParams:pFIXED_INFO;
\r
339 fixed_info : pfixed_info;
\r
340 fixed_info_len : longint;
\r
343 if iphlpapi=0 then iphlpapi := loadlibrary('iphlpapi.dll');
\r
344 if not assigned(getnetworkparams) then @getnetworkparams := getprocaddress(iphlpapi,'GetNetworkParams');
\r
345 if not assigned(getnetworkparams) then exit;
\r
346 fixed_info_len := 0;
\r
347 if GetNetworkParams(nil,@fixed_info_len)<>ERROR_BUFFER_OVERFLOW then exit;
\r
348 //fixed_info_len :=sizeof(tfixed_info);
\r
349 getmem(fixed_info,fixed_info_len);
\r
350 if GetNetworkParams(fixed_info,@fixed_info_len)<>0 then begin
\r
351 freemem(fixed_info);
\r
354 result := fixed_info;
\r
359 function getsystemdnsservers:tbiniplist;
\r
362 fixed_info : pfixed_info;
\r
363 currentdnsserver : pip_addr_string;
\r
373 result := biniplist_new;
\r
376 fixed_info := callgetnetworkparams;
\r
377 if fixed_info = nil then exit;
\r
379 currentdnsserver := @(fixed_info.dnsserverlist);
\r
380 while assigned(currentdnsserver) do begin
\r
381 ip := ipstrtobinf(currentdnsserver.IpAddress);
\r
382 if (ip.family <> 0) then biniplist_add(result,ip);
\r
383 currentdnsserver := currentdnsserver.next;
\r
385 freemem(fixed_info);
\r
388 assignfile(t,'/etc/resolv.conf');
\r
389 {$i-}reset(t);{$i+}
\r
390 if ioresult <> 0 then exit;
\r
392 while not eof(t) do begin
\r
394 if not (copy(s,1,10) = 'nameserver') then continue;
\r
395 s := copy(s,11,500);
\r
396 while s <> '' do begin
\r
397 if (s[1] = #32) or (s[1] = #9) then s := copy(s,2,500) else break;
\r
400 if a <> 0 then s := copy(s,1,a-1);
\r
402 if a <> 0 then s := copy(s,1,a-1);
\r
404 ip := ipstrtobinf(s);
\r
405 if (ip.family <> 0) then biniplist_add(result,ip);
\r
412 function have_ipv6_connectivity:boolean;
\r
417 ipmask_global,ipmask_6to4,ipmask_teredo:tbinip;
\r
419 function ip_is_suitable_v6:boolean;
\r
422 if (ip.family <> AF_INET6) then exit;
\r
423 if not comparebinipmask(ip,ipmask_global,3) then exit;
\r
424 if comparebinipmask(ip,ipmask_teredo,32) then exit;
\r
425 if comparebinipmask(ip,ipmask_6to4,16) then exit;
\r
432 ipstrtobin('2000::',ipmask_global);
\r
433 ipstrtobin('2001::',ipmask_teredo);
\r
434 ipstrtobin('2002::',ipmask_6to4);
\r
437 //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
439 ip := getlocalipforip(ipstrtobinf(v6_check_ip));
\r
440 if ip_is_suitable_v6 then result := true;
\r
445 l := getv6localips;
\r
446 if biniplist_getcount(l) = 0 then exit;
\r
448 {if there is any v6 IP which is globally routable and not 6to4 and not teredo, prefer v6}
\r
449 for a := biniplist_getcount(l)-1 downto 0 do begin
\r
450 ip := biniplist_get(l,a);
\r
451 if not ip_is_suitable_v6 then continue;
\r
460 function gethostname:ansistring;
\r
462 fixed_info : pfixed_info;
\r
465 fixed_info := callgetnetworkparams;
\r
466 if fixed_info = nil then exit;
\r
468 result := fixed_info.hostname;
\r
469 if fixed_info.domainname <> '' then result := result + '.'+fixed_info.domainname;
\r
471 freemem(fixed_info);
\r