From 86d7b8d1d2b53b9fa93bd456abb4ea1b4f2949b6 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sat, 1 Apr 2023 14:42:45 +0200 Subject: container: T5047: bugfix TypeError: argument of type 'NoneType' is not iterable Commit 52e51ffb ("container: T5047: restart only containers that changed") started to iterate over a NoneType which is invalid. This happened when a network description was changed but no container was due for restart. --- src/conf_mode/container.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/conf_mode/container.py') diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index bf83416b2..4e4db7180 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -459,7 +459,8 @@ def apply(container): os.unlink(file_path) continue - if name in dict_search('container_restart', container): + tmp = dict_search('container_restart', container) + if tmp and name in tmp: cmd(f'systemctl restart vyos-container-{name}.service') if disabled_new: -- cgit v1.2.3 From 0ea3e1420c373027bdf57ea9e794b81dd6b6ad4f Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sat, 1 Apr 2023 15:31:46 +0200 Subject: container: T5082: switch to netavark network stack We now support assigning discrete IPv6 addresses to a container. --- data/templates/container/containers.conf.j2 | 2 +- debian/control | 1 + interface-definitions/container.xml.in | 7 ++- src/conf_mode/container.py | 95 +++++++++++++++-------------- 4 files changed, 56 insertions(+), 49 deletions(-) (limited to 'src/conf_mode/container.py') diff --git a/data/templates/container/containers.conf.j2 b/data/templates/container/containers.conf.j2 index 9f66aed27..c635ca213 100644 --- a/data/templates/container/containers.conf.j2 +++ b/data/templates/container/containers.conf.j2 @@ -282,7 +282,7 @@ default_sysctls = [ # Before changing this value all containers must be stopped otherwise it is likely that # iptables rules and network interfaces might leak on the host. A reboot will fix this. # -network_backend = "cni" +network_backend = "netavark" # Path to directory where CNI plugin binaries are located. # diff --git a/debian/control b/debian/control index 028b7cd43..8cd49f62a 100644 --- a/debian/control +++ b/debian/control @@ -99,6 +99,7 @@ Depends: mtr-tiny, ndisc6, ndppd, + netavark, netplug, nfct, nftables (>= 0.9.3), diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index 0849af656..b09536a16 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -191,15 +191,20 @@ - Assign static IP address to container ipv4 IPv4 address + + ipv6 + IPv6 address + + + diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 4e4db7180..5cfbfc30c 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -16,6 +16,7 @@ import os +from hashlib import sha256 from ipaddress import ip_address from ipaddress import ip_network from json import dumps as json_write @@ -27,7 +28,6 @@ from vyos.configdict import node_changed from vyos.configdict import is_node_changed from vyos.util import call from vyos.util import cmd -from vyos.util import dict_search from vyos.util import run from vyos.util import rc_cmd from vyos.util import write_file @@ -166,21 +166,29 @@ def verify(container): raise ConfigError(f'Container network "{network_name}" does not exist!') if 'address' in container_config['network'][network_name]: - address = container_config['network'][network_name]['address'] - network = None - if is_ipv4(address): - network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0] - elif is_ipv6(address): - network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0] - - # Specified container IP address must belong to network prefix - if ip_address(address) not in ip_network(network): - raise ConfigError(f'Used container address "{address}" not in network "{network}"!') - - # We can not use the first IP address of a network prefix as this is used by podman - if ip_address(address) == ip_network(network)[1]: - raise ConfigError(f'IP address "{address}" can not be used for a container, '\ - 'reserved for the container engine!') + cnt_ipv4 = 0 + cnt_ipv6 = 0 + for address in container_config['network'][network_name]['address']: + network = None + if is_ipv4(address): + network = [x for x in container['network'][network_name]['prefix'] if is_ipv4(x)][0] + cnt_ipv4 += 1 + elif is_ipv6(address): + network = [x for x in container['network'][network_name]['prefix'] if is_ipv6(x)][0] + cnt_ipv6 += 1 + + # Specified container IP address must belong to network prefix + if ip_address(address) not in ip_network(network): + raise ConfigError(f'Used container address "{address}" not in network "{network}"!') + + # We can not use the first IP address of a network prefix as this is used by podman + if ip_address(address) == ip_network(network)[1]: + raise ConfigError(f'IP address "{address}" can not be used for a container, '\ + 'reserved for the container engine!') + + if cnt_ipv4 > 1 or cnt_ipv6 > 1: + raise ConfigError(f'Only one IP address per address family can be used for '\ + f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!') if 'device' in container_config: for dev, dev_config in container_config['device'].items(): @@ -338,9 +346,13 @@ def generate_run_arguments(name, container_config): ip_param = '' networks = ",".join(container_config['network']) for network in container_config['network']: - if 'address' in container_config['network'][network]: - address = container_config['network'][network]['address'] - ip_param = f'--ip {address}' + if 'address' not in container_config['network'][network]: + continue + for address in container_config['network'][network]['address']: + if is_ipv6(address): + ip_param += f' --ip6 {address}' + else: + ip_param += f' --ip {address}' return f'{container_base_cmd} --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip() @@ -355,33 +367,26 @@ def generate(container): if 'network' in container: for network, network_config in container['network'].items(): tmp = { - 'cniVersion' : '0.4.0', - 'name' : network, - 'plugins' : [{ - 'type': 'bridge', - 'bridge': f'cni-{network}', - 'isGateway': True, - 'ipMasq': False, - 'hairpinMode': False, - 'ipam' : { - 'type': 'host-local', - 'routes': [], - 'ranges' : [], - }, - }] + 'name': network, + 'id' : sha256(f'{network}'.encode()).hexdigest(), + 'driver': 'bridge', + 'network_interface': f'podman-{network}', + 'subnets': [], + 'ipv6_enabled': False, + 'internal': False, + 'dns_enabled': False, + 'ipam_options': { + 'driver': 'host-local' + } } - for prefix in network_config['prefix']: - net = [{'gateway' : inc_ip(prefix, 1), 'subnet' : prefix}] - tmp['plugins'][0]['ipam']['ranges'].append(net) + net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)} + tmp['subnets'].append(net) - # install per address-family default orutes - default_route = '0.0.0.0/0' if is_ipv6(prefix): - default_route = '::/0' - tmp['plugins'][0]['ipam']['routes'].append({'dst': default_route}) + tmp['ipv6_enabled'] = True - write_file(f'/etc/cni/net.d/{network}.conflist', json_write(tmp, indent=2)) + write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2)) if 'registry' in container: cmd = f'podman logout --all' @@ -432,10 +437,7 @@ def apply(container): # Delete old networks if needed if 'network_remove' in container: for network in container['network_remove']: - call(f'podman network rm {network}') - tmp = f'/etc/cni/net.d/{network}.conflist' - if os.path.exists(tmp): - os.unlink(tmp) + call(f'podman network rm {network} >/dev/null 2>&1') # Add container disabled_new = False @@ -459,8 +461,7 @@ def apply(container): os.unlink(file_path) continue - tmp = dict_search('container_restart', container) - if tmp and name in tmp: + if 'container_restart' in container and name in container['container_restart']: cmd(f'systemctl restart vyos-container-{name}.service') if disabled_new: -- cgit v1.2.3 From b53c25a7bcd0a825cadf0e6c754297004ed3f0e4 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sat, 1 Apr 2023 18:56:02 +0200 Subject: container: T4959: bugfix credential validation on registries Commit fe82d86d ("container: T4959: add registry authentication option") looked up the wrong config dict level when validating that both username and password need to be specified when registries are in use. --- src/conf_mode/container.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/conf_mode/container.py') diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 5cfbfc30c..3827f4c70 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -258,9 +258,11 @@ def verify(container): if 'network' in container_config and network in container_config['network']: raise ConfigError(f'Can not remove network "{network}", used by container "{container}"!') - if 'registry' in container and 'authentication' in container['registry']: - for registry, registry_config in container['registry']['authentication'].items(): - if not {'username', 'password'} <= set(registry_config): + if 'registry' in container: + for registry, registry_config in container['registry'].items(): + if 'authentication' not in registry_config: + continue + if not {'username', 'password'} <= set(registry_config['authentication']): raise ConfigError('If registry username or or password is defined, so must be the other!') return None -- cgit v1.2.3 From b65296a0ff39e66d87e916971477cce351f6d5a5 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 2 Apr 2023 09:27:03 +0200 Subject: container: T5134: support binding container network to specific VRF Container networks now can be bound to a specific VRF instance. set vrf name table set container network vrf --- interface-definitions/container.xml.in | 1 + src/conf_mode/container.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) (limited to 'src/conf_mode/container.py') diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index b52054dd0..9b6d2369d 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -367,6 +367,7 @@ + #include diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 3827f4c70..05595f86f 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -26,6 +26,8 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf +from vyos.ifconfig import Interface from vyos.util import call from vyos.util import cmd from vyos.util import run @@ -250,6 +252,8 @@ def verify(container): if v6_prefix > 1: raise ConfigError(f'Only one IPv6 prefix can be defined for network "{network}"!') + # Verify VRF exists + verify_vrf(network_config) # A network attached to a container can not be deleted if {'network_remove', 'name'} <= set(container): @@ -469,6 +473,15 @@ def apply(container): if disabled_new: call('systemctl daemon-reload') + # Start network and assign it to given VRF if requested. this can only be done + # after the containers got started as the podman network interface will + # only be enabled by the first container and yet I do not know how to enable + # the network interface in advance + if 'network' in container: + for network, network_config in container['network'].items(): + tmp = Interface(f'podman-{network}') + tmp.set_vrf(network_config.get('vrf', '')) + return None if __name__ == '__main__': -- cgit v1.2.3 From 5f94bde6d6024b753765d28d2fdb69806f1968b5 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 6 Apr 2023 08:05:19 +0200 Subject: container: T5147: ensure container network exists before VRF operation Networks are started only as soon as there is a consumer. If only a network is created in the first place, no need to assign it to a VRF as there's no consumer, yet. --- src/conf_mode/container.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/conf_mode/container.py') diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 05595f86f..4b7ab3444 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -479,8 +479,13 @@ def apply(container): # the network interface in advance if 'network' in container: for network, network_config in container['network'].items(): - tmp = Interface(f'podman-{network}') - tmp.set_vrf(network_config.get('vrf', '')) + network_name = f'podman-{network}' + # T5147: Networks are started only as soon as there is a consumer. + # If only a network is created in the first place, no need to assign + # it to a VRF as there's no consumer, yet. + if os.path.exists(f'/sys/class/net/{network_name}'): + tmp = Interface(network_name) + tmp.set_vrf(network_config.get('vrf', '')) return None -- cgit v1.2.3 From 2a876059826927ef204e359a40395955f27503ce Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 14 Apr 2023 08:22:52 +0200 Subject: container: T5082: shorten container network prefix to allow longer names If the name of the network + the length of the podman- prefix exceeds the maximum supported length of netavark we get an error: Error: netavark: get bridge interface: Netlink error: Numerical result out of range (os error 34) --- src/conf_mode/container.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/conf_mode/container.py') diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 4b7ab3444..cb39f19b6 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -376,7 +376,7 @@ def generate(container): 'name': network, 'id' : sha256(f'{network}'.encode()).hexdigest(), 'driver': 'bridge', - 'network_interface': f'podman-{network}', + 'network_interface': f'pod-{network}', 'subnets': [], 'ipv6_enabled': False, 'internal': False, @@ -479,7 +479,7 @@ def apply(container): # the network interface in advance if 'network' in container: for network, network_config in container['network'].items(): - network_name = f'podman-{network}' + network_name = f'pod-{network}' # T5147: Networks are started only as soon as there is a consumer. # If only a network is created in the first place, no need to assign # it to a VRF as there's no consumer, yet. -- cgit v1.2.3 From 42775f287cca3e08dc8b2e58958018ecd1c626c9 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 14 Apr 2023 08:29:50 +0200 Subject: container: T5082: enable aardvark-dns support With commit 0ea3e1420 ("container: T5082: switch to netavark network stack") moving to a new network stack we should also enable the new DNS plugin provided by default. TODO: add CLI nodes to manually disable DNS and/or supply external DNS servers to the container. --- debian/control | 1 + src/conf_mode/container.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/conf_mode/container.py') diff --git a/debian/control b/debian/control index 856f57030..3126e6ad9 100644 --- a/debian/control +++ b/debian/control @@ -34,6 +34,7 @@ Package: vyos-1x Architecture: amd64 arm64 Depends: ${python3:Depends}, + aardvark-dns, accel-ppp, auditd, avahi-daemon, diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index cb39f19b6..aceb27fb0 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -380,7 +380,7 @@ def generate(container): 'subnets': [], 'ipv6_enabled': False, 'internal': False, - 'dns_enabled': False, + 'dns_enabled': True, 'ipam_options': { 'driver': 'host-local' } -- cgit v1.2.3