localinterfaces.py
278 lines
| 8.2 KiB
| text/x-python
|
PythonLexer
MinRK
|
r3143 | """Simple utility for building a list of local IPs using the socket module. | ||
This module defines two constants: | ||||
Bernardo B. Marques
|
r4872 | LOCALHOST : The loopback interface, or the first interface that points to this | ||
MinRK
|
r3143 | machine. It will *almost* always be '127.0.0.1' | ||
LOCAL_IPS : A list of IP addresses, loopback first, that point to this machine. | ||||
MinRK
|
r12830 | This will include LOCALHOST, PUBLIC_IPS, and aliases for all hosts, | ||
such as '0.0.0.0'. | ||||
W. Trevor King
|
r9250 | |||
PUBLIC_IPS : A list of public IP addresses that point to this machine. | ||||
Use these to tell remote clients where to find you. | ||||
MinRK
|
r3143 | """ | ||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r12832 | # Copyright (C) 2010 The IPython Development Team | ||
MinRK
|
r3143 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r12832 | import os | ||
MinRK
|
r13613 | import re | ||
MinRK
|
r3143 | import socket | ||
W. Trevor King
|
r9248 | from .data import uniq_stable | ||
MinRK
|
r12832 | from .process import get_output_error_code | ||
from .warn import warn | ||||
W. Trevor King
|
r9248 | |||
MinRK
|
r3143 | #----------------------------------------------------------------------------- | ||
# Code | ||||
#----------------------------------------------------------------------------- | ||||
LOCAL_IPS = [] | ||||
W. Trevor King
|
r9250 | PUBLIC_IPS = [] | ||
MinRK
|
r12591 | |||
MinRK
|
r12830 | LOCALHOST = '' | ||
MinRK
|
r12591 | |||
def _only_once(f): | ||||
"""decorator to only run a function once""" | ||||
f.called = False | ||||
Thomas Kluyver
|
r12960 | def wrapped(**kwargs): | ||
MinRK
|
r12591 | if f.called: | ||
return | ||||
Thomas Kluyver
|
r12960 | ret = f(**kwargs) | ||
MinRK
|
r12591 | f.called = True | ||
return ret | ||||
return wrapped | ||||
def _requires_ips(f): | ||||
"""decorator to ensure load_ips has been run before f""" | ||||
def ips_loaded(*args, **kwargs): | ||||
_load_ips() | ||||
return f(*args, **kwargs) | ||||
return ips_loaded | ||||
MinRK
|
r12832 | # subprocess-parsing ip finders | ||
class NoIPAddresses(Exception): | ||||
pass | ||||
def _populate_from_list(addrs): | ||||
"""populate local and public IPs from flat list of all IPs""" | ||||
if not addrs: | ||||
raise NoIPAddresses | ||||
global LOCALHOST | ||||
public_ips = [] | ||||
local_ips = [] | ||||
for ip in addrs: | ||||
local_ips.append(ip) | ||||
if not ip.startswith('127.'): | ||||
public_ips.append(ip) | ||||
elif not LOCALHOST: | ||||
LOCALHOST = ip | ||||
if not LOCALHOST: | ||||
LOCALHOST = '127.0.0.1' | ||||
local_ips.insert(0, LOCALHOST) | ||||
local_ips.extend(['0.0.0.0', '']) | ||||
LOCAL_IPS[:] = uniq_stable(local_ips) | ||||
PUBLIC_IPS[:] = uniq_stable(public_ips) | ||||
def _load_ips_ifconfig(): | ||||
"""load ip addresses from `ifconfig` output (posix)""" | ||||
out, err, rc = get_output_error_code('ifconfig') | ||||
if rc: | ||||
# no ifconfig, it's usually in /sbin and /sbin is not on everyone's PATH | ||||
out, err, rc = get_output_error_code('/sbin/ifconfig') | ||||
if rc: | ||||
raise IOError("no ifconfig: %s" % err) | ||||
Thomas Kluyver
|
r12961 | lines = out.splitlines() | ||
MinRK
|
r12832 | addrs = [] | ||
for line in lines: | ||||
blocks = line.lower().split() | ||||
Thomas Kluyver
|
r12961 | if (len(blocks) >= 2) and (blocks[0] == 'inet'): | ||
James Porter
|
r13986 | if blocks[1].startswith("addr:"): | ||
James Porter
|
r13982 | addrs.append(blocks[1].split(":")[1]) | ||
else: | ||||
addrs.append(blocks[1]) | ||||
MinRK
|
r12832 | _populate_from_list(addrs) | ||
def _load_ips_ip(): | ||||
"""load ip addresses from `ip addr` output (Linux)""" | ||||
out, err, rc = get_output_error_code('ip addr') | ||||
if rc: | ||||
raise IOError("no ip: %s" % err) | ||||
Thomas Kluyver
|
r12961 | lines = out.splitlines() | ||
MinRK
|
r12832 | addrs = [] | ||
for line in lines: | ||||
blocks = line.lower().split() | ||||
Thomas Kluyver
|
r12961 | if (len(blocks) >= 2) and (blocks[0] == 'inet'): | ||
MinRK
|
r12832 | addrs.append(blocks[1].split('/')[0]) | ||
_populate_from_list(addrs) | ||||
MinRK
|
r16307 | _ipconfig_ipv4_pat = re.compile(r'ipv4.*?(\d+\.\d+\.\d+\.\d+)$', re.IGNORECASE) | ||
MinRK
|
r12832 | |||
def _load_ips_ipconfig(): | ||||
"""load ip addresses from `ipconfig` output (Windows)""" | ||||
out, err, rc = get_output_error_code('ipconfig') | ||||
if rc: | ||||
raise IOError("no ipconfig: %s" % err) | ||||
Thomas Kluyver
|
r12961 | lines = out.splitlines() | ||
MinRK
|
r13614 | addrs = [] | ||
MinRK
|
r12832 | for line in lines: | ||
MinRK
|
r13613 | m = _ipconfig_ipv4_pat.match(line.strip()) | ||
if m: | ||||
addrs.append(m.group(1)) | ||||
MinRK
|
r12832 | _populate_from_list(addrs) | ||
MinRK
|
r12830 | def _load_ips_netifaces(): | ||
"""load ip addresses with netifaces""" | ||||
import netifaces | ||||
global LOCALHOST | ||||
local_ips = [] | ||||
public_ips = [] | ||||
MinRK
|
r12591 | |||
MinRK
|
r12830 | # list of iface names, 'lo0', 'eth0', etc. | ||
for iface in netifaces.interfaces(): | ||||
# list of ipv4 addrinfo dicts | ||||
ipv4s = netifaces.ifaddresses(iface).get(netifaces.AF_INET, []) | ||||
for entry in ipv4s: | ||||
addr = entry.get('addr') | ||||
if not addr: | ||||
continue | ||||
if not (iface.startswith('lo') or addr.startswith('127.')): | ||||
public_ips.append(addr) | ||||
elif not LOCALHOST: | ||||
LOCALHOST = addr | ||||
local_ips.append(addr) | ||||
if not LOCALHOST: | ||||
# we never found a loopback interface (can this ever happen?), assume common default | ||||
LOCALHOST = '127.0.0.1' | ||||
local_ips.insert(0, LOCALHOST) | ||||
local_ips.extend(['0.0.0.0', '']) | ||||
LOCAL_IPS[:] = uniq_stable(local_ips) | ||||
PUBLIC_IPS[:] = uniq_stable(public_ips) | ||||
MinRK
|
r12832 | |||
MinRK
|
r12830 | def _load_ips_gethostbyname(): | ||
"""load ip addresses with socket.gethostbyname_ex | ||||
This can be slow. | ||||
MinRK
|
r12591 | """ | ||
global LOCALHOST | ||||
try: | ||||
LOCAL_IPS[:] = socket.gethostbyname_ex('localhost')[2] | ||||
except socket.error: | ||||
MinRK
|
r12830 | # assume common default | ||
LOCAL_IPS[:] = ['127.0.0.1'] | ||||
MinRK
|
r12591 | |||
try: | ||||
hostname = socket.gethostname() | ||||
PUBLIC_IPS[:] = socket.gethostbyname_ex(hostname)[2] | ||||
# try hostname.local, in case hostname has been short-circuited to loopback | ||||
if not hostname.endswith('.local') and all(ip.startswith('127') for ip in PUBLIC_IPS): | ||||
PUBLIC_IPS[:] = socket.gethostbyname_ex(socket.gethostname() + '.local')[2] | ||||
except socket.error: | ||||
pass | ||||
finally: | ||||
PUBLIC_IPS[:] = uniq_stable(PUBLIC_IPS) | ||||
LOCAL_IPS.extend(PUBLIC_IPS) | ||||
MinRK
|
r12832 | |||
MinRK
|
r12591 | # include all-interface aliases: 0.0.0.0 and '' | ||
LOCAL_IPS.extend(['0.0.0.0', '']) | ||||
LOCAL_IPS[:] = uniq_stable(LOCAL_IPS) | ||||
LOCALHOST = LOCAL_IPS[0] | ||||
MinRK
|
r12830 | def _load_ips_dumb(): | ||
"""Fallback in case of unexpected failure""" | ||||
global LOCALHOST | ||||
LOCALHOST = '127.0.0.1' | ||||
LOCAL_IPS[:] = [LOCALHOST, '0.0.0.0', ''] | ||||
PUBLIC_IPS[:] = [] | ||||
@_only_once | ||||
Thomas Kluyver
|
r12960 | def _load_ips(suppress_exceptions=True): | ||
MinRK
|
r12830 | """load the IPs that point to this machine | ||
This function will only ever be called once. | ||||
MinRK
|
r12832 | It will use netifaces to do it quickly if available. | ||
Then it will fallback on parsing the output of ifconfig / ip addr / ipconfig, as appropriate. | ||||
Finally, it will fallback on socket.gethostbyname_ex, which can be slow. | ||||
MinRK
|
r12830 | """ | ||
MinRK
|
r12832 | |||
MinRK
|
r12830 | try: | ||
MinRK
|
r12832 | # first priority, use netifaces | ||
MinRK
|
r12830 | try: | ||
MinRK
|
r12832 | return _load_ips_netifaces() | ||
MinRK
|
r12830 | except ImportError: | ||
MinRK
|
r12832 | pass | ||
# second priority, parse subprocess output (how reliable is this?) | ||||
if os.name == 'nt': | ||||
try: | ||||
return _load_ips_ipconfig() | ||||
except (IOError, NoIPAddresses): | ||||
pass | ||||
else: | ||||
try: | ||||
return _load_ips_ifconfig() | ||||
except (IOError, NoIPAddresses): | ||||
pass | ||||
try: | ||||
return _load_ips_ip() | ||||
except (IOError, NoIPAddresses): | ||||
pass | ||||
# lowest priority, use gethostbyname | ||||
return _load_ips_gethostbyname() | ||||
except Exception as e: | ||||
Thomas Kluyver
|
r12960 | if not suppress_exceptions: | ||
raise | ||||
MinRK
|
r12830 | # unexpected error shouldn't crash, load dumb default values instead. | ||
MinRK
|
r12832 | warn("Unexpected error discovering local network interfaces: %s" % e) | ||
_load_ips_dumb() | ||||
MinRK
|
r12830 | |||
MinRK
|
r12591 | @_requires_ips | ||
def local_ips(): | ||||
"""return the IP addresses that point to this machine""" | ||||
return LOCAL_IPS | ||||
@_requires_ips | ||||
def public_ips(): | ||||
"""return the IP addresses for this machine that are visible to other machines""" | ||||
return PUBLIC_IPS | ||||
@_requires_ips | ||||
def localhost(): | ||||
"""return ip for localhost (almost always 127.0.0.1)""" | ||||
return LOCALHOST | ||||
@_requires_ips | ||||
def is_local_ip(ip): | ||||
"""does `ip` point to this machine?""" | ||||
return ip in LOCAL_IPS | ||||
@_requires_ips | ||||
def is_public_ip(ip): | ||||
"""is `ip` a publicly visible address?""" | ||||
return ip in PUBLIC_IPS | ||||