1# Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc. 2# 3# Permission to use, copy, modify, and distribute this software and its 4# documentation for any purpose with or without fee is hereby granted, 5# provided that the above copyright notice and this permission notice 6# appear in all copies. 7# 8# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 14# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16"""IPv6 helper functions.""" 17 18import re 19 20import dns.exception 21import dns.ipv4 22 23_leading_zero = re.compile(r'0+([0-9a-f]+)') 24 25def inet_ntoa(address): 26 """Convert a network format IPv6 address into text. 27 28 @param address: the binary address 29 @type address: string 30 @rtype: string 31 @raises ValueError: the address isn't 16 bytes long 32 """ 33 34 if len(address) != 16: 35 raise ValueError("IPv6 addresses are 16 bytes long") 36 hex = address.encode('hex_codec') 37 chunks = [] 38 i = 0 39 l = len(hex) 40 while i < l: 41 chunk = hex[i : i + 4] 42 # strip leading zeros. we do this with an re instead of 43 # with lstrip() because lstrip() didn't support chars until 44 # python 2.2.2 45 m = _leading_zero.match(chunk) 46 if not m is None: 47 chunk = m.group(1) 48 chunks.append(chunk) 49 i += 4 50 # 51 # Compress the longest subsequence of 0-value chunks to :: 52 # 53 best_start = 0 54 best_len = 0 55 start = -1 56 last_was_zero = False 57 for i in xrange(8): 58 if chunks[i] != '0': 59 if last_was_zero: 60 end = i 61 current_len = end - start 62 if current_len > best_len: 63 best_start = start 64 best_len = current_len 65 last_was_zero = False 66 elif not last_was_zero: 67 start = i 68 last_was_zero = True 69 if last_was_zero: 70 end = 8 71 current_len = end - start 72 if current_len > best_len: 73 best_start = start 74 best_len = current_len 75 if best_len > 0: 76 if best_start == 0 and \ 77 (best_len == 6 or 78 best_len == 5 and chunks[5] == 'ffff'): 79 # We have an embedded IPv4 address 80 if best_len == 6: 81 prefix = '::' 82 else: 83 prefix = '::ffff:' 84 hex = prefix + dns.ipv4.inet_ntoa(address[12:]) 85 else: 86 hex = ':'.join(chunks[:best_start]) + '::' + \ 87 ':'.join(chunks[best_start + best_len:]) 88 else: 89 hex = ':'.join(chunks) 90 return hex 91 92_v4_ending = re.compile(r'(.*):(\d+)\.(\d+)\.(\d+)\.(\d+)$') 93_colon_colon_start = re.compile(r'::.*') 94_colon_colon_end = re.compile(r'.*::$') 95 96def inet_aton(text): 97 """Convert a text format IPv6 address into network format. 98 99 @param text: the textual address 100 @type text: string 101 @rtype: string 102 @raises dns.exception.SyntaxError: the text was not properly formatted 103 """ 104 105 # 106 # Our aim here is not something fast; we just want something that works. 107 # 108 109 if text == '::': 110 text = '0::' 111 # 112 # Get rid of the icky dot-quad syntax if we have it. 113 # 114 m = _v4_ending.match(text) 115 if not m is None: 116 text = "%s:%04x:%04x" % (m.group(1), 117 int(m.group(2)) * 256 + int(m.group(3)), 118 int(m.group(4)) * 256 + int(m.group(5))) 119 # 120 # Try to turn '::<whatever>' into ':<whatever>'; if no match try to 121 # turn '<whatever>::' into '<whatever>:' 122 # 123 m = _colon_colon_start.match(text) 124 if not m is None: 125 text = text[1:] 126 else: 127 m = _colon_colon_end.match(text) 128 if not m is None: 129 text = text[:-1] 130 # 131 # Now canonicalize into 8 chunks of 4 hex digits each 132 # 133 chunks = text.split(':') 134 l = len(chunks) 135 if l > 8: 136 raise dns.exception.SyntaxError 137 seen_empty = False 138 canonical = [] 139 for c in chunks: 140 if c == '': 141 if seen_empty: 142 raise dns.exception.SyntaxError 143 seen_empty = True 144 for i in xrange(0, 8 - l + 1): 145 canonical.append('0000') 146 else: 147 lc = len(c) 148 if lc > 4: 149 raise dns.exception.SyntaxError 150 if lc != 4: 151 c = ('0' * (4 - lc)) + c 152 canonical.append(c) 153 if l < 8 and not seen_empty: 154 raise dns.exception.SyntaxError 155 text = ''.join(canonical) 156 157 # 158 # Finally we can go to binary. 159 # 160 try: 161 return text.decode('hex_codec') 162 except TypeError: 163 raise dns.exception.SyntaxError 164