Joshua M. Boniface
6ce28c43af
Ensures that if a specified MTU is more than the maximum it is set to the maximum instead, and adds warning messages for both situations.
943 lines
38 KiB
Python
943 lines
38 KiB
Python
#!/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-2021 Joshua M. Boniface <joshua@boniface.me>
|
|
#
|
|
# 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 <https://www.gnu.org/licenses/>.
|
|
#
|
|
###############################################################################
|
|
|
|
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='o'
|
|
)
|
|
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='o'
|
|
)
|
|
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='o'
|
|
)
|
|
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))
|
|
except Exception:
|
|
self.vx_mtu = None
|
|
|
|
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
|
|
# 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:
|
|
pass
|
|
|
|
# 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 self.vx_mtu != data.decode('ascii'):
|
|
self.vx_mtu = data.decode('ascii')
|
|
self.updateNetworkMTU()
|
|
except Exception:
|
|
pass
|
|
|
|
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))
|
|
except Exception:
|
|
self.vx_mtu = None
|
|
|
|
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
|
|
# 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:
|
|
pass
|
|
|
|
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 self.vx_mtu != data.decode('ascii'):
|
|
self.vx_mtu = data.decode('ascii')
|
|
self.updateNetworkMTU()
|
|
except Exception:
|
|
pass
|
|
|
|
@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.router_state in ['primary', 'takeover']:
|
|
if orig_gateway:
|
|
self.removeGateway6Address()
|
|
self.ip6_gateway = data.decode('ascii')
|
|
if self.this_node.router_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.router_state in ['primary', 'takeover']:
|
|
self.startDHCPServer()
|
|
elif self.dhcp_server_daemon and not self.dhcp4_flag and self.this_node.router_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.router_state in ['primary', 'takeover']:
|
|
if orig_gateway:
|
|
self.removeGateway4Address()
|
|
self.ip4_gateway = data.decode('ascii')
|
|
if self.this_node.router_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.router_state in ['primary', 'takeover']:
|
|
self.startDHCPServer()
|
|
elif self.dhcp_server_daemon and not self.dhcp6_flag and self.this_node.router_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.router_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 updateNetworkMTU(self):
|
|
# 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='o'
|
|
)
|
|
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 {} MTU {}'.format(
|
|
self.base_nic,
|
|
self.bridge_dev,
|
|
self.vx_mtu
|
|
),
|
|
prefix='VNI {}'.format(self.vni),
|
|
state='o'
|
|
)
|
|
|
|
# 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 {} MTU {}'.format(
|
|
self.cluster_dev,
|
|
self.vx_mtu
|
|
),
|
|
prefix='VNI {}'.format(self.vni),
|
|
state='o'
|
|
)
|
|
|
|
# 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.router_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='o'
|
|
)
|
|
common.createIPAddress(self.ip6_gateway, self.ip6_cidrnetmask, self.bridge_nic)
|
|
|
|
def createGateway4Address(self):
|
|
if self.this_node.router_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='o'
|
|
)
|
|
common.createIPAddress(self.ip4_gateway, self.ip4_cidrnetmask, self.bridge_nic)
|
|
|
|
def startDHCPServer(self):
|
|
if self.this_node.router_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='o'
|
|
)
|
|
|
|
# Recreate the environment we need for dnsmasq
|
|
pvcnoded_config_file = os.environ['PVCD_CONFIG_FILE']
|
|
dhcp_environment = {
|
|
'DNSMASQ_BRIDGE_INTERFACE': self.bridge_nic,
|
|
'PVCD_CONFIG_FILE': pvcnoded_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='o'
|
|
)
|
|
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='o'
|
|
)
|
|
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='o'
|
|
)
|
|
|
|
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='o'
|
|
)
|
|
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='o'
|
|
)
|
|
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='o'
|
|
)
|
|
# Terminate, then kill
|
|
self.dhcp_server_daemon.signal('term')
|
|
time.sleep(0.2)
|
|
self.dhcp_server_daemon.signal('kill')
|
|
self.dhcp_server_daemon = None
|