1 { Copyright (C) 2005 Bas Steendijk and Peter Green
2 For conditions of distribution and use, see copyright notice in zlib_license.txt
3 which is included in the package
4 ----------------------------------------------------------------------------- }
7 unit to get various local system config
10 - get IP addresses assigned to local interfaces.
11 both IPv4 and IPv6, or one address family in isolation.
12 works on both windows and linux.
19 - mac OS X (probably works on freeBSD too)
23 - localhost IPs (127.0.0.1, ::1) may be returned, the app must not expect them to be in or not in.
24 (typically, they're returned on linux and not on windows)
26 - normal behavior is to return all v6 IPs, including link local (fe80::).
27 an app that doesn't want link local IPs has to filter them out.
28 windows XP returns only one, global scope, v6 IP, due to shortcomings.
32 - get system DNS servers
34 - get system hostname (if not on windows, use freepascal's "unix")
42 uses binipstuff,pgtypes;
44 {$include lcoreconfig.inc}
46 function getlocalips:tbiniplist;
47 function getv4localips:tbiniplist;
49 function getv6localips:tbiniplist;
52 function getsystemdnsservers:tbiniplist;
55 function gethostname:ansistring;
63 baseunix,sockets,sysutils;
66 function getlocalips_internal(wantfamily:integer):tbiniplist;
70 {$ifdef linux}SIOCGIFCONF=$8912;{$endif}
71 {$ifdef bsd}{$ifdef cpu386}SIOCGIFCONF=$C0086924;{$endif}{$endif}
73 {amd64: mac OS X: $C00C6924; freeBSD: $c0106924}
81 ifr_ifrn:array [0..IF_NAMESIZE-1] of char;
88 ifr,ifr2,ifrmax:^tifrec;
93 result := biniplist_new;
95 {must create a socket for this}
96 s := fpsocket(AF_INET,SOCK_DGRAM,0);
97 if (s < 0) then raise exception.create('getv4localips unable to create socket');
99 fillchar(ifc,sizeof(ifc),0);
104 len := 2*sizeof(tifrec);
111 if (fpioctl(s,SIOCGIFCONF,@ifc) < 0) then begin
112 raise exception.create('getv4localips ioctl failed');
114 if (lastlen = ifc.ifc_len) then break;
115 lastlen := ifc.ifc_len;
120 ifrmax := pointer(taddrint(ifr) + ifc.ifc_len);
121 while (ifr2 < ifrmax) do begin
122 lastlen := taddrint(ifrmax) - taddrint(ifr2);
123 if (lastlen < sizeof(tifrec)) then break; {not enough left}
125 ad := @ifr2.ifru_addr;
128 len := ad.inaddr.len + IF_NAMESIZE;
129 if (len < sizeof(tifrec)) then
131 len := sizeof(tifrec);
133 if (len < sizeof(tifrec)) then break; {not enough left}
135 ip := inaddrvtobinip(ad^);
136 if (ip.family <> 0) and ((ip.family = wantfamily) or (wantfamily = 0)) then biniplist_add(result,ip);
137 inc(taddrint(ifr2),len);
145 function getv6localips:tbiniplist;
152 result := biniplist_new;
154 assignfile(t,'/proc/net/if_inet6');
156 if ioresult <> 0 then begin
157 {not on linux, try if this OS uses the other way to return v6 addresses}
158 result := getlocalips_internal(AF_INET6);
161 while not eof(t) do begin
164 for a := 0 to 7 do begin
165 if (s2 <> '') then s2 := s2 + ':';
166 s2 := s2 + copy(s,(a shl 2)+1,4);
169 if ip.family <> 0 then biniplist_add(result,ip);
175 function getv4localips:tbiniplist;
177 result := getlocalips_internal(AF_INET);
180 function getlocalips:tbiniplist;
182 result := getv4localips;
184 biniplist_addlist(result,getv6localips);
191 sysutils,windows,winsock,dnssync;
193 {the following code's purpose is to determine what IP windows would come from, to reach an IP
194 it can be abused to find if there's any global v6 IPs on a local interface}
196 SIO_ROUTING_INTERFACE_QUERY = $c8000014;
197 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';
199 function getlocalipforip(const ip:tbinip):tbinip;
203 inaddrv,inaddrv2:tinetsockaddrv;
204 srcx:winsock.tsockaddr absolute inaddrv2;
206 makeinaddrv(ip,'0',inaddrv);
207 handle := Socket(inaddrv.inaddr.family,SOCK_DGRAM,IPPROTO_UDP);
208 if (handle < 0) then begin
209 {this happens on XP without an IPv6 stack
210 i can either fail with an exception, or with a "null result". an exception is annoying in the IDE}
211 {fillchar(result,sizeof(result),0);
213 raise exception.create('getlocalipforip: can''t create socket');
215 if WSAIoctl(handle, SIO_ROUTING_INTERFACE_QUERY, inaddrv, sizeof(inaddrv), inaddrv2, sizeof(inaddrv2), a, nil, nil) <> 0
216 then raise exception.create('getlocalipforip failed with error: '+inttostr(wsagetlasterror));
217 result := inaddrvtobinip(inaddrv2);
222 function getv4localips:tbiniplist;
228 result := biniplist_new;
230 templist := getlocalips;
231 for a := biniplist_getcount(templist)-1 downto 0 do begin
232 biniptemp := biniplist_get(templist,a);
233 if biniptemp.family = AF_INET then biniplist_add(result,biniptemp);
238 function getv6localips:tbiniplist;
244 result := biniplist_new;
246 templist := getlocalips;
247 for a := biniplist_getcount(templist)-1 downto 0 do begin
248 biniptemp := biniplist_get(templist,a);
249 if biniptemp.family = AF_INET6 then biniplist_add(result,biniptemp);
254 function getlocalips:tbiniplist;
259 result := forwardlookuplist('',0);
263 {windows XP doesn't add v6 IPs
264 if we find no v6 IPs in the list, add one using a hack}
265 for a := biniplist_getcount(result)-1 downto 0 do begin
266 ip := biniplist_get(result,a);
267 if ip.family = AF_INET6 then exit;
271 ip := getlocalipforip(ipstrtobinf('2001:200::'));
272 if (ip.family = AF_INET6) then biniplist_add(result,ip);
287 MAX_HOSTNAME_LEN = 132;
288 MAX_DOMAIN_NAME_LEN = 132;
289 MAX_SCOPE_ID_LEN = 260 ;
290 MAX_ADAPTER_NAME_LENGTH = 260;
291 MAX_ADAPTER_ADDRESS_LENGTH = 8;
292 MAX_ADAPTER_DESCRIPTION_LENGTH = 132;
293 ERROR_BUFFER_OVERFLOW = 111;
294 MIB_IF_TYPE_ETHERNET = 6;
295 MIB_IF_TYPE_TOKENRING = 9;
296 MIB_IF_TYPE_FDDI = 15;
297 MIB_IF_TYPE_PPP = 23;
298 MIB_IF_TYPE_LOOPBACK = 24;
299 MIB_IF_TYPE_SLIP = 28;
303 tip_addr_string=packed record
305 IpAddress : array[0..15] of ansichar;
306 ipmask : array[0..15] of ansichar;
309 pip_addr_string=^tip_addr_string;
310 tFIXED_INFO=packed record
311 HostName : array[0..MAX_HOSTNAME_LEN-1] of ansichar;
312 DomainName : array[0..MAX_DOMAIN_NAME_LEN-1] of ansichar;
313 currentdnsserver : pip_addr_string;
314 dnsserverlist : tip_addr_string;
316 ScopeId : array[0..MAX_SCOPE_ID_LEN + 4] of ansichar;
317 enablerouting : longbool;
318 enableproxy : longbool;
319 enabledns : longbool;
321 pFIXED_INFO=^tFIXED_INFO;
325 getnetworkparams : function(pFixedInfo : PFIXED_INFO;OutBufLen : plongint) : longint;stdcall;
327 function callGetNetworkParams:pFIXED_INFO;
329 fixed_info : pfixed_info;
330 fixed_info_len : longint;
333 if iphlpapi=0 then iphlpapi := loadlibrary('iphlpapi.dll');
334 if not assigned(getnetworkparams) then @getnetworkparams := getprocaddress(iphlpapi,'GetNetworkParams');
335 if not assigned(getnetworkparams) then exit;
337 if GetNetworkParams(nil,@fixed_info_len)<>ERROR_BUFFER_OVERFLOW then exit;
338 //fixed_info_len :=sizeof(tfixed_info);
339 getmem(fixed_info,fixed_info_len);
340 if GetNetworkParams(fixed_info,@fixed_info_len)<>0 then begin
344 result := fixed_info;
349 function getsystemdnsservers:tbiniplist;
352 fixed_info : pfixed_info;
353 currentdnsserver : pip_addr_string;
363 result := biniplist_new;
366 fixed_info := callgetnetworkparams;
367 if fixed_info = nil then exit;
369 currentdnsserver := @(fixed_info.dnsserverlist);
370 while assigned(currentdnsserver) do begin
371 ip := ipstrtobinf(currentdnsserver.IpAddress);
372 if (ip.family <> 0) then biniplist_add(result,ip);
373 currentdnsserver := currentdnsserver.next;
378 assignfile(t,'/etc/resolv.conf');
380 if ioresult <> 0 then exit;
382 while not eof(t) do begin
384 if not (copy(s,1,10) = 'nameserver') then continue;
386 while s <> '' do begin
387 if (s[1] = #32) or (s[1] = #9) then s := copy(s,2,500) else break;
390 if a <> 0 then s := copy(s,1,a-1);
392 if a <> 0 then s := copy(s,1,a-1);
394 ip := ipstrtobinf(s);
395 if (ip.family <> 0) then biniplist_add(result,ip);
402 function gethostname:ansistring;
404 fixed_info : pfixed_info;
407 fixed_info := callgetnetworkparams;
408 if fixed_info = nil then exit;
410 result := fixed_info.hostname;
411 if fixed_info.domainname <> '' then result := result + '.'+fixed_info.domainname;