def admin_login(request): context = {} post_dict = request.POST.dict() if not is_registered(): dendrite = Dendrite() try: dendrite.call('register', post_dict) except RequestTimeout: context.update( form_errors={'non_field_errors': [_('Request timeout')]}) except RequestError as e: if e.errors: context.update(form_errors=e.errors) else: context.update(form_errors={'non_field_errors': [e.error_str]}) else: # Registration succeeded -> redirect to same to avoid repost admin_session_login(request.session, post_dict['login']) return redirect('dashboard') else: # Authenticate admin login = post_dict.get('login', None) if login: admin = Administrator.get(login=login) if admin and admin.check_password( request.POST.get('password', None)): admin_session_login(request.session, login) return redirect('dashboard') context['form_errors'] = [_('Invalid Credentials')] context.update(**post_dict) return dashboard(request, context)
async def poll(self, ip, timeout=10): ''' poll and cache result''' try: device_snmp = await asyncio.wait_for(self._poll(ip), timeout) except asyncio.TimeoutError: device_snmp = None if device_snmp is None: event = Event('runtime-failure', source='snmp', level='warning') event.add_data('ip', ip) event.notify() return # Try to find a cached device device_id = self.get_id_by_ip(ip) if not device_id and 'ports' in device_snmp: # Try to find cached device by mac for port in device_snmp['ports']: device_id = self.get_id_by_mac(port['mac']) if device_id: break if device_id: cached_device = self.get_device_by_id(device_id) else: device_id = self.get_new_device_id() cached_device = None device_snmp['local_id'] = device_id # Update cached device if needed if cached_device != device_snmp: if cached_device is None or self.switch_has_changed(cached_device, device_snmp): # notify if has changed. Only send relevant keys Dendrite.publish_single('snmp', { k:v for k, v in device_snmp.items() if k not in IGNORE_SWITCH_KEYS }) # cache the device, including dynamic fields like fw_mac with self.synapse.pipeline() as pipe: pipe.hset(self.DEVICE_SNMP_CACHE_PATH, device_id, device_snmp) pipe.hset(self.DEVICE_IP_SNMP_CACHE_PATH, ip, device_id) cached_macs = set() if cached_device: for port in cached_device['ports']: if port['mac']: cached_macs.add(port['mac']) device_macs = set() for port in device_snmp['ports']: if port['mac']: device_macs.add(port['mac']) for mac in cached_macs - device_macs: # macs not longer valid pipe.hdel(self.DEVICE_MAC_SNMP_CACHE_PATH, mac) for mac in device_macs - cached_macs: # new macs pipe.hset(self.DEVICE_MAC_SNMP_CACHE_PATH, mac, device_id) pipe.execute() return device_snmp
def test_get(self): dummy = str(uuid4()) self.dendrite.publish('test/get', dummy, retain=True) result = self.dendrite.get('test/get') self.assertEqual(result, dummy) Dendrite.publish_single('test/get', retain=True) time.sleep(1) with self.assertRaises(RequestTimeout): self.dendrite.get('test/get')
def test_notify_with_Dendrite(self): Dendrite = MagicMock() event = Event(event_type='test_event', source='test', dendrite=Dendrite()) event.notify() self.assertEqual(event.dendrite.publish.call_count, 1)
def test_get_from_cb(self): get_result = [] dummy = str(uuid4()) def cb(data): self.dendrite.publish('test/get', dummy, retain=True) get_result.append(self.dendrite.get('test/get')) return 'Test OK: ' + data service = 'test/fct' self.dendrite.provide('test/fct', cb) params = str(uuid4()) call_result = self.dendrite.call(service, params, timeout=5) self.assertEqual(call_result, 'Test OK: ' + params) self.assertEqual(get_result[0], dummy) Dendrite.publish_single('test/get', retain=True)
def __init__(self, dendrite=None): if dendrite is None: dendrite = Dendrite() self.dendrite = dendrite self.authentications = None self.provided_services = set() self.policy_template = Template( filename="/elan-agent/authentication/freeradius/policy") self.ldap_template = Template( filename="/elan-agent/authentication/freeradius/ldap-module") self.ad_template = Template( filename="/elan-agent/authentication/freeradius/ad-module") self.external_auth_template = Template(''' update session-state { &ELAN-Auth-Provider := ${id} } external-auth { invalid = 1 fail = 2 reject = 3 notfound = 4 ok = return updated = return } ''') self.ldap_auth_template = Template(''' update session-state { &ELAN-Auth-Provider := ${id} } ldap-auth-${id} { invalid = 1 fail = 2 reject = 3 notfound = 4 ok = return updated = return } ''') self.ad_auth_template = Template(''' update session-state { &ELAN-Auth-Provider := ${id} &ELAN-AD-Auth-Provider := ${id} } ADldap { invalid = 1 fail = 2 reject = 3 notfound = 4 ok = return updated = return } ''')
def notify(self): data = { 'type': self.type, 'source': self.source, 'level': self.level, 'timestamp': datetime.datetime.utcfromtimestamp(self.timestamp).strftime('%Y-%m-%dT%H:%M:%SZ'), 'data': self.data, } if self.dendrite is None: return Dendrite.publish_single(self.EVENT_TOPIC, data) else: return self.dendrite.publish(self.EVENT_TOPIC, data)
def __init__(self, dendrite=None): if dendrite is None: self.dendrite = Dendrite() self.fw_mac_allowed_vlans = {} self.fw_mac_bridged_vlans = {} self.dendrite = dendrite self.synapse = Synapse() self.next_check = None self.check_authz_sema = threading.BoundedSemaphore() self.check_expired_authz() self.init_macs()
def __init__(self, dendrite=None, directory=DEFAULT_DIRECTORY, prefix=DEFAULT_PREFIX, delete_files=True, loop=None): if dendrite is None: dendrite = Dendrite() if loop is None: loop = asyncio.get_event_loop() self.dendrite = dendrite self.directory = directory self.prefix = prefix self.delete_files = delete_files self.loop = loop
class NetworkConfiguration: 'Class to manipulate IP configuration and retrieve current status' dendrite = Dendrite() def get_current_ips(self, cidr=True): return self.get_current_ipv4(cidr=cidr)['ips'] + self.get_current_ipv6( cidr=cidr)['ips'] def get_current_ipv4(self, cidr=True): current_ipv4 = self.dendrite.get_conf(IPv4_CURRENT_TOPIC) or { 'ips': [], 'gw': None, 'dns': [] } if not cidr: current_ipv4['ips'] = [ ip.split('/')[0] for ip in current_ipv4['ips'] ] return current_ipv4 def get_ipv4_conf(self): return self.dendrite.get_conf(IPv4_CONF_TOPIC) or DEFAULT_IPv4_CONF def set_ipv4_conf(self, conf): self.dendrite.publish_conf(IPv4_CONF_TOPIC, conf) def get_current_ipv6(self, cidr=True): current_ipv6 = self.dendrite.get_conf(IPv6_CURRENT_TOPIC) or { 'ips': [], 'gw': None, 'dns': [] } if not cidr: current_ipv6['ips'] = [ ip.split('/')[0] for ip in current_ipv6['ips'] ] return current_ipv6 def get_ipv6_conf(self): return self.dendrite.get_conf(IPv6_CONF_TOPIC) or DEFAULT_IPv6_CONF def set_ipv6_conf(self, conf): self.dendrite.publish_conf(IPv6_CONF_TOPIC, conf)
#!/usr/bin/env python3 from elan.authentication import AuthenticationProvider from elan.neuron import Dendrite if __name__ == "__main__": dendrite = Dendrite() provider = AuthenticationProvider(dendrite=dendrite) dendrite.subscribe_conf('authentication', cb=provider.new_authentication_conf) dendrite.wait_complete()
pipe.set(VLAN_LOCAL_INDEX_INDEX_PATH, index) pipe.hset(VLAN_LOCAL_INDEX_PATH, nic, index) synapse.transaction(safe_index, VLAN_LOCAL_INDEX_INDEX_PATH) index = synapse.hget(VLAN_LOCAL_INDEX_PATH, nic) return index if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--default-conf-only", help="just generate default configuration if needed", action="store_true") args = parser.parse_args() dendrite = Dendrite() try: vlans = dendrite.get_conf('vlans', timeout=5) except ConnectionFailed: vlans = None if vlans is None: # Default vlan conf: first 2 interfaces bridged default_vlans = [] count = 0 for interface in physical_ifaces(): count += 1 default_vlans.append({'interface': interface}) if count == 2: break
class AxonMapper: def __init__(self): self.dendrite = Dendrite() def run(self): configure_axon() self.dendrite.provide('register', self.register) self.dendrite.provide('check-connectivity', self.check_connectivity) self.dendrite.provide('guest-request', self.guest_request) self.dendrite.subscribe('notify-knowledge', self.notify_knowledge) self.dendrite.wait_complete() def notify_knowledge(self, info): if 'sessions' in info: session.notify_current_sessions() if 'hostnames' in info: device.notify_known_hostnames() if 'fingerprints' in info: device.notify_known_fingerprints() def guest_request(self, request): response = self.dendrite.call('elan-center/guest-request', request) if response['sponsor_email'] or response['fixed_recipients']: # send mail lookup = TemplateLookup(['/elan-agent/elan-center', '.']) html_template = Template( filename='/elan-agent/elan-center/guest-request-email.html', lookup=lookup) text_template = Template( filename='/elan-agent/elan-center/guest-request-email.txt', lookup=lookup) html = html_template.render(**response) text = text_template.render(**response) if not response['sponsor_email']: recipients = response['fixed_recipients'] bcc_recipients = [] else: recipients = [response['sponsor_email']] bcc_recipients = response['fixed_recipients'] send_mail(recipients=recipients, bcc_recipients=bcc_recipients, html=html, text=text, mail_subject='Guest Request for Network Access') return response def check_connectivity(self, data=None): # check elan-center connectivity try: connected = bool( int( self.dendrite.get( '$SYS/broker/connection/{uuid}/state'.format( uuid=synapse.get(AGENT_UUID_PATH))))) except RequestTimeout: connected = False if connected: return {'status': 'connected'} raise RequestError('Connection to ELAN Center down') def register(self, data): # settings.configure must have been called before this imort from django.contrib.auth.hashers import make_password # check elan-center connectivity self.check_connectivity() if not data: return {'status': 'available'} data['interfaces'] = sorted(utils.physical_ifaces()) result = self.dendrite.call( 'elan-center/register', data) # raises RequestError if registration fails # store this admin in conf (should be overridien once Axon correctly configured) self.dendrite.publish_conf('administrator', [ dict(login=data['login'], password=make_password(data['password'])) ]) synapse.set(ACCOUNT_ID_PATH, result['account']) synapse.set(AGENT_ID_PATH, result['id']) synapse.set(AGENT_UUID_PATH, result['uuid']) # delay configuration so that caller has time to receive response... def delayed_action(): configure_axon() # wait axon really started. time.sleep(5.0) session.notify_current_sessions() device.notify_known_hostnames() device.notify_known_fingerprints() t = threading.Timer(3.0, delayed_action) t.start() return {'status': 'registered'}
def __init__(self): self.dendrite = Dendrite()
#!/usr/bin/env python3 from elan.captive_portal import GuestAccessManager from elan.neuron import Dendrite if __name__ == "__main__": dendrite = Dendrite() manager = GuestAccessManager() dendrite.subscribe_conf('guest-access/active-authorizations', manager.new_authorizations) dendrite.wait_complete()
#!/usr/bin/env python3 import logging from elan.network import NetworkConfigurator from elan.neuron import Dendrite logging.basicConfig() if __name__ == "__main__": dendrite = Dendrite() configurator = NetworkConfigurator() dendrite.subscribe_conf('ipv4', configurator.set_ipv4) dendrite.subscribe_conf('ipv6', configurator.set_ipv6) dendrite.wait_complete()
LAST_SEEN_PATH = 'device:macs:last_seen' SESSION_IDS_PATH = 'device:mac:session-ids' SESSION_IDS_SEQUENCE_PATH = 'device:mac:session-ids:sequence' MAC_PORT_PATH = 'device:mac:port' MAC_LAST_PORT_PATH = 'device:mac:last_port' PORT_MACS_PATH = 'device:{local_id}:port:{interface}' MAC_VLANS_PATH = 'device:mac:{mac}:vlans' MAC_VLAN_IPS_PATH = 'device:mac:{mac}:vlan:{vlan}:ips' MAC_AUTH_SESSION_PATH = 'device:mac:{mac}:authentication' synapse = Synapse() dendrite = Dendrite() def format_date(date): if not date: return datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') else: return datetime.datetime.utcfromtimestamp(date).strftime( '%Y-%m-%dT%H:%M:%SZ') def is_online(mac, vlan=None, ip=None): ''' returns True if Mac (optionnaly, on VLAN, with IP) is connected ''' data = dict(mac=mac) if vlan is not None:
class DendriteTest(unittest.TestCase): 'These tests require a MQTT broker' def setUp(self): self.dendrite = Dendrite() self.mqtt = client.Client() self.mqtt.connect(Dendrite.MQTT_HOST, Dendrite.MQTT_PORT) def tearDown(self): self.dendrite.finish() self.mqtt.disconnect() def test_publish(self): topic1 = 'test/topic1' topic2 = 'test/topic2' msg1 = dict(test='OK') self.mqtt.subscribe(topic1) self.mqtt.subscribe(topic2) future = concurrent.futures.Future() def on_message(client, userdata, message): future.set_result(message) self.mqtt.on_message = on_message self.mqtt.loop_start() time.sleep(1) self.dendrite.publish(topic1, msg1) msg = future.result(2) self.assertEqual(json.loads(msg.payload.decode()), msg1) self.assertEqual(msg.topic, topic1) msg2 = dict(test=dict(OK='ok'), OK='ok') results = [] future = concurrent.futures.Future() def on_message2(client, userdata, message): results.append(message) if len(results) == 3: future.set_result(True) self.mqtt.on_message = on_message2 self.dendrite.publish(topic2, msg1) self.dendrite.publish(topic2, msg2) self.dendrite.publish(topic1, msg2) future.result(2) self.assertEqual(json.loads(results[0].payload.decode()), msg1) self.assertEqual(results[0].topic, topic2) self.assertEqual(json.loads(results[1].payload.decode()), msg2) self.assertEqual(results[1].topic, topic2) self.assertEqual(json.loads(results[2].payload.decode()), msg2) self.assertEqual(results[2].topic, topic1) def test_subscribe(self): topic1 = 'test/topic1' msg1 = dict(test='OK', msg=1) future = concurrent.futures.Future() def get_msg(msg, topic): future.set_result({ 'msg': msg, 'topic': topic}) self.dendrite.subscribe(topic1, get_msg) time.sleep(1) self.mqtt.publish(topic1, json.dumps(msg1)) result = future.result(2) self.assertEqual(result, {'msg': msg1, 'topic': topic1}) def test_subscribe_1arg(self): topic1 = 'test/topic1' msg1 = dict(test='OK', msg=1) future = concurrent.futures.Future() def get_msg(msg): future.set_result(msg) self.dendrite.subscribe(topic1, get_msg) time.sleep(1) self.mqtt.publish(topic1, json.dumps(msg1)) result = future.result(2) self.assertEqual(result, msg1) def test_subscribe_1arg_method(self): topic1 = 'test/topic1' msg1 = dict(test='OK', msg=1) future = concurrent.futures.Future() class Dummy: def get_msg(self, msg): future.set_result(msg) d = Dummy() self.dendrite.subscribe(topic1, d.get_msg) time.sleep(1) self.mqtt.publish(topic1, json.dumps(msg1)) result = future.result(2) self.assertEqual(result, msg1) def test_call_and_provide(self): def cb(data): return 'Test OK: ' + data service = 'test/fct' self.dendrite.provide('test/fct', cb) params = str(uuid4()) result = self.dendrite.call(service, params) self.assertEqual(result, 'Test OK: ' + params) def test_call_and_provide_exception(self): def cb(data): raise RequestError({'something': 'happened'}, 'Error: Something Happened !') service = 'test/fct2' self.dendrite.provide('test/fct2', cb) with self.assertRaises(RequestError) as cm: self.dendrite.call(service, {}) self.assertEqual(cm.exception.error_str, 'Error: Something Happened !') self.assertEqual(cm.exception.errors, {'something': 'happened'}) def test_get(self): dummy = str(uuid4()) self.dendrite.publish('test/get', dummy, retain=True) result = self.dendrite.get('test/get') self.assertEqual(result, dummy) Dendrite.publish_single('test/get', retain=True) time.sleep(1) with self.assertRaises(RequestTimeout): self.dendrite.get('test/get') def test_get_from_cb(self): get_result = [] dummy = str(uuid4()) def cb(data): self.dendrite.publish('test/get', dummy, retain=True) get_result.append(self.dendrite.get('test/get')) return 'Test OK: ' + data service = 'test/fct' self.dendrite.provide('test/fct', cb) params = str(uuid4()) call_result = self.dendrite.call(service, params, timeout=5) self.assertEqual(call_result, 'Test OK: ' + params) self.assertEqual(get_result[0], dummy) Dendrite.publish_single('test/get', retain=True) def test_subscribe_cb_exception_catch(self): event_instance = Mock() with patch('elan.event.ExceptionEvent', return_value=event_instance) as ExceptionEventMock: topic1 = 'test/topic1' msg1 = dict(test='OK', msg=1) future = concurrent.futures.Future() def get_msg(msg, topic): future.set_result({ 'msg': msg, 'topic': topic}) raise KeyError("foo") self.dendrite.subscribe(topic1, get_msg) time.sleep(1) self.mqtt.publish(topic1, json.dumps(msg1)) result = future.result(2) time.sleep(1) ExceptionEventMock.assert_called_with(source='dendrite-subscribe-cb') event_instance.notify.assert_called_once_with()
self.synapse = Synapse() def agent_conf_updated(self, conf): conf_changed = False for key in self.KEYS: if self.conf.get(key, {}) != conf[key]: self.conf[key] = conf[key] conf_changed = True # save credentials for other program use... if key == 'credentials': self.synapse.set(SNMP_DEFAULT_CREDENTIALS_PATH, conf[key]) if conf_changed: # Grab template snmp_template = Template(filename="/elan-agent/nac/snmp/snmptrapd.conf") with open ("/etc/snmp/snmptrapd.conf", "w") as server_file: server_file.write(snmp_template.render(**self.conf)) restart_service('elan-snmp-notification-receiver') if __name__ == "__main__": dendrite = Dendrite() conf = SnmpConfigurator() dendrite.subscribe_conf('snmp', conf.agent_conf_updated) dendrite.wait_complete()
#!/usr/bin/env python3 from elan.nac.manager import MacAuthorizationManager from elan.nac import AUTHORIZATION_CHANGE_TOPIC, CHECK_AUTHZ_PATH from elan.session import MAC_SESSION_TOPIC from elan.neuron import Dendrite if __name__ == "__main__": dendrite = Dendrite() manager = MacAuthorizationManager(dendrite=dendrite) def handle_disconnection(data): # we are only interested in deconnections if 'end' in data: manager.handle_disconnection(data['mac']) dendrite.subscribe(AUTHORIZATION_CHANGE_TOPIC, manager.handle_authz_changed) dendrite.subscribe(MAC_SESSION_TOPIC, handle_disconnection) dendrite.subscribe(CHECK_AUTHZ_PATH, manager.check_authz) dendrite.wait_complete()
#!/usr/bin/env python3 from elan.ids import generate_suricata_conf from elan.neuron import Dendrite from elan.utils import reload_service def ip_conf_changed(*args, **kwargs): if generate_suricata_conf(): reload_service('suricata') if __name__ == "__main__": dendrite = Dendrite() dendrite.subscribe_conf('ipv4/current', ip_conf_changed) dendrite.subscribe_conf('ipv6/current', ip_conf_changed) dendrite.wait_complete()
def dashboard(request, context=None): if context is None: context = {} dendrite = Dendrite() registered = is_registered() try: # will raise error if not connected dendrite.call('check-connectivity', timeout=2) is_connected = True connectivity_error = '' except RequestTimeout: is_connected = None # Unknown connectivity_error = 'Connectivity check not implemented' except RequestError as e: is_connected = False connectivity_error = e.error_str registration_available = False registration_error = '' if not registered: try: dendrite.call('register', timeout=2) registration_available = True except RequestTimeout: registration_available = False registration_error = 'Registration service not implemented' except RequestError as e: registration_available = False registration_error = e.error_str current_ipv4 = netconf.get_current_ipv4(cidr=True) current_ipv4['ips'] = [ *map( lambda x: dict(zip(['address', 'prefix_length'], x.split('/', 1))), current_ipv4['ips']) ] current_ipv6 = netconf.get_current_ipv6(cidr=True) current_ipv6['ips'] = [ *map( lambda x: dict(zip(['address', 'prefix_length'], x.split('/', 1))), current_ipv6['ips']) ] context.update( registration_available=registration_available, registration_error=registration_error, is_admin=bool(request.session.get('admin', False)), is_connected=is_connected, connectivity_error=connectivity_error, is_registered=registered, interfaces={ iface: { 'up': is_iface_up(iface) } for iface in physical_ifaces() }, ipv4=current_ipv4, ipv6=current_ipv6, ) if not context.get('location', ''): # TODO: context['location'] = '' ip_conf = NetworkConfiguration() if not context.get('ipv4_form', None): context['ipv4_form'] = Ip4ConfigurationForm( initial=ip_conf.get_ipv4_conf()) if not context.get('ipv6_form', None): context['ipv6_form'] = Ip6ConfigurationForm( initial=ip_conf.get_ipv6_conf()) return render(request, 'captive-portal/dashboard.html', context)
class NetworkConfigurator: 'Class that does apply the network configuration. Use NetworkConfiguration class to manipulate Network Configuration' ip_conf_template = '/elan-agent/network/netplan-ip-conf.yaml' vlans_conf_template = '/elan-agent/network/netplan-vlans.yaml' ip_conf_file = '/etc/netplan/elan-ip-conf.yaml' vlans_conf_file = '/etc/netplan/elan-vlans.yaml' dendrite = Dendrite() def __init__(self): self.load_configuration() self.apply_ip_conf() def load_configuration(self): try: self.ipv4 = self.dendrite.get_conf(IPv4_CONF_TOPIC) if self.ipv4 is None: self.ipv4 = DEFAULT_IPv4_CONF self.ipv6 = self.dendrite.get_conf(IPv6_CONF_TOPIC) if self.ipv6 is None: self.ipv6 = DEFAULT_IPv6_CONF except ConnectionFailed: if os.path.exists(self.ip_conf_file): self.ipv4 = None self.ipv6 = None else: self.ipv4 = DEFAULT_IPv4_CONF self.ipv6 = DEFAULT_IPv6_CONF def set_ipv4(self, kwargs): self.ipv4 = kwargs self.apply_ip_conf() def set_ipv6(self, kwargs): self.ipv6 = kwargs self.apply_ip_conf() def apply_ip_conf(self): self.generate_ip_conf() self.reload() def generate_ip_conf(self): if self.ipv4 is not None and self.ipv6 is not None: self.generate_ip_conf_files(ipv4_conf=self.ipv4, ipv6_conf=self.ipv6) @classmethod def generate_ip_conf_files(cls, ipv4_conf, ipv6_conf): template = Template(filename=cls.ip_conf_template) with open(cls.ip_conf_file, 'w') as conf_file: conf_file.write( template.render(ipv4=ipv4_conf, ipv6=ipv6_conf, bridge_name=BRIDGE_NAME)) @classmethod def vlans_conf_files_exists(cls): return os.path.exists(cls.vlans_conf_file) @classmethod def generate_vlans_conf_files(cls, vlans): template = Template(filename=cls.vlans_conf_template) with open(cls.vlans_conf_file, 'w') as conf_file: conf_file.write( template.render(vlans=vlans, bridge_name=BRIDGE_NAME)) @staticmethod def reload(): subprocess.run(['netplan', 'apply'])
def setUp(self): self.dendrite = Dendrite() self.mqtt = client.Client() self.mqtt.connect(Dendrite.MQTT_HOST, Dendrite.MQTT_PORT)
#!/usr/bin/env python3 from elan.neuron import Dendrite if __name__ == "__main__": dendrite = Dendrite() dendrite.run_conf_cacher()
class NetworkMonitor: ''' class that will monitor and interface and publish current configuration (IPs, default gateway, and DNS) ''' dendrite = Dendrite() def __init__(self, interface=BRIDGE_NAME, ipv4_topic=IPv4_CURRENT_TOPIC, ipv6_topic=IPv6_CURRENT_TOPIC): self.interface = interface self.ipv4_topic = ipv4_topic self.ipv6_topic = ipv6_topic self.current_ipv4 = self.dendrite.get_conf(self.ipv4_topic) or { 'ips': [], 'gw': None, 'dns': [] } self.current_ipv6 = self.dendrite.get_conf(self.ipv6_topic) or { 'ips': [], 'gw': None, 'dns': [] } def run(self): with IPRoute() as ipr: # Bind before we get current values so that is they changed, we will be notified. ipr.bind(groups=rtnl.RTNLGRP_IPV4_IFADDR | rtnl.RTNLGRP_IPV6_IFADDR | rtnl.RTNLGRP_IPV4_ROUTE | rtnl.RTNLGRP_IPV6_ROUTE) # all current values and publich after self.check_ips(publish=False) self.check_gw4(publish=False) self.check_gw6(publish=False) self.check_dns(publish=False) self.publish_current_ipv4() self.publish_current_ipv6() # set monitoring of DNS (executed in another thread) observer = watchdog.observers.Observer() observer.schedule(self.FileChangeHandler( file=RESOLV_FILE, moved_cb=self.check_dns, created_cb=self.check_dns), path=os.path.dirname(RESOLV_FILE), recursive=False) # monitor network Changes while True: events = ipr.get() for event in events: if event['event'] in ['RTM_NEWROUTE', 'RTM_DELROUTE']: if event['family'] == socket.AF_INET: self.check_gw4() elif event['family'] == socket.AF_INET6: self.check_gw6() elif event['event'] in [ 'RTM_NEWADDR', 'RTM_DELADDR' ] and event['index'] == ipr.link_lookup( ifname=self.interface)[0]: self.check_ips(ipr) def check_gw4(self, publish=True): gw4 = self.get_gw4() if self.current_ipv4['gw'] != gw4: self.current_ipv4['gw'] = gw4 if publish: self.publish_current_ipv4() def check_gw6(self, publish=True): gw6 = self.get_gw6() if self.current_ipv6['gw'] != gw6: self.current_ipv6['gw'] = gw6 if publish: self.publish_current_ipv6() def check_ips(self, publish=True): ips = self.get_ips(self.interface) ipv4s = [ip for ip in ips if '.' in ip] if set(self.current_ipv4['ips']) != set(ipv4s): self.current_ipv4['ips'] = ipv4s if publish: self.publish_current_ipv4() ipv6s = [ip for ip in ips if ':' in ip] if set(self.current_ipv6['ips']) != set(ipv6s): self.current_ipv6['ips'] = ipv6s if publish: self.publish_current_ipv6() def check_dns(self, publish=True): dns = self.parse_resolvconf(RESOLV_FILE) dns4 = [d for d in dns if '.' in d] if dns4 != self.current_ipv4['dns']: self.current_ipv4['dns'] = dns4 if publish: self.publish_current_ipv4() dns6 = [d for d in dns if ':' in d] if dns6 != self.current_ipv6['dns']: self.current_ipv6['dns'] = dns6 if publish: self.publish_current_ipv6() class FileChangeHandler(FileSystemEventHandler): def __init__(self, file, created_cb, moved_cb): self.file = file self.created_cb = created_cb self.moved_cb = moved_cb def on_moved(self, event): if event.dest_path == self.file: self.moved_cb() def on_created(self, event): if event.src_path == self.file: self.created_cb() def get_ips(self, interface): ipr = IPRoute() ips = set() index = ipr.link_lookup(ifname=interface)[0] for entry in ipr.get_addr(index=index): for key, value in entry['attrs']: if key == 'IFA_ADDRESS': ip = '{ip}/{mask}'.format(ip=value, mask=entry['prefixlen']) ips.add(ip) break return ips def get_gw4(self): try: return resolver.query('_gateway', 'A')[0].address except: return None def get_gw6(self): try: return resolver.query('_gateway', 'AAAA')[0].address except: return None def parse_resolvconf(self, file=RESOLV_FILE): dns = [] try: with open(file) as resolvconf: for line in resolvconf: if line.startswith('nameserver '): line = line.rstrip() dns += [n for n in line.split()[1:] if n] except FileNotFoundError: pass return dns def publish_current_ipv4(self): self.dendrite.publish_conf(self.ipv4_topic, self.current_ipv4) def publish_current_ipv6(self): self.dendrite.publish_conf(self.ipv6_topic, self.current_ipv6)