You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

181 lines
5.4 KiB

4 years ago
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2003-2017 Nominum, Inc.
  3. #
  4. # Permission to use, copy, modify, and distribute this software and its
  5. # documentation for any purpose with or without fee is hereby granted,
  6. # provided that the above copyright notice and this permission notice
  7. # appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  15. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """IPv6 helper functions."""
  17. import re
  18. import binascii
  19. import dns.exception
  20. import dns.ipv4
  21. from ._compat import xrange, binary_type, maybe_decode
  22. _leading_zero = re.compile(r'0+([0-9a-f]+)')
  23. def inet_ntoa(address):
  24. """Convert an IPv6 address in binary form to text form.
  25. *address*, a ``binary``, the IPv6 address in binary form.
  26. Raises ``ValueError`` if the address isn't 16 bytes long.
  27. Returns a ``text``.
  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 = maybe_decode(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] != '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] == 'ffff'):
  74. # We have an embedded IPv4 address
  75. if best_len == 6:
  76. prefix = '::'
  77. else:
  78. prefix = '::ffff:'
  79. hex = prefix + dns.ipv4.inet_ntoa(address[12:])
  80. else:
  81. hex = ':'.join(chunks[:best_start]) + '::' + \
  82. ':'.join(chunks[best_start + best_len:])
  83. else:
  84. hex = ':'.join(chunks)
  85. return hex
  86. _v4_ending = re.compile(br'(.*):(\d+\.\d+\.\d+\.\d+)$')
  87. _colon_colon_start = re.compile(br'::.*')
  88. _colon_colon_end = re.compile(br'.*::$')
  89. def inet_aton(text):
  90. """Convert an IPv6 address in text form to binary form.
  91. *text*, a ``text``, the IPv6 address in textual form.
  92. Returns a ``binary``.
  93. """
  94. #
  95. # Our aim here is not something fast; we just want something that works.
  96. #
  97. if not isinstance(text, binary_type):
  98. text = text.encode()
  99. if text == b'::':
  100. text = b'0::'
  101. #
  102. # Get rid of the icky dot-quad syntax if we have it.
  103. #
  104. m = _v4_ending.match(text)
  105. if not m is None:
  106. b = bytearray(dns.ipv4.inet_aton(m.group(2)))
  107. text = (u"{}:{:02x}{:02x}:{:02x}{:02x}".format(m.group(1).decode(),
  108. b[0], b[1], b[2],
  109. b[3])).encode()
  110. #
  111. # Try to turn '::<whatever>' into ':<whatever>'; if no match try to
  112. # turn '<whatever>::' into '<whatever>:'
  113. #
  114. m = _colon_colon_start.match(text)
  115. if not m is None:
  116. text = text[1:]
  117. else:
  118. m = _colon_colon_end.match(text)
  119. if not m is None:
  120. text = text[:-1]
  121. #
  122. # Now canonicalize into 8 chunks of 4 hex digits each
  123. #
  124. chunks = text.split(b':')
  125. l = len(chunks)
  126. if l > 8:
  127. raise dns.exception.SyntaxError
  128. seen_empty = False
  129. canonical = []
  130. for c in chunks:
  131. if c == b'':
  132. if seen_empty:
  133. raise dns.exception.SyntaxError
  134. seen_empty = True
  135. for i in xrange(0, 8 - l + 1):
  136. canonical.append(b'0000')
  137. else:
  138. lc = len(c)
  139. if lc > 4:
  140. raise dns.exception.SyntaxError
  141. if lc != 4:
  142. c = (b'0' * (4 - lc)) + c
  143. canonical.append(c)
  144. if l < 8 and not seen_empty:
  145. raise dns.exception.SyntaxError
  146. text = b''.join(canonical)
  147. #
  148. # Finally we can go to binary.
  149. #
  150. try:
  151. return binascii.unhexlify(text)
  152. except (binascii.Error, TypeError):
  153. raise dns.exception.SyntaxError
  154. _mapped_prefix = b'\x00' * 10 + b'\xff\xff'
  155. def is_mapped(address):
  156. """Is the specified address a mapped IPv4 address?
  157. *address*, a ``binary`` is an IPv6 address in binary form.
  158. Returns a ``bool``.
  159. """
  160. return address.startswith(_mapped_prefix)