def __init__(self, ipdb: IPDB, nsp_name: str): """ Creats a namespace for a specific vlan_iface :param nsp_name: :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. """ logging.debug("%sCreate Namespace ...", LoggerSetup.get_log_deep(2)) self.ipdb = ipdb if ipdb else IPDB() self.ipdb_netns = None self.nsp_name = nsp_name try: self.ipdb_netns = IPDB(nl=NetNS(nsp_name)) self.ipdb_netns.interfaces['lo'].up().commit() logging.debug( "%s[+] Namespace(" + nsp_name + ") successfully created", LoggerSetup.get_log_deep(3)) # self.encapsulate_interface() except Exception as e: logging.debug("%s[-] Couldn't create Namespace(" + nsp_name + ")", LoggerSetup.get_log_deep(3)) for tb in traceback.format_tb(sys.exc_info()[2]): logging.error("%s" + tb, LoggerSetup.get_log_deep(3)) logging.error("%s" + str(e), LoggerSetup.get_log_deep(3)) self.remove()
def test_timeout(self): print("Test Dhclient: timeout") router = self._create_router() # Set VLAN that doesn't exist, so no response occur. router._vlan_iface_id = 99 router._vlan_iface_name = "vlan99" ipdb = IPDB() try: print("Get link-interface ...") # Get the real link interface link_iface = ipdb.interfaces["eth0"] # Create a Vlan print("Create VLAN ...") with ipdb.create(kind="vlan", ifname=router.vlan_iface_name, link=link_iface, vlan_id=router.vlan_iface_id).commit() as iface: print("Update IP with Dhclient ...") Dhclient.update_ip(router.vlan_iface_name, timeout=10) iface.mtu = 1400 except TimeoutError: print("[+] Test was successful") except Exception as e: print("Error: " + str(e)) raise e finally: print("Delete VLAN ...") ipdb.interfaces[router.vlan_iface_name].remove().commit() ipdb.release()
def test_normal_functionality(self): print("Test Dhclient: update_ip") router = self._create_router() ipdb = IPDB() try: print("Get link-interface ...") # Get the real link interface link_iface = ipdb.interfaces["eth0"] # Create a Vlan print("Create VLAN ...") with ipdb.create(kind="vlan", ifname=router.vlan_iface_name, link=link_iface, vlan_id=router.vlan_iface_id).commit() as iface: print("Update IP with Dhclient ...") Dhclient.update_ip(router.vlan_iface_name) iface.mtu = 1400 # Because the IPDB need some time to update the given IP sleep(10) process = Popen(["ping", "-c", "1", "-I", router.vlan_iface_name, router.ip], stdout=PIPE, stderr=PIPE) stdout, sterr = process.communicate() if not(sterr.decode('utf-8') == "" and "Unreachable" not in stdout.decode('utf-8')): raise Exception(str(sterr.decode('utf-8'))) print("[+] Test was successful") except Exception as e: print("Error: " + str(e)) raise e finally: print("Delete VLAN ...") ipdb.interfaces[router.vlan_iface_name].remove().commit() ipdb.release()
def test_create_namespace(self): router = self._create_router() # Create VLAN ipdb = IPDB() vlan = Vlan(ipdb, router, "eth0") assert isinstance(vlan, Vlan) vlan.create_interface() # Create Namespace namespace = Namespace(ipdb, router.namespace_name) assert isinstance(namespace, Namespace) # encapsulate VLAN namespace.encapsulate_interface(vlan.vlan_iface_name) # Test if the namespace now exists process = Popen(["ip", "netns"], stdout=PIPE, stderr=PIPE) stdout, sterr = process.communicate() assert sterr.decode('utf-8') == "" assert namespace.nsp_name in stdout.decode('utf-8') # Remove the Namespace vlan.delete_interface(close_ipdb=True) namespace.remove() ipdb.release() process = Popen(["ip", "netns"], stdout=PIPE, stderr=PIPE) stdout, sterr = process.communicate() assert stdout.decode('utf-8') == ""
def __init__(self, nsp_name: str, ipdb: IPDB): """ Creats a namespace for a specific vlan_iface :param nsp_name: :param vlan_iface_name: :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. """ Logger().debug("Create Namespace ...", 2) self.nsp_name = nsp_name self.id = id self.vlan_iface_name = "" self.vlan_iface_ip = "0.0.0.0" self.ipdb = ipdb self.ipdb_netns = None try: self.ipdb_netns = IPDB(nl=NetNS(nsp_name)) netns.setns(nsp_name) self.ipdb_netns.interfaces['lo'].up().commit() Logger().debug("[+] Namespace(" + nsp_name + ") successfully created", 3) # self.encapsulate_interface() except Exception as e: Logger().debug("[-] Couldn't create Namespace(" + nsp_name + ")", 3) for tb in traceback.format_tb(sys.exc_info()[2]): Logger().error(tb, 3) Logger().error(str(e), 3) self.remove()
def __init__(self, link_iface_name: str): """ :param link_iface_name: 'physical'-interface like 'eth0' to which the VLAN is connected to """ self.ipdb = IPDB() self.link_iface_name = link_iface_name self.vlan_dict = dict() self.nsp_dict = dict()
def __init__(self, link_iface_name: str): """ Initialize the IPDB for the interfaces and creates a Bridge (virtual switch). :param link_iface_name: 'physical'-interface like 'eth0' """ self.ipdb = IPDB() self.link_iface_name = link_iface_name self.vlan_dict = dict() self.veth_dict = dict() self.nsp_dict = dict()
def __init__(self, ipdb: IPDB, veth_iface_name1: str, veth_iface_name2: str, veth_iface_ip1: str = None, veth_iface_ip_mask1: int = None, veth_iface_ip2: str = None, veth_iface_ip_mask2: int = None): """ Represents a 'veth'-interface. :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. :param veth_iface_name1: like veth00 :param veth_iface_name2: like veth0 :param veth_iface_ip1: :param veth_iface_ip_mask1: :param veth_iface_ip2: :param veth_iface_ip_mask2: """ self.ipdb = ipdb if ipdb else IPDB() self.veth_iface_name1 = veth_iface_name1 self.veth_iface_name2 = veth_iface_name2 self.veth_iface_ip1 = veth_iface_ip1 self.veth_iface_ip_mask1 = veth_iface_ip_mask1 self.veth_iface_ip2 = veth_iface_ip2 self.veth_iface_ip_mask2 = veth_iface_ip_mask2
def test_create_vlan(self): print("Test if a VLAN can be created") router = self._create_router() # Create VLAN ipdb = IPDB() vlan = Vlan(ipdb, router, "eth0") vlan.create_interface() assert isinstance(vlan, Vlan) # Test if the VLAN now exists process = Popen(["ip", "link", "show", "dev", vlan.vlan_iface_name], stdout=PIPE, stderr=PIPE) stdout, sterr = process.communicate() assert sterr.decode('utf-8') == "" assert vlan.vlan_iface_name in stdout.decode('utf-8') # Remove the VLAN vlan.delete_interface(close_ipdb=True) process = Popen(["ip", "link", "show", "dev", vlan.vlan_iface_name], stdout=PIPE, stderr=PIPE) stdout, sterr = process.communicate() assert stdout.decode('utf-8') == ""
def __init__(self, link_iface_name: str, vlan_iface_name: str, vlan_iface_id: int, vlan_iface_ip: str=None, vlan_iface_ip_mask: int=None): """ Creats a virtual interface on a existing interface (like eth0). It uses IPDB: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. :param link_iface_name: name of the existing interface (eth0, wlan0, ...) :param vlan_iface_name: name of the vlan :param vlan_iface_id: the id of the vlan :param vlan_iface_ip: ip of the virtual interface :param vlan_iface_ip_mask: network-mask of the virtual interface """ self.link_iface_name = link_iface_name self.vlan_iface_name = vlan_iface_name self.vlan_iface_id = vlan_iface_id self.ipdb = IPDB()
def __init__(self, ipdb: IPDB, remote_system: RemoteSystem, link_iface_name): """ :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. :param link_iface_name: name of the existing interface (eth0, wlan0, ...) """ self.ipdb = ipdb if ipdb else IPDB() self.remote_system = remote_system self.link_iface_name = link_iface_name self.vlan_iface_name = str(remote_system.vlan_iface_name) self.vlan_iface_id = int(remote_system.vlan_iface_id)
def test_normal_functionality(self): print("Test Dhclient: update_ip") router = self._create_router() ipdb = IPDB() try: print("Get link-interface ...") # Get the real link interface link_iface = ipdb.interfaces["eth0"] # Create a Vlan print("Create VLAN ...") with ipdb.create(kind="vlan", ifname=router.vlan_iface_name, link=link_iface, vlan_id=router.vlan_iface_id).commit() as iface: print("Update IP with Dhclient ...") Dhclient.update_ip(router.vlan_iface_name) iface.mtu = 1400 # Because the IPDB need some time to update the given IP sleep(10) process = Popen( ["ping", "-c", "1", "-I", router.vlan_iface_name, router.ip], stdout=PIPE, stderr=PIPE) stdout, sterr = process.communicate() if not (sterr.decode('utf-8') == "" and "Unreachable" not in stdout.decode('utf-8')): raise Exception(str(sterr.decode('utf-8'))) print("[+] Test was successful") except Exception as e: print("Error: " + str(e)) raise e finally: print("Delete VLAN ...") ipdb.interfaces[router.vlan_iface_name].remove().commit() ipdb.release()
def __init__(self, ipdb: IPDB, bridge_name: str = 'br0', link_iface_name: str = 'eth0'): """ Creates a 'bridge'-interface. :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. :param bridge_name: like 'br0' :param link_iface_name: like 'eth0' """ self.ipdb = ipdb if ipdb else IPDB() self.bridge_name = bridge_name self.ipdb.create(kind='bridge', ifname=self.bridge_name).commit() # self.ipdb.interfaces[self.bridge_name].up() self.add_iface(link_iface_name) self.set_new_ip(dhcp=True)
def __init__(self, ipdb: IPDB, nsp_name: str): """ :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immediately in OS, but waits until commit() is called. :param nsp_name: Name of the Namespace """ logging.debug("%sCreate Namespace ...", LoggerSetup.get_log_deep(2)) self.ipdb = ipdb if ipdb else IPDB() self.ipdb_netns = None self.nsp_name = nsp_name try: self.ipdb_netns = IPDB(nl=NetNS(nsp_name)) self.ipdb_netns.interfaces['lo'].up().commit() logging.debug("%s[+] Namespace(" + nsp_name + ") successfully created", LoggerSetup.get_log_deep(3)) # self.encapsulate_interface() except Exception as e: logging.error("%s[-] Couldn't create Namespace(" + nsp_name + ")", LoggerSetup.get_log_deep(3)) for tb in traceback.format_tb(sys.exc_info()[2]): logging.error("%s" + tb, LoggerSetup.get_log_deep(3)) logging.error("%s" + str(e), LoggerSetup.get_log_deep(3)) self.remove()
import logging log = logging.getLogger(__name__) __virtual_name__ = 'network_settings' ATTRS = [ 'family', 'txqlen', 'ipdb_scope', 'index', 'operstate', 'group', 'carrier_changes', 'ipaddr', 'neighbours', 'ifname', 'promiscuity', 'linkmode', 'broadcast', 'address', 'num_tx_queues', 'ipdb_priority', 'change', 'kind', 'qdisc', 'mtu', 'num_rx_queues', 'carrier', 'flags', 'ifi_type', 'ports' ] LAST_STATS = {} IP = IPDB() class Hashabledict(dict): ''' Helper class that implements a hash function for a dictionary ''' def __hash__(self): return hash(tuple(sorted(self.items()))) def __virtual__(): if HAS_PYROUTE2: return __virtual_name__ return False
class NVAssistent: """ The NVAssistent (NamespaceVlanAssistent) provides the following Features: 1. Creates a IPDB: stores the Network-Interfaces 2. Creates VLANs and Namespaces 3. Encapsulates the VLANs inside the Namespaces 4. Delete VLANs and Namespaces IPDB: Is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. If we want to use VLANs for Systems (with identical IPs) on the network, we need Namespaces. Each Namespace encapsulate one VLAN for the current process. The Namespace only contains the given VLAN and the 'lo'-interface. If we want to use a physical interface (like 'eth0') we have to create a bridge that connects the physical interface with Namespace by the 'veth'-interfaces. """ "" def __init__(self, link_iface_name: str): """ :param link_iface_name: 'physical'-interface like 'eth0' to which the VLAN is connected to """ self.ipdb = IPDB() self.link_iface_name = link_iface_name self.vlan_dict = dict() self.nsp_dict = dict() def create_namespace_vlan(self, remote_system: RemoteSystem): """ Creats a Namespace and a VLAN. Encapsulate the VLAN inside the Namespace. :param remote_system: Router-Obj or Powerstrip-Obj with which we want to connect to. """ if remote_system.namespace_name in self.nsp_dict.keys(): logging.debug("%s[-] Namespace already exists", LoggerSetup.get_log_deep(2)) return vlan = Vlan(self.ipdb, remote_system, self.link_iface_name) vlan.create_interface() self.vlan_dict[vlan.vlan_iface_name] = vlan namespace = Namespace(self.ipdb, str(remote_system.namespace_name)) namespace.encapsulate_interface(vlan.vlan_iface_name) self.nsp_dict[namespace.nsp_name] = namespace def get_ip_address(self, namespace_name: str, vlan_iface_name: str) -> (str, int): """ Returns the the first IP of the VLAN in the IPDB of the Namespace :param namespace_name: Namespace_name :param vlan_iface_name: VLAN_name :return: IP address (ip/mask) """ ip_address = self.nsp_dict[namespace_name].ipdb_get_ip( ipdb=False, iface_name=vlan_iface_name).split("/") ip = ip_address[0] mask = int(ip_address[1]) return ip, mask def delete_vlan(self, vlan_iface_name: str): """ Removes the given VLAN. :param vlan_iface_name: VLAN_name """ self.vlan_dict[vlan_iface_name].delete_interface() def delete_namespace(self, namespace_name: str): """ Removes the given Namespace. :param namespace_name: Namespace_name """ self.nsp_dict[namespace_name].remove() def close(self): """ Deletes all VLANs and Namespaces and releases the IPDB. """ for vlan in self.vlan_dict: self.delete_vlan(vlan) for nsp in self.nsp_dict: self.delete_namespace(nsp) self.ipdb.release()
class Vlan: def __init__(self, link_iface_name: str, vlan_iface_name: str, vlan_iface_id: int, vlan_iface_ip: str=None, vlan_iface_ip_mask: int=None): """ Creats a virtual interface on a existing interface (like eth0). It uses IPDB: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. :param link_iface_name: name of the existing interface (eth0, wlan0, ...) :param vlan_iface_name: name of the vlan :param vlan_iface_id: the id of the vlan :param vlan_iface_ip: ip of the virtual interface :param vlan_iface_ip_mask: network-mask of the virtual interface """ self.link_iface_name = link_iface_name self.vlan_iface_name = vlan_iface_name self.vlan_iface_id = vlan_iface_id self.ipdb = IPDB() # self.create_interface(link_iface_name, vlan_iface_name, vlan_iface_id, vlan_iface_ip, vlan_iface_ip_mask) def create_interface(self, vlan_iface_ip: str=None, vlan_iface_ip_mask: int=None): """ Creats a virtual interface on a existing interface (like eth0) :param vlan_iface_ip: ip of the virtual interface :param vlan_iface_ip_mask: network-mask of the virtual interface """ Logger().debug("Create VLAN Interface ...", 2) try: link_iface = self.ipdb.interfaces[self.link_iface_name] with self.ipdb.create(kind="vlan", ifname=self.vlan_iface_name, link=link_iface, vlan_id=self.vlan_iface_id).commit() as i: if vlan_iface_ip: i.add_ip(vlan_iface_ip, vlan_iface_ip_mask) i.mtu = 1400 if not vlan_iface_ip: self._wait_for_ip_assignment() vlan_iface_ip = self._get_ipv4_from_dictionary(self.ipdb.interfaces[self.vlan_iface_name]) Logger().debug("[+] " + self.vlan_iface_name + " created with: Link=" + self.link_iface_name + ", VLAN_ID=" + str(self.vlan_iface_id) + ", IP=" + vlan_iface_ip, 3) except Exception as e: Logger().debug("[-] " + self.vlan_iface_name + " couldn't be created", 3) Logger().error(str(e), 3) def delete_interface(self): """ Removes the virtual interface """ Logger().debug("Delete VLAN Interface ...", 2) try: self.ipdb.interfaces[self.vlan_iface_name].remove().commit() self.ipdb.release() Logger().debug("[+] Interface(" + self.vlan_iface_name + ") successfully deleted", 3) except KeyError: Logger().debug("[+] Interface(" + self.vlan_iface_name + ") is already deleted", 3) return except Exception as e: Logger().debug("[-] Interface(" + self.vlan_iface_name + ") couldn't be deleted. Try 'ip link delete <vlan_name>'", 3) Logger().error(str(e), 3) def _wait_for_ip_assignment(self): """ Waits until the dhcp-client got an ip """ Logger().debug("Wait for ip assignment via dhcp for VLAN Interface(" + self.vlan_iface_name + ") ...", 3) if not self.get_ip(): os.system('dhclient ' + self.vlan_iface_name) while self.get_ip() is None: time.sleep(0.5) def get_ip(self) -> str: """ Gets the ip of a specific interface :return: the ip of an interface without network-mask """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sockfd = sock.fileno() ifreq = struct.pack('16sH14s', self.vlan_iface_name.encode('utf-8'), socket.AF_INET, b'\x00' * 14) try: res = fcntl.ioctl(sockfd, 0x8915, ifreq) except: return None ip = struct.unpack('16sH2x4s8x', res)[2] return socket.inet_ntoa(ip) def _get_ipv4_from_dictionary(self, iface) -> str: """ Gets the ip and network-mask from the ipdb :param iface: the interface from ipdb :return: ip with network-mask """ ipaddr_dictionary = iface.ipaddr for i in range(len(ipaddr_dictionary)): ip = ipaddr_dictionary[i]['address'] mask = ipaddr_dictionary[i]['prefixlen'] if re.match("((((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3})(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))", ip): return ip + "/" + str(mask) return None
class Namespace: """ A Network-Namespace is logically another copy of the network stack, with its own routes, firewall rules, and network devices. By default a process inherits its Network-Namespace from its parent. Initially all the processes share the same default Network-Namespace from the init process. """"" def __init__(self, ipdb: IPDB, nsp_name: str): """ :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immediately in OS, but waits until commit() is called. :param nsp_name: Name of the Namespace """ logging.debug("%sCreate Namespace ...", LoggerSetup.get_log_deep(2)) self.ipdb = ipdb if ipdb else IPDB() self.ipdb_netns = None self.nsp_name = nsp_name try: self.ipdb_netns = IPDB(nl=NetNS(nsp_name)) self.ipdb_netns.interfaces['lo'].up().commit() logging.debug("%s[+] Namespace(" + nsp_name + ") successfully created", LoggerSetup.get_log_deep(3)) # self.encapsulate_interface() except Exception as e: logging.error("%s[-] Couldn't create Namespace(" + nsp_name + ")", LoggerSetup.get_log_deep(3)) for tb in traceback.format_tb(sys.exc_info()[2]): logging.error("%s" + tb, LoggerSetup.get_log_deep(3)) logging.error("%s" + str(e), LoggerSetup.get_log_deep(3)) self.remove() def remove(self): """ Removes the Namespace and all included Network-Interfaces. """ logging.debug("%sDelete Namespace ...", LoggerSetup.get_log_deep(2)) try: netns.remove(self.nsp_name) self.ipdb_netns.release() logging.debug("%s[+] Namespace(" + self.nsp_name + ") successfully deleted", LoggerSetup.get_log_deep(3)) except Exception as e: if re.match("\[Errno 2\]*", str(e)): logging.debug("%s[+] Namespace(" + self.nsp_name + ") is already deleted", LoggerSetup.get_log_deep(3)) return logging.error("%s[-] Namespace(" + self.nsp_name + ") couldn't be deleted. Try 'ip netns delete <namespace_name>'", LoggerSetup.get_log_deep(3)) logging.error("%s" + str(e), LoggerSetup.get_log_deep(3)) def encapsulate_interface(self, iface_name: str): """ Encapsulate the the given Network-Interface inside the Namespace. :param iface_name: Name of the selected Network-Interface """ iface_ip = self.ipdb_get_ip(True, iface_name) try: with self.ipdb.interfaces[iface_name] as iface: iface.net_ns_fd = self.nsp_name # the interface automatically switched the database and is now inside ipdb_netns_dictionary[vlan_iface_name] with self.ipdb_netns.interfaces[iface_name] as iface: iface.add_ip(iface_ip) # '192.168.1.11/24' iface.up() logging.debug("%s[+] Encapsulate Interface(" + iface_name + ")", LoggerSetup.get_log_deep(3)) except Exception as e: logging.error("%s[-] Couldn't encapsulate the Interface(" + iface_name + ")", LoggerSetup.get_log_deep(3)) logging.error("%s" + str(e), LoggerSetup.get_log_deep(3)) def ipdb_get_ip(self, ipdb: bool, iface_name: str, not_this_ip: str = None): """ Reads the 'first' IP from the IPDB or if intended from IPDN_NETNS. :param ipdb: If 'True' IPDB is used, 'False' IPDB_NETNS is used :param iface_name: Name of the selected Network-Interface :param not_this_ip: If we know the first IP and are searching for another :return: IP with the format ip/mask """ if ipdb: iface = self.ipdb.interfaces[iface_name] else: iface = self.ipdb_netns.interfaces[iface_name] ipaddr_dictionary = iface.ipaddr for i in range(len(ipaddr_dictionary)): ip = ipaddr_dictionary[i]['address'] mask = ipaddr_dictionary[i]['prefixlen'] if re.match("((((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3})(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))", ip): if ip != not_this_ip: return ip + "/" + str(mask) return ""
class NVAssistent: """ 1. Creates a IPDB: stores interfaces 2. Creates a Bridge: connects multiple interfaces via a virtual switch 3. Creates VLANs 4. Creates Veths: connects a Namespace with the Bridge 5. Creates Namespaces: encapsulte a given Interface If we want to use VLANs for Systems (with identical IPs) on the network, we need Namespaces. Each Namespace encapsulate one VLAN for the current process. The Namespace only contains the given VLAN and the 'lo'-interface. If we want to use a physical interface (like 'eth0') we have to create a bridge that connects the physical interface with Namespace by the 'veth'-interfaces. """"" def __init__(self, link_iface_name: str): """ Initialize the IPDB for the interfaces and creates a Bridge (virtual switch). :param link_iface_name: 'physical'-interface like 'eth0' """ self.ipdb = IPDB() self.link_iface_name = link_iface_name self.vlan_dict = dict() self.veth_dict = dict() self.nsp_dict = dict() def create_namespace_vlan(self, remote_system: RemoteSystem): """ Creats a Namespace and a VLAN. Encapsulate the VLAN inside the Namespace. :param remote_system: Router or powerstrip """ if remote_system.namespace_name in self.nsp_dict.keys(): logging.debug("%s[-] Namespace already exists", LoggerSetup.get_log_deep(2)) return vlan = Vlan(self.ipdb, remote_system, self.link_iface_name) vlan.create_interface() self.vlan_dict[vlan.vlan_iface_name] = vlan namespace = Namespace(self.ipdb, str(remote_system.namespace_name)) namespace.encapsulate_interface(vlan.vlan_iface_name) self.nsp_dict[namespace.nsp_name] = namespace def get_ip_address(self, namespace_name: str, vlan_iface_name: str) -> (str, int): """ Returns the the first IP of the VLAN in the IPDB of the Namespace :param namespace_name: Namespace name :param vlan_iface_name: VLAN name :return: IP address (ip, mask) """ ip_address = self.nsp_dict[namespace_name].ipdb_get_ip(ipdb=False, iface_name=vlan_iface_name).split("/") ip = ip_address[0] mask = int(ip_address[1]) return ip, mask def delete_vlan(self, vlan_iface_name: str): self.vlan_dict[vlan_iface_name].delete_interface() def delete_veth(self, veth_iface_name: str): self.veth_dict[veth_iface_name].delete_interface() def delete_namespace(self, nsp_name: str): self.nsp_dict[nsp_name].remove() def close(self): """ Deletes all VLANs, 'veth'-interfaces and Namespaces """ for vlan in self.vlan_dict: self.delete_vlan(vlan) for veth in self.veth_dict: self.delete_veth(veth) for nsp in self.nsp_dict: self.delete_namespace(nsp) self.ipdb.release() logging.debug("%sKill dhclient ...", LoggerSetup.get_log_deep(2)) os.system('pkill dhclient')
class Namespace: def __init__(self, nsp_name: str, ipdb: IPDB): """ Creats a namespace for a specific vlan_iface :param nsp_name: :param vlan_iface_name: :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. """ Logger().debug("Create Namespace ...", 2) self.nsp_name = nsp_name self.id = id self.vlan_iface_name = "" self.vlan_iface_ip = "0.0.0.0" self.ipdb = ipdb self.ipdb_netns = None try: self.ipdb_netns = IPDB(nl=NetNS(nsp_name)) netns.setns(nsp_name) self.ipdb_netns.interfaces['lo'].up().commit() Logger().debug("[+] Namespace(" + nsp_name + ") successfully created", 3) # self.encapsulate_interface() except Exception as e: Logger().debug("[-] Couldn't create Namespace(" + nsp_name + ")", 3) for tb in traceback.format_tb(sys.exc_info()[2]): Logger().error(tb, 3) Logger().error(str(e), 3) self.remove() def remove(self): """ Removes the virtual namespace and interface. """ Logger().debug("Delete Namespace ...", 2) try: if self.ipdb_netns is None: netns.remove(self.nsp_name) else: self.ipdb_netns.interfaces[self.vlan_iface_name].nl.remove() self.ipdb_netns.release() Logger().debug("[+] Namespace(" + self.nsp_name + ") successfully deleted", 3) except Exception as e: if re.match("\[Errno 2\]*",str(e)): Logger().debug("[+] Namespace(" + self.nsp_name + ") is already deleted", 3) return Logger().debug("[-] Namespace(" + self.nsp_name + ") couldn't be deleted. Try 'ip netns delete <namespace_name>'", 3) Logger().error(str(e), 3) def encapsulate_interface(self, vlan_iface_name: str): """ Capture the assigned interface in a namespace. """ self.vlan_iface_name = vlan_iface_name self.vlan_iface_ip = self._get_ipv4_from_dictionary(self.ipdb.interfaces[self.vlan_iface_name]) try: with self.ipdb.interfaces[self.vlan_iface_name] as vlan: vlan.net_ns_fd = self.nsp_name # the interface automatically switched the database and is now inside ipdb_netns_dictionary[vlan_iface_name] with self.ipdb_netns.interfaces[self.vlan_iface_name] as vlan: vlan.add_ip(self.vlan_iface_ip) # '192.168.1.11/24' vlan.up() Logger().debug("[+] Encapsulate Interface(" + self.vlan_iface_name + ")", 3) except Exception as e: Logger().debug("[-] Couldn't encapsulate the Interface(" + self.vlan_iface_name + ")", 3) Logger().error(str(e), 3) def get_ip_of_encapsulate_interface(self) -> str: """ :return: The IP(without ip_mask) of the interface encapsulated in this namespace """ return self.vlan_iface_ip.split('/')[0] def _get_ipv4_from_dictionary(self, iface) -> str: """ Gets the ip and network-mask from the ipdb :param iface: the interface from ipdb :return: ip with network-mask """ ipaddr_dictionary = iface.ipaddr for i in range(len(ipaddr_dictionary)): ip = ipaddr_dictionary[i]['address'] mask = ipaddr_dictionary[i]['prefixlen'] if re.match("((((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3})(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))", ip): return ip+"/"+str(mask)
class Namespace: """ A Network-Namespace is logically another copy of the network stack, with its own routes, firewall rules, and network devices. By default a process inherits its Network-Namespace from its parent. Initially all the processes share the same default Network-Namespace from the init process. """ "" def __init__(self, ipdb: IPDB, nsp_name: str): """ :param ipdb: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immediately in OS, but waits until commit() is called. :param nsp_name: Name of the Namespace """ logging.debug("%sCreate Namespace ...", LoggerSetup.get_log_deep(2)) self.ipdb = ipdb if ipdb else IPDB() self.ipdb_netns = None self.nsp_name = nsp_name try: self.ipdb_netns = IPDB(nl=NetNS(nsp_name)) self.ipdb_netns.interfaces['lo'].up().commit() logging.debug( "%s[+] Namespace(" + nsp_name + ") successfully created", LoggerSetup.get_log_deep(3)) # self.encapsulate_interface() except Exception as e: logging.error("%s[-] Couldn't create Namespace(" + nsp_name + ")", LoggerSetup.get_log_deep(3)) for tb in traceback.format_tb(sys.exc_info()[2]): logging.error("%s" + tb, LoggerSetup.get_log_deep(3)) logging.error("%s" + str(e), LoggerSetup.get_log_deep(3)) self.remove() def remove(self): """ Removes the Namespace and all included Network-Interfaces. """ logging.debug("%sDelete Namespace ...", LoggerSetup.get_log_deep(2)) try: netns.remove(self.nsp_name) self.ipdb_netns.release() logging.debug( "%s[+] Namespace(" + self.nsp_name + ") successfully deleted", LoggerSetup.get_log_deep(3)) except Exception as e: if re.match("\[Errno 2\]*", str(e)): logging.debug( "%s[+] Namespace(" + self.nsp_name + ") is already deleted", LoggerSetup.get_log_deep(3)) return logging.error( "%s[-] Namespace(" + self.nsp_name + ") couldn't be deleted. Try 'ip netns delete <namespace_name>'", LoggerSetup.get_log_deep(3)) logging.error("%s" + str(e), LoggerSetup.get_log_deep(3)) def encapsulate_interface(self, iface_name: str): """ Encapsulate the the given Network-Interface inside the Namespace. :param iface_name: Name of the selected Network-Interface """ iface_ip = self.ipdb_get_ip(True, iface_name) try: with self.ipdb.interfaces[iface_name] as iface: iface.net_ns_fd = self.nsp_name # the interface automatically switched the database and is now inside ipdb_netns_dictionary[vlan_iface_name] with self.ipdb_netns.interfaces[iface_name] as iface: iface.add_ip(iface_ip) # '192.168.1.11/24' iface.up() logging.debug("%s[+] Encapsulate Interface(" + iface_name + ")", LoggerSetup.get_log_deep(3)) except Exception as e: logging.error( "%s[-] Couldn't encapsulate the Interface(" + iface_name + ")", LoggerSetup.get_log_deep(3)) logging.error("%s" + str(e), LoggerSetup.get_log_deep(3)) def ipdb_get_ip(self, ipdb: bool, iface_name: str, not_this_ip: str = None): """ Reads the 'first' IP from the IPDB or if intended from IPDN_NETNS. :param ipdb: If 'True' IPDB is used, 'False' IPDB_NETNS is used :param iface_name: Name of the selected Network-Interface :param not_this_ip: If we know the first IP and are searching for another :return: IP with the format ip/mask """ if ipdb: iface = self.ipdb.interfaces[iface_name] else: iface = self.ipdb_netns.interfaces[iface_name] ipaddr_dictionary = iface.ipaddr for i in range(len(ipaddr_dictionary)): ip = ipaddr_dictionary[i]['address'] mask = ipaddr_dictionary[i]['prefixlen'] if re.match( "((((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3})(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))", ip): if ip != not_this_ip: return ip + "/" + str(mask) return ""
class Vlan: def __init__(self, link_iface_name: str, vlan_iface_name: str, vlan_iface_id: int, vlan_iface_ip: str = None, vlan_iface_ip_mask: int = None): """ Creats a virtual interface on a existing interface (like eth0). It uses IPDB: IPDB is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. :param link_iface_name: name of the existing interface (eth0, wlan0, ...) :param vlan_iface_name: name of the vlan :param vlan_iface_id: the id of the vlan :param vlan_iface_ip: ip of the virtual interface :param vlan_iface_ip_mask: network-mask of the virtual interface """ self.link_iface_name = link_iface_name self.vlan_iface_name = vlan_iface_name self.vlan_iface_id = vlan_iface_id self.ipdb = IPDB() # self.create_interface(link_iface_name, vlan_iface_name, vlan_iface_id, vlan_iface_ip, vlan_iface_ip_mask) def create_interface(self, vlan_iface_ip: str = None, vlan_iface_ip_mask: int = None): """ Creats a virtual interface on a existing interface (like eth0) :param vlan_iface_ip: ip of the virtual interface :param vlan_iface_ip_mask: network-mask of the virtual interface """ Logger().debug("Create VLAN Interface ...", 2) try: link_iface = self.ipdb.interfaces[self.link_iface_name] with self.ipdb.create(kind="vlan", ifname=self.vlan_iface_name, link=link_iface, vlan_id=self.vlan_iface_id).commit() as i: if vlan_iface_ip: i.add_ip(vlan_iface_ip, vlan_iface_ip_mask) i.mtu = 1400 if not vlan_iface_ip: self._wait_for_ip_assignment() vlan_iface_ip = self._get_ipv4_from_dictionary( self.ipdb.interfaces[self.vlan_iface_name]) Logger().debug( "[+] " + self.vlan_iface_name + " created with: Link=" + self.link_iface_name + ", VLAN_ID=" + str(self.vlan_iface_id) + ", IP=" + vlan_iface_ip, 3) except Exception as e: Logger().debug( "[-] " + self.vlan_iface_name + " couldn't be created", 3) Logger().error(str(e), 3) def delete_interface(self): """ Removes the virtual interface """ Logger().debug("Delete VLAN Interface ...", 2) try: self.ipdb.interfaces[self.vlan_iface_name].remove().commit() self.ipdb.release() Logger().debug( "[+] Interface(" + self.vlan_iface_name + ") successfully deleted", 3) except KeyError: Logger().debug( "[+] Interface(" + self.vlan_iface_name + ") is already deleted", 3) return except Exception as e: Logger().debug( "[-] Interface(" + self.vlan_iface_name + ") couldn't be deleted. Try 'ip link delete <vlan_name>'", 3) Logger().error(str(e), 3) def _wait_for_ip_assignment(self): """ Waits until the dhcp-client got an ip """ Logger().debug( "Wait for ip assignment via dhcp for VLAN Interface(" + self.vlan_iface_name + ") ...", 3) if not self.get_ip(): os.system('dhclient ' + self.vlan_iface_name) while self.get_ip() is None: time.sleep(0.5) def get_ip(self) -> str: """ Gets the ip of a specific interface :return: the ip of an interface without network-mask """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sockfd = sock.fileno() ifreq = struct.pack('16sH14s', self.vlan_iface_name.encode('utf-8'), socket.AF_INET, b'\x00' * 14) try: res = fcntl.ioctl(sockfd, 0x8915, ifreq) except: return None ip = struct.unpack('16sH2x4s8x', res)[2] return socket.inet_ntoa(ip) def _get_ipv4_from_dictionary(self, iface) -> str: """ Gets the ip and network-mask from the ipdb :param iface: the interface from ipdb :return: ip with network-mask """ ipaddr_dictionary = iface.ipaddr for i in range(len(ipaddr_dictionary)): ip = ipaddr_dictionary[i]['address'] mask = ipaddr_dictionary[i]['prefixlen'] if re.match( "((((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3})(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))", ip): return ip + "/" + str(mask) return None
class NVAssistent: """ The NVAssistent (NamespaceVlanAssistent) provides the following Features: 1. Creates a IPDB: stores the Network-Interfaces 2. Creates VLANs and Namespaces 3. Encapsulates the VLANs inside the Namespaces 4. Delete VLANs and Namespaces IPDB: Is a transactional database, containing records, representing network stack objects. Any change in the database is not reflected immidiately in OS, but waits until commit() is called. If we want to use VLANs for Systems (with identical IPs) on the network, we need Namespaces. Each Namespace encapsulate one VLAN for the current process. The Namespace only contains the given VLAN and the 'lo'-interface. If we want to use a physical interface (like 'eth0') we have to create a bridge that connects the physical interface with Namespace by the 'veth'-interfaces. """"" def __init__(self, link_iface_name: str): """ :param link_iface_name: 'physical'-interface like 'eth0' to which the VLAN is connected to """ self.ipdb = IPDB() self.link_iface_name = link_iface_name self.vlan_dict = dict() self.nsp_dict = dict() def create_namespace_vlan(self, remote_system: RemoteSystem): """ Creats a Namespace and a VLAN. Encapsulate the VLAN inside the Namespace. :param remote_system: Router-Obj or Powerstrip-Obj with which we want to connect to. """ if remote_system.namespace_name in self.nsp_dict.keys(): logging.debug("%s[-] Namespace already exists", LoggerSetup.get_log_deep(2)) return vlan = Vlan(self.ipdb, remote_system, self.link_iface_name) vlan.create_interface() self.vlan_dict[vlan.vlan_iface_name] = vlan namespace = Namespace(self.ipdb, str(remote_system.namespace_name)) namespace.encapsulate_interface(vlan.vlan_iface_name) self.nsp_dict[namespace.nsp_name] = namespace def get_ip_address(self, namespace_name: str, vlan_iface_name: str) -> (str, int): """ Returns the the first IP of the VLAN in the IPDB of the Namespace :param namespace_name: Namespace_name :param vlan_iface_name: VLAN_name :return: IP address (ip/mask) """ ip_address = self.nsp_dict[namespace_name].ipdb_get_ip(ipdb=False, iface_name=vlan_iface_name).split("/") ip = ip_address[0] mask = int(ip_address[1]) return ip, mask def delete_vlan(self, vlan_iface_name: str): """ Removes the given VLAN. :param vlan_iface_name: VLAN_name """ self.vlan_dict[vlan_iface_name].delete_interface() def delete_namespace(self, namespace_name: str): """ Removes the given Namespace. :param namespace_name: Namespace_name """ self.nsp_dict[namespace_name].remove() def close(self): """ Deletes all VLANs and Namespaces and releases the IPDB. """ for vlan in self.vlan_dict: self.delete_vlan(vlan) for nsp in self.nsp_dict: self.delete_namespace(nsp) self.ipdb.release()