class NICMonitor(threading.Thread): def __init__(self, context): threading.Thread.__init__(self) self.logger = logging.getLogger(__name__) self.context = context self.ip = IPRSocket() self.ip.bind(rtnl.RTNLGRP_LINK | rtnl.RTNLGRP_NOTIFY) self.control = None self.state = '' self.carrier = -1 def setup(self): self.logger.info('NICMon:setup()') self.control = self.context.socket(zmq.PAIR) self.control.bind(const.fmNICMonitorEndpoint) return self.control def run(self): self.command = self.context.socket(zmq.PAIR) self.command.connect(const.fmNICMonitorEndpoint) self.poller = zmq.Poller() self.poller.register(self.ip._sock, zmq.POLLIN) self.poller.register(self.command, zmq.POLLIN) while True: sockets = dict(self.poller.poll()) if self.ip._sock.fileno() in sockets: infos = self.ip.get() for info in infos: attrs = dict(info['attrs']) ifname = attrs['IFLA_IFNAME'] state = attrs['IFLA_OPERSTATE'] carrier = attrs['IFLA_CARRIER'] if ifname == Config.NIC_NAME and state != self.state and carrier != self.carrier: self.logger.info( "NICMonitor: state has changed (%s,%d)" % (state, carrier)) if state != 'UP' or carrier != 1: # NIC down and/or carrier lost info = ('nic-', ) self.command.send_pyobj(info) elif state == 'UP' and carrier == 1: # NIC UP and carrier is on info = ('nic+', ) self.command.send_pyobj(info) self.state, self.carrier = state, carrier del sockets[self.ip._sock.fileno()] elif self.command in sockets: msg = self.command.recv_pyobj() if msg == 'stop': break del sockets[self.command] self.command.close() self.ip.close() def terminate(self): self.control.send_pyobj('stop')
def scan_netdevs(): scan = [] nl_socket = IPRSocket() msg = ifinfmsg() msg["family"] = socket.AF_UNSPEC msg["header"]["type"] = RTM_GETLINK msg["header"]["flags"] = NLM_F_REQUEST | NLM_F_DUMP msg["header"]["pid"] = os.getpid() msg["header"]["sequence_number"] = 1 msg.encode() nl_socket.sendto(msg.buf.getvalue(), (0,0)) finished = False while not finished: parts = nl_socket.get() for part in parts: if part["header"]["type"] in [NLMSG_DONE, NLMSG_ERROR]: finished = True continue if part["header"]["sequence_number"] != 1: continue if part["header"]["type"] == RTM_NEWLINK: new_link = {} new_link["netlink_msg"] = part new_link["index"] = part["index"] new_link["name"] = part.get_attr("IFLA_IFNAME") hwaddr = part.get_attr("IFLA_ADDRESS") new_link["hwaddr"] = normalize_hwaddr(hwaddr) scan.append(new_link) nl_socket.close() return scan
class InterfaceManager(object): def __init__(self, server_handler): self._device_classes = {} self._devices = {} #ifindex to device self._nl_socket = IPRSocket() self._nl_socket.bind(groups=NL_GROUPS) self._msg_queue = deque() #TODO split DevlinkManager away from the InterfaceManager #self._dl_manager = DevlinkManager() self._server_handler = server_handler def clear_dev_classes(self): self._device_classes = {} def add_device_class(self, name, cls): if name in self._device_classes: raise InterfaceManagerError("Device class name conflict %s" % name) self._device_classes[name] = cls return cls def reconnect_netlink(self): if self._nl_socket != None: self._nl_socket.close() self._nl_socket = None self._nl_socket = IPRSocket() self._nl_socket.bind(groups=NL_GROUPS) self.rescan_devices() def get_nl_socket(self): return self._nl_socket def pull_netlink_messages_into_queue(self): try: while True: rl, wl, xl = select.select([self._nl_socket], [], [], 0) if not len(rl): break self._msg_queue.extend(self._nl_socket.get()) except socket.error: self.reconnect_netlink() return [] def rescan_devices(self): self.request_netlink_dump() self.handle_netlink_msgs() def request_netlink_dump(self): self._nl_socket.put(None, RTM_GETLINK, msg_flags=NLM_F_REQUEST | NLM_F_DUMP) self._nl_socket.put(None, RTM_GETADDR, msg_flags=NLM_F_REQUEST | NLM_F_DUMP) def handle_netlink_msgs(self): self.pull_netlink_messages_into_queue() while len(self._msg_queue): msg = self._msg_queue.popleft() self._handle_netlink_msg(msg) # self._dl_manager.rescan_ports() # for device in self._devices.values(): # dl_port = self._dl_manager.get_port(device.name) # device._set_devlink(dl_port) def _handle_netlink_msg(self, msg): if msg['header']['type'] in [RTM_NEWLINK, RTM_NEWADDR, RTM_DELADDR]: if msg['index'] in self._devices: self._devices[msg['index']]._update_netlink(msg) elif msg['header']['type'] == RTM_NEWLINK: dev = self._device_classes["Device"](self) dev._init_netlink(msg) self._devices[msg['index']] = dev update_msg = { "type": "dev_created", "dev_data": dev._get_if_data() } self._server_handler.send_data_to_ctl(update_msg) dev._disable() elif msg['header']['type'] == RTM_DELLINK: if msg['family'] == PF_BRIDGE: return if msg['index'] in self._devices: dev = self._devices[msg['index']] dev._deleted = True del self._devices[msg['index']] del_msg = {"type": "dev_deleted", "ifindex": msg['index']} self._server_handler.send_data_to_ctl(del_msg) else: return def untrack_device(self, dev): if dev.ifindex in self._devices: del self._devices[dev.ifindex] def get_device(self, ifindex): self.rescan_devices() if ifindex in self._devices: return self._devices[ifindex] else: raise DeviceNotFound() def get_devices(self): self.rescan_devices() return list(self._devices.values()) def get_device_by_hwaddr(self, hwaddr): self.rescan_devices() for dev in list(self._devices.values()): if dev.hwaddr == hwaddr: return dev raise DeviceNotFound() def get_device_by_name(self, name): self.rescan_devices() for dev in list(self._devices.values()): if dev.name == name: return dev raise DeviceNotFound() def get_device_by_params(self, params): self.rescan_devices() matched = None for dev in list(self._devices.values()): matched = dev dev_data = dev.get_if_data() for key, value in params.items(): if key not in dev_data or dev_data[key] != value: matched = None break if matched: break return matched def deconfigure_all(self): for dev in self._devices.values(): pass # dev.clear_configuration() def create_device(self, clsname, args=[], kwargs={}): devcls = self._device_classes[clsname] try: device = devcls(self, *args, **kwargs) except KeyError as e: raise DeviceConfigError("%s is a mandatory argument" % e) device._create() device._bulk_enabled = False self.request_netlink_dump() self.pull_netlink_messages_into_queue() device_found = False while len(self._msg_queue): msg = self._msg_queue.popleft() if msg.get_attr("IFLA_IFNAME") == device.name: device_found = True device._init_netlink(msg) self._devices[msg['index']] = device else: self._handle_netlink_msg(msg) if device_found: return device else: raise DeviceError("Device creation failed") def replace_dev(self, if_id, dev): del self._devices[if_id] self._devices[if_id] = dev def _is_name_used(self, name): self.rescan_devices() for device in self._devices.values(): if name == device.name: return True out, _ = exec_cmd("ovs-vsctl --columns=name list Interface", log_outputs=False, die_on_err=False) for line in out.split("\n"): m = re.match(r'.*: \"(.*)\"', line) if m is not None: if name == m.group(1): return True return False def assign_name(self, prefix): index = 0 while (self._is_name_used(prefix + str(index))): index += 1 return prefix + str(index) def _assign_name_pair(self, prefix): index1 = 0 index2 = 0 while (self._is_name_used(prefix + str(index1))): index1 += 1 index2 = index1 + 1 while (self._is_name_used(prefix + str(index2))): index2 += 1 return prefix + str(index1), prefix + str(index2)
"10.200.0.2": "02:42:0a:00:00:02", "10.200.0.3": "02:42:0a:00:00:03" } ctn_fib = { "02:42:0a:00:00:02": "10.200.129.100", "02:42:0a:00:00:03": "10.200.128.218" } logging.basicConfig(format='%(levelname)s %(message)s', level=logging.INFO) ipr = IPRoute() s = IPRSocket() s.bind() while True: msg = s.get() for m in msg: logging.debug('Received an event: {}'.format(m['event'])) if m['event'] != 'RTM_GETNEIGH': continue logging.debug("Received a Neighbor miss") ifindex = m['ifindex'] ifname = ipr.get_links(ifindex)[0].get_attr("IFLA_IFNAME") logging.debug("Family: {}".format( if_family.get(m['family'], m['family']))) logging.debug("Interface: {} (index: {})".format(ifname, ifindex)) logging.debug("NUD State: {}".format( nud_state.get(m['state'], m['state']))) logging.debug("Flags: {}".format(m['flags']))
''' Simplest example to monitor Netlink events with a Python script. ''' from pyroute2 import IPRSocket from pprint import pprint ip = IPRSocket() ip.bind() pprint(ip.get()) ip.close()
class NetLinkListener(Plugin): """This plugin reads kernel messages and is able to react to certain network events""" states = {"UP": True, "DOWN": False} def __init__(self, api, config, *args, **kw): super(NetLinkListener, self).__init__(api, config, *args, **kw) self.listen = True self.socket = IPRSocket() def _init(self, *args, **kw): # super(NetLinkListener, self).__init__(*args, **kw) self.queue = Queue.Queue() self.listen = False # add handlers here self.event_handlers = { "RTM_NEWLINK": self.rtm_newlink_handler, "RTM_NEWADDR": self.rtm_newaddr_handler, "RTM_DELADDR": self.rtm_deladdr_handler } self.api.register_connectivity_handler(self.connectivity_request) self.logger.debug("Connectivity request handler registered") self._initialized() def _start(self): self.api.run_task(self.start_listening) self.api.run_task(self.start_queue_handler) self._started() def _stop(self): self.stop_listening() self._stopped() def start_listening(self): if self.socket is None: self.socket = IPRSocket() self.socket.bind() self.logger.info("Starting listener...") self.listen = True while self.listen: r, _, _ = select((self.socket, ), (), (), 0.5) if r: msg_array = self.socket.get() for event_msg in msg_array: try: self.queue.put(event_msg) except Queue.Full: self.logger.warn( "Message queue is full! it has %s elements", self.queue.qsize()) # stop after listening self.socket.close() self.logger.debug("Socket closed") def start_queue_handler(self): """Start reading the queue in a while loop until plugin stops listening to kernel messages""" self.logger.debug("Starting queue handler...") while self.listen or not self.queue.empty(): try: self.handle_event(self.queue.get(timeout=0.5)) except Queue.Empty: pass self.logger.debug("Queue handler finished") def stop_listening(self): """Stop listening to kernel messages -> shuts down queue handler""" self.logger.info("Stopping listener...") self.listen = False def handle_event(self, event_msg): """Find the handler matching the message event and run the handler with the message :param event_msg: event message from kernel message (kernel message is usually an array with 1 event message """ event = event_msg["event"] # self.logger.debug("new event:\r\n%s", json.dumps(event_msg, indent=2, sort_keys=True)) # self.logger.debug("new event: %s", event) handler = self.event_handlers.get(event) if handler is not None: self.logger.info("Handling event: %s", event) handler(event_msg) else: # self.logger.debug("No handler for event: %s", event) pass def rtm_newlink_handler(self, msg): """Handler parsing RTM_NEWLINK event messages :param msg: RTM_NEWLINK event message """ name = str() state = str() for attr in msg["attrs"]: if attr[0] == "IFLA_IFNAME": name = attr[1] elif attr[0] == "IFLA_OPERSTATE": state = attr[1] if name == str(): self.logger.debug( "No IFLA_IFNAME attribute found. Using index to assume interface" ) name = self.get_interface_name_from_index(msg) if name != str() and state != str(): try: interface = self.get_interface(name) if self.states[state]: self.api.events.interface_created.fire(interface) else: self.api.events.interface_removed.fire(interface) except InterfaceNotFoundException: self.logger.error( "RTM_NEWLINK: Unable to fire event for interface state change: %s is an unknown interface", name) self.logger.debug("Debug print of skipped message:\r\n%s", json.dumps(msg, indent=2, sort_keys=True)) else: self.logger.warn( "RTM_NEWLINK: interface not found or unknown state in msg!") self.logger.debug("Debug print of skipped message:\r\n%s", json.dumps(msg, indent=2, sort_keys=True)) def rtm_newaddr_handler(self, msg): """Handler parsing RTM_NEWADDR event messages :param msg: RTM_NEWADDR event message """ interface = str() address = str() for attr in msg["attrs"]: if attr[0] == "IFA_LABEL": interface = attr[1] elif attr[0] == "IFA_ADDRESS": address = attr[1] if interface == str(): self.logger.debug( "No IFA_LABEL attribute found. Using index to assume interface" ) interface = self.get_interface_name_from_index(msg) if interface != str() and address != str(): try: self.api.events.address_created.fire( self.get_interface(interface), Address(address=address, family=msg["family"])) except InterfaceNotFoundException: self.logger.error( "RTM_NEWADDR: Unable to fire address_created event: %s is an unknown interface", interface) self.logger.debug("Debug print of skipped message:\r\n%s", json.dumps(msg, indent=2, sort_keys=True)) else: self.logger.warn( "RTM_NEWADDR: interface and/or address not found in msg!") self.logger.debug("Debug print of skipped message:\r\n%s", json.dumps(msg, indent=2, sort_keys=True)) def rtm_deladdr_handler(self, msg): """Handler parsing RTM_DELADDR event messages :param msg: RTM_DELADDR event message """ interface = str() address = str() for attr in msg["attrs"]: if attr[0] == "IFA_LABEL": interface = attr[1] elif attr[0] == "IFA_ADDRESS": address = attr[1] if interface == str(): self.logger.debug( "No IFA_LABEL attribute found. Using index to assume interface" ) interface = self.get_interface_name_from_index(msg) if interface != str() and address != str(): try: self.api.events.address_removed.fire( self.get_interface(interface), Address(address=address, family=msg["family"])) except InterfaceNotFoundException: self.logger.error( "RTM_DELADDR: Unable to fire address_removed event: %s is an unknown interface", interface) self.logger.debug("Debug print of skipped message:\r\n%s", json.dumps(msg, indent=2, sort_keys=True)) else: self.logger.warn( "RTM_DELADDR: interface and/or address not found in msg!") self.logger.debug("Debug print of skipped message:\r\n%s", json.dumps(msg, indent=2, sort_keys=True)) def get_addresses(self, interface): """Get addresses of a given interface :param interface: name of interface :return: list of addresses """ n_addresses = netifaces.ifaddresses(interface) addresses = [] for family in n_addresses: for addr in n_addresses[family]: addresses.append(Address(address=addr["addr"], family=family)) return addresses def get_interface(self, name): """Returns an Interface object identified by name :param name: name of interface :return Interface: interface :raise UnknownInterface: if interface was not found """ if name not in netifaces.interfaces(): raise InterfaceNotFoundException("%s was not found" % name) else: addresses = self.get_addresses(name) hwaddress = [ addr for addr in addresses if addr[1] == netifaces.AF_LINK ][0] return Interface(name=name, addresses=addresses, hwaddress=hwaddress) def get_interface_name_from_index(self, msg): """Parse event message and try to assume interface from index attribute :param msg: event message :return: interface name corresponding to index value, or empty string if unsuccessful """ interface = str() try: interface = netifaces.interfaces()[msg["index"] - 1] self.logger.debug("Index: %s -> %s", msg["index"], interface) except IndexError: self.logger.debug("Unable to get interface from index") finally: return interface def connectivity_request(self, rcat=0): """Handles connectivity requests""" with Promise() as p: blacklist = ['lo'] interfaces = netifaces.interfaces() interface = next((x for x in interfaces if (x not in blacklist)), None) if interface is None: p.reject( InterfaceNotFoundException( "No interfaces found matching request")) else: p.fulfill((self.get_interface(interface), 0)) return p