Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/net/net_util.py : 57%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# This file is part of Xpra.
3# Copyright (C) 2013-2020 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7# taken from the code I wrote for winswitch
9import socket
10import sys
12from xpra.os_util import WIN32
13from xpra.log import Logger
15log = Logger("network", "util")
18netifaces_version = 0
19_netifaces = None
20def import_netifaces() -> object:
21 global _netifaces, netifaces_version
22 if _netifaces is None:
23 try:
24 import netifaces #@UnresolvedImport
25 log("netifaces loaded sucessfully")
26 _netifaces = netifaces
27 netifaces_version = netifaces.version #@UndefinedVariable
28 except ImportError:
29 _netifaces = False
30 log.warn("Warning: the python netifaces package is missing")
31 return _netifaces
33iface_ipmasks = {}
34bind_IPs = None
37def get_free_tcp_port() -> int:
38 s = socket.socket()
39 s.bind(('', 0))
40 port = s.getsockname()[1]
41 s.close()
42 return port
45def get_interfaces():
46 netifaces = import_netifaces()
47 if not netifaces:
48 return []
49 return netifaces.interfaces() #@UndefinedVariable pylint: disable=no-member
51def get_interfaces_addresses() -> dict:
52 d = {}
53 netifaces = import_netifaces()
54 if netifaces:
55 for iface in get_interfaces():
56 d[iface] = netifaces.ifaddresses(iface) #@UndefinedVariable pylint: disable=no-member
57 return d
59def get_interface(address):
60 for iface, idefs in get_interfaces_addresses().items():
61 #ie: {
62 # 17: [{'broadcast': u'ff:ff:ff:ff:ff:ff', 'addr': u'00:e0:4c:68:46:a6'}],
63 # 2: [{'broadcast': u'192.168.1.255', 'netmask': u'255.255.255.0', 'addr': u'192.168.1.7'}],
64 # 10: [{'netmask': u'ffff:ffff:ffff:ffff::/64', 'addr': u'fe80::6c45:655:c59e:92a1%eth0'}]
65 #}
66 for _itype, defs in idefs.items():
67 #ie: itype=2, defs=[{'broadcast': u'192.168.1.255', 'netmask': u'255.255.255.0', 'addr': u'192.168.1.7'}]
68 for props in defs:
69 if props.get("addr")==address:
70 return iface
71 return None
73def get_gateways() -> dict:
74 netifaces = import_netifaces()
75 if not netifaces:
76 return {}
77 #versions older than 0.10.5 can crash when calling gateways()
78 #https://bitbucket.org/al45tair/netifaces/issues/15/gateways-function-crash-segmentation-fault
79 if netifaces.version<'0.10.5': #@UndefinedVariable pylint: disable=no-member
80 return {}
81 try:
82 d = netifaces.gateways() #@UndefinedVariable pylint: disable=no-member
83 AF_NAMES = {}
84 for k in dir(netifaces):
85 if k.startswith("AF_"):
86 v = getattr(netifaces, k)
87 AF_NAMES[v] = k[3:]
88 gateways = {}
89 for family, gws in d.items():
90 if family=="default":
91 continue
92 gateways[AF_NAMES.get(family, family)] = gws
93 return gateways
94 except Exception:
95 log("get_gateways() failed", exc_info=True)
96 return {}
98def get_bind_IPs():
99 global bind_IPs
100 if not bind_IPs:
101 netifaces = import_netifaces()
102 if netifaces:
103 bind_IPs = do_get_bind_IPs()
104 else:
105 bind_IPs = ["127.0.0.1"]
106 return bind_IPs
108def do_get_bind_IPs():
109 global iface_ipmasks
110 ips = []
111 netifaces = import_netifaces()
112 assert netifaces
113 ifaces = netifaces.interfaces() #@UndefinedVariable pylint: disable=no-member
114 log("ifaces=%s", ifaces)
115 for iface in ifaces:
116 if_ipmasks = []
117 try:
118 ipmasks = do_get_bind_ifacemask(iface)
119 for ipmask in ipmasks:
120 (ip,_) = ipmask
121 if ip not in ips:
122 ips.append(ip)
123 if ipmask not in if_ipmasks:
124 if_ipmasks.append(ipmask)
125 except Exception as e:
126 log("do_get_bind_IPs()", exc_info=True)
127 log.error("Error parsing network interface '%s':", iface)
128 log.error(" %s", iface, e)
129 iface_ipmasks[iface] = if_ipmasks
130 log("do_get_bind_IPs()=%s", ips)
131 log("iface_ipmasks=%s", iface_ipmasks)
132 return ips
134def do_get_bind_ifacemask(iface):
135 ipmasks = []
136 netifaces = import_netifaces()
137 assert netifaces
138 address_types = netifaces.ifaddresses(iface) #@UndefinedVariable pylint: disable=no-member
139 for addresses in address_types.values():
140 for address in addresses:
141 if 'netmask' in address and 'addr' in address:
142 addr = address['addr']
143 mask = address['netmask']
144 if addr!= '::1' and addr != '0.0.0.0' and addr.find("%")<0:
145 try:
146 socket.inet_aton(addr)
147 ipmasks.append((addr,mask))
148 except Exception as e:
149 log.error("do_get_bind_ifacemask(%s) error on %s", iface, addr, e)
150 log("do_get_bind_ifacemask(%s)=%s", iface, ipmasks)
151 return ipmasks
153def get_iface(ip) -> str:
154 log("get_iface(%s)", ip)
155 if not ip:
156 return None
157 if ip.find("%")>=0:
158 iface = ip.split("%", 1)[1]
159 if if_nametoindex:
160 try:
161 if_nametoindex(iface)
162 except OSError:
163 return None
164 else:
165 return iface
166 if ip.find(":")>=0:
167 #ipv6?
168 return None
169 if any(x for x in ip if (".:0123456789").find(x)<0):
170 #extra characters, assume this is a hostname:
171 try:
172 v = socket.getaddrinfo(ip, None)
173 assert len(v)>0
174 except Exception as e:
175 log("socket.getaddrinfo(%s, None)", ip, exc_info=True)
176 log.error("Error: cannot revolve '%s'", ip)
177 return None
178 for i, x in enumerate(v):
179 family, socktype, proto, canonname, sockaddr = x
180 log("get_iface(%s) [%i]=%s", ip, i, (family, socktype, proto, canonname, sockaddr))
181 if family==socket.AF_INET:
182 break
183 log("get_iface(%s) sockaddr=%s", ip, sockaddr)
184 ip = sockaddr[0]
186 ip_parts = ip.split(".")
187 if len(ip_parts)!=4:
188 return None
190 best_match = None
191 get_bind_IPs()
192 for (iface, ipmasks) in iface_ipmasks.items():
193 for (test_ip,mask) in ipmasks:
194 if test_ip == ip:
195 #exact match
196 log("get_iface(%s)=%s", iface, ip)
197 return iface
198 test_ip_parts = test_ip.split(".")
199 mask_parts = mask.split(".")
200 if len(test_ip_parts)!=4 or len(mask_parts)!=4:
201 log.error("incorrect ip or mask: %s/%s", test_ip, mask)
202 match = True
203 try:
204 for i in (0,1,2,3):
205 mask_part = int(mask_parts[i])
206 ip_part = int(ip_parts[i]) & mask_part
207 test_ip_part = int(test_ip_parts[i]) & mask_part
208 if ip_part!=test_ip_part:
209 match = False
210 break
211 if match:
212 best_match = iface
213 except Exception as e:
214 log.error("error parsing ip (%s) or its mask (%s): %s", test_ip, mask, e)
215 log("get_iface(%s)=%s", ip, best_match)
216 return best_match
219# Found this recipe here:
220# http://code.activestate.com/recipes/442490/
221if_nametoindex = None
222if_indextoname = None
224if WIN32: # pragma: no cover
225 def int_if_nametoindex(iface):
226 #IPv6 addresses give us the interface as a string:
227 #fe80:....%11, so try to convert "11" into 11
228 try:
229 return int(iface)
230 except (TypeError, ValueError):
231 return None
232 if_nametoindex = int_if_nametoindex
233else:
234 if_nametoindex = socket.if_nametoindex
235 def socket_if_indextoname(index):
236 if index<0:
237 return None
238 return socket.if_indextoname(index)
239 if_indextoname = socket_if_indextoname
242net_sys_config = None
243def get_net_sys_config():
244 global net_sys_config
245 if net_sys_config is None:
246 net_sys_config = {}
247 if sys.platform.startswith("linux"):
248 def stripnl(v):
249 return str(v).rstrip("\r").rstrip("\n")
250 def addproc(procpath, subsystem, name, conv=stripnl):
251 assert name
252 try:
253 with open(procpath) as f:
254 data = f.read()
255 subdict = net_sys_config.setdefault(subsystem, {})
256 if name.find("/")>0:
257 sub, name = name.split("/", 1)
258 subdict = subdict.setdefault(sub, {})
259 for sub in ("ip", "tcp", "ipfrag", "icmp", "igmp"):
260 if name.startswith("%s_" % sub):
261 name = name[len(sub)+1:]
262 subdict = subdict.setdefault(sub, {})
263 break
264 subdict[name] = conv(data)
265 except Exception as e:
266 log("cannot read '%s': %s", procpath, e)
267 for k in ("netdev_max_backlog", "optmem_max", "rmem_default", "rmem_max", "wmem_default", "wmem_max", "max_skb_frags",
268 "busy_poll", "busy_read", "somaxconn"):
269 addproc("/proc/sys/net/core/%s" % k, "core", k, int)
270 for k in ("default_qdisc", ):
271 addproc("/proc/sys/net/core/%s" % k, "core", k)
272 for k in ("max_dgram_qlen", ):
273 addproc("/proc/sys/net/unix/%s" % k, "unix", k, int)
274 for k in ("ip_forward", "ip_forward_use_pmtu", "tcp_abort_on_overflow", "fwmark_reflect", "tcp_autocorking", "tcp_dsack",
275 "tcp_ecn_fallback", "tcp_fack",
276 #"tcp_l3mdev_accept",
277 "tcp_low_latency", "tcp_no_metrics_save", "tcp_recovery", "tcp_retrans_collapse", "tcp_timestamps",
278 "tcp_workaround_signed_windows", "tcp_thin_linear_timeouts", "tcp_thin_dupack", "ip_nonlocal_bind",
279 "ip_dynaddr", "ip_early_demux", "icmp_echo_ignore_all", "icmp_echo_ignore_broadcasts",
280 ):
281 addproc("/proc/sys/net/ipv4/%s" % k, "ipv4", k, bool)
282 for k in ("tcp_allowed_congestion_control", "tcp_available_congestion_control", "tcp_congestion_control", "tcp_early_retrans",
283 "tcp_moderate_rcvbuf", "tcp_rfc1337", "tcp_sack", "tcp_slow_start_after_idle", "tcp_stdurg",
284 "tcp_syncookies", "tcp_tw_recycle", "tcp_tw_reuse", "tcp_window_scaling",
285 "icmp_ignore_bogus_error_responses", "icmp_errors_use_inbound_ifaddr"):
286 addproc("/proc/sys/net/ipv4/%s" % k, "ipv4", k)
287 def parsenums(v):
288 return tuple(int(x.strip()) for x in v.split("\t") if len(x.strip())>0)
289 for k in ("tcp_mem", "tcp_rmem", "tcp_wmem", "ip_local_port_range", "ip_local_reserved_ports", ):
290 addproc("/proc/sys/net/ipv4/%s" % k, "ipv4", k, parsenums)
291 for k in ("ip_default_ttl", "ip_no_pmtu_disc", "route/min_pmtu",
292 "route/mtu_expires", "route/min_adv_mss",
293 "ipfrag_high_thresh", "ipfrag_low_thresh", "ipfrag_time", "ipfrag_max_dist",
294 "tcp_adv_win_scale", "tcp_app_win", "tcp_base_mss", "tcp_ecn", "tcp_fin_timeout", "tcp_frto",
295 "tcp_invalid_ratelimit", "tcp_keepalive_time", "tcp_keepalive_probes", "tcp_keepalive_intvl",
296 "tcp_max_orphans", "tcp_max_syn_backlog", "tcp_max_tw_buckets",
297 "tcp_min_rtt_wlen", "tcp_mtu_probing", "tcp_probe_interval", "tcp_probe_threshold", "tcp_orphan_retries",
298 "tcp_reordering", "tcp_max_reordering", "tcp_retries1", "tcp_retries2", "tcp_synack_retries",
299 "tcp_fastopen", "tcp_syn_retries", "tcp_min_tso_segs", "tcp_pacing_ss_ratio",
300 "tcp_pacing_ca_ratio", "tcp_tso_win_divisor", "tcp_notsent_lowat",
301 "tcp_limit_output_bytes", "tcp_challenge_ack_limit",
302 "icmp_ratelimit", "icmp_msgs_per_sec", "icmp_msgs_burst", "icmp_ratemask",
303 "igmp_max_memberships", "igmp_max_msf", "igmp_qrv",
304 ):
305 addproc("/proc/sys/net/ipv4/%s" % k, "ipv4", k, int)
306 return net_sys_config
308def get_net_config() -> dict:
309 config = {}
310 try:
311 from xpra.net.bytestreams import VSOCK_TIMEOUT, SOCKET_TIMEOUT, SOCKET_NODELAY
312 config = {
313 "vsocket.timeout" : VSOCK_TIMEOUT,
314 "socket.timeout" : SOCKET_TIMEOUT,
315 }
316 if SOCKET_NODELAY is not None:
317 config["socket.nodelay"] = SOCKET_NODELAY
318 except Exception: # pragma: no cover
319 log("get_net_config()", exc_info=True)
320 return config
323def get_ssl_info(show_constants=False) -> dict:
324 try:
325 import ssl
326 except ImportError as e: # pragma: no cover
327 log("no ssl: %s", e)
328 return {}
329 info = {}
330 if show_constants:
331 protocols = dict((k,int(getattr(ssl, k))) for k in dir(ssl) if k.startswith("PROTOCOL_"))
332 ops = dict((k,int(getattr(ssl, k))) for k in dir(ssl) if k.startswith("OP_"))
333 vers = dict((k,int(getattr(ssl, k))) for k in dir(ssl) if k.startswith("VERIFY_"))
334 info.update({
335 "protocols" : protocols,
336 "options" : ops,
337 "verify" : vers,
338 })
339 for k,name in {
340 "HAS_ALPN" : "alpn",
341 "HAS_ECDH" : "ecdh",
342 "HAS_SNI" : "sni",
343 "HAS_NPN" : "npn",
344 "CHANNEL_BINDING_TYPES" : "channel-binding-types",
345 }.items():
346 v = getattr(ssl, k, None)
347 if v is not None:
348 info[name] = v
349 for k, idef in {
350 "" : ("version", str),
351 "_INFO" : ("version-info", str),
352 "_NUMBER" : ("version-number", int),
353 }.items():
354 v = getattr(ssl, "OPENSSL_VERSION%s" % k, None)
355 if v is not None:
356 name, conv = idef
357 info.setdefault("openssl", {})[name] = conv(v)
358 return info
361def get_network_caps() -> dict:
362 from xpra.net.digest import get_digests
363 from xpra.net.crypto import get_crypto_caps
364 from xpra.net.compression import get_enabled_compressors, get_compression_caps
365 from xpra.net.packet_encoding import get_enabled_encoders, get_packet_encoding_caps
366 digests = get_digests()
367 #"hmac" is the legacy name, "xor" and "des" should not be used for salt:
368 salt_digests = tuple(x for x in digests if x not in ("hmac", "xor", "des"))
369 caps = {
370 "digest" : digests,
371 "salt-digest" : salt_digests,
372 "compressors" : get_enabled_compressors(),
373 "encoders" : get_enabled_encoders(),
374 }
375 from xpra.net.common import FLUSH_HEADER
376 if FLUSH_HEADER:
377 caps["flush"] = True
378 caps.update(get_crypto_caps())
379 caps.update(get_compression_caps())
380 caps.update(get_packet_encoding_caps())
381 return caps
384def get_info() -> dict:
385 i = get_network_caps()
386 netifaces = import_netifaces()
387 if netifaces:
388 i["interfaces"] = get_interfaces()
389 i["gateways"] = get_gateways()
390 if "ssl" in sys.modules:
391 ssli = get_ssl_info()
392 ssli[""] = True
393 i["ssl"] = ssli
394 s = get_net_sys_config()
395 if s:
396 i["system"] = s
397 i["config"] = get_net_config()
398 paramiko = sys.modules.get("paramiko")
399 if paramiko:
400 i["paramiko"] = {
401 "version" : paramiko.__version_info__,
402 }
403 return i
406def main(): # pragma: no cover
407 from xpra.os_util import POSIX
408 from xpra.util import print_nested_dict, csv
409 from xpra.platform import program_context
410 from xpra.platform.netdev_query import get_interface_info
411 from xpra.log import enable_color, add_debug_category, enable_debug_for
412 with program_context("Network-Info", "Network Info"):
413 enable_color()
414 verbose = "-v" in sys.argv or "--verbose" in sys.argv
415 if verbose:
416 enable_debug_for("network")
417 add_debug_category("network")
418 log.enable_debug()
420 print("Network interfaces found:")
421 netifaces = import_netifaces()
422 for iface in get_interfaces():
423 if if_nametoindex:
424 print("* %s (index=%s)" % (iface.ljust(20), if_nametoindex(iface)))
425 else:
426 print("* %s" % iface)
427 addresses = netifaces.ifaddresses(iface) #@UndefinedVariable pylint: disable=no-member
428 for addr, defs in addresses.items():
429 if addr in (socket.AF_INET, socket.AF_INET6):
430 for d in defs:
431 ip = d.get("addr")
432 if ip:
433 stype = {
434 socket.AF_INET : "IPv4",
435 socket.AF_INET6 : "IPv6",
436 }[addr]
437 print(" * %s: %s" % (stype, ip))
438 if POSIX:
439 from xpra.net.socket_util import create_tcp_socket
440 try:
441 sock = create_tcp_socket(ip, 0)
442 sockfd = sock.fileno()
443 info = get_interface_info(sockfd, iface)
444 if info:
445 print(" %s" % info)
446 finally:
447 sock.close()
448 if not POSIX:
449 info = get_interface_info(0, iface)
450 if info:
451 print(" %s" % info)
453 from xpra.os_util import bytestostr
454 def pver(v):
455 if isinstance(v, (tuple, list)):
456 s = ""
457 lastx = None
458 for x in v:
459 if lastx is not None:
460 #dot seperated numbers
461 if isinstance(lastx, int):
462 s += "."
463 else:
464 s += ", "
465 s += bytestostr(x)
466 lastx = x
467 return s
468 if isinstance(v, bytes):
469 v = bytestostr(v)
470 if isinstance(v, str) and v.startswith("v"):
471 return v[1:]
472 return str(v)
474 print("Gateways found:")
475 for gt,idefs in get_gateways().items():
476 print("* %s" % gt) #ie: "INET"
477 for i, idef in enumerate(idefs):
478 if isinstance(idef, (list, tuple)):
479 print(" [%i] %s" % (i, csv(idef)))
480 continue
482 print("")
483 print("Protocol Capabilities:")
484 netcaps = get_network_caps()
485 netif = {"" : bool(netifaces)}
486 if netifaces_version:
487 netif["version"] = netifaces_version
488 netcaps["netifaces"] = netif
489 print_nested_dict(netcaps, vformat=pver)
491 print("")
492 print("Network Config:")
493 print_nested_dict(get_net_config())
495 net_sys = get_net_sys_config()
496 if net_sys:
497 print("")
498 print("Network System Config:")
499 print_nested_dict(net_sys)
501 print("")
502 print("SSL:")
503 print_nested_dict(get_ssl_info(True))
505 try:
506 from xpra.net.crypto import crypto_backend_init, get_crypto_caps
507 crypto_backend_init()
508 ccaps = get_crypto_caps()
509 if ccaps:
510 print("")
511 print("Crypto Capabilities:")
512 print_nested_dict(ccaps)
513 except Exception as e:
514 print("No Crypto:")
515 print(" %s" % e)
516 return 0
519if __name__ == "__main__": # pragma: no cover
520 main()