#!/usr/bin/env python3 # VXNetworkInstance.py - Class implementing a PVC VM network and run by pvcnoded # Part of the Parallel Virtual Cluster (PVC) system # # Copyright (C) 2018-2024 Joshua M. Boniface # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ############################################################################### import os import time from textwrap import dedent import daemon_lib.common as common class VXNetworkInstance(object): # Initialization function def __init__(self, vni, zkhandler, config, logger, this_node, dns_aggregator): self.vni = vni self.zkhandler = zkhandler self.config = config self.logger = logger self.this_node = this_node self.dns_aggregator = dns_aggregator self.cluster_dev = config["cluster_dev"] self.cluster_mtu = config["cluster_mtu"] self.bridge_dev = config["bridge_dev"] self.bridge_mtu = config["bridge_mtu"] self.nettype = self.zkhandler.read(("network.type", self.vni)) if self.nettype == "bridged": self.base_nic = "vlan{}".format(self.vni) self.bridge_nic = "vmbr{}".format(self.vni) self.max_mtu = self.bridge_mtu self.logger.out( "Creating new bridged network", prefix="VNI {}".format(self.vni), state="i", ) self.init_bridged() elif self.nettype == "managed": self.base_nic = "vxlan{}".format(self.vni) self.bridge_nic = "vmbr{}".format(self.vni) self.max_mtu = self.cluster_mtu - 50 self.logger.out( "Creating new managed network", prefix="VNI {}".format(self.vni), state="i", ) self.init_managed() else: self.base_nic = None self.bridge_nic = None self.max_mtu = 0 self.logger.out( "Invalid network type {}".format(self.nettype), prefix="VNI {}".format(self.vni), state="i", ) pass # Initialize a bridged network def init_bridged(self): self.old_description = None self.description = None try: self.vx_mtu = self.zkhandler.read(("network.mtu", self.vni)) self.validateNetworkMTU() except Exception: self.vx_mtu = None # Zookeper handlers for changed states @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network", self.vni) ) def watch_network_description(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.description != data.decode("ascii"): self.old_description = self.description self.description = data.decode("ascii") # Try block for migration purposes try: @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.mtu", self.vni) ) def watch_network_mtu(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and str(self.vx_mtu) != data.decode("ascii"): self.vx_mtu = data.decode("ascii") self.validateNetworkMTU() self.updateNetworkMTU() except Exception: self.validateNetworkMTU() self.createNetworkBridged() # Initialize a managed network def init_managed(self): self.old_description = None self.description = None self.domain = None self.name_servers = None self.ip6_gateway = self.zkhandler.read(("network.ip6.gateway", self.vni)) self.ip6_network = self.zkhandler.read(("network.ip6.network", self.vni)) self.ip6_cidrnetmask = self.zkhandler.read( ("network.ip6.network", self.vni) ).split("/")[-1] self.dhcp6_flag = self.zkhandler.read(("network.ip6.dhcp", self.vni)) self.ip4_gateway = self.zkhandler.read(("network.ip4.gateway", self.vni)) self.ip4_network = self.zkhandler.read(("network.ip4.network", self.vni)) self.ip4_cidrnetmask = self.zkhandler.read( ("network.ip4.network", self.vni) ).split("/")[-1] self.dhcp4_flag = self.zkhandler.read(("network.ip4.dhcp", self.vni)) self.dhcp4_start = self.zkhandler.read(("network.ip4.dhcp_start", self.vni)) self.dhcp4_end = self.zkhandler.read(("network.ip4.dhcp_end", self.vni)) try: self.vx_mtu = self.zkhandler.read(("network.mtu", self.vni)) self.validateNetworkMTU() except Exception: self.vx_mtu = None self.nftables_netconf_filename = "{}/networks/{}.nft".format( self.config["nft_dynamic_directory"], self.vni ) self.firewall_rules = [] self.dhcp_server_daemon = None self.dnsmasq_hostsdir = "{}/{}".format( self.config["dnsmasq_dynamic_directory"], self.vni ) self.dhcp_reservations = [] # Create the network hostsdir common.run_os_command("/bin/mkdir --parents {}".format(self.dnsmasq_hostsdir)) self.firewall_rules_base = """# Rules for network {vxlannic} add chain inet filter {vxlannic}-in add chain inet filter {vxlannic}-out add rule inet filter {vxlannic}-in counter add rule inet filter {vxlannic}-out counter # Allow ICMP traffic into the router from network add rule inet filter input ip protocol icmp meta iifname {bridgenic} counter accept add rule inet filter input ip6 nexthdr icmpv6 meta iifname {bridgenic} counter accept # Allow DNS, DHCP, and NTP traffic into the router from network add rule inet filter input tcp dport 53 meta iifname {bridgenic} counter accept add rule inet filter input udp dport 53 meta iifname {bridgenic} counter accept add rule inet filter input udp dport 67 meta iifname {bridgenic} counter accept add rule inet filter input udp dport 123 meta iifname {bridgenic} counter accept add rule inet filter input ip6 nexthdr udp udp dport 547 meta iifname {bridgenic} counter accept # Allow metadata API into the router from network add rule inet filter input tcp dport 80 meta iifname {bridgenic} counter accept # Block traffic into the router from network add rule inet filter input meta iifname {bridgenic} counter drop """.format( vxlannic=self.base_nic, bridgenic=self.bridge_nic ) self.firewall_rules_v4 = """# Jump from forward chain to this chain when matching net (IPv4) add rule inet filter forward ip daddr {netaddr4} counter jump {vxlannic}-in add rule inet filter forward ip saddr {netaddr4} counter jump {vxlannic}-out """.format( netaddr4=self.ip4_network, vxlannic=self.base_nic, ) self.firewall_rules_v6 = """# Jump from forward chain to this chain when matching net (IPv4) add rule inet filter forward ip6 daddr {netaddr6} counter jump {vxlannic}-in add rule inet filter forward ip6 saddr {netaddr6} counter jump {vxlannic}-out """.format( netaddr6=self.ip6_network, vxlannic=self.base_nic, ) self.firewall_rules_in = self.zkhandler.children(("network.rule.in", self.vni)) self.firewall_rules_out = self.zkhandler.children( ("network.rule.out", self.vni) ) # Zookeper handlers for changed states @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network", self.vni) ) def watch_network_description(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.description != data.decode("ascii"): self.old_description = self.description self.description = data.decode("ascii") if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.domain", self.vni) ) def watch_network_domain(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.domain != data.decode("ascii"): domain = data.decode("ascii") if self.dhcp_server_daemon: self.dns_aggregator.remove_network(self) self.domain = domain if self.dhcp_server_daemon: self.dns_aggregator.add_network(self) self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.nameservers", self.vni) ) def watch_network_name_servers(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.name_servers != data.decode("ascii"): name_servers = data.decode("ascii").split(",") if self.dhcp_server_daemon: self.dns_aggregator.remove_network(self) self.name_servers = name_servers if self.dhcp_server_daemon: self.dns_aggregator.add_network(self) self.stopDHCPServer() self.startDHCPServer() # Try block for migration purposes try: @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.mtu", self.vni) ) def watch_network_mtu(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and str(self.vx_mtu) != data.decode("ascii"): self.vx_mtu = data.decode("ascii") self.validateNetworkMTU() self.updateNetworkMTU() except Exception: self.validateNetworkMTU() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.ip6.network", self.vni) ) def watch_network_ip6_network(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.ip6_network != data.decode("ascii"): ip6_network = data.decode("ascii") self.ip6_network = ip6_network self.ip6_cidrnetmask = ip6_network.split("/")[-1] if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.ip6.gateway", self.vni) ) def watch_network_gateway6(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.ip6_gateway != data.decode("ascii"): orig_gateway = self.ip6_gateway if self.this_node.coordinator_state in ["primary", "takeover"]: if orig_gateway: self.removeGateway6Address() self.ip6_gateway = data.decode("ascii") if self.this_node.coordinator_state in ["primary", "takeover"]: self.createGateway6Address() if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.ip6.dhcp", self.vni) ) def watch_network_dhcp6_status(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.dhcp6_flag != (data.decode("ascii") == "True"): self.dhcp6_flag = data.decode("ascii") == "True" if ( self.dhcp6_flag and not self.dhcp_server_daemon and self.this_node.coordinator_state in ["primary", "takeover"] ): self.startDHCPServer() elif ( self.dhcp_server_daemon and not self.dhcp4_flag and self.this_node.coordinator_state in ["primary", "takeover"] ): self.stopDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.ip4.network", self.vni) ) def watch_network_ip4_network(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.ip4_network != data.decode("ascii"): ip4_network = data.decode("ascii") self.ip4_network = ip4_network self.ip4_cidrnetmask = ip4_network.split("/")[-1] if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.ip4.gateway", self.vni) ) def watch_network_gateway4(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.ip4_gateway != data.decode("ascii"): orig_gateway = self.ip4_gateway if self.this_node.coordinator_state in ["primary", "takeover"]: if orig_gateway: self.removeGateway4Address() self.ip4_gateway = data.decode("ascii") if self.this_node.coordinator_state in ["primary", "takeover"]: self.createGateway4Address() if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.ip4.dhcp", self.vni) ) def watch_network_dhcp4_status(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.dhcp4_flag != (data.decode("ascii") == "True"): self.dhcp4_flag = data.decode("ascii") == "True" if ( self.dhcp4_flag and not self.dhcp_server_daemon and self.this_node.coordinator_state in ["primary", "takeover"] ): self.startDHCPServer() elif ( self.dhcp_server_daemon and not self.dhcp6_flag and self.this_node.coordinator_state in ["primary", "takeover"] ): self.stopDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.ip4.dhcp_start", self.vni) ) def watch_network_dhcp4_start(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.dhcp4_start != data.decode("ascii"): self.dhcp4_start = data.decode("ascii") if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.DataWatch( self.zkhandler.schema.path("network.ip4.dhcp_end", self.vni) ) def watch_network_dhcp4_end(data, stat, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if data and self.dhcp4_end != data.decode("ascii"): self.dhcp4_end = data.decode("ascii") if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.ChildrenWatch( self.zkhandler.schema.path("network.reservation", self.vni) ) def watch_network_dhcp_reservations(new_reservations, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False if self.dhcp_reservations != new_reservations: old_reservations = self.dhcp_reservations self.dhcp_reservations = new_reservations if self.this_node.coordinator_state in ["primary", "takeover"]: self.updateDHCPReservations(old_reservations, new_reservations) if self.dhcp_server_daemon: self.stopDHCPServer() self.startDHCPServer() @self.zkhandler.zk_conn.ChildrenWatch( self.zkhandler.schema.path("network.rule.in", self.vni) ) def watch_network_firewall_rules_in(new_rules, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False # Don't run on the first pass if self.firewall_rules_in != new_rules: self.firewall_rules_in = new_rules self.updateFirewallRules() @self.zkhandler.zk_conn.ChildrenWatch( self.zkhandler.schema.path("network.rule.out", self.vni) ) def watch_network_firewall_rules_out(new_rules, event=""): if event and event.type == "DELETED": # The key has been deleted after existing before; terminate this watcher # because this class instance is about to be reaped in Daemon.py return False # Don't run on the first pass if self.firewall_rules_out != new_rules: self.firewall_rules_out = new_rules self.updateFirewallRules() self.createNetworkManaged() self.createFirewall() def getvni(self): return self.vni def validateNetworkMTU(self): update_mtu = False # Explicitly set the MTU to max_mtu if unset (in Zookeeper too assuming the key exists) if self.vx_mtu == "" or self.vx_mtu is None: self.logger.out( "MTU not specified; setting to maximum MTU {} instead".format( self.max_mtu ), prefix="VNI {}".format(self.vni), state="w", ) self.vx_mtu = self.max_mtu update_mtu = True # Set MTU to an integer (if it's not) if not isinstance(self.vx_mtu, int): self.vx_mtu = int(self.vx_mtu) # Ensure the MTU is valid if self.vx_mtu > self.max_mtu: self.logger.out( "MTU {} is larger than maximum MTU {}; setting to maximum MTU instead".format( self.vx_mtu, self.max_mtu ), prefix="VNI {}".format(self.vni), state="w", ) self.vx_mtu = self.max_mtu update_mtu = True if update_mtu: # Try block for migration purposes try: self.zkhandler.write([(("network.mtu", self.vni), self.vx_mtu)]) except Exception as e: self.logger.out( "Could not update MTU in Zookeeper: {}".format(e), prefix="VNI {}".format(self.vni), state="w", ) def updateNetworkMTU(self): self.logger.out( "Setting network MTU to {}".format(self.vx_mtu), prefix="VNI {}".format(self.vni), state="i", ) # Set MTU of base and bridge NICs common.run_os_command( "ip link set {} mtu {} up".format(self.base_nic, self.vx_mtu) ) common.run_os_command( "ip link set {} mtu {} up".format(self.bridge_nic, self.vx_mtu) ) def updateDHCPReservations(self, old_reservations_list, new_reservations_list): for reservation in new_reservations_list: if reservation not in old_reservations_list: # Add new reservation file filename = "{}/{}".format(self.dnsmasq_hostsdir, reservation) ipaddr = self.zkhandler.read( ("network.reservation", self.vni, "reservation.ip", reservation) ) entry = "{},{}".format(reservation, ipaddr) # Write the entry with open(filename, "w") as outfile: outfile.write(entry) for reservation in old_reservations_list: if reservation not in new_reservations_list: # Remove old reservation file filename = "{}/{}".format(self.dnsmasq_hostsdir, reservation) try: os.remove(filename) self.dhcp_server_daemon.signal("hup") except Exception: pass def updateFirewallRules(self): if not self.ip4_network: return self.logger.out( "Updating firewall rules", prefix="VNI {}".format(self.vni), state="i" ) ordered_acls_in = {} ordered_acls_out = {} sorted_acl_list = {"in": [], "out": []} full_ordered_rules = [] for acl in self.firewall_rules_in: order = self.zkhandler.read( ("network.rule.in", self.vni, "rule.order", acl) ) ordered_acls_in[order] = acl for acl in self.firewall_rules_out: order = self.zkhandler.read( ("network.rule.out", self.vni, "rule.order", acl) ) ordered_acls_out[order] = acl for order in sorted(ordered_acls_in.keys()): sorted_acl_list["in"].append(ordered_acls_in[order]) for order in sorted(ordered_acls_out.keys()): sorted_acl_list["out"].append(ordered_acls_out[order]) for direction in "in", "out": for acl in sorted_acl_list[direction]: rule_prefix = "add rule inet filter vxlan{}-{} counter".format( self.vni, direction ) rule_data = self.zkhandler.read( (f"network.rule.{direction}", self.vni, "rule.rule", acl) ) rule = "{} {}".format(rule_prefix, rule_data) full_ordered_rules.append(rule) firewall_rules = self.firewall_rules_base if self.ip6_gateway != "None": firewall_rules += self.firewall_rules_v6 if self.ip4_gateway != "None": firewall_rules += self.firewall_rules_v4 output = "{}\n# User rules\n{}\n".format( firewall_rules, "\n".join(full_ordered_rules) ) with open(self.nftables_netconf_filename, "w") as nfnetfile: nfnetfile.write(dedent(output)) # Reload firewall rules nftables_base_filename = "{}/base.nft".format( self.config["nft_dynamic_directory"] ) common.reload_firewall_rules(nftables_base_filename, logger=self.logger) # Create bridged network configuration def createNetworkBridged(self): self.logger.out( "Creating bridged vLAN device {} on interface {}".format( self.base_nic, self.bridge_dev ), prefix="VNI {}".format(self.vni), state="i", ) # Create vLAN interface common.run_os_command( "ip link add link {} name {} type vlan id {}".format( self.bridge_dev, self.base_nic, self.vni ) ) # Create bridge interface common.run_os_command("brctl addbr {}".format(self.bridge_nic)) self.updateNetworkMTU() # Disable tx checksum offload on bridge interface (breaks DHCP on Debian < 9) common.run_os_command("ethtool -K {} tx off".format(self.bridge_nic)) # Disable IPv6 on bridge interface (prevents leakage) common.run_os_command( "sysctl net.ipv6.conf.{}.disable_ipv6=1".format(self.bridge_nic) ) # Add vLAN interface to bridge interface common.run_os_command( "brctl addif {} {}".format(self.bridge_nic, self.base_nic) ) # Create managed network configuration def createNetworkManaged(self): self.logger.out( "Creating VXLAN device on interface {}".format(self.cluster_dev), prefix="VNI {}".format(self.vni), state="i", ) # Create VXLAN interface common.run_os_command( "ip link add {} type vxlan id {} dstport 4789 dev {}".format( self.base_nic, self.vni, self.cluster_dev ) ) # Create bridge interface common.run_os_command("brctl addbr {}".format(self.bridge_nic)) self.updateNetworkMTU() # Disable tx checksum offload on bridge interface (breaks DHCP on Debian < 9) common.run_os_command("ethtool -K {} tx off".format(self.bridge_nic)) # Disable IPv6 DAD on bridge interface common.run_os_command( "sysctl net.ipv6.conf.{}.accept_dad=0".format(self.bridge_nic) ) # Add VXLAN interface to bridge interface common.run_os_command( "brctl addif {} {}".format(self.bridge_nic, self.base_nic) ) def createFirewall(self): if self.nettype == "managed": # For future use self.updateFirewallRules() def createGateways(self): if self.nettype == "managed": if self.ip6_gateway != "None": self.createGateway6Address() if self.ip4_gateway != "None": self.createGateway4Address() def createGateway6Address(self): if self.this_node.coordinator_state in ["primary", "takeover"]: self.logger.out( "Creating gateway {}/{} on interface {}".format( self.ip6_gateway, self.ip6_cidrnetmask, self.bridge_nic ), prefix="VNI {}".format(self.vni), state="i", ) common.createIPAddress( self.ip6_gateway, self.ip6_cidrnetmask, self.bridge_nic ) def createGateway4Address(self): if self.this_node.coordinator_state in ["primary", "takeover"]: self.logger.out( "Creating gateway {}/{} on interface {}".format( self.ip4_gateway, self.ip4_cidrnetmask, self.bridge_nic ), prefix="VNI {}".format(self.vni), state="i", ) common.createIPAddress( self.ip4_gateway, self.ip4_cidrnetmask, self.bridge_nic ) def startDHCPServer(self): if ( self.this_node.coordinator_state in ["primary", "takeover"] and self.nettype == "managed" ): self.logger.out( "Starting dnsmasq DHCP server on interface {}".format(self.bridge_nic), prefix="VNI {}".format(self.vni), state="i", ) # Recreate the environment we need for dnsmasq pvc_config_file = None try: _config_file = "/etc/pvc/pvcnoded.yaml" if not os.path.exists(_config_file): raise pvc_config_file = _config_file except Exception: pass try: _config_file = os.environ["PVC_CONFIG_FILE"] if not os.path.exists(_config_file): raise pvc_config_file = _config_file except Exception: pass if pvc_config_file is None: raise Exception dhcp_environment = { "DNSMASQ_BRIDGE_INTERFACE": self.bridge_nic, "PVC_CONFIG_FILE": pvc_config_file, } # Define the dnsmasq config fragments dhcp_configuration_base = [ "--domain-needed", "--bogus-priv", "--no-hosts", "--dhcp-authoritative", "--filterwin2k", "--expand-hosts", "--domain-needed", "--domain={}".format(self.domain), "--local=/{}/".format(self.domain), "--log-facility=-", "--log-dhcp", "--keep-in-foreground", "--leasefile-ro", "--dhcp-script={}/pvcnoded/dnsmasq-zookeeper-leases.py".format( os.getcwd() ), "--dhcp-hostsdir={}".format(self.dnsmasq_hostsdir), "--bind-interfaces", ] dhcp_configuration_v4 = [ "--listen-address={}".format(self.ip4_gateway), "--auth-zone={}".format(self.domain), "--auth-peer={}".format(self.ip4_gateway), "--auth-server={}".format(self.ip4_gateway), "--auth-sec-servers={}".format(self.ip4_gateway), ] dhcp_configuration_v4_dhcp = [ "--dhcp-option=option:ntp-server,{}".format(self.ip4_gateway), "--dhcp-range={},{},48h".format(self.dhcp4_start, self.dhcp4_end), ] dhcp_configuration_v6 = [ "--listen-address={}".format(self.ip6_gateway), "--auth-zone={}".format(self.domain), "--auth-peer={}".format(self.ip6_gateway), "--auth-server={}".format(self.ip6_gateway), "--auth-sec-servers={}".format(self.ip6_gateway), "--dhcp-option=option6:dns-server,[{}]".format(self.ip6_gateway), "--dhcp-option=option6:sntp-server,[{}]".format(self.ip6_gateway), "--enable-ra", ] dhcp_configuration_v6_dualstack = [ "--dhcp-range=net:{nic},::,constructor:{nic},ra-stateless,ra-names".format( nic=self.bridge_nic ), ] dhcp_configuration_v6_only = [ "--auth-server={}".format(self.ip6_gateway), "--dhcp-range=net:{nic},::2,::ffff:ffff:ffff:ffff,constructor:{nic},64,24h".format( nic=self.bridge_nic ), ] # Assemble the DHCP configuration dhcp_configuration = dhcp_configuration_base if self.dhcp6_flag: dhcp_configuration += dhcp_configuration_v6 if self.dhcp4_flag: dhcp_configuration += dhcp_configuration_v6_dualstack else: dhcp_configuration += dhcp_configuration_v6_only else: dhcp_configuration += dhcp_configuration_v4 if self.dhcp4_flag: dhcp_configuration += dhcp_configuration_v4_dhcp # Start the dnsmasq process in a thread print("/usr/sbin/dnsmasq {}".format(" ".join(dhcp_configuration))) self.dhcp_server_daemon = common.run_os_daemon( "/usr/sbin/dnsmasq {}".format(" ".join(dhcp_configuration)), environment=dhcp_environment, logfile="{}/dnsmasq-{}.log".format( self.config["dnsmasq_log_directory"], self.vni ), ) # Remove network def removeNetwork(self): if self.nettype == "bridged": self.removeNetworkBridged() elif self.nettype == "managed": self.removeNetworkManaged() # Remove bridged network configuration def removeNetworkBridged(self): self.logger.out( "Removing VNI device on interface {}".format(self.cluster_dev), prefix="VNI {}".format(self.vni), state="i", ) common.run_os_command("ip link set {} down".format(self.bridge_nic)) common.run_os_command("ip link set {} down".format(self.base_nic)) common.run_os_command( "brctl delif {} {}".format(self.bridge_nic, self.base_nic) ) common.run_os_command("brctl delbr {}".format(self.bridge_nic)) common.run_os_command("ip link delete {}".format(self.base_nic)) # Remove managed network configuration def removeNetworkManaged(self): self.logger.out( "Removing VNI device on interface {}".format(self.cluster_dev), prefix="VNI {}".format(self.vni), state="i", ) common.run_os_command("ip link set {} down".format(self.bridge_nic)) common.run_os_command("ip link set {} down".format(self.base_nic)) common.run_os_command( "brctl delif {} {}".format(self.bridge_nic, self.base_nic) ) common.run_os_command("brctl delbr {}".format(self.bridge_nic)) common.run_os_command("ip link delete {}".format(self.base_nic)) def removeFirewall(self): self.logger.out( "Removing firewall rules", prefix="VNI {}".format(self.vni), state="i" ) try: os.remove(self.nftables_netconf_filename) except Exception: pass # Reload firewall rules nftables_base_filename = "{}/base.nft".format( self.config["nft_dynamic_directory"] ) common.reload_firewall_rules(nftables_base_filename, logger=self.logger) def removeGateways(self): if self.nettype == "managed": if self.ip6_gateway != "None": self.removeGateway6Address() if self.ip4_gateway != "None": self.removeGateway4Address() def removeGateway6Address(self): self.logger.out( "Removing gateway {}/{} from interface {}".format( self.ip6_gateway, self.ip6_cidrnetmask, self.bridge_nic ), prefix="VNI {}".format(self.vni), state="i", ) common.removeIPAddress(self.ip6_gateway, self.ip6_cidrnetmask, self.bridge_nic) def removeGateway4Address(self): self.logger.out( "Removing gateway {}/{} from interface {}".format( self.ip4_gateway, self.ip4_cidrnetmask, self.bridge_nic ), prefix="VNI {}".format(self.vni), state="i", ) common.removeIPAddress(self.ip4_gateway, self.ip4_cidrnetmask, self.bridge_nic) def stopDHCPServer(self): if self.nettype == "managed" and self.dhcp_server_daemon: self.logger.out( "Stopping dnsmasq DHCP server on interface {}".format(self.bridge_nic), prefix="VNI {}".format(self.vni), state="i", ) # Terminate, then kill self.dhcp_server_daemon.signal("term") time.sleep(0.2) self.dhcp_server_daemon.signal("kill") self.dhcp_server_daemon = None