first stab at creating my own version of the inet_pton compatibility functions (for...
authorDavid Blacka <david@blacka.com>
Sat, 21 Jun 2008 15:28:26 +0000 (11:28 -0400)
committerDavid Blacka <david@blacka.com>
Sat, 21 Jun 2008 15:28:26 +0000 (11:28 -0400)
rwhoisd/v6addr.py [new file with mode: 0644]

diff --git a/rwhoisd/v6addr.py b/rwhoisd/v6addr.py
new file mode 100644 (file)
index 0000000..8917f79
--- /dev/null
@@ -0,0 +1,187 @@
+# This file is part of python-rwhoisd
+#
+# Copyright (C) 2008 David E. Blacka
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+import socket, re, struct
+
+# a simplified regex that just makes sure that the IPv6 address string
+# isn't obviously invalid.
+v6char_re = re.compile(r'[0-9a-f:]+(:\d+\.\d+\.\d+\.\d+)?', re.I)
+
+def v6str_to_addr(addrstr):
+    """Convert IPv6 addresses into its packed numerical value."""
+
+    # first make sure the address is made of valid IPv6 address
+    # characters.
+    if not v6char_re.match(addrstr):
+        raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+
+    toks = addrstr.split(":")
+    blanks = toks.count('')
+
+    # convert our IPv4 section.
+    if '.' in toks[-1]:
+        packed_v4 = socket.inet_aton(toks[-1])
+        unpacked_v4 = [ "%x" % (x) for x in struct.unpack("!HH", packed_v4) ]
+        toks[-1:] = unpacked_v4
+
+    if len(toks) > 8 or blanks > 3:
+        raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+
+    # three blanks must be ::
+    if blanks == 3:
+        if addrstr != "::":
+            raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+        return '\x00' * 16;
+
+    # convert the tokens into a regular array of 8 things by inserting
+    # zero strings for the elided section.
+
+    # two blanks must be ::blah or blah::
+    if blanks == 2:
+        z = ['0'] * (8 - len(toks) + 2)
+        if addrstr.startswith("::"):
+            toks[:2] = z
+        elif addrstr.endswith("::"):
+            toks[-2:] = z
+        else:
+            raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+    # one blank is the blah::blah
+    elif blanks == 1:
+        z = ['0'] * (8 - len(toks) + 1)
+        i = toks.index('')
+        toks[i:i+1] = z
+
+    if len(toks) != 8:
+        raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+
+    toks = [ int(t, 16) for t in toks ]
+    for t in toks:
+        if t & 0xFFFF != t:
+            raise socket.error("Invalid IPv6 address: '%s'" % (addrstr))
+    return struct.pack("!8H", *toks)
+
+def v6addr_to_str(addr):
+    """Convert a packed numerical IPv6 address into a string.  This
+    routine doesn't (yet) create an elided section."""
+
+    if len(addr) != 16:
+        raise socket.error("incorrect address length: %s (should be 16)" %
+                           (len(addr)))
+    nums = [ x for x in struct.unpack("!8H", addr)]
+
+    # elding support mostly cobbled from the glibc version of
+    # inet_ntop
+
+    # look for the longest string of zeros.
+    cur_base = best_base = cur_len = best_len = -1
+
+    for i in range(8):
+        if nums[i] == 0:
+            if cur_base == -1:
+                cur_base, cur_len = i, 1
+            else:
+                cur_len += 1
+        else:
+            if cur_base != -1:
+                if best_base == -1 or cur_len > best_len:
+                    best_base, best_len = cur_base, cur_len
+                cur_base = -1
+
+    if cur_base != -1:
+        if best_base == -1 or cur_len > best_len:
+            best_base, best_len = cur_base, cur_len
+
+    # if we have a valid string of zeros, replace them with the token.
+    if best_base != -1 and best_len > 1:
+        nums[best_base:best_base + best_len] = [':']
+
+    if nums[0] == ':':
+        nums.insert(0, ':')
+    if nums[-1] == ':':
+        nums.append(':')
+
+    def n_to_str(n):
+        if n == ':':
+            return ''
+        return "%x" % (n)
+    strs = [ n_to_str(x) for x in nums ]
+    return ":".join(strs)
+
+
+def inet_pton(af, ip):
+    if af == socket.AF_INET:
+        return socket.inet_aton(ip)
+    if af == socket.AF_INET6:
+        return v6str_to_addr(ip)
+    raise socket.error("Address family not supported by protocol")
+
+def inet_ntop(af, packed_ip):
+    if af == socket.AF_INET:
+        return socket.inet.ntoa(packed_ip)
+    if af == socket.AF_INET6:
+        return v6addr_to_str(packed_ip)
+    raise socket.error("Address family not supported by protocol")
+
+
+try:
+    socket.inet_pton(socket.AF_INET6, "::1")
+except (AttributeError, NameError, socket.error):
+    socket.inet_pton = inet_pton
+    socket.inet_ntop = inet_ntop
+    socket.AF_INET6 = 'AF_INET6'
+
+
+# test driver
+if __name__ == "__main__":
+
+    def try_good_addr(addr):
+        try:
+            a = v6str_to_addr(addr)
+            b = v6addr_to_str(a)
+        except socket.error, e:
+            print e
+        else:
+            print "%s => %s" % (addr, b)
+
+    try_good_addr("::");
+    try_good_addr("::7");
+    try_good_addr("f::");
+    try_good_addr("ab:0:0:c:0:0:0:d")
+    try_good_addr("ab:0:0:0:c:0:0:d")
+    try_good_addr("1:2:3:4:5:6:7:8")
+    try_good_addr("1:2:3::7:8")
+    try_good_addr("2001:3c09:102::23:af")
+    try_good_addr("::ffff:1.2.3.4")
+    try_good_addr("1:2:3:4:5:6:7:4.3.2.1")
+
+    def try_bad_addr(addr):
+        try:
+            a = v6str_to_addr(addr)
+        except socket.error, e:
+            print e
+        else:
+            print "addr was valid!", addr
+
+    # things that shouldn't parse
+    try_bad_addr(":")
+    try_bad_addr(":::")
+    try_bad_addr("1::2::3")
+    try_bad_addr("::3::")
+    try_bad_addr("::1.2.3")
+    try_bad_addr("12345::1")