Update TODO based on work for 0.4.1
[python-rwhoisd.git] / rwhoisd / v6addr.py
1 # This file is part of python-rwhoisd
2 #
3 # Copyright (C) 2008 David E. Blacka
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18 # USA
19
20 import socket, re, struct
21
22 # a simplified regex that just makes sure that the IPv6 address string
23 # isn't obviously invalid.
24 v6char_re = re.compile(r'^[0-9a-f:]+(:(\d{1,3}\.){3}\d{1,3})?$', re.I)
25
26 def v6str_to_addr(addrstr):
27     """Convert IPv6 addresses into its packed numerical value."""
28
29     # first make sure the address is made of valid IPv6 address
30     # characters.
31     if not v6char_re.match(addrstr):
32         raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
33
34     toks = addrstr.split(":")
35     blanks = toks.count('')
36
37     # convert our IPv4 section.
38     if '.' in toks[-1]:
39         packed_v4 = socket.inet_aton(toks[-1])
40         unpacked_v4 = [ "%x" % (x) for x in struct.unpack("!HH", packed_v4) ]
41         toks[-1:] = unpacked_v4
42
43     if len(toks) > 8 or blanks > 3:
44         raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
45
46     # three blanks must be ::
47     if blanks == 3:
48         if addrstr != "::":
49             raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
50         return '\x00' * 16;
51
52     # convert the tokens into a regular array of 8 things by inserting
53     # zero strings for the elided section.
54
55     # two blanks must be ::blah or blah::
56     if blanks == 2:
57         z = ['0'] * (8 - len(toks) + 2)
58         if addrstr.startswith("::"):
59             toks[:2] = z
60         elif addrstr.endswith("::"):
61             toks[-2:] = z
62         else:
63             raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
64     # one blank is the blah::blah
65     elif blanks == 1:
66         z = ['0'] * (8 - len(toks) + 1)
67         i = toks.index('')
68         toks[i:i+1] = z
69
70     if len(toks) != 8:
71         raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
72
73     toks = [ int(t, 16) for t in toks ]
74     for t in toks:
75         if t & 0xFFFF != t:
76             raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
77     return struct.pack("!8H", *toks)
78
79 def v6addr_to_str(addr):
80     """Convert a packed numerical IPv6 address into a string.  This
81     routine doesn't (yet) create an elided section."""
82
83     if len(addr) != 16:
84         raise socket.error("incorrect address length: %s (should be 16)" %
85                            (len(addr)))
86     nums = [ x for x in struct.unpack("!8H", addr)]
87
88     # elding support mostly cobbled from the glibc version of
89     # inet_ntop
90
91     # look for the longest string of zeros.
92     cur_base = best_base = cur_len = best_len = -1
93
94     for i in range(8):
95         if nums[i] == 0:
96             if cur_base == -1:
97                 cur_base, cur_len = i, 1
98             else:
99                 cur_len += 1
100         else:
101             if cur_base != -1:
102                 if best_base == -1 or cur_len > best_len:
103                     best_base, best_len = cur_base, cur_len
104                 cur_base = -1
105
106     if cur_base != -1:
107         if best_base == -1 or cur_len > best_len:
108             best_base, best_len = cur_base, cur_len
109
110     # if we have a valid string of zeros, replace them with the token.
111     if best_base != -1 and best_len > 1:
112         nums[best_base:best_base + best_len] = [':']
113
114     if nums[0] == ':':
115         nums.insert(0, ':')
116     if nums[-1] == ':':
117         nums.append(':')
118
119     def n_to_str(n):
120         if n == ':':
121             return ''
122         return "%x" % (n)
123     strs = [ n_to_str(x) for x in nums ]
124     return ":".join(strs)
125
126
127 def inet_pton(af, ip):
128     if af == socket.AF_INET:
129         return socket.inet_aton(ip)
130     if af == socket.AF_INET6:
131         return v6str_to_addr(ip)
132     raise socket.error("Address family not supported by protocol")
133
134 def inet_ntop(af, packed_ip):
135     if af == socket.AF_INET:
136         return socket.inet.ntoa(packed_ip)
137     if af == socket.AF_INET6:
138         return v6addr_to_str(packed_ip)
139     raise socket.error("Address family not supported by protocol")
140
141
142 try:
143     socket.inet_pton(socket.AF_INET6, "::1")
144 except (AttributeError, NameError, socket.error):
145     socket.inet_pton = inet_pton
146     socket.inet_ntop = inet_ntop
147     socket.AF_INET6 = 'AF_INET6'
148
149
150 # test driver
151 if __name__ == "__main__":
152
153     def try_good_addr(addr):
154         try:
155             a = v6str_to_addr(addr)
156             b = v6addr_to_str(a)
157         except socket.error, e:
158             print "addr was invalid!:", e
159         else:
160             print "%s => %s" % (addr, b)
161
162     try_good_addr("::");
163     try_good_addr("::7");
164     try_good_addr("f::");
165     try_good_addr("ab:0:0:c:0:0:0:d")
166     try_good_addr("ab:0:0:0:c:0:0:d")
167     try_good_addr("1:2:3:4:5:6:7:8")
168     try_good_addr("1:2:3::7:8")
169     try_good_addr("2001:3c09:102::23:af")
170     try_good_addr("::ffff:1.2.3.4")
171     try_good_addr("1:2:3:4:5:6:4.3.2.1")
172
173     def try_bad_addr(addr):
174         try:
175             a = v6str_to_addr(addr)
176         except socket.error, e:
177             print e
178         else:
179             print "addr was valid! %s => %s" % (addr, v6addr_to_str(a))
180
181     # things that shouldn't parse
182     try_bad_addr(":")
183     try_bad_addr(":::")
184     try_bad_addr("1::2::3")
185     try_bad_addr("::3::")
186     try_bad_addr("::1.2.3")
187     try_bad_addr("12345::1")
188     try_bad_addr("1:2:3:4:5:6:7:4.3.2.1")