removed redundant, and no longer valid, email address (another one)
[lcore.git] / binipstuff.pas
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
5 unit binipstuff;\r
6 \r
7 interface\r
8 \r
9 {$include lcoreconfig.inc}\r
10 \r
11 {$ifndef win32}\r
12 {$ifdef ipv6}\r
13 uses sockets;\r
14 {$endif}\r
15 {$endif}\r
16 \r
17 {$ifdef fpc}\r
18   {$mode delphi}\r
19 {$endif}\r
20 {$ifdef cpu386}{$define i386}{$endif}\r
21 {$ifdef i386}{$define ENDIAN_LITTLE}{$endif}\r
22 \r
23 {$include uint32.inc}\r
24 \r
25 const\r
26   hexchars:array[0..15] of char='0123456789abcdef';\r
27   AF_INET=2;\r
28   {$ifdef win32}\r
29     AF_INET6=23;\r
30   {$else}\r
31     AF_INET6=10;\r
32   {$endif}\r
33 \r
34 type\r
35   {$ifdef ipv6}\r
36     \r
37     {$ifdef win32}\r
38       {$define want_Tin6_addr}\r
39     {$endif}\r
40     {$ifdef ver1_0}\r
41       {$define want_Tin6_addr}\r
42     {$endif}\r
43     {$ifdef want_Tin6_addr}\r
44       Tin6_addr = packed record\r
45         case byte of\r
46           0: (u6_addr8  : array[0..15] of byte);\r
47           1: (u6_addr16 : array[0..7] of Word);\r
48           2: (u6_addr32 : array[0..3] of uint32);\r
49           3: (s6_addr8  : array[0..15] of shortint);\r
50           4: (s6_addr   : array[0..15] of shortint);\r
51           5: (s6_addr16 : array[0..7] of smallint);\r
52           6: (s6_addr32 : array[0..3] of LongInt);\r
53       end;\r
54     {$endif}\r
55   {$endif}\r
56 \r
57   tbinip=record\r
58     family:integer;\r
59     {$ifdef ipv6}\r
60       case integer of\r
61         0: (ip:longint);\r
62         1: (ip6:tin6_addr);\r
63     {$else}\r
64       ip:longint;\r
65     {$endif}\r
66   end;\r
67 \r
68   {$ifdef win32}\r
69     TInetSockAddr = packed Record\r
70       family:Word;\r
71       port  :Word;\r
72       addr  :uint32;\r
73       pad   :array [1..8] of byte;\r
74     end;\r
75     {$ifdef ipv6}\r
76 \r
77       TInetSockAddr6 = packed record\r
78         sin6_family: word;\r
79         sin6_port: word;\r
80         sin6_flowinfo: uint32;\r
81         sin6_addr: tin6_addr;\r
82         sin6_scope_id: uint32;\r
83       end;\r
84     {$endif}\r
85   {$endif}\r
86 \r
87 \r
88 \r
89   {$ifdef ipv6}\r
90     {$ifdef ver1_0}\r
91       cuint16=word;\r
92       cuint32=dword;\r
93       sa_family_t=word;\r
94 \r      TInetSockAddr6 = packed record\r
95         sin6_family: word;\r
96         sin6_port: word;\r
97         sin6_flowinfo: uint32;\r
98         sin6_addr: tin6_addr;\r
99         sin6_scope_id: uint32;\r
100       end;\r
101     {$endif}\r
102   {$endif}\r
103   TinetSockAddrv = packed record\r
104     case integer of\r
105       0: (InAddr:TInetSockAddr);\r
106       {$ifdef ipv6}\r
107       1: (InAddr6:TInetSockAddr6);\r
108       {$endif}\r
109   end;\r
110   Pinetsockaddrv = ^Tinetsockaddrv;\r
111 \r
112   type\r
113     tsockaddrin=TInetSockAddr;\r
114 \r
115 \r
116 \r
117 {\r
118 bin IP list code, by beware\r
119 while this is really just a string, on the interface side it must be treated\r
120 as an opaque var which is passed as "var" when it needs to be modified}\r
121 \r
122   tbiniplist=string;\r
123 \r
124 function biniplist_new:tbiniplist;\r
125 procedure biniplist_add(var l:tbiniplist;ip:tbinip);\r
126 function biniplist_getcount(const l:tbiniplist):integer;\r
127 function biniplist_get(const l:tbiniplist;index:integer):tbinip;\r
128 procedure biniplist_set(var l:tbiniplist;index:integer;ip:tbinip);\r
129 procedure biniplist_setcount(var l:tbiniplist;newlen:integer);\r
130 procedure biniplist_free(var l:tbiniplist);\r
131 procedure biniplist_addlist(var l:tbiniplist;const l2:tbiniplist);\r
132 function biniplist_tostr(const l:tbiniplist):string;\r
133 function isbiniplist(const l:tbiniplist):boolean;\r
134 \r
135 function htons(w:word):word;\r
136 function htonl(i:uint32):uint32;\r
137 \r
138 function ipstrtobin(const s:string;var binip:tbinip):boolean;\r
139 function ipstrtobinf(const s:string):tbinip;\r
140 function ipbintostr(const binip:tbinip):string;\r
141 {$ifdef ipv6}\r
142 function ip6bintostr(const bin:tin6_addr):string;\r
143 function ip6strtobin(const s:string;var bin:tin6_addr):boolean;\r
144 {$endif}\r
145 \r
146 function comparebinip(const ip1,ip2:tbinip):boolean;\r
147 procedure maskbits(var binip:tbinip;bits:integer);\r
148 function comparebinipmask(ip1,ip2:tbinip;bits:integer):boolean;\r
149 \r
150 procedure addipsoffamily(var l:tbiniplist;const l2:tbiniplist;family:integer);\r
151 \r
152 {deprecated}\r
153 function longip(s:string):longint;\r
154 \r
155 function needconverttov4(const ip:tbinip):boolean;\r
156 procedure converttov4(var ip:tbinip);\r
157 \r
158 function inaddrvtobinip(inaddrv:tinetsockaddrv):tbinip;\r
159 function makeinaddrv(addr:tbinip;port:string;var inaddr:tinetsockaddrv):integer;\r
160 function inaddrsize(inaddr:tinetsockaddrv):integer;\r
161 \r
162 implementation\r
163 \r
164 uses sysutils;\r
165 \r
166 function htons(w:word):word;\r
167 begin\r
168   {$ifdef ENDIAN_LITTLE}\r
169   result := ((w and $ff00) shr 8) or ((w and $ff) shl 8);\r
170   {$else}\r
171   result := w;\r
172   {$endif}\r
173 end;\r
174 \r
175 function htonl(i:uint32):uint32;\r
176 begin\r
177   {$ifdef ENDIAN_LITTLE}\r
178   result := (i shr 24) or (i shr 8 and $ff00) or (i shl 8 and $ff0000) or (i shl 24 and $ff000000);\r
179   {$else}\r
180   result := i;\r
181   {$endif}\r
182 end;\r
183 \r
184 \r
185 function inaddrvtobinip(inaddrv:tinetsockaddrv):tbinip;\r
186 begin\r
187   result.family := inaddrv.inaddr.family;\r
188   if result.family = AF_INET then result.ip := inaddrv.inaddr.addr;\r
189   {$ifdef ipv6}\r
190   if result.family = AF_INET6 then result.ip6 := inaddrv.inaddr6.sin6_addr;\r
191   {$endif}\r
192 end;\r
193 \r
194 function makeinaddrv(addr:tbinip;port:string;var inaddr:tinetsockaddrv):integer;\r
195 begin\r
196   result := 0;\r
197 {  biniptemp := forwardlookup(addr,10);}\r
198   fillchar(inaddr,sizeof(inaddr),0);\r
199   //writeln('converted address '+addr+' to binip '+ipbintostr(biniptemp));\r
200   if addr.family = AF_INET then begin\r
201     inAddr.InAddr.family:=AF_INET;\r
202     inAddr.InAddr.port:=htons(strtointdef(port,0));\r
203     inAddr.InAddr.addr:=addr.ip;\r
204     result := sizeof(tinetsockaddr);\r
205   end else\r
206   {$ifdef ipv6}\r
207   if addr.family = AF_INET6 then begin\r
208     inAddr.InAddr6.sin6_family:=AF_INET6;\r
209     inAddr.InAddr6.sin6_port:=htons(strtointdef(port,0));\r
210     inAddr.InAddr6.sin6_addr:=addr.ip6;\r
211     result := sizeof(tinetsockaddr6);\r
212   end;\r
213   {$endif}\r
214 end;\r
215 \r
216 function inaddrsize(inaddr:tinetsockaddrv):integer;\r
217 begin\r
218   {$ifdef ipv6}\r
219   if inaddr.inaddr.family = AF_INET6 then result := sizeof(tinetsockaddr6) else\r
220   {$endif}\r
221   result := sizeof(tinetsockaddr);\r
222 end;\r
223 \r
224 {internal}\r
225 {converts dotted v4 IP to longint. returns host endian order}\r
226 function longip(s:string):longint;\r
227 var\r
228   l:longint;\r
229   a,b:integer;\r
230 function convertbyte(const s:string):integer;\r
231 begin\r
232   result := strtointdef(s,-1);\r
233   if result < 0 then begin\r
234     result := -1;\r
235     exit;\r
236   end;\r
237   if result > 255 then begin\r
238     result := -1;\r
239     exit;\r
240   end;\r
241   {01 exception}\r
242   if (result <> 0) and (s[1] = '0') then begin\r
243     result := -1;\r
244     exit;\r
245   end;\r
246   {+1 exception}\r
247   if not (s[1] in ['0'..'9']) then begin\r
248     result := -1;\r
249     exit\r
250   end;\r
251 end;\r
252 \r
253 begin\r
254   result := 0;\r
255   a := pos('.',s);\r
256   if a = 0 then exit;\r
257   b := convertbyte(copy(s,1,a-1));if (b < 0) then exit;\r
258   l := b shl 24;\r
259   s := copy(s,a+1,256);\r
260   a := pos('.',s);\r
261   if a = 0 then exit;\r
262   b := convertbyte(copy(s,1,a-1));if (b < 0) then exit;\r
263   l := l or b shl 16;\r
264   s := copy(s,a+1,256);\r
265   a := pos('.',s);\r
266   if a = 0 then exit;\r
267   b := convertbyte(copy(s,1,a-1));if (b < 0) then exit;\r
268   l := l or b shl 8;\r
269   s := copy(s,a+1,256);\r
270   b := convertbyte(copy(s,1,256));if (b < 0) then exit;\r
271   l := l or b;\r
272   result := l;\r
273 end;\r
274 \r
275 \r
276 function ipstrtobinf;\r
277 begin\r
278   ipstrtobin(s,result);\r
279 end;\r
280 \r
281 function ipstrtobin(const s:string;var binip:tbinip):boolean;\r
282 begin\r
283   binip.family := 0;\r
284   result := false;\r
285   {$ifdef ipv6}\r
286   if pos(':',s) <> 0 then begin\r
287     {try ipv6. use builtin routine}\r
288     result := ip6strtobin(s,binip.ip6);\r
289     if result then binip.family := AF_INET6;\r
290     exit;\r
291   end;\r
292   {$endif}\r
293 \r
294   {try v4}\r
295   binip.ip := htonl(longip(s));\r
296   if (binip.ip <> 0) or (s = '0.0.0.0') then begin\r
297     result := true;\r
298     binip.family := AF_INET;\r
299     exit;\r
300   end;\r
301 end;\r
302 \r
303 function ipbintostr(const binip:tbinip):string;\r
304 var\r
305   a:integer;\r
306 begin\r
307   result := '';\r
308   {$ifdef ipv6}\r
309   if binip.family = AF_INET6 then begin\r
310     result := ip6bintostr(binip.ip6);\r
311   end else\r
312   {$endif}\r
313   if binip.family = AF_INET then begin\r
314     a := htonl(binip.ip);\r
315     result := inttostr(a shr 24)+'.'+inttostr((a shr 16) and $ff)+'.'+inttostr((a shr 8) and $ff)+'.'+inttostr(a and $ff);\r
316   end;\r
317 end;\r
318 \r
319 \r
320 {------------------------------------------------------------------------------}\r
321 \r
322 {$ifdef ipv6}\r
323 \r
324 {\r
325 IPv6 address binary to/from string conversion routines\r
326 written by beware\r
327 \r
328 - implementation does not depend on other ipv6 code such as the tin6_addr type,\r
329   the parameter can also be untyped.\r
330 - it is host endian neutral - binary format is aways network order\r
331 - it supports compression of zeroes\r
332 - it supports ::ffff:192.168.12.34 style addresses\r
333 - they are made to do the Right Thing, more efficient implementations are possible\r
334 }\r
335 \r
336 {fpc has hostaddrtostr6 and strtohostaddr6 but the later isnt implemented yet}\r
337 \r
338 \r
339 function ip6bintostr(const bin:tin6_addr):string;\r
340 {base16 with lowercase output}\r
341 function makehex(w:word):string;\r
342 begin\r
343   result := '';\r
344   if w >= 4096 then result := result + hexchars[w shr 12];\r
345   if w >= 256 then result := result + hexchars[w shr 8 and $f];\r
346   if w >= 16 then result := result + hexchars[w shr 4 and $f];\r
347   result := result + hexchars[w and $f];\r
348 end;\r
349 \r
350 var\r
351   a,b,c,addrlen:integer;\r
352   runbegin,runlength:integer;\r
353   bytes:array[0..15] of byte absolute bin;\r
354   words:array[0..7] of word;\r
355   dwords:array[0..3] of integer absolute words;\r
356 begin\r
357   for a := 0 to 7 do begin\r
358     words[a] := bytes[a shl 1] shl 8 or bytes[a shl 1 or 1];\r
359   end;\r
360   if (dwords[0] = 0) and (dwords[1] = 0) and (words[4] = 0) and (words[5] = $ffff) then begin\r
361     {::ffff:/96 exception: v4 IP}\r
362     addrlen := 6;\r
363   end else begin\r
364     addrlen := 8;\r
365   end;\r
366   {find longest run of zeroes}\r
367   runbegin := 0;\r
368   runlength := 0;\r
369   for a := 0 to addrlen-1 do begin\r
370     if words[a] = 0 then begin\r
371       c := 0;\r
372       for b := a to addrlen-1 do if words[b] = 0 then begin\r
373         inc(c);\r
374       end else break;\r
375       if (c > runlength) then begin\r
376         runlength := c;\r
377         runbegin := a;\r
378       end;\r
379     end;\r
380   end;\r
381   result := '';\r
382   for a := 0 to runbegin-1 do begin\r
383     if (a <> 0) then result := result + ':';\r
384     result := result + makehex(words[a]);\r
385   end;\r
386   if runlength > 0 then result := result + '::';\r
387   c := runbegin+runlength;\r
388   for a := c to addrlen-1 do begin\r
389     if (a > c) then result := result + ':';\r
390     result := result + makehex(words[a]);\r
391   end;\r
392   if addrlen = 6 then begin\r
393     result := result + ':'+inttostr(bytes[12])+'.'+inttostr(bytes[13])+'.'+inttostr(bytes[14])+'.'+inttostr(bytes[15]);\r
394   end;\r
395 end;\r
396 \r
397 function ip6strtobin(const s:string;var bin:tin6_addr):boolean;\r
398 var\r
399   a,b:integer;\r
400   fields:array[0..7] of string;\r
401   fieldcount:integer;\r
402   emptyfield:integer;\r
403   wordcount:integer;\r
404   words:array[0..7] of word;\r
405   bytes:array[0..15] of byte absolute bin;\r
406 begin\r
407   result := false;\r
408   for a := 0 to 7 do fields[a] := '';\r
409   fieldcount := 0;\r
410   for a := 1 to length(s) do begin\r
411     if s[a] = ':' then inc(fieldcount) else fields[fieldcount] := fields[fieldcount] + s[a];\r
412     if fieldcount > 7 then exit;\r
413   end;\r
414   if fieldcount < 2 then exit;\r
415 \r
416   {find the empty field (compressed zeroes), not counting the first and last there may be at most 1}\r
417   emptyfield := -1;\r
418   for a := 1 to fieldcount-1 do begin\r
419     if fields[a] = '' then begin\r
420       if emptyfield = -1 then emptyfield := a else exit;\r
421     end;\r
422   end;\r
423 \r
424   {check if last field is a valid v4 IP}\r
425   a := longip(fields[fieldcount]);\r
426   if (a <> 0) or (fields[fieldcount] = '0.0.0.0') then wordcount := 6 else wordcount := 8;\r
427   {0:1:2:3:4:5:6.6.6.6\r
428    0:1:2:3:4:5:6:7}\r
429   fillchar(words,sizeof(words),0);\r
430   if wordcount = 6 then begin\r
431     if fieldcount > 6 then exit;\r
432     words[6] := a shr 16;\r
433     words[7] := a and $ffff;\r
434   end;\r
435   if emptyfield = -1 then begin\r
436     {no run length: must be an exact number of fields}\r
437     if wordcount = 6 then begin\r
438       if fieldcount <> 6 then exit;\r
439       emptyfield := 5;\r
440     end else if wordcount = 8 then begin\r
441       if fieldcount <> 7 then exit;\r
442       emptyfield := 7;\r
443     end else exit;\r
444   end;\r
445   for a := 0 to emptyfield do begin\r
446     if fields[a] = '' then b := 0 else b := strtointdef('$'+fields[a],-1);\r
447     if (b < 0) or (b > $ffff) then exit;\r
448     words[a] := b;\r
449   end;\r
450   if wordcount = 6 then dec(fieldcount);\r
451   for a := wordcount-1 downto wordcount-(fieldcount-emptyfield) do begin\r
452     b := a+fieldcount-wordcount+1;\r
453     if fields[b] = '' then b := 0 else b := strtointdef('$'+fields[b],-1);\r
454     if (b < 0) or (b > $ffff) then exit;\r
455     words[a] := b;\r
456   end;\r
457   for a := 0 to 7 do begin\r
458     bytes[a shl 1] := words[a] shr 8;\r
459     bytes[a shl 1 or 1] := words[a] and $ff;\r
460   end;\r
461   result := true;\r
462 end;\r
463 {$endif}\r
464 \r
465 function comparebinip(const ip1,ip2:tbinip):boolean;\r
466 begin\r
467   if (ip1.ip <> ip2.ip) then begin\r
468     result := false;\r
469     exit;\r
470   end;\r
471 \r
472   {$ifdef ipv6}\r
473   if ip1.family = AF_INET6 then begin\r
474     if (ip1.ip6.s6_addr32[1] <> ip2.ip6.s6_addr32[1])\r
475     or (ip1.ip6.s6_addr32[2] <> ip2.ip6.s6_addr32[2])\r
476     or (ip1.ip6.s6_addr32[3] <> ip2.ip6.s6_addr32[3]) then begin\r
477       result := false;\r
478       exit;\r
479     end;\r
480   end;\r
481   {$endif}\r
482 \r
483   result := (ip1.family = ip2.family);\r
484 end;\r
485 \r
486 procedure maskbits(var binip:tbinip;bits:integer);\r
487 const\r
488   ipmax={$ifdef ipv6}15{$else}3{$endif};\r
489 type tarr=array[0..ipmax] of byte;\r
490 var\r
491   arr:^tarr;\r
492   a,b:integer;\r
493 begin\r
494   arr := @binip.ip;\r
495   if bits = 0 then b := 0 else b := ((bits-1) div 8)+1;\r
496   for a := b to ipmax do begin\r
497     arr[a] := 0;\r
498   end;\r
499   if (bits and 7 <> 0) then begin\r
500     arr[bits shr 3] := arr[bits div 8] and not ($ff shr (bits and 7))\r
501   end;\r
502 end;\r
503 \r
504 function comparebinipmask;\r
505 begin\r
506   maskbits(ip1,bits);\r
507   maskbits(ip2,bits);\r
508   result := comparebinip(ip1,ip2);\r
509 end;\r
510 \r
511 function needconverttov4(const ip:tbinip):boolean;\r
512 begin\r
513   {$ifdef ipv6}\r
514   if ip.family = AF_INET6 then begin\r
515     if (ip.ip6.u6_addr32[0] = 0) and (ip.ip6.u6_addr32[1] = 0) and\r
516     (ip.ip6.u6_addr16[4] = 0) and (ip.ip6.u6_addr16[5] = $ffff) then begin\r
517       result := true;\r
518       exit;\r
519     end;\r
520   end;\r
521   {$endif}\r
522 \r
523   result := false;\r
524 end;\r
525 \r
526 {converts a binary IP to v4 if it is a v6 IP in the v4 range}\r
527 procedure converttov4(var ip:tbinip);\r
528 begin\r
529   {$ifdef ipv6}\r
530   if needconverttov4(ip) then begin\r
531     ip.family := AF_INET;\r
532     ip.ip := ip.ip6.s6_addr32[3];\r
533   end;\r
534   {$endif}\r
535 end;\r
536 \r
537 {-----------biniplist stuff--------------------------------------------------}\r
538 \r
539 const\r
540   biniplist_prefix='bipl'#0;\r
541   //fpc 1.0.x doesn't seem to like use of length function in a constant \r
542   //definition\r
543   //biniplist_prefixlen=length(biniplist_prefix);\r
544 \r  biniplist_prefixlen=5;\r
545   \r
546 function biniplist_new:tbiniplist;\r
547 begin\r
548   result := biniplist_prefix;\r
549 end;\r
550 \r
551 procedure biniplist_add(var l:tbiniplist;ip:tbinip);\r
552 var\r
553   a:integer;\r
554 begin\r
555   a := biniplist_getcount(l);\r
556   biniplist_setcount(l,a+1);\r
557   biniplist_set(l,a,ip);\r
558 end;\r
559 \r
560 function biniplist_getcount(const l:tbiniplist):integer;\r
561 begin\r
562   result := (length(l)-biniplist_prefixlen) div sizeof(tbinip);\r
563 end;\r
564 \r
565 function biniplist_get(const l:tbiniplist;index:integer):tbinip;\r
566 begin\r
567   if (index >= biniplist_getcount(l)) then begin\r
568     fillchar(result,sizeof(result),0);\r
569     exit;\r
570   end;\r
571   move(l[index*sizeof(tbinip)+1+biniplist_prefixlen],result,sizeof(result));\r
572 end;\r
573 \r
574 procedure biniplist_set(var l:tbiniplist;index:integer;ip:tbinip);\r
575 begin\r
576   uniquestring(l);\r
577   move(ip,l[index*sizeof(tbinip)+1+biniplist_prefixlen],sizeof(ip));\r
578 end;\r
579 \r
580 procedure biniplist_setcount(var l:tbiniplist;newlen:integer);\r
581 begin\r
582   setlength(l,(sizeof(tbinip)*newlen)+biniplist_prefixlen);\r
583 end;\r
584 \r
585 procedure biniplist_free(var l:tbiniplist);\r
586 begin\r
587   l := '';\r
588 end;\r
589 \r
590 procedure biniplist_addlist;\r
591 begin\r
592   l := l + copy(l2,biniplist_prefixlen+1,maxlongint);\r
593 end;\r
594 \r
595 function biniplist_tostr(const l:tbiniplist):string;\r
596 var\r
597   a:integer;\r
598 begin\r
599   result := '(';\r
600   for a := 0 to biniplist_getcount(l)-1 do begin\r
601     if result <> '(' then result := result + ', ';\r
602     result := result + ipbintostr(biniplist_get(l,a));\r
603   end;\r
604   result := result + ')';\r
605 end;\r
606 \r
607 function isbiniplist(const l:tbiniplist):boolean;\r
608 var\r
609   i : integer;\r
610 begin\r
611   for i := 1 to biniplist_prefixlen do begin\r
612     if biniplist_prefix[i] <> l[i] then begin\r
613       result := false;\r
614       exit;\r
615     end;\r
616   end;\r
617   result := true;\r
618 end;\r
619 \r
620 procedure addipsoffamily(var l:tbiniplist;const l2:tbiniplist;family:integer);\r
621 var\r
622   a:integer;\r
623   biniptemp:tbinip;\r
624 begin\r
625   for a := biniplist_getcount(l2)-1 downto 0 do begin\r
626     biniptemp := biniplist_get(l2,a);\r
627     if (biniptemp.family = family) then biniplist_add(l,biniptemp);\r
628   end;\r
629 end;\r
630 \r
631 \r
632 end.\r