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 
  13 {$include lcoreconfig.inc}
\r 
  22 {$include pgtypes.inc}
\r 
  24 {$include uint32.inc}
\r 
  27   hexchars:array[0..15] of ansichar='0123456789abcdef';
\r 
  32     //redeclare these constants so units that use us can use them
\r 
  33     //without using sockets directly
\r 
  43       {$define want_Tin6_addr}
\r 
  46       {$define want_Tin6_addr}
\r 
  48     {$ifdef want_Tin6_addr}
\r 
  49       Tin6_addr = packed record
\r 
  51           0: (u6_addr8  : array[0..15] of byte);
\r 
  52           1: (u6_addr16 : array[0..7] of Word);
\r 
  53           2: (u6_addr32 : array[0..3] of uint32);
\r 
  54           3: (s6_addr8  : array[0..15] of shortint);
\r 
  55           4: (s6_addr   : array[0..15] of shortint);
\r 
  56           5: (s6_addr16 : array[0..7] of smallint);
\r 
  57           6: (s6_addr32 : array[0..3] of LongInt);
\r 
  73   {zipplet 20170204: FPC 3.0.0 changed the definition of TInetSockAddr:
\r 
  74     - http://www.freepascal.org/docs-html/rtl/sockets/tinetsockaddr.html
\r 
  75     - http://www.freepascal.org/docs-html/rtl/sockets/sockaddr_in.html
\r 
  76    Due to this, TInetSockAddr -> TLInetSockAddr4 / TLInetSockAddr6
\r 
  77    Using our own types no matter what OS or compiler version will prevent future problems.
\r 
  78    Adding "4" to non IPv6 record names improves code clarity }
\r 
  81     //zipplet 20170204: Do we still need to support ver1_0? Perhaps a cleanup is in order.
\r 
  82     //For now keep supporting it for compatibility.
\r 
  90   TLInetSockAddr4 = packed Record
\r 
  94     pad   :array [0..7] of byte;   //zipplet 20170204 - originally this was 1..8 for some reason
\r 
  98     TLInetSockAddr6 = packed record
\r 
 101       sin6_flowinfo: uint32;
\r 
 102       sin6_addr: tin6_addr;
\r 
 103       sin6_scope_id: uint32;
\r 
 107   //zipplet 20170204: I did not rename the unioned record. We might want to rename this to TLinetSockAddrv
\r 
 108   TinetSockAddrv = packed record
\r 
 110       0: (InAddr:TLInetSockAddr4);
\r 
 112       1: (InAddr6:TLInetSockAddr6);
\r 
 115   Pinetsockaddrv = ^Tinetsockaddrv;
\r 
 118     tsockaddrin=TLInetSockAddr4;
\r 
 121 bin IP list code, by beware
\r 
 122 while this is really just a string, on the interface side it must be treated
\r 
 123 as an opaque var which is passed as "var" when it needs to be modified}
\r 
 125   tbiniplist=tbufferstring;
\r 
 127 function biniplist_new:tbiniplist;
\r 
 128 procedure biniplist_add(var l:tbiniplist;ip:tbinip);
\r 
 129 function biniplist_getcount(const l:tbiniplist):integer;
\r 
 130 function biniplist_get(const l:tbiniplist;index:integer):tbinip;
\r 
 131 procedure biniplist_set(var l:tbiniplist;index:integer;ip:tbinip);
\r 
 132 procedure biniplist_setcount(var l:tbiniplist;newlen:integer);
\r 
 133 procedure biniplist_free(var l:tbiniplist);
\r 
 134 procedure biniplist_addlist(var l:tbiniplist;const l2:tbiniplist);
\r 
 135 function biniplist_tostr(const l:tbiniplist):thostname;
\r 
 136 function isbiniplist(const l:tbiniplist):boolean;
\r 
 138 function htons(w:word):word;
\r 
 139 function htonl(i:uint32):uint32;
\r 
 141 function ipstrtobin(const s:thostname;var binip:tbinip):boolean;
\r 
 142 function ipstrtobinf(const s:thostname):tbinip;
\r 
 143 function ipbintostr(const binip:tbinip):thostname;
\r 
 145 function ip6bintostr(const bin:tin6_addr):thostname;
\r 
 146 function ip6strtobin(const s:thostname;var bin:tin6_addr):boolean;
\r 
 149 function comparebinip(const ip1,ip2:tbinip):boolean;
\r 
 150 procedure maskbits(var binip:tbinip;bits:integer);
\r 
 151 function comparebinipmask(ip1,ip2:tbinip;bits:integer):boolean;
\r 
 153 procedure addipsoffamily(var l:tbiniplist;const l2:tbiniplist;family:integer);
\r 
 156 function longip(s:thostname):longint;
\r 
 158 function needconverttov4(const ip:tbinip):boolean;
\r 
 159 procedure converttov4(var ip:tbinip);
\r 
 160 procedure converttov6(var ip:tbinip);
\r 
 162 function inaddrvtobinip(inaddrv:tinetsockaddrv):tbinip;
\r 
 163 function makeinaddrv(addr:tbinip;port:ansistring;var inaddr:tinetsockaddrv):integer;
\r 
 164 function inaddrsize(inaddr:tinetsockaddrv):integer;
\r 
 166 function getbinipbitlength(const ip:tbinip):integer;
\r 
 167 function getipstrbitlength(const ip:thostname):integer;
\r 
 168 function getfamilybitlength(family:integer):integer;
\r 
 174 function htons(w:word):word;
\r 
 176   {$ifdef ENDIAN_LITTLE}
\r 
 177   result := ((w and $ff00) shr 8) or ((w and $ff) shl 8);
\r 
 183 function htonl(i:uint32):uint32;
\r 
 185   {$ifdef ENDIAN_LITTLE}
\r 
 186   result := (i shr 24) or (i shr 8 and $ff00) or (i shl 8 and $ff0000) or (i shl 24 and $ff000000);
\r 
 193 function inaddrvtobinip(inaddrv:tinetsockaddrv):tbinip;
\r 
 195   result.family := inaddrv.inaddr.family;
\r 
 196   if result.family = AF_INET then result.ip := inaddrv.inaddr.addr;
\r 
 198   if result.family = AF_INET6 then result.ip6 := inaddrv.inaddr6.sin6_addr;
\r 
 202 function makeinaddrv(addr:tbinip;port:ansistring;var inaddr:tinetsockaddrv):integer;
\r 
 205 {  biniptemp := forwardlookup(addr,10);}
\r 
 206   fillchar(inaddr,sizeof(inaddr),0);
\r 
 207   //writeln('converted address '+addr+' to binip '+ipbintostr(biniptemp));
\r 
 208   if addr.family = AF_INET then begin
\r 
 209     inAddr.InAddr.family:=AF_INET;
\r 
 210     inAddr.InAddr.port:=htons(strtointdef(port,0));
\r 
 211     inAddr.InAddr.addr:=addr.ip;
\r 
 212     result := sizeof(tlinetsockaddr4);
\r 
 215   if addr.family = AF_INET6 then begin
\r 
 216     inAddr.InAddr6.sin6_family:=AF_INET6;
\r 
 217     inAddr.InAddr6.sin6_port:=htons(strtointdef(port,0));
\r 
 218     inAddr.InAddr6.sin6_addr:=addr.ip6;
\r 
 219     result := sizeof(tlinetsockaddr6);
\r 
 224 function inaddrsize(inaddr:tinetsockaddrv):integer;
\r 
 227   if inaddr.inaddr.family = AF_INET6 then result := sizeof(tlinetsockaddr6) else
\r 
 229   result := sizeof(tlinetsockaddr4);
\r 
 233 {converts dotted v4 IP to longint. returns host endian order}
\r 
 234 function longip(s:thostname):longint;
\r 
 238 function convertbyte(const s:ansistring):integer;
\r 
 240   result := strtointdef(s,-1);
\r 
 241   if result < 0 then begin
\r 
 245   if result > 255 then begin
\r 
 250   if (result <> 0) and (s[1] = '0') then begin
\r 
 255   if not (s[1] in ['0'..'9']) then begin
\r 
 264   if a = 0 then exit;
\r 
 265   b := convertbyte(copy(s,1,a-1));if (b < 0) then exit;
\r 
 267   s := copy(s,a+1,256);
\r 
 269   if a = 0 then exit;
\r 
 270   b := convertbyte(copy(s,1,a-1));if (b < 0) then exit;
\r 
 271   l := l or b shl 16;
\r 
 272   s := copy(s,a+1,256);
\r 
 274   if a = 0 then exit;
\r 
 275   b := convertbyte(copy(s,1,a-1));if (b < 0) then exit;
\r 
 277   s := copy(s,a+1,256);
\r 
 278   b := convertbyte(copy(s,1,256));if (b < 0) then exit;
\r 
 284 function ipstrtobinf;
\r 
 286   ipstrtobin(s,result);
\r 
 289 function ipstrtobin(const s:thostname;var binip:tbinip):boolean;
\r 
 294   if pos(':',s) <> 0 then begin
\r 
 295     {try ipv6. use builtin routine}
\r 
 296     result := ip6strtobin(s,binip.ip6);
\r 
 297     if result then binip.family := AF_INET6;
\r 
 303   // zipplet: htonl() expects a uint32 but longip() spits out longint.
\r 
 304   // Because longip() is deprecated, we do not fix it but typecast.
\r 
 305   //binip.ip := htonl(longip(s));
\r 
 306   binip.ip := htonl(uint32(longip(s)));
\r 
 307   if (binip.ip <> 0) or (s = '0.0.0.0') then begin
\r 
 309     binip.family := AF_INET;
\r 
 314 function ipbintostr(const binip:tbinip):thostname;
\r 
 320   if binip.family = AF_INET6 then begin
\r 
 321     result := ip6bintostr(binip.ip6);
\r 
 324   if binip.family = AF_INET then begin
\r 
 325     a := htonl(binip.ip);
\r 
 326     result := inttostr(a shr 24)+'.'+inttostr((a shr 16) and $ff)+'.'+inttostr((a shr 8) and $ff)+'.'+inttostr(a and $ff);
\r 
 331 {------------------------------------------------------------------------------}
\r 
 336 IPv6 address binary to/from string conversion routines
\r 
 339 - implementation does not depend on other ipv6 code such as the tin6_addr type,
\r 
 340   the parameter can also be untyped.
\r 
 341 - it is host endian neutral - binary format is always network order
\r 
 342 - it supports compression of zeroes
\r 
 343 - it supports ::ffff:192.168.12.34 style addresses
\r 
 344 - they are made to do the Right Thing, more efficient implementations are possible
\r 
 347 {fpc has hostaddrtostr6 and strtohostaddr6 but the later isnt implemented yet}
\r 
 350 function ip6bintostr(const bin:tin6_addr):thostname;
\r 
 351 {base16 with lowercase output}
\r 
 352 function makehex(w:word):ansistring;
\r 
 355   if w >= 4096 then result := result + hexchars[w shr 12];
\r 
 356   if w >= 256 then result := result + hexchars[w shr 8 and $f];
\r 
 357   if w >= 16 then result := result + hexchars[w shr 4 and $f];
\r 
 358   result := result + hexchars[w and $f];
\r 
 362   a,b,c,addrlen:integer;
\r 
 363   runbegin,runlength:integer;
\r 
 364   bytes:array[0..15] of byte absolute bin;
\r 
 365   words:array[0..7] of word;
\r 
 366   dwords:array[0..3] of integer absolute words;
\r 
 368   for a := 0 to 7 do begin
\r 
 369     words[a] := bytes[a shl 1] shl 8 or bytes[a shl 1 or 1];
\r 
 371   if (dwords[0] = 0) and (dwords[1] = 0) and (words[4] = 0) and (words[5] = $ffff) then begin
\r 
 372     {::ffff:/96 exception: v4 IP}
\r 
 377   {find longest run of zeroes}
\r 
 380   for a := 0 to addrlen-1 do begin
\r 
 381     if words[a] = 0 then begin
\r 
 383       for b := a to addrlen-1 do if words[b] = 0 then begin
\r 
 386       if (c > runlength) then begin
\r 
 393   {run length at least 2 0 words}
\r 
 394   if (runlength = 1) then begin
\r 
 400   for a := 0 to runbegin-1 do begin
\r 
 401     if (a <> 0) then result := result + ':';
\r 
 402     result := result + makehex(words[a]);
\r 
 404   if runlength > 0 then result := result + '::';
\r 
 405   c := runbegin+runlength;
\r 
 406   for a := c to addrlen-1 do begin
\r 
 407     if (a > c) then result := result + ':';
\r 
 408     result := result + makehex(words[a]);
\r 
 410   if addrlen = 6 then begin
\r 
 411     result := result + ':'+inttostr(bytes[12])+'.'+inttostr(bytes[13])+'.'+inttostr(bytes[14])+'.'+inttostr(bytes[15]);
\r 
 415 function ip6strtobin(const s:thostname;var bin:tin6_addr):boolean;
\r 
 418   fields:array[0..7] of ansistring;
\r 
 419   fieldcount:integer;
\r 
 420   emptyfield:integer;
\r 
 422   words:array[0..7] of word;
\r 
 423   bytes:array[0..15] of byte absolute bin;
\r 
 426   for a := 0 to 7 do fields[a] := '';
\r 
 428   for a := 1 to length(s) do begin
\r 
 429     if s[a] = ':' then inc(fieldcount) else fields[fieldcount] := fields[fieldcount] + s[a];
\r 
 430     if fieldcount > 7 then exit;
\r 
 432   if fieldcount < 2 then exit;
\r 
 434   {find the empty field (compressed zeroes), not counting the first and last there may be at most 1}
\r 
 436   for a := 1 to fieldcount-1 do begin
\r 
 437     if fields[a] = '' then begin
\r 
 438       if emptyfield = -1 then emptyfield := a else exit;
\r 
 442   {check if last field is a valid v4 IP}
\r 
 443   a := longip(fields[fieldcount]);
\r 
 444   if (a <> 0) or (fields[fieldcount] = '0.0.0.0') then wordcount := 6 else wordcount := 8;
\r 
 445   {0:1:2:3:4:5:6.6.6.6
\r 
 447   fillchar(words,sizeof(words),0);
\r 
 448   if wordcount = 6 then begin
\r 
 449     if fieldcount > 6 then exit;
\r 
 450     words[6] := a shr 16;
\r 
 451     words[7] := a and $ffff;
\r 
 453   if emptyfield = -1 then begin
\r 
 454     {no run length: must be an exact number of fields}
\r 
 455     if wordcount = 6 then begin
\r 
 456       if fieldcount <> 6 then exit;
\r 
 458     end else if wordcount = 8 then begin
\r 
 459       if fieldcount <> 7 then exit;
\r 
 463   for a := 0 to emptyfield do begin
\r 
 464     if fields[a] = '' then b := 0 else b := strtointdef('$'+fields[a],-1);
\r 
 465     if (b < 0) or (b > $ffff) then exit;
\r 
 468   if wordcount = 6 then dec(fieldcount);
\r 
 469   for a := wordcount-1 downto wordcount-(fieldcount-emptyfield) do begin
\r 
 470     b := a+fieldcount-wordcount+1;
\r 
 471     if fields[b] = '' then b := 0 else b := strtointdef('$'+fields[b],-1);
\r 
 472     if (b < 0) or (b > $ffff) then exit;
\r 
 475   for a := 0 to 7 do begin
\r 
 476     bytes[a shl 1] := words[a] shr 8;
\r 
 477     bytes[a shl 1 or 1] := words[a] and $ff;
\r 
 483 function comparebinip(const ip1,ip2:tbinip):boolean;
\r 
 485   if (ip1.ip <> ip2.ip) then begin
\r 
 491   if ip1.family = AF_INET6 then begin
\r 
 492     if (ip1.ip6.s6_addr32[1] <> ip2.ip6.s6_addr32[1])
\r 
 493     or (ip1.ip6.s6_addr32[2] <> ip2.ip6.s6_addr32[2])
\r 
 494     or (ip1.ip6.s6_addr32[3] <> ip2.ip6.s6_addr32[3]) then begin
\r 
 501   result := (ip1.family = ip2.family);
\r 
 504 procedure maskbits(var binip:tbinip;bits:integer);
\r 
 506   ipmax={$ifdef ipv6}15{$else}3{$endif};
\r 
 507 type tarr=array[0..ipmax] of byte;
\r 
 513   if bits = 0 then b := 0 else b := ((bits-1) div 8)+1;
\r 
 514   for a := b to ipmax do begin
\r 
 517   if (bits and 7 <> 0) then begin
\r 
 518     arr[bits shr 3] := arr[bits div 8] and not ($ff shr (bits and 7))
\r 
 522 function comparebinipmask;
\r 
 524   maskbits(ip1,bits);
\r 
 525   maskbits(ip2,bits);
\r 
 526   result := comparebinip(ip1,ip2);
\r 
 529 function needconverttov4(const ip:tbinip):boolean;
\r 
 532   if ip.family = AF_INET6 then begin
\r 
 533     if (ip.ip6.u6_addr32[0] = 0) and (ip.ip6.u6_addr32[1] = 0) and
\r 
 534     (ip.ip6.u6_addr16[4] = 0) and (ip.ip6.u6_addr16[5] = $ffff) then begin
\r 
 544 {converts a binary IP to v4 if it is a v6 IP in the v4 range}
\r 
 545 procedure converttov4(var ip:tbinip);
\r 
 548   if needconverttov4(ip) then begin
\r 
 549     ip.family := AF_INET;
\r 
 550     ip.ip := ip.ip6.s6_addr32[3];
\r 
 556 {converts a binary IP to v6 if it is a v4 IP}
\r 
 557 procedure converttov6(var ip:tbinip);
\r 
 560     if ip.family = AF_INET then begin
\r 
 561       ip.family := AF_INET6;
\r 
 562       ip.ip6.s6_addr32[3] := ip.ip;
\r 
 563       ip.ip6.u6_addr32[0] := 0;
\r 
 564       ip.ip6.u6_addr32[1] := 0;
\r 
 565       ip.ip6.u6_addr16[4] := 0;
\r 
 566       ip.ip6.u6_addr16[5] := $ffff;
\r 
 572 {-----------biniplist stuff--------------------------------------------------}
\r 
 575   biniplist_prefix: ansistring = 'bipl'#0;
\r 
 576   //fpc 1.0.x doesn't seem to like use of length function in a constant 
\r 
 578   //biniplist_prefixlen=length(biniplist_prefix);
\r 
 580   biniplist_prefixlen=5;
\r 
 582 function biniplist_new:tbiniplist;
\r 
 584   result := biniplist_prefix;
\r 
 587 procedure biniplist_add(var l:tbiniplist;ip:tbinip);
\r 
 591   a := biniplist_getcount(l);
\r 
 592   biniplist_setcount(l,a+1);
\r 
 593   biniplist_set(l,a,ip);
\r 
 596 function biniplist_getcount(const l:tbiniplist):integer;
\r 
 598   result := (length(l)-biniplist_prefixlen) div sizeof(tbinip);
\r 
 601 function biniplist_get(const l:tbiniplist;index:integer):tbinip;
\r 
 603   if (index >= biniplist_getcount(l)) then begin
\r 
 604     fillchar(result,sizeof(result),0);
\r 
 607   move(l[index*sizeof(tbinip)+1+biniplist_prefixlen],result,sizeof(result));
\r 
 610 procedure biniplist_set(var l:tbiniplist;index:integer;ip:tbinip);
\r 
 613   move(ip,l[index*sizeof(tbinip)+1+biniplist_prefixlen],sizeof(ip));
\r 
 616 procedure biniplist_setcount(var l:tbiniplist;newlen:integer);
\r 
 618   setlength(l,(sizeof(tbinip)*newlen)+biniplist_prefixlen);
\r 
 621 procedure biniplist_free(var l:tbiniplist);
\r 
 626 procedure biniplist_addlist;
\r 
 628   l := l + copy(l2,biniplist_prefixlen+1,maxlongint);
\r 
 631 function biniplist_tostr(const l:tbiniplist):thostname;
\r 
 636   for a := 0 to biniplist_getcount(l)-1 do begin
\r 
 637     if result <> '(' then result := result + ', ';
\r 
 638     result := result + ipbintostr(biniplist_get(l,a));
\r 
 640   result := result + ')';
\r 
 643 function isbiniplist(const l:tbiniplist):boolean;
\r 
 647   for i := 1 to biniplist_prefixlen do begin
\r 
 648     if biniplist_prefix[i] <> l[i] then begin
\r 
 656 procedure addipsoffamily(var l:tbiniplist;const l2:tbiniplist;family:integer);
\r 
 661   for a := biniplist_getcount(l2)-1 downto 0 do begin
\r 
 662     biniptemp := biniplist_get(l2,a);
\r 
 663     if (biniptemp.family = family) then biniplist_add(l,biniptemp);
\r 
 667 function getfamilybitlength(family:integer):integer;
\r 
 670   if family = AF_INET6 then result := 128 else
\r 
 672   if family = AF_INET then result := 32
\r 
 676 function getbinipbitlength(const ip:tbinip):integer;
\r 
 678   result := getfamilybitlength(ip.family);
\r 
 681 function getipstrbitlength(const ip:thostname):integer;
\r 
 685   ipstrtobin(ip,biniptemp);
\r 
 686   result := getbinipbitlength(biniptemp);
\r