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.

184 lines
6.9 KiB

4 years ago
  1. # Copyright (c) 2014 Stefan C. Mueller
  2. # Permission is hereby granted, free of charge, to any person obtaining a copy
  3. # of this software and associated documentation files (the "Software"), to
  4. # deal in the Software without restriction, including without limitation the
  5. # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  6. # sell copies of the Software, and to permit persons to whom the Software is
  7. # furnished to do so, subject to the following conditions:
  8. #
  9. # The above copyright notice and this permission notice shall be included in
  10. # all copies or substantial portions of the Software.
  11. #
  12. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  17. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  18. # IN THE SOFTWARE.
  19. import ctypes
  20. import socket
  21. import ipaddress
  22. import platform
  23. class Adapter(object):
  24. """
  25. Represents a network interface device controller (NIC), such as a
  26. network card. An adapter can have multiple IPs.
  27. On Linux aliasing (multiple IPs per physical NIC) is implemented
  28. by creating 'virtual' adapters, each represented by an instance
  29. of this class. Each of those 'virtual' adapters can have both
  30. a IPv4 and an IPv6 IP address.
  31. """
  32. def __init__(self, name, nice_name, ips):
  33. #: Unique name that identifies the adapter in the system.
  34. #: On Linux this is of the form of `eth0` or `eth0:1`, on
  35. #: Windows it is a UUID in string representation, such as
  36. #: `{846EE342-7039-11DE-9D20-806E6F6E6963}`.
  37. self.name = name
  38. #: Human readable name of the adpater. On Linux this
  39. #: is currently the same as :attr:`name`. On Windows
  40. #: this is the name of the device.
  41. self.nice_name = nice_name
  42. #: List of :class:`ifaddr.IP` instances in the order they were
  43. #: reported by the system.
  44. self.ips = ips
  45. def __repr__(self):
  46. return "Adapter(name={name}, nice_name={nice_name}, ips={ips})".format(
  47. name = repr(self.name),
  48. nice_name = repr(self.nice_name),
  49. ips = repr(self.ips)
  50. )
  51. class IP(object):
  52. """
  53. Represents an IP address of an adapter.
  54. """
  55. def __init__(self, ip, network_prefix, nice_name):
  56. #: IP address. For IPv4 addresses this is a string in
  57. #: "xxx.xxx.xxx.xxx" format. For IPv6 addresses this
  58. #: is a three-tuple `(ip, flowinfo, scope_id)`, where
  59. #: `ip` is a string in the usual collon separated
  60. #: hex format.
  61. self.ip = ip
  62. #: Number of bits of the IP that represent the
  63. #: network. For a `255.255.255.0` netmask, this
  64. #: number would be `24`.
  65. self.network_prefix = network_prefix
  66. #: Human readable name for this IP.
  67. #: On Linux is this currently the same as the adapter name.
  68. #: On Windows this is the name of the network connection
  69. #: as configured in the system control panel.
  70. self.nice_name = nice_name
  71. @property
  72. def is_IPv4(self):
  73. """
  74. Returns `True` if this IP is an IPv4 address and `False`
  75. if it is an IPv6 address.
  76. """
  77. return not isinstance(self.ip, tuple)
  78. @property
  79. def is_IPv6(self):
  80. """
  81. Returns `True` if this IP is an IPv6 address and `False`
  82. if it is an IPv4 address.
  83. """
  84. return isinstance(self.ip, tuple)
  85. def __repr__(self):
  86. return "IP(ip={ip}, network_prefix={network_prefix}, nice_name={nice_name})".format(
  87. ip = repr(self.ip),
  88. network_prefix = repr(self.network_prefix),
  89. nice_name = repr(self.nice_name)
  90. )
  91. if platform.system() == "Darwin":
  92. # Darwin uses marginally different structures
  93. # than either Linux or Windows.
  94. # I still keep it in `shared` since we can use
  95. # both structures equally.
  96. class sockaddr(ctypes.Structure):
  97. _fields_= [('sa_len', ctypes.c_uint8),
  98. ('sa_familiy', ctypes.c_uint8),
  99. ('sa_data', ctypes.c_uint8 * 14)]
  100. class sockaddr_in(ctypes.Structure):
  101. _fields_= [('sa_len', ctypes.c_uint8),
  102. ('sa_familiy', ctypes.c_uint8),
  103. ('sin_port', ctypes.c_uint16),
  104. ('sin_addr', ctypes.c_uint8 * 4),
  105. ('sin_zero', ctypes.c_uint8 * 8)]
  106. class sockaddr_in6(ctypes.Structure):
  107. _fields_= [('sa_len', ctypes.c_uint8),
  108. ('sa_familiy', ctypes.c_uint8),
  109. ('sin6_port', ctypes.c_uint16),
  110. ('sin6_flowinfo', ctypes.c_uint32),
  111. ('sin6_addr', ctypes.c_uint8 * 16),
  112. ('sin6_scope_id', ctypes.c_uint32)]
  113. else:
  114. class sockaddr(ctypes.Structure):
  115. _fields_= [('sa_familiy', ctypes.c_uint16),
  116. ('sa_data', ctypes.c_uint8 * 14)]
  117. class sockaddr_in(ctypes.Structure):
  118. _fields_= [('sin_familiy', ctypes.c_uint16),
  119. ('sin_port', ctypes.c_uint16),
  120. ('sin_addr', ctypes.c_uint8 * 4),
  121. ('sin_zero', ctypes.c_uint8 * 8)]
  122. class sockaddr_in6(ctypes.Structure):
  123. _fields_= [('sin6_familiy', ctypes.c_uint16),
  124. ('sin6_port', ctypes.c_uint16),
  125. ('sin6_flowinfo', ctypes.c_uint32),
  126. ('sin6_addr', ctypes.c_uint8 * 16),
  127. ('sin6_scope_id', ctypes.c_uint32)]
  128. def sockaddr_to_ip(sockaddr_ptr):
  129. if sockaddr_ptr:
  130. if sockaddr_ptr[0].sa_familiy == socket.AF_INET:
  131. ipv4 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in))
  132. ippacked = bytes(bytearray(ipv4[0].sin_addr))
  133. ip = str(ipaddress.ip_address(ippacked))
  134. return ip
  135. elif sockaddr_ptr[0].sa_familiy == socket.AF_INET6:
  136. ipv6 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in6))
  137. flowinfo = ipv6[0].sin6_flowinfo
  138. ippacked = bytes(bytearray(ipv6[0].sin6_addr))
  139. ip = str(ipaddress.ip_address(ippacked))
  140. scope_id = ipv6[0].sin6_scope_id
  141. return(ip, flowinfo, scope_id)
  142. return None
  143. def ipv6_prefixlength(address):
  144. prefix_length = 0
  145. for i in range(address.max_prefixlen):
  146. if int(address) >> i & 1:
  147. prefix_length = prefix_length + 1
  148. return prefix_length