ipv6.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # Copyright (C) 2003-2007, 2009-2011 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. """IPv6 helper functions."""
  16. import re
  17. import binascii
  18. import dns.exception
  19. import dns.ipv4
  20. from ._compat import xrange, binary_type, maybe_decode
  21. _leading_zero = re.compile(b'0+([0-9a-f]+)')
  22. def inet_ntoa(address):
  23. """Convert a network format IPv6 address into text.
  24. @param address: the binary address
  25. @type address: string
  26. @rtype: string
  27. @raises ValueError: the address isn't 16 bytes long
  28. """
  29. if len(address) != 16:
  30. raise ValueError("IPv6 addresses are 16 bytes long")
  31. hex = binascii.hexlify(address)
  32. chunks = []
  33. i = 0
  34. l = len(hex)
  35. while i < l:
  36. chunk = hex[i : i + 4]
  37. # strip leading zeros. we do this with an re instead of
  38. # with lstrip() because lstrip() didn't support chars until
  39. # python 2.2.2
  40. m = _leading_zero.match(chunk)
  41. if not m is None:
  42. chunk = m.group(1)
  43. chunks.append(chunk)
  44. i += 4
  45. #
  46. # Compress the longest subsequence of 0-value chunks to ::
  47. #
  48. best_start = 0
  49. best_len = 0
  50. start = -1
  51. last_was_zero = False
  52. for i in xrange(8):
  53. if chunks[i] != b'0':
  54. if last_was_zero:
  55. end = i
  56. current_len = end - start
  57. if current_len > best_len:
  58. best_start = start
  59. best_len = current_len
  60. last_was_zero = False
  61. elif not last_was_zero:
  62. start = i
  63. last_was_zero = True
  64. if last_was_zero:
  65. end = 8
  66. current_len = end - start
  67. if current_len > best_len:
  68. best_start = start
  69. best_len = current_len
  70. if best_len > 1:
  71. if best_start == 0 and \
  72. (best_len == 6 or
  73. best_len == 5 and chunks[5] == b'ffff'):
  74. # We have an embedded IPv4 address
  75. if best_len == 6:
  76. prefix = b'::'
  77. else:
  78. prefix = b'::ffff:'
  79. hex = prefix + dns.ipv4.inet_ntoa(address[12:])
  80. else:
  81. hex = b':'.join(chunks[:best_start]) + b'::' + \
  82. b':'.join(chunks[best_start + best_len:])
  83. else:
  84. hex = b':'.join(chunks)
  85. return maybe_decode(hex)
  86. _v4_ending = re.compile(b'(.*):(\d+\.\d+\.\d+\.\d+)$')
  87. _colon_colon_start = re.compile(b'::.*')
  88. _colon_colon_end = re.compile(b'.*::$')
  89. def inet_aton(text):
  90. """Convert a text format IPv6 address into network format.
  91. @param text: the textual address
  92. @type text: string
  93. @rtype: string
  94. @raises dns.exception.SyntaxError: the text was not properly formatted
  95. """
  96. #
  97. # Our aim here is not something fast; we just want something that works.
  98. #
  99. if not isinstance(text, binary_type):
  100. text = text.encode()
  101. if text == b'::':
  102. text = b'0::'
  103. #
  104. # Get rid of the icky dot-quad syntax if we have it.
  105. #
  106. m = _v4_ending.match(text)
  107. if not m is None:
  108. b = bytearray(dns.ipv4.inet_aton(m.group(2)))
  109. text = (u"%s:%02x%02x:%02x%02x" % (m.group(1).decode(), b[0], b[1],
  110. b[2], b[3])).encode()
  111. #
  112. # Try to turn '::<whatever>' into ':<whatever>'; if no match try to
  113. # turn '<whatever>::' into '<whatever>:'
  114. #
  115. m = _colon_colon_start.match(text)
  116. if not m is None:
  117. text = text[1:]
  118. else:
  119. m = _colon_colon_end.match(text)
  120. if not m is None:
  121. text = text[:-1]
  122. #
  123. # Now canonicalize into 8 chunks of 4 hex digits each
  124. #
  125. chunks = text.split(b':')
  126. l = len(chunks)
  127. if l > 8:
  128. raise dns.exception.SyntaxError
  129. seen_empty = False
  130. canonical = []
  131. for c in chunks:
  132. if c == b'':
  133. if seen_empty:
  134. raise dns.exception.SyntaxError
  135. seen_empty = True
  136. for i in xrange(0, 8 - l + 1):
  137. canonical.append(b'0000')
  138. else:
  139. lc = len(c)
  140. if lc > 4:
  141. raise dns.exception.SyntaxError
  142. if lc != 4:
  143. c = (b'0' * (4 - lc)) + c
  144. canonical.append(c)
  145. if l < 8 and not seen_empty:
  146. raise dns.exception.SyntaxError
  147. text = b''.join(canonical)
  148. #
  149. # Finally we can go to binary.
  150. #
  151. try:
  152. return binascii.unhexlify(text)
  153. except (binascii.Error, TypeError):
  154. raise dns.exception.SyntaxError
  155. _mapped_prefix = b'\x00' * 10 + b'\xff\xff'
  156. def is_mapped(address):
  157. return address.startswith(_mapped_prefix)