mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Use the windows API to get the routing table instead of parsing command output
This commit is contained in:
parent
8e503a4988
commit
eae72e9ec7
@ -26,7 +26,7 @@ def get_addresses_for_interface(name, family='AF_INET'):
|
||||
|
||||
if iswindows:
|
||||
|
||||
def get_default_route_src_address():
|
||||
def get_default_route_src_address_external():
|
||||
# Use -6 for IPv6 addresses
|
||||
raw = subprocess.check_output('route -4 print 0.0.0.0'.split(), creationflags=0x08).decode('utf-8', 'replace')
|
||||
in_table = False
|
||||
@ -44,6 +44,15 @@ if iswindows:
|
||||
if parts == 'Network Destination Netmask Gateway Interface Metric'.split():
|
||||
in_table = True
|
||||
|
||||
def get_default_route_src_address_api():
|
||||
from calibre.utils.iphlpapi import routes
|
||||
for route in routes():
|
||||
if route.interface and route.destination == '0.0.0.0':
|
||||
for addr in get_addresses_for_interface(route.interface):
|
||||
return addr
|
||||
|
||||
get_default_route_src_address = get_default_route_src_address_api
|
||||
|
||||
|
||||
elif isosx:
|
||||
|
||||
|
380
src/calibre/utils/iphlpapi.py
Normal file
380
src/calibre/utils/iphlpapi.py
Normal file
@ -0,0 +1,380 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
import ctypes
|
||||
from ctypes import windll
|
||||
from ctypes import wintypes
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
from calibre.constants import is64bit
|
||||
|
||||
class GUID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("data1", wintypes.DWORD),
|
||||
("data2", wintypes.WORD),
|
||||
("data3", wintypes.WORD),
|
||||
("data4", wintypes.BYTE * 8)]
|
||||
|
||||
def __init__(self, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8):
|
||||
self.data1 = l
|
||||
self.data2 = w1
|
||||
self.data3 = w2
|
||||
self.data4[0] = b1
|
||||
self.data4[1] = b2
|
||||
self.data4[2] = b3
|
||||
self.data4[3] = b4
|
||||
self.data4[4] = b5
|
||||
self.data4[5] = b6
|
||||
self.data4[6] = b7
|
||||
self.data4[7] = b8
|
||||
|
||||
class SOCKADDR(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_family', wintypes.USHORT),
|
||||
('sa_data', ctypes.c_char * 14),
|
||||
]
|
||||
|
||||
ERROR_SUCCESS = 0
|
||||
ERROR_INSUFFICIENT_BUFFER = 122
|
||||
ERROR_BUFFER_OVERFLOW = 111
|
||||
MAX_ADAPTER_NAME_LENGTH = 256
|
||||
MAX_ADAPTER_DESCRIPTION_LENGTH = 128
|
||||
MAX_ADAPTER_ADDRESS_LENGTH = 8
|
||||
|
||||
# Do not return IPv6 anycast addresses.
|
||||
GAA_FLAG_SKIP_ANYCAST = 2
|
||||
GAA_FLAG_SKIP_MULTICAST = 4
|
||||
|
||||
IP_ADAPTER_DHCP_ENABLED = 4
|
||||
IP_ADAPTER_IPV4_ENABLED = 0x80
|
||||
IP_ADAPTER_IPV6_ENABLED = 0x0100
|
||||
|
||||
MAX_DHCPV6_DUID_LENGTH = 130
|
||||
|
||||
IF_TYPE_ETHERNET_CSMACD = 6
|
||||
IF_TYPE_SOFTWARE_LOOPBACK = 24
|
||||
IF_TYPE_IEEE80211 = 71
|
||||
IF_TYPE_TUNNEL = 131
|
||||
|
||||
IP_ADAPTER_ADDRESSES_SIZE_2003 = 144
|
||||
|
||||
|
||||
class SOCKET_ADDRESS(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('lpSockaddr', ctypes.POINTER(SOCKADDR)),
|
||||
('iSockaddrLength', wintypes.INT),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_ADDRESSES_Struct1(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('IfIndex', wintypes.DWORD),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_ADDRESSES_Union1(ctypes.Union):
|
||||
_fields_ = [
|
||||
('Alignment', wintypes.ULARGE_INTEGER),
|
||||
('Struct1', IP_ADAPTER_ADDRESSES_Struct1),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Union1', IP_ADAPTER_ADDRESSES_Union1),
|
||||
('Next', wintypes.LPVOID),
|
||||
('Address', SOCKET_ADDRESS),
|
||||
('PrefixOrigin', wintypes.DWORD),
|
||||
('SuffixOrigin', wintypes.DWORD),
|
||||
('DadState', wintypes.DWORD),
|
||||
('ValidLifetime', wintypes.ULONG),
|
||||
('PreferredLifetime', wintypes.ULONG),
|
||||
('LeaseLifetime', wintypes.ULONG),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_DNS_SERVER_ADDRESS_Struct1(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('Reserved', wintypes.DWORD),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_DNS_SERVER_ADDRESS_Union1(ctypes.Union):
|
||||
_fields_ = [
|
||||
('Alignment', wintypes.ULARGE_INTEGER),
|
||||
('Struct1', IP_ADAPTER_DNS_SERVER_ADDRESS_Struct1),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_DNS_SERVER_ADDRESS(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Union1', IP_ADAPTER_DNS_SERVER_ADDRESS_Union1),
|
||||
('Next', wintypes.LPVOID),
|
||||
('Address', SOCKET_ADDRESS),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_PREFIX_Struct1(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('Flags', wintypes.DWORD),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_PREFIX_Union1(ctypes.Union):
|
||||
_fields_ = [
|
||||
('Alignment', wintypes.ULARGE_INTEGER),
|
||||
('Struct1', IP_ADAPTER_PREFIX_Struct1),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_PREFIX(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Union1', IP_ADAPTER_PREFIX_Union1),
|
||||
('Next', wintypes.LPVOID),
|
||||
('Address', SOCKET_ADDRESS),
|
||||
('PrefixLength', wintypes.ULONG),
|
||||
]
|
||||
|
||||
class IP_ADAPTER_DNS_SUFFIX(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Next', wintypes.LPVOID),
|
||||
('String', wintypes.LPWSTR),
|
||||
]
|
||||
|
||||
|
||||
class NET_LUID_LH(ctypes.Union):
|
||||
_fields_ = [
|
||||
('Value', wintypes.ULARGE_INTEGER),
|
||||
('Info', wintypes.ULARGE_INTEGER),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_ADDRESSES(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('Union1', IP_ADAPTER_ADDRESSES_Union1),
|
||||
('Next', wintypes.LPVOID),
|
||||
('AdapterName', ctypes.c_char_p),
|
||||
('FirstUnicastAddress',
|
||||
ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
|
||||
('FirstAnycastAddress',
|
||||
ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)),
|
||||
('FirstMulticastAddress',
|
||||
ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)),
|
||||
('FirstDnsServerAddress',
|
||||
ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)),
|
||||
('DnsSuffix', wintypes.LPWSTR),
|
||||
('Description', wintypes.LPWSTR),
|
||||
('FriendlyName', wintypes.LPWSTR),
|
||||
('PhysicalAddress', ctypes.c_ubyte * MAX_ADAPTER_ADDRESS_LENGTH),
|
||||
('PhysicalAddressLength', wintypes.DWORD),
|
||||
('Flags', wintypes.DWORD),
|
||||
('Mtu', wintypes.DWORD),
|
||||
('IfType', wintypes.DWORD),
|
||||
('OperStatus', wintypes.DWORD),
|
||||
('Ipv6IfIndex', wintypes.DWORD),
|
||||
('ZoneIndices', wintypes.DWORD * 16),
|
||||
('FirstPrefix', ctypes.POINTER(IP_ADAPTER_PREFIX)),
|
||||
# Vista and later
|
||||
('TransmitLinkSpeed', wintypes.ULARGE_INTEGER),
|
||||
('ReceiveLinkSpeed', wintypes.ULARGE_INTEGER),
|
||||
('FirstWinsServerAddress',
|
||||
ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)),
|
||||
('FirstGatewayAddress',
|
||||
ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)),
|
||||
('Ipv4Metric', wintypes.ULONG),
|
||||
('Ipv6Metric', wintypes.ULONG),
|
||||
('Luid', NET_LUID_LH),
|
||||
('Dhcpv4Server', SOCKET_ADDRESS),
|
||||
('CompartmentId', wintypes.DWORD),
|
||||
('NetworkGuid', GUID),
|
||||
('ConnectionType', wintypes.DWORD),
|
||||
('TunnelType', wintypes.DWORD),
|
||||
('Dhcpv6Server', SOCKET_ADDRESS),
|
||||
('Dhcpv6ClientDuid', ctypes.c_ubyte * MAX_DHCPV6_DUID_LENGTH),
|
||||
('Dhcpv6ClientDuidLength', wintypes.ULONG),
|
||||
('Dhcpv6Iaid', wintypes.ULONG),
|
||||
# Vista SP1 and later, so we comment it out as we dont need it
|
||||
# ('FirstDnsSuffix', ctypes.POINTER(IP_ADAPTER_DNS_SUFFIX)),
|
||||
]
|
||||
|
||||
|
||||
class Win32_MIB_IPFORWARDROW(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('dwForwardDest', wintypes.DWORD),
|
||||
('dwForwardMask', wintypes.DWORD),
|
||||
('dwForwardPolicy', wintypes.DWORD),
|
||||
('dwForwardNextHop', wintypes.DWORD),
|
||||
('dwForwardIfIndex', wintypes.DWORD),
|
||||
('dwForwardType', wintypes.DWORD),
|
||||
('dwForwardProto', wintypes.DWORD),
|
||||
('dwForwardAge', wintypes.DWORD),
|
||||
('dwForwardNextHopAS', wintypes.DWORD),
|
||||
('dwForwardMetric1', wintypes.DWORD),
|
||||
('dwForwardMetric2', wintypes.DWORD),
|
||||
('dwForwardMetric3', wintypes.DWORD),
|
||||
('dwForwardMetric4', wintypes.DWORD),
|
||||
('dwForwardMetric5', wintypes.DWORD)
|
||||
]
|
||||
|
||||
|
||||
class Win32_MIB_IPFORWARDTABLE(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('dwNumEntries', wintypes.DWORD),
|
||||
('table', Win32_MIB_IPFORWARDROW * 1)
|
||||
]
|
||||
|
||||
|
||||
GetAdaptersAddresses = windll.Iphlpapi.GetAdaptersAddresses
|
||||
GetAdaptersAddresses.argtypes = [
|
||||
wintypes.ULONG, wintypes.ULONG, wintypes.LPVOID,
|
||||
ctypes.POINTER(IP_ADAPTER_ADDRESSES),
|
||||
ctypes.POINTER(wintypes.ULONG)]
|
||||
GetAdaptersAddresses.restype = wintypes.ULONG
|
||||
|
||||
GetIpForwardTable = windll.Iphlpapi.GetIpForwardTable
|
||||
GetIpForwardTable.argtypes = [
|
||||
ctypes.POINTER(Win32_MIB_IPFORWARDTABLE),
|
||||
ctypes.POINTER(wintypes.ULONG),
|
||||
wintypes.BOOL]
|
||||
GetIpForwardTable.restype = wintypes.DWORD
|
||||
|
||||
GetProcessHeap = windll.kernel32.GetProcessHeap
|
||||
GetProcessHeap.argtypes = []
|
||||
GetProcessHeap.restype = wintypes.HANDLE
|
||||
|
||||
HeapAlloc = windll.kernel32.HeapAlloc
|
||||
HeapAlloc.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.c_uint64 if is64bit else ctypes.c_uint32]
|
||||
HeapAlloc.restype = wintypes.LPVOID
|
||||
|
||||
HeapFree = windll.kernel32.HeapFree
|
||||
HeapFree.argtypes = [wintypes.HANDLE, wintypes.DWORD, wintypes.LPVOID]
|
||||
HeapFree.restype = wintypes.BOOL
|
||||
ERROR_NO_DATA = 232
|
||||
GAA_FLAG_INCLUDE_PREFIX = 0x0010
|
||||
Ws2_32 = windll.Ws2_32
|
||||
Ws2_32.inet_ntoa.restype = ctypes.c_char_p
|
||||
|
||||
def _heap_alloc(heap, size):
|
||||
table_mem = HeapAlloc(heap, 0, ctypes.c_size_t(size.value))
|
||||
if not table_mem:
|
||||
raise MemoryError('Unable to allocate memory for the IP forward table')
|
||||
return table_mem
|
||||
|
||||
@contextmanager
|
||||
def _get_forward_table():
|
||||
heap = GetProcessHeap()
|
||||
size = wintypes.ULONG(0)
|
||||
p_forward_table = table_mem = None
|
||||
max_tries = 10
|
||||
|
||||
try:
|
||||
while max_tries > 0:
|
||||
max_tries -= 1
|
||||
err = GetIpForwardTable(p_forward_table, ctypes.byref(size), 0)
|
||||
if err == ERROR_INSUFFICIENT_BUFFER:
|
||||
if p_forward_table is not None:
|
||||
HeapFree(heap, 0, p_forward_table)
|
||||
p_forward_table = None
|
||||
table_mem = _heap_alloc(heap, size)
|
||||
p_forward_table = ctypes.cast(table_mem, ctypes.POINTER(Win32_MIB_IPFORWARDTABLE))
|
||||
elif err in (ERROR_SUCCESS, ERROR_NO_DATA):
|
||||
yield p_forward_table
|
||||
break
|
||||
else:
|
||||
raise OSError('Unable to get IP forward table. Error: %s' % err)
|
||||
finally:
|
||||
if p_forward_table is not None:
|
||||
HeapFree(heap, 0, p_forward_table)
|
||||
|
||||
@contextmanager
|
||||
def _get_adapters():
|
||||
heap = GetProcessHeap()
|
||||
size = wintypes.ULONG(0)
|
||||
addresses = buf = None
|
||||
max_tries = 10
|
||||
try:
|
||||
while max_tries > 0:
|
||||
max_tries -= 1
|
||||
err = GetAdaptersAddresses(0, GAA_FLAG_INCLUDE_PREFIX, None, addresses, ctypes.byref(size))
|
||||
if err in (ERROR_SUCCESS, ERROR_NO_DATA):
|
||||
yield addresses
|
||||
break
|
||||
elif err == ERROR_BUFFER_OVERFLOW:
|
||||
if addresses is not None:
|
||||
HeapFree(heap, 0, addresses)
|
||||
addresses = None
|
||||
buf = _heap_alloc(heap, size)
|
||||
addresses = ctypes.cast(buf, ctypes.POINTER(IP_ADAPTER_ADDRESSES))
|
||||
else:
|
||||
raise OSError('Failed to determine size for adapters table with error: %s' % err)
|
||||
finally:
|
||||
if addresses is not None:
|
||||
HeapFree(heap, 0, addresses)
|
||||
addresses = None
|
||||
|
||||
Adapter = namedtuple('Adapter', 'name if_index if_index6 friendly_name status transmit_speed receive_speed')
|
||||
|
||||
def adapters():
|
||||
ans = []
|
||||
smap = {1:'up', 2:'down', 3:'testing', 4:'unknown', 5:'dormant', 6:'not-present', 7:'lower-layer-down'}
|
||||
with _get_adapters() as p_adapters_list:
|
||||
adapter = p_adapters_list
|
||||
while adapter:
|
||||
adapter = adapter.contents
|
||||
if not adapter:
|
||||
break
|
||||
ans.append(Adapter(
|
||||
name=adapter.AdapterName.decode(),
|
||||
if_index=adapter.Union1.Struct1.IfIndex,
|
||||
if_index6=adapter.Ipv6IfIndex,
|
||||
friendly_name=adapter.FriendlyName,
|
||||
status=smap.get(adapter.OperStatus, 'unknown'),
|
||||
transmit_speed=adapter.TransmitLinkSpeed,
|
||||
receive_speed=adapter.ReceiveLinkSpeed
|
||||
))
|
||||
adapter = ctypes.cast(adapter.Next, ctypes.POINTER(IP_ADAPTER_ADDRESSES))
|
||||
return ans
|
||||
|
||||
|
||||
Route = namedtuple('Route', 'destination gateway netmask interface metric flags')
|
||||
|
||||
def routes():
|
||||
"""Get a collection of the available routes."""
|
||||
ans = []
|
||||
adapter_map = {a.if_index:a.name for a in adapters()}
|
||||
with _get_forward_table() as p_forward_table:
|
||||
if p_forward_table is None:
|
||||
return ans
|
||||
forward_table = p_forward_table.contents
|
||||
table = ctypes.cast(
|
||||
ctypes.addressof(forward_table.table),
|
||||
ctypes.POINTER(Win32_MIB_IPFORWARDROW * forward_table.dwNumEntries)
|
||||
).contents
|
||||
|
||||
for row in table:
|
||||
destination = Ws2_32.inet_ntoa(row.dwForwardDest).decode()
|
||||
netmask = Ws2_32.inet_ntoa(row.dwForwardMask).decode()
|
||||
gateway = Ws2_32.inet_ntoa(row.dwForwardNextHop).decode()
|
||||
ans.append(Route(
|
||||
destination=destination,
|
||||
gateway=gateway,
|
||||
netmask=netmask,
|
||||
interface=adapter_map.get(row.dwForwardIfIndex),
|
||||
metric=row.dwForwardMetric1,
|
||||
flags=row.dwForwardProto
|
||||
))
|
||||
|
||||
return ans
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pprint import pprint
|
||||
pprint(adapters())
|
||||
pprint(routes())
|
||||
|
Loading…
x
Reference in New Issue
Block a user