_posixifaces.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. POSIX implementation of local network interface enumeration.
  5. """
  6. from __future__ import division, absolute_import
  7. import sys, socket
  8. from socket import AF_INET, AF_INET6, inet_ntop
  9. from ctypes import (
  10. CDLL, POINTER, Structure, c_char_p, c_ushort, c_int,
  11. c_uint32, c_uint8, c_void_p, c_ubyte, pointer, cast)
  12. from ctypes.util import find_library
  13. from twisted.python.compat import _PY3, nativeString
  14. if _PY3:
  15. # Once #6070 is implemented, this can be replaced with the implementation
  16. # from that ticket:
  17. def chr(i):
  18. """
  19. Python 3 implementation of Python 2 chr(), i.e. convert an integer to
  20. corresponding byte.
  21. """
  22. return bytes([i])
  23. libc = CDLL(find_library("c"))
  24. if sys.platform.startswith('freebsd') or sys.platform == 'darwin':
  25. _sockaddrCommon = [
  26. ("sin_len", c_uint8),
  27. ("sin_family", c_uint8),
  28. ]
  29. else:
  30. _sockaddrCommon = [
  31. ("sin_family", c_ushort),
  32. ]
  33. class in_addr(Structure):
  34. _fields_ = [
  35. ("in_addr", c_ubyte * 4),
  36. ]
  37. class in6_addr(Structure):
  38. _fields_ = [
  39. ("in_addr", c_ubyte * 16),
  40. ]
  41. class sockaddr(Structure):
  42. _fields_ = _sockaddrCommon + [
  43. ("sin_port", c_ushort),
  44. ]
  45. class sockaddr_in(Structure):
  46. _fields_ = _sockaddrCommon + [
  47. ("sin_port", c_ushort),
  48. ("sin_addr", in_addr),
  49. ]
  50. class sockaddr_in6(Structure):
  51. _fields_ = _sockaddrCommon + [
  52. ("sin_port", c_ushort),
  53. ("sin_flowinfo", c_uint32),
  54. ("sin_addr", in6_addr),
  55. ]
  56. class ifaddrs(Structure):
  57. pass
  58. ifaddrs_p = POINTER(ifaddrs)
  59. ifaddrs._fields_ = [
  60. ('ifa_next', ifaddrs_p),
  61. ('ifa_name', c_char_p),
  62. ('ifa_flags', c_uint32),
  63. ('ifa_addr', POINTER(sockaddr)),
  64. ('ifa_netmask', POINTER(sockaddr)),
  65. ('ifa_dstaddr', POINTER(sockaddr)),
  66. ('ifa_data', c_void_p)]
  67. getifaddrs = libc.getifaddrs
  68. getifaddrs.argtypes = [POINTER(ifaddrs_p)]
  69. getifaddrs.restype = c_int
  70. freeifaddrs = libc.freeifaddrs
  71. freeifaddrs.argtypes = [ifaddrs_p]
  72. def _maybeCleanupScopeIndex(family, packed):
  73. """
  74. On FreeBSD, kill the embedded interface indices in link-local scoped
  75. addresses.
  76. @param family: The address family of the packed address - one of the
  77. I{socket.AF_*} constants.
  78. @param packed: The packed representation of the address (ie, the bytes of a
  79. I{in_addr} field).
  80. @type packed: L{bytes}
  81. @return: The packed address with any FreeBSD-specific extra bits cleared.
  82. @rtype: L{bytes}
  83. @see: U{https://twistedmatrix.com/trac/ticket/6843}
  84. @see: U{http://www.freebsd.org/doc/en/books/developers-handbook/ipv6.html#ipv6-scope-index}
  85. @note: Indications are that the need for this will be gone in FreeBSD >=10.
  86. """
  87. if sys.platform.startswith('freebsd') and packed[:2] == b"\xfe\x80":
  88. return packed[:2] + b"\x00\x00" + packed[4:]
  89. return packed
  90. def _interfaces():
  91. """
  92. Call C{getifaddrs(3)} and return a list of tuples of interface name, address
  93. family, and human-readable address representing its results.
  94. """
  95. ifaddrs = ifaddrs_p()
  96. if getifaddrs(pointer(ifaddrs)) < 0:
  97. raise OSError()
  98. results = []
  99. try:
  100. while ifaddrs:
  101. if ifaddrs[0].ifa_addr:
  102. family = ifaddrs[0].ifa_addr[0].sin_family
  103. if family == AF_INET:
  104. addr = cast(ifaddrs[0].ifa_addr, POINTER(sockaddr_in))
  105. elif family == AF_INET6:
  106. addr = cast(ifaddrs[0].ifa_addr, POINTER(sockaddr_in6))
  107. else:
  108. addr = None
  109. if addr:
  110. packed = b''.join(map(chr, addr[0].sin_addr.in_addr[:]))
  111. packed = _maybeCleanupScopeIndex(family, packed)
  112. results.append((
  113. ifaddrs[0].ifa_name,
  114. family,
  115. inet_ntop(family, packed)))
  116. ifaddrs = ifaddrs[0].ifa_next
  117. finally:
  118. freeifaddrs(ifaddrs)
  119. return results
  120. def posixGetLinkLocalIPv6Addresses():
  121. """
  122. Return a list of strings in colon-hex format representing all the link local
  123. IPv6 addresses available on the system, as reported by I{getifaddrs(3)}.
  124. """
  125. retList = []
  126. for (interface, family, address) in _interfaces():
  127. interface = nativeString(interface)
  128. address = nativeString(address)
  129. if family == socket.AF_INET6 and address.startswith('fe80:'):
  130. retList.append('%s%%%s' % (address, interface))
  131. return retList