diff --git a/oml/localnodes.py b/oml/localnodes.py index a17a903..e2af640 100644 --- a/oml/localnodes.py +++ b/oml/localnodes.py @@ -2,10 +2,9 @@ # vi:si:et:sw=4:sts=4:ts=4 import socket -import time +import netifaces from zeroconf import ( - get_all_addresses, ServiceBrowser, ServiceInfo, ServiceStateChange, Zeroconf ) from tornado.ioloop import PeriodicCallback @@ -13,7 +12,6 @@ from tornado.ioloop import PeriodicCallback import settings import state from tor_request import get_opener -from utils import get_local_ipv4 import logging logger = logging.getLogger(__name__) @@ -45,6 +43,14 @@ def can_connect(data): #logger.debug('failed to connect to local node %s', data, exc_info=True) return False +def get_broadcast_interfaces(): + return list(set( + addr['addr'] + for iface in netifaces.interfaces() + for addr in netifaces.ifaddresses(iface).get(socket.AF_INET, []) + if addr.get('netmask') != '255.255.255.255' and addr.get('broadcast') + )) + class LocalNodes(dict): service_type = '_oml._tcp.local.' local_info = None @@ -57,57 +63,71 @@ class LocalNodes(dict): self._ip_changed = PeriodicCallback(self._update_if_ip_changed, 60000) def setup(self): - self.local_ips = sorted(get_all_addresses(socket.AF_INET)) - self.zeroconf = Zeroconf() - time.sleep(0.02) + self.local_ips = get_broadcast_interfaces() + self.zeroconf = {ip: Zeroconf(interfaces=[ip]) for ip in self.local_ips} self.register_service() self.browse() def _update_if_ip_changed(self): - local_ips = sorted(get_all_addresses(socket.AF_INET)) + local_ips = get_broadcast_interfaces() if local_ips != self.local_ips: self.close() self.setup() def browse(self): - self.browser = ServiceBrowser(self.zeroconf, self.service_type, handlers=[self.on_service_state_change]) + self.browser = { + ip: ServiceBrowser(self.zeroconf[ip], self.service_type, handlers=[self.on_service_state_change]) + for ip in self.zeroconf + } def register_service(self): if self.local_info: - self.zeroconf.unregister_service(self.local_info) + for local_ip, local_info in self.local_info: + self.zeroconf[local_ip].unregister_service(local_info) self.local_info = None - local_ip = get_local_ipv4() - if local_ip: - local_name = socket.gethostname().partition('.')[0] + '.local.' - port = settings.server['node_port'] - desc = { - 'username': settings.preferences.get('username', 'anonymous'), - } - self.local_info = ServiceInfo(self.service_type, - '%s.%s' % (settings.USER_ID, self.service_type), - socket.inet_aton(local_ip), port, 0, 0, - desc, local_name) - self.zeroconf.register_service(self.local_info) + local_name = socket.gethostname().partition('.')[0] + '.local.' + port = settings.server['node_port'] + desc = { + 'username': settings.preferences.get('username', 'anonymous') + } + self.local_info = [] + for i, local_ip in enumerate(get_broadcast_interfaces()): + if i: + name = '%s-%s [%s].%s' % (desc['username'], i, settings.USER_ID, self.service_type) + else: + name = '%s [%s].%s' % (desc['username'], settings.USER_ID, self.service_type) + local_info = ServiceInfo(self.service_type, name, + socket.inet_aton(local_ip), port, 0, 0, desc, local_name) + self.zeroconf[local_ip].register_service(local_info) + self.local_info.append((local_ip, local_info)) def __del__(self): self.close() def close(self): if self.local_info: - self.zeroconf.unregister_service(self.local_info) + for local_ip, local_info in self.local_info: + try: + self.zeroconf[local_ip].unregister_service(local_info) + except: + logger.debug('exception closing zeroconf', exc_info=True) self.local_info = None if self.zeroconf: - try: - self.zeroconf.close() - except: - logger.debug('exception closing zeroconf', exc_info=True) + for local_ip in self.zeroconf: + try: + self.zeroconf[local_ip].close() + except: + logger.debug('exception closing zeroconf', exc_info=True) self.zeroconf = None for id in list(self): self.pop(id, None) def on_service_state_change(self, zeroconf, service_type, name, state_change): - id = name.split('.')[0] + if not '[' in name: + id = name.split('.')[0] + else: + id = name.split('[')[1].split(']')[0] if id == settings.USER_ID: return if state_change is ServiceStateChange.Added: diff --git a/oml/utils.py b/oml/utils.py index a81cf7c..fbb8eb0 100644 --- a/oml/utils.py +++ b/oml/utils.py @@ -218,58 +218,6 @@ def get_interface(): interface = '' return interface -def get_local_ipv4(): - ip = None - if sys.platform == 'darwin' or sys.platform.startswith('freebsd'): - cmd = ['/sbin/route', '-n', 'get', 'default'] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=True) - stdout, stderr = p.communicate() - stdout = stdout.decode('utf-8') - interface = [[p.strip() for p in s.split(':', 1)] - for s in stdout.strip().split('\n') if 'interface' in s] - if interface: - interface = interface[0][1] - cmd = ['ifconfig', interface] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=True) - stdout, stderr = p.communicate() - stdout = stdout.decode('utf-8') - ips = [l for l in stdout.split('\n') if 'inet ' in l] - if ips: - ip = ips[0].strip().split(' ')[1] - elif sys.platform.startswith('linux'): - cmd = ['ip', 'route', 'show'] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=True) - stdout, stderr = p.communicate() - stdout = stdout.decode('utf-8') - local = [l for l in stdout.split('\n') if 'default' in l] - if local: - dev = local[0].split(' ')[4] - local_ip = [l for l in stdout.split('\n') - if dev in l and not 'default' in l and 'src' in l] - if local_ip: - local_ip = [p for p in local_ip[0].split(' ')[1:] if '.' in p] - if local_ip: - ip = local_ip[0] - if not ip: - cmd = ['ip', 'addr', 'show'] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=True) - stdout, stderr = p.communicate() - stdout = stdout.decode('utf-8') - parts = stdout.split(' ') - local_ip = [p for p in parts if dev in p] - if local_ip: - local_ip = re.compile('inet (\d+\.\d+\.\d+.\d+)').findall(local_ip[0]) - if local_ip: - ip = local_ip[0] - if not ip: - try: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.connect(("8.8.8.8", 53)) - return s.getsockname()[0] - except: - pass - return ip - def update_dict(root, data): for key in data: keys = [part.replace('\0', '.') for part in key.replace('\\.', '\0').split('.')]