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