inet.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. """Generic Internet address helper functions."""
  17. import socket
  18. from typing import Any, Optional, Tuple
  19. import dns.ipv4
  20. import dns.ipv6
  21. # We assume that AF_INET and AF_INET6 are always defined. We keep
  22. # these here for the benefit of any old code (unlikely though that
  23. # is!).
  24. AF_INET = socket.AF_INET
  25. AF_INET6 = socket.AF_INET6
  26. def inet_pton(family: int, text: str) -> bytes:
  27. """Convert the textual form of a network address into its binary form.
  28. *family* is an ``int``, the address family.
  29. *text* is a ``str``, the textual address.
  30. Raises ``NotImplementedError`` if the address family specified is not
  31. implemented.
  32. Returns a ``bytes``.
  33. """
  34. if family == AF_INET:
  35. return dns.ipv4.inet_aton(text)
  36. elif family == AF_INET6:
  37. return dns.ipv6.inet_aton(text, True)
  38. else:
  39. raise NotImplementedError
  40. def inet_ntop(family: int, address: bytes) -> str:
  41. """Convert the binary form of a network address into its textual form.
  42. *family* is an ``int``, the address family.
  43. *address* is a ``bytes``, the network address in binary form.
  44. Raises ``NotImplementedError`` if the address family specified is not
  45. implemented.
  46. Returns a ``str``.
  47. """
  48. if family == AF_INET:
  49. return dns.ipv4.inet_ntoa(address)
  50. elif family == AF_INET6:
  51. return dns.ipv6.inet_ntoa(address)
  52. else:
  53. raise NotImplementedError
  54. def af_for_address(text: str) -> int:
  55. """Determine the address family of a textual-form network address.
  56. *text*, a ``str``, the textual address.
  57. Raises ``ValueError`` if the address family cannot be determined
  58. from the input.
  59. Returns an ``int``.
  60. """
  61. try:
  62. dns.ipv4.inet_aton(text)
  63. return AF_INET
  64. except Exception:
  65. try:
  66. dns.ipv6.inet_aton(text, True)
  67. return AF_INET6
  68. except Exception:
  69. raise ValueError
  70. def is_multicast(text: str) -> bool:
  71. """Is the textual-form network address a multicast address?
  72. *text*, a ``str``, the textual address.
  73. Raises ``ValueError`` if the address family cannot be determined
  74. from the input.
  75. Returns a ``bool``.
  76. """
  77. try:
  78. first = dns.ipv4.inet_aton(text)[0]
  79. return first >= 224 and first <= 239
  80. except Exception:
  81. try:
  82. first = dns.ipv6.inet_aton(text, True)[0]
  83. return first == 255
  84. except Exception:
  85. raise ValueError
  86. def is_address(text: str) -> bool:
  87. """Is the specified string an IPv4 or IPv6 address?
  88. *text*, a ``str``, the textual address.
  89. Returns a ``bool``.
  90. """
  91. try:
  92. dns.ipv4.inet_aton(text)
  93. return True
  94. except Exception:
  95. try:
  96. dns.ipv6.inet_aton(text, True)
  97. return True
  98. except Exception:
  99. return False
  100. def low_level_address_tuple(
  101. high_tuple: Tuple[str, int], af: Optional[int] = None
  102. ) -> Any:
  103. """Given a "high-level" address tuple, i.e.
  104. an (address, port) return the appropriate "low-level" address tuple
  105. suitable for use in socket calls.
  106. If an *af* other than ``None`` is provided, it is assumed the
  107. address in the high-level tuple is valid and has that af. If af
  108. is ``None``, then af_for_address will be called.
  109. """
  110. address, port = high_tuple
  111. if af is None:
  112. af = af_for_address(address)
  113. if af == AF_INET:
  114. return (address, port)
  115. elif af == AF_INET6:
  116. i = address.find("%")
  117. if i < 0:
  118. # no scope, shortcut!
  119. return (address, port, 0, 0)
  120. # try to avoid getaddrinfo()
  121. addrpart = address[:i]
  122. scope = address[i + 1 :]
  123. if scope.isdigit():
  124. return (addrpart, port, 0, int(scope))
  125. try:
  126. return (addrpart, port, 0, socket.if_nametoindex(scope))
  127. except AttributeError: # pragma: no cover (we can't really test this)
  128. ai_flags = socket.AI_NUMERICHOST
  129. ((*_, tup), *_) = socket.getaddrinfo(address, port, flags=ai_flags)
  130. return tup
  131. else:
  132. raise NotImplementedError(f"unknown address family {af}")
  133. def any_for_af(af):
  134. """Return the 'any' address for the specified address family."""
  135. if af == socket.AF_INET:
  136. return "0.0.0.0"
  137. elif af == socket.AF_INET6:
  138. return "::"
  139. raise NotImplementedError(f"unknown address family {af}")
  140. def canonicalize(text: str) -> str:
  141. """Verify that *address* is a valid text form IPv4 or IPv6 address and return its
  142. canonical text form. IPv6 addresses with scopes are rejected.
  143. *text*, a ``str``, the address in textual form.
  144. Raises ``ValueError`` if the text is not valid.
  145. """
  146. try:
  147. return dns.ipv6.canonicalize(text)
  148. except Exception:
  149. try:
  150. return dns.ipv4.canonicalize(text)
  151. except Exception:
  152. raise ValueError