def _associate(self): """ Send an Association request. """ err_occurred = False # Prepare the RSN IEs information_elements = bytes() if self._rsn: information_elements = bytes(self._rsn) if self._ms_wpa_ie: information_elements += bytes(self._ms_wpa_ie) try: with IW() as iw: # pylint: disable-msg=C0103 g_default_logger.info("Trying to associate ...") iw.associate( self._iface_index, self._bssid, self._ssid, self._frequency, info_elements=information_elements ) except NetlinkError as netlink_err: g_exception_logger.exception(netlink_err) err_occurred = True # At this point, the association process probably failed while err_occurred: time.sleep(0.05) try: with IW() as iw: # pylint: disable-msg=C0103 g_default_logger.info("Trying to associate ...") iw.associate( self._iface_index, self._bssid, self._ssid, self._frequency, info_elements=information_elements ) err_occurred = False except NetlinkError as netlink_err_: g_exception_logger.exception(netlink_err_) err_occurred = True return err_occurred
def _detect_association_loss(self): association_loss = False if self._previously_associated: with IW() as iw: # pylint: disable-msg=C0103 data = iw.get_stations(self._iface_index) if data: for attr in data[0]["attrs"]: if attr[0] == 'NL80211_ATTR_STA_INFO': for sta_info in attr[1]["attrs"]: if sta_info[0] == 'NL80211_STA_INFO_STA_FLAGS': if not sta_info[1]["AUTHENTICATED"]: association_loss = True self._authentication_status = \ WiFiTrafficHandler.\ AUTH_STATUS_NOT_AUTHENTICATED if not sta_info[1]["ASSOCIATED"]: association_loss = True self._association_status = \ WiFiTrafficHandler.\ ASSO_STATUS_NOT_ASSOCIATED else: # Assume that we're no longer associated association_loss = True self._association_status = \ WiFiTrafficHandler.ASSO_STATUS_NOT_ASSOCIATED self._authentication_status = \ WiFiTrafficHandler.AUTH_STATUS_NOT_AUTHENTICATED return association_loss
def remove_monitor_interface(monitor_iface_name): """ Remove the virtual monitor interface that should have been added at startup. """ index = get_interface_index(monitor_iface_name) if index: try: with IW() as iw: iw.del_interface(index) except NetlinkError as netlink_err: g_exception_logger.exception(netlink_err)
def setup(self): try: self.iw = IW() except NetlinkError as e: if e.code == errno.ENOENT: raise SkipTest('nl80211 not supported') else: raise ifaces = self.iw.get_interfaces_dump() if not ifaces: raise SkipTest('no wireless interfaces found') self.ifname = ifaces[0].get_attr('NL80211_ATTR_IFNAME') self.ifindex = ifaces[0].get_attr('NL80211_ATTR_IFINDEX') self.wiphy = ifaces[0].get_attr('NL80211_ATTR_WIPHY')
def is_wireless_interface(interface_index): """ Return True if the interface which index is interface_index is a wireless interface, False otherwise. """ is_valid = True try: with IW() as iw: iw.get_interface_by_ifindex(interface_index) except NetlinkError: is_valid = False return is_valid
def _authenticate(self): """ Send an Authentication frame. """ err_occurred = False try: with IW() as iw: # pylint: disable-msg=C0103 iw.authenticate(self._iface_index, self._bssid, self._ssid, self._frequency) except NetlinkError as netlink_err: g_exception_logger.exception(netlink_err) err_occurred = True return err_occurred
def get_wireless_interface_mac_addr(interface_index): """ Return the MAC address of a wireless interface. """ mac_address = None try: with IW() as iw: data = iw.get_interface_by_ifindex(interface_index) if data: mac_address = data[0].get_attr("NL80211_ATTR_MAC") except NetlinkError as netlink_err: g_exception_logger.exception(netlink_err) return mac_address
def _scan(self): """ Perform a scan and retrieve information related to the target SSID. """ err_occurred = False bss_status_attr = None found = False rsn_ie_data = None vendor_ie_data = None try: with IW() as iw: # pylint: disable-msg=C0103 scan_results = iw.scan( self._iface_index, [self._ssid], flush_cache=True ) bss_attr = None for result in scan_results: bss_attr = result.get_attr('NL80211_ATTR_BSS') if bss_attr is not None: attrs = bss_attr["attrs"] bss_ie = [x[1] for x in attrs if x[0] == \ "NL80211_BSS_INFORMATION_ELEMENTS"] if bss_ie: # Get SSID ssid = bss_ie[0].get("SSID", None) if ssid.decode("ascii") == self._ssid: # Get RSN IEs rsn_ie_data = bss_ie[0].get("RSN", None) vendor_ie_data = bss_ie[0].get("VENDOR", None) found = True break except NetlinkError as netlink_err: g_exception_logger.exception(netlink_err) err_occurred = True if found: self._bssid = bss_attr.get_attr("NL80211_BSS_BSSID") self._frequency = bss_attr.get_attr("NL80211_BSS_FREQUENCY") from_presp = bss_attr.get_attr('NL80211_BSS_PRESP_DATA') self._ssid_info_learned_in_presp = True if from_presp else False bss_status_attr = bss_attr.get_attr("NL80211_BSS_STATUS") return err_occurred, bss_status_attr, rsn_ie_data, vendor_ie_data
def _disassociate(self): """ Send a Disassociation request. """ err_occurred = False try: with IW() as iw: # pylint: disable-msg=C0103 iw.disassociate(self._iface_index, self._bssid, reason_code=0x08) except NetlinkError as netlink_err: g_exception_logger.exception(netlink_err) err_occurred = True return err_occurred
def _deauthenticate(self): """ Send a Deauthentication frame. """ err_occurred = False try: with IW() as iw: # pylint: disable-msg=C0103 iw.deauthenticate(self._iface_index, self._bssid, reason_code=0x01) except NetlinkError as netlink_err: g_exception_logger.exception(netlink_err) err_occurred = True return err_occurred
def setup(self): try: self.iw = IW() except NetlinkError as e: if e.code == errno.ENOENT: raise SkipTest('nl80211 not supported') else: raise ifaces = self.iw.get_interfaces_dump() if not ifaces: raise SkipTest('no wireless interfaces found') for i in ifaces: self.ifname = i.get_attr('NL80211_ATTR_IFNAME') self.ifindex = i.get_attr('NL80211_ATTR_IFINDEX') self.wiphy = i.get_attr('NL80211_ATTR_WIPHY') if self.ifindex: return raise Exception('can not detect the interface to use')
def setup_wireless_interfaces(wireless_iface_name): """ Create a monitor interface ("ra0"), if it does not exist already. Bring the wireless interface up if necessary. """ setup_complete = True # Get interface index iface_index = get_interface_index(wireless_iface_name) # Check if it is a provided interface if not is_wireless_interface(iface_index): setup_complete = False g_exception_logger.error("%s is not a wireless interface.",\ wireless_iface_name) else: if not get_interface_index(MONITOR_INTERFACE_NAME): # Create a monitor interface try: with IW() as iw: iw.add_interface(MONITOR_INTERFACE_NAME, 6, iface_index) except NetlinkError as netlink_err: g_exception_logger.exception(netlink_err) return False # Set the monitor interface "administratively" up if not set_interface_admin_state(MONITOR_INTERFACE_NAME, admin_up=True): g_exception_logger.error("Could not set %s administratively up",\ MONITOR_INTERFACE_NAME) setup_complete = False # Set the interface "administratively" up if not set_interface_admin_state(wireless_iface_name, admin_up=True): g_exception_logger.error("Could not set %s administratively up",\ wireless_iface_name) setup_complete = False return setup_complete
def add_interface(self, ifaceName, mode, **kwargs): if ifaceName in pyw.winterfaces(): return False iw = IW() mode2int = { 'adhoc': 1, 'sta': 2, 'station': 2, 'managed': 2, 'ap': 3, 'ap_vlan': 4, 'wds': 5, 'monitor': 6, 'mesh_point': 7, 'p2p_client': 8, 'p2p_go': 9, 'p2p_device': 10, 'ocb': 11 } modeInt = mode2int.get(mode, None) if modeInt is None: return False iw.add_interface(ifaceName, modeInt, None, 0) ''' Other option: w0 = pyw.getcard('wlan1‘) if 'monitor' in pyw.devmodes(w0): m0 = pyw.devadd(w0,'mon0','monitor’) pyw.winterfaces() pyw.up(m0) # bring the new card up to use pyw.chset(m0,6,None) ''' return True
from pyroute2 import IW # register IW to get all the messages iw = IW(groups=~0) print(iw.get()) iw.close()
from pyroute2 import IW # register IW to get all the messages iw = IW(groups=0xFFF) print(iw.get()) iw.close()
class EthernetManager: # RTNL interface ndb = NDB(log="on") # WIFI interface iw = IW() # IP abstraction interface ipr = IPRoute() result: List[Dict[str, Any]] = [] def __init__(self) -> None: self.settings = settings.Settings() # Load settings and do the initial configuration if not self.settings.load(): print("Failed to load previous settings.") return print("Previous settings loaded:") for item in self.settings.root["content"]: print(f"Configuration with: {item}") if not self.set_configuration(item): print("Failed.") def save(self) -> None: """Save actual configuration""" try: self.get_interfaces() except Exception as exception: print( f"Failed to fetch actual configuration, going to use the previous info: {exception}" ) if not self.result: print("Configuration is empty, aborting.") return for item in self.result: item.pop("info") self.settings.save(self.result) def set_configuration(self, configuration: Dict[str, Any]) -> bool: """Modify hardware based in the configuration Args: configuration (dict): Configuration struct { 'name': 'interface_name', 'configuration': { 'ip': 'ip_address', 'mode': 'mode' // [unmanaged, client, server], } } Returns: bool: Configuration was accepted """ interfaces = self.get_interfaces() valid_names = [interface["name"] for interface in interfaces] name = configuration["name"] ip = configuration["configuration"]["ip"] mode = configuration["configuration"]["mode"] if name not in valid_names: return False if mode == "client": self.set_dynamic_ip(name) return True if mode == "unmanaged": self.set_static_ip(name, ip) return True return False def _get_wifi_interfaces(self) -> List[str]: """Get wifi interface list Returns: list: List with the name of the wifi interfaces """ interfaces = self.iw.list_dev() result = [] for interface in interfaces: for flag, value in interface["attrs"]: # Extract interface name from IFNAME flag if flag == "NL80211_ATTR_IFNAME": result += [value] return result def is_valid_interface(self, interface: str) -> bool: """Check if an interface is valid Args: interface (str): Network interface Returns: bool: True if valid, False if not """ blacklist = ["lo", "ham.*", "docker.*"] blacklist += self._get_wifi_interfaces() if not interface: return False for pattern in blacklist: if re.match(pattern, interface): return False return True def validate_interface_data(self, data: Dict[str, Any]) -> bool: """Check if interface configuration is valid Args: data (dict): Interface dict structure Returns: bool: True if valid, False if not """ name = data["name"] return self.is_valid_interface(name) @staticmethod def weak_is_ip_address(ip: str) -> bool: """Check if ip address is valid Args: ip (str): ip address Returns: bool: True if valid, False if not """ return re.match(r"\d+.\d+.\d+.\d+", ip) is not None def is_static_ip(self, ip: str) -> bool: """Check if ip address is static or dynamic For more information: https://code.woboq.org/qt5/include/linux/if_addr.h.html https://www.systutorials.com/docs/linux/man/8-ip-address/ Args: ip (str): ip address Returns: bool: true if static false if not """ for address in list(self.ipr.get_addr()): def get_item(items: List[Tuple[str, Any]], name: str) -> Any: return [value for key, value in items if key == name][0] if get_item(address["attrs"], "IFA_ADDRESS") != ip: continue flags = get_item(address["attrs"], "IFA_FLAGS") result = "IFA_F_PERMANENT" in ifaddrmsg.flags2names(flags) return result return False def _get_interface_index(self, interface_name: str) -> int: """Get interface index for internal usage Args: interface_name (str): Interface name Returns: int: Interface index """ interface_index = int(self.ipr.link_lookup(ifname=interface_name)[0]) return interface_index def flush_interface(self, interface_name: str) -> None: """Flush all ip addresses in a specific interface Args: interface_name (str): Interface name """ interface_index = self._get_interface_index(interface_name) self.ipr.flush_addr(index=interface_index) def enable_interface(self, interface_name: str, enable: bool = True) -> None: """Enable interface Args: interface_name (str): Interface name enable (bool, optional): Set interface status. Defaults to True """ interface_index = self._get_interface_index(interface_name) interface_state = "up" if enable else "down" self.ipr.link("set", index=interface_index, state=interface_state) async def _trigger_dhcp_service(self, interface_name: str) -> None: """Internal async trigger for dhcp service Args: interface_name (str): Interface name """ self.enable_interface(interface_name, False) await asyncio.sleep(1) self.enable_interface(interface_name, True) def trigger_dhcp_service(self, interface_name: str) -> None: """Trigger DHCP service via async Args: interface_name (str): Interface name """ asyncio.run(self._trigger_dhcp_service(interface_name)) def set_ip(self, interface_name: str, ip: str) -> None: """Set ip address for a specific interface Args: interface_name (str): Interface name ip (str): Desired ip address """ interface_index = self._get_interface_index(interface_name) self.ipr.addr("add", index=interface_index, address=ip, prefixlen=24) def set_dynamic_ip(self, interface_name: str) -> None: """Set interface to use dynamic ip address Args: interface_name (str): Interface name """ # Remove all address self.flush_interface(interface_name) # Trigger DHCP service to add a new dynamic ip address self.trigger_dhcp_service(interface_name) def set_static_ip(self, interface_name: str, ip: str) -> None: """Set interface to use static ip address Args: interface_name (str): Interface name ip (str): ip address """ # Remove all address self.flush_interface(interface_name) # Set new ip address self.set_ip(interface_name, ip) def get_interfaces(self) -> List[Dict[str, Any]]: """Get interfaces information Returns: dict: Interface information that uses the following struct: [ { 'name': 'interface_name', 'configuration': { 'ip': 'ip_address', 'mode': 'mode' // [unmanaged, client, server], }, 'info': { 'connected': True, 'number_of_disconnections': 4, } }, ... ] """ result = [] for interface, addresses in psutil.net_if_addrs().items(): for address in addresses: # We don't care about ipv6 if address.family == AddressFamily.AF_INET6: continue # If there is no ip address the mac address will be provided (⊙_⊙') valid_ip = EthernetManager.weak_is_ip_address(address.address) ip = address.address if valid_ip else "undefined" is_static_ip = self.is_static_ip(ip) # Populate our output item mode = "unmanaged" if is_static_ip and valid_ip else "client" info = self.get_interface_info(interface) data = { "name": interface, "configuration": { "ip": ip, "mode": mode }, "info": info, } # Check if it's valid and add to the result if self.validate_interface_data(data): result += [data] break self.result = result return result def get_interface_ndb(self, interface_name: str) -> Any: """Get interface NDB information for interface Args: interface_name (str): Interface name Returns: pyroute2.ndb.objects.interface.Interface: pyroute2 interface object """ return self.ndb.interfaces.dump().filter(ifname=interface_name)[0] def get_interface_info(self, interface_name: str) -> Dict[str, Any]: """Get interface info field Args: interface_name (str): Interface name Returns: dict: Info field of `get_interfaces` """ interface = self.get_interface_ndb(interface_name) return { "connected": interface.carrier != 0, "number_of_disconnections": interface.carrier_down_count, }
from pyroute2 import IW from pyroute2 import IPRoute from pyroute2.netlink import NetlinkError # interface name to check ifname = 'lo' ip = IPRoute() iw = IW() index = ip.link_lookup(ifname=ifname)[0] try: iw.get_interface_by_ifindex(index) print("wireless interface") except NetlinkError as e: if e.code == 19: # 19 'No such device' print("not a wireless interface") finally: iw.close() ip.close()
class EthernetManager: # RTNL interface ndb = NDB(log="on") # WIFI interface iw = IW() # IP abstraction interface ipr = IPRoute() result: List[EthernetInterface] = [] def __init__(self, default_config: EthernetInterface) -> None: self.settings = settings.Settings() self._dhcp_servers: List[DHCPServerManager] = [] # Load settings and do the initial configuration if not self.settings.load(): logger.error( f"Failed to load previous settings. Using default configuration: {default_config}" ) try: self.set_configuration(default_config) except Exception as error: logger.error(f"Failed loading default configuration. {error}") return logger.info("Loading previous settings.") for item in self.settings.root["content"]: logger.info(f"Loading following configuration: {item}.") try: self.set_configuration(EthernetInterface(**item)) except Exception as error: logger.error(f"Failed loading saved configuration. {error}") def save(self) -> None: """Save actual configuration""" try: self.get_interfaces() except Exception as exception: logger.error( f"Failed to fetch actual configuration, going to use the previous info: {exception}" ) if not self.result: logger.error("Configuration is empty, aborting.") return result = [ interface.dict(exclude={"info"}) for interface in self.result ] self.settings.save(result) def set_configuration(self, interface: EthernetInterface) -> None: """Modify hardware based in the configuration Args: interface: EthernetInterface """ interfaces = self.get_interfaces() logger.debug(f"Found following ethernet interfaces: {interfaces}.") valid_names = [interface.name for interface in interfaces] if interface.name not in valid_names: raise ValueError( f"Invalid interface name ('{interface.name}'). Valid names are: {valid_names}" ) # Reset the interface by removing all IPs and DHCP servers associated with it self.flush_interface(interface.name) self.remove_dhcp_server_from_interface(interface.name) # Even if it happened to receive more than one dynamic IP, only one trigger is necessary if any(address.mode == AddressMode.Client for address in interface.addresses): self.trigger_dynamic_ip_acquisition(interface.name) for address in interface.addresses: if address.mode == AddressMode.Unmanaged: self.add_static_ip(interface.name, address.ip) elif address.mode == AddressMode.Server: self.add_dhcp_server_to_interface(interface.name, address.ip) def _get_wifi_interfaces(self) -> List[str]: """Get wifi interface list Returns: list: List with the name of the wifi interfaces """ interfaces = self.iw.list_dev() result = [] for interface in interfaces: for flag, value in interface["attrs"]: # Extract interface name from IFNAME flag if flag == "NL80211_ATTR_IFNAME": result += [value] return result def is_valid_interface_name(self, interface_name: str) -> bool: """Check if an interface name is valid Args: interface_name (str): Network interface name Returns: bool: True if valid, False if not """ blacklist = ["lo", "ham.*", "docker.*", "veth.*"] wifi_interfaces = self._get_wifi_interfaces() blacklist += wifi_interfaces if not interface_name: logger.error("Interface name cannot be blank or null.") return False for pattern in blacklist: if re.match(pattern, interface_name): return False return True def validate_interface_data(self, interface: EthernetInterface) -> bool: """Check if interface configuration is valid Args: interface: EthernetInterface instance Returns: bool: True if valid, False if not """ return self.is_valid_interface_name(interface.name) @staticmethod def _is_server_address_present(interface: EthernetInterface) -> bool: return any(address.mode == AddressMode.Server for address in interface.addresses) @staticmethod def weak_is_ip_address(ip: str) -> bool: """Check if ip address is valid Args: ip (str): ip address Returns: bool: True if valid, False if not """ return re.match(r"\d+.\d+.\d+.\d+", ip) is not None def is_static_ip(self, ip: str) -> bool: """Check if ip address is static or dynamic For more information: https://code.woboq.org/qt5/include/linux/if_addr.h.html https://www.systutorials.com/docs/linux/man/8-ip-address/ Args: ip (str): ip address Returns: bool: true if static false if not """ for address in list(self.ipr.get_addr()): def get_item(items: List[Tuple[str, Any]], name: str) -> Any: return [value for key, value in items if key == name][0] if get_item(address["attrs"], "IFA_ADDRESS") != ip: continue flags = get_item(address["attrs"], "IFA_FLAGS") result = "IFA_F_PERMANENT" in ifaddrmsg.flags2names(flags) return result return False def _get_interface_index(self, interface_name: str) -> int: """Get interface index for internal usage Args: interface_name (str): Interface name Returns: int: Interface index """ interface_index = int(self.ipr.link_lookup(ifname=interface_name)[0]) return interface_index def flush_interface(self, interface_name: str) -> None: """Flush all ip addresses in a specific interface Args: interface_name (str): Interface name """ interface_index = self._get_interface_index(interface_name) self.ipr.flush_addr(index=interface_index) logger.info(f"Flushing IP addresses from interface {interface_name}.") def enable_interface(self, interface_name: str, enable: bool = True) -> None: """Enable interface Args: interface_name (str): Interface name enable (bool, optional): Set interface status. Defaults to True """ interface_index = self._get_interface_index(interface_name) interface_state = "up" if enable else "down" self.ipr.link("set", index=interface_index, state=interface_state) logger.info( f"Setting interface {interface_name} to '{interface_state}' state." ) def trigger_dynamic_ip_acquisition(self, interface_name: str) -> None: """Trigger external DHCP servers to possibly aquire a dynamic IP by restarting the interface. Args: interface_name (str): Interface name """ logger.info( f"Restaring interface {interface_name} to trigger dynamic IP acquisition." ) self.enable_interface(interface_name, False) time.sleep(1) self.enable_interface(interface_name, True) def add_static_ip(self, interface_name: str, ip: str) -> None: """Set ip address for a specific interface Args: interface_name (str): Interface name ip (str): Desired ip address """ logger.info( f"Adding static IP '{ip}' to interface '{interface_name}'.") interface_index = self._get_interface_index(interface_name) self.ipr.addr("add", index=interface_index, address=ip, prefixlen=24) def remove_ip(self, interface_name: str, ip_address: str) -> None: """Delete IP address appended on the interface Args: interface_name (str): Interface name ip_address (str): IP address to be deleted """ logger.info( f"Deleting IP {ip_address} from interface {interface_name}.") try: if (self._is_dhcp_server_running_on_interface(interface_name) and self._dhcp_server_on_interface(interface_name).ipv4_gateway == ip_address): self.remove_dhcp_server_from_interface(interface_name) interface_index = self._get_interface_index(interface_name) self.ipr.addr("del", index=interface_index, address=ip_address, prefixlen=24) except Exception as error: raise RuntimeError( f"Cannot delete IP '{ip_address}' from interface {interface_name}." ) from error def get_interface_by_name(self, name: str) -> EthernetInterface: for interface in self.get_interfaces(): if interface.name == name: return interface raise ValueError(f"No interface with name '{name}' is present.") def get_interfaces(self) -> List[EthernetInterface]: """Get interfaces information Returns: List of EthernetInterface instances available """ result = [] for interface, addresses in psutil.net_if_addrs().items(): # We don't care about virtual ethernet interfaces ## Virtual interfaces are created by programs such as docker ## and they are an abstraction of real interfaces, the ones that we want to configure. if not self.is_valid_interface_name(interface): continue valid_addresses = [] for address in addresses: # We just care about IPV4 addresses if not address.family == AddressFamily.AF_INET: continue valid_ip = EthernetManager.weak_is_ip_address(address.address) ip = address.address if valid_ip else "undefined" is_static_ip = self.is_static_ip(ip) # Populate our output item if (self._is_dhcp_server_running_on_interface(interface) and self._dhcp_server_on_interface(interface).ipv4_gateway == ip): mode = AddressMode.Server else: mode = AddressMode.Unmanaged if is_static_ip and valid_ip else AddressMode.Client valid_addresses.append(InterfaceAddress(ip=ip, mode=mode)) info = self.get_interface_info(interface) interface_data = EthernetInterface(name=interface, addresses=valid_addresses, info=info) # Check if it's valid and add to the result if self.validate_interface_data(interface_data): result += [interface_data] self.result = result return result def get_interface_ndb(self, interface_name: str) -> Any: """Get interface NDB information for interface Args: interface_name (str): Interface name Returns: pyroute2.ndb.objects.interface.Interface: pyroute2 interface object """ return self.ndb.interfaces.dump().filter(ifname=interface_name)[0] def get_interface_info(self, interface_name: str) -> InterfaceInfo: """Get interface info field Args: interface_name (str): Interface name Returns: InterfaceInfo object """ interface = self.get_interface_ndb(interface_name) return InterfaceInfo( connected=interface.carrier != 0, number_of_disconnections=interface.carrier_down_count) def _is_ip_on_interface(self, interface_name: str, ip_address: str) -> bool: interface = self.get_interface_by_name(interface_name) return any(True for address in interface.addresses if address.ip == ip_address) def _dhcp_server_on_interface(self, interface_name: str) -> DHCPServerManager: try: return next(dhcp_server for dhcp_server in self._dhcp_servers if dhcp_server.interface == interface_name) except StopIteration as error: raise ValueError( f"No DHCP server running on interface {interface_name}." ) from error def _is_dhcp_server_running_on_interface(self, interface_name: str) -> bool: try: return bool(self._dhcp_server_on_interface(interface_name)) except Exception: return False def remove_dhcp_server_from_interface( self, interface_name: str) -> DHCPServerManager: logger.info(f"Removing DHCP server from interface '{interface_name}'.") try: self._dhcp_servers.remove( self._dhcp_server_on_interface(interface_name)) except ValueError: # If the interface does not have a DHCP server running on, no need to raise pass except Exception as error: raise RuntimeError( "Cannot remove DHCP server from interface.") from error def add_dhcp_server_to_interface(self, interface_name: str, ipv4_gateway: str) -> None: if self._is_dhcp_server_running_on_interface(interface_name): self.remove_dhcp_server_from_interface(interface_name) if self._is_ip_on_interface(interface_name, ipv4_gateway): self.remove_ip(interface_name, ipv4_gateway) self.add_static_ip(interface_name, ipv4_gateway) logger.info( f"Adding DHCP server with gateway '{ipv4_gateway}' to interface '{interface_name}'." ) self._dhcp_servers.append( DHCPServerManager(interface_name, ipv4_gateway)) def stop(self) -> None: """Perform steps necessary to properly stop the manager.""" for dhcp_server in self._dhcp_servers: dhcp_server.stop() def __del__(self) -> None: self.stop()
from pyroute2 import IW # register IW to get all the messages iw = IW(groups=0xfff) print(iw.get()) iw.close()
class EthernetManager: # RTNL interface ndb = NDB(log="on") # WIFI interface iw = IW() # IP abstraction interface ipr = IPRoute() result: List[EthernetInterface] = [] def __init__(self, default_config: EthernetInterface, dhcp_gateway: str) -> None: self.settings = settings.Settings() self._config_path = pathlib.Path(__file__).parent.absolute().joinpath( "settings", "dnsmasq.conf") self._server = Dnsmasq(self._config_path) self._dhcp_server_gateway = dhcp_gateway # Load settings and do the initial configuration if not self.settings.load(): logger.error( f"Failed to load previous settings. Using default configuration: {default_config}" ) self.set_configuration(default_config) return logger.info("Previous settings loaded:") for item in self.settings.root["content"]: logger.info(f"Configuration with: {item}") if not self.set_configuration(EthernetInterface(**item)): logger.error("Failed.") def save(self) -> None: """Save actual configuration""" try: self.get_interfaces() except Exception as exception: logger.error( f"Failed to fetch actual configuration, going to use the previous info: {exception}" ) if not self.result: logger.error("Configuration is empty, aborting.") return result = [ interface.dict(exclude={"info"}) for interface in self.result ] self.settings.save(result) def set_configuration(self, interface: EthernetInterface) -> bool: """Modify hardware based in the configuration Args: interface: EthernetInterface Returns: bool: Configuration was accepted """ interfaces = self.get_interfaces() logger.debug(f"Found following ethernet interfaces: {interfaces}.") valid_names = [interface.name for interface in interfaces] name = interface.name ip = interface.configuration.ip mode = interface.configuration.mode if name not in valid_names: logger.error( f"Invalid interface name ('{name}'). Valid names are: {valid_names}" ) return False if mode != InterfaceMode.Server and self._server.is_running(): self._server.stop() if mode == InterfaceMode.Client: self.set_dynamic_ip(name) logger.info(f"Interface '{name}' configured with dynamic IP.") return True if mode == InterfaceMode.Server: self.set_static_ip(name, self._dhcp_server_gateway) if not self._server.is_running(): self._server.start() logger.info( f"Interface '{name}' configured as DHCP server with static IP." ) return True if mode == InterfaceMode.Unmanaged: self.set_static_ip(name, ip) logger.info(f"Interface '{name}' configured with static IP.") return True logger.error(f"Could not configure interface '{name}'.") return False def _get_wifi_interfaces(self) -> List[str]: """Get wifi interface list Returns: list: List with the name of the wifi interfaces """ interfaces = self.iw.list_dev() result = [] for interface in interfaces: for flag, value in interface["attrs"]: # Extract interface name from IFNAME flag if flag == "NL80211_ATTR_IFNAME": result += [value] return result def is_valid_interface_name(self, interface_name: str) -> bool: """Check if an interface name is valid Args: interface_name (str): Network interface name Returns: bool: True if valid, False if not """ blacklist = ["lo", "ham.*", "docker.*"] wifi_interfaces = self._get_wifi_interfaces() blacklist += wifi_interfaces if not interface_name: logger.error("Interface name cannot be blank or null.") return False for pattern in blacklist: if re.match(pattern, interface_name): return False return True def validate_interface_data(self, interface: EthernetInterface) -> bool: """Check if interface configuration is valid Args: interface: EthernetInterface instance Returns: bool: True if valid, False if not """ return self.is_valid_interface_name(interface.name) @staticmethod def weak_is_ip_address(ip: str) -> bool: """Check if ip address is valid Args: ip (str): ip address Returns: bool: True if valid, False if not """ return re.match(r"\d+.\d+.\d+.\d+", ip) is not None def is_static_ip(self, ip: str) -> bool: """Check if ip address is static or dynamic For more information: https://code.woboq.org/qt5/include/linux/if_addr.h.html https://www.systutorials.com/docs/linux/man/8-ip-address/ Args: ip (str): ip address Returns: bool: true if static false if not """ for address in list(self.ipr.get_addr()): def get_item(items: List[Tuple[str, Any]], name: str) -> Any: return [value for key, value in items if key == name][0] if get_item(address["attrs"], "IFA_ADDRESS") != ip: continue flags = get_item(address["attrs"], "IFA_FLAGS") result = "IFA_F_PERMANENT" in ifaddrmsg.flags2names(flags) return result return False def _get_interface_index(self, interface_name: str) -> int: """Get interface index for internal usage Args: interface_name (str): Interface name Returns: int: Interface index """ interface_index = int(self.ipr.link_lookup(ifname=interface_name)[0]) return interface_index def flush_interface(self, interface_name: str) -> None: """Flush all ip addresses in a specific interface Args: interface_name (str): Interface name """ interface_index = self._get_interface_index(interface_name) self.ipr.flush_addr(index=interface_index) logger.info(f"Flushing IP addresses from interface {interface_name}.") def enable_interface(self, interface_name: str, enable: bool = True) -> None: """Enable interface Args: interface_name (str): Interface name enable (bool, optional): Set interface status. Defaults to True """ interface_index = self._get_interface_index(interface_name) interface_state = "up" if enable else "down" self.ipr.link("set", index=interface_index, state=interface_state) logger.info( f"Setting interface {interface_name} to '{interface_state}' state." ) async def _trigger_dhcp_service(self, interface_name: str) -> None: """Internal async trigger for dhcp service Args: interface_name (str): Interface name """ self.enable_interface(interface_name, False) await asyncio.sleep(1) self.enable_interface(interface_name, True) def trigger_dhcp_service(self, interface_name: str) -> None: """Trigger DHCP service via async Args: interface_name (str): Interface name """ asyncio.run(self._trigger_dhcp_service(interface_name)) def set_ip(self, interface_name: str, ip: str) -> None: """Set ip address for a specific interface Args: interface_name (str): Interface name ip (str): Desired ip address """ interface_index = self._get_interface_index(interface_name) self.ipr.addr("add", index=interface_index, address=ip, prefixlen=24) logger.info(f"Setting interface {interface_name} to IP '{ip}'.") def set_dynamic_ip(self, interface_name: str) -> None: """Set interface to use dynamic ip address Args: interface_name (str): Interface name """ # Remove all address self.flush_interface(interface_name) # Trigger DHCP service to add a new dynamic ip address self.trigger_dhcp_service(interface_name) logger.info(f"Getting dynamic IP to interface {interface_name}.") def set_static_ip(self, interface_name: str, ip: str) -> None: """Set interface to use static ip address Args: interface_name (str): Interface name ip (str): ip address """ # Remove all address self.flush_interface(interface_name) # Set new ip address self.set_ip(interface_name, ip) def get_interfaces(self) -> List[EthernetInterface]: """Get interfaces information Returns: List of EthernetInterface instances available """ result = [] for interface, addresses in psutil.net_if_addrs().items(): # We don't care about virtual ethernet interfaces ## Virtual interfaces are created by programs such as docker ## and they are an abstraction of real interfaces, the ones that we want to configure. if str(interface).startswith("veth"): continue for address in addresses: # We don't care about ipv6 if address.family == AddressFamily.AF_INET6: continue # If there is no ip address the mac address will be provided (⊙_⊙') valid_ip = EthernetManager.weak_is_ip_address(address.address) ip = address.address if valid_ip else "undefined" is_static_ip = self.is_static_ip(ip) is_gateway_ip = ip == self._dhcp_server_gateway # Populate our output item if self._server.is_running() and is_gateway_ip: mode = InterfaceMode.Server else: mode = InterfaceMode.Unmanaged if is_static_ip and valid_ip else InterfaceMode.Client info = self.get_interface_info(interface) data = EthernetInterface(name=interface, configuration=InterfaceConfiguration( ip=ip, mode=mode), info=info) # Check if it's valid and add to the result if self.validate_interface_data(data): result += [data] break self.result = result return result def get_interface_ndb(self, interface_name: str) -> Any: """Get interface NDB information for interface Args: interface_name (str): Interface name Returns: pyroute2.ndb.objects.interface.Interface: pyroute2 interface object """ return self.ndb.interfaces.dump().filter(ifname=interface_name)[0] def get_interface_info(self, interface_name: str) -> InterfaceInfo: """Get interface info field Args: interface_name (str): Interface name Returns: InterfaceInfo object """ interface = self.get_interface_ndb(interface_name) return InterfaceInfo( connected=interface.carrier != 0, number_of_disconnections=interface.carrier_down_count)