diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-08-05 14:10:53 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-05 14:10:53 +0200 |
| commit | 636f74e180a6481892fd87f7957b9b3758bb17e8 (patch) | |
| tree | 5080ff64af8c4cd183ee374c6057a88b1c98cb6f /src | |
| parent | 02ab78102df4f58c149d375de8a88494ee9e02df (diff) | |
| parent | 1373026ff54e6496ae182ede56ada5eca120fb83 (diff) | |
| download | vyos-1x-636f74e180a6481892fd87f7957b9b3758bb17e8.tar.gz vyos-1x-636f74e180a6481892fd87f7957b9b3758bb17e8.zip | |
Merge pull request #5352 from c-po/container-veth
container: T7736: give container veths a deterministic host_interface_name
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/container.py | 36 | ||||
| -rwxr-xr-x | src/op_mode/container.py | 39 |
2 files changed, 71 insertions, 4 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 91f44d2d8..15bec36ec 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -29,6 +29,7 @@ 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.container import get_container_host_ifname from vyos.container import restart_network from vyos.utils.configfs import delete_cli_node from vyos.utils.configfs import add_cli_node @@ -122,6 +123,7 @@ def verify(container): net_dict = {} net_dict['mac'] = {} net_dict['address'] = {} + net_dict['host_ifname'] = {} for name, container_config in container['name'].items(): # Container image is a mandatory option @@ -158,6 +160,19 @@ def verify(container): if network_name not in container.get('network', {}): raise ConfigError(f'Container network "{network_name}" does not exist!') + # T7736: two distinct (long) container names could truncate to + # the same host_interface_name - not applicable to macvlan networks, + # they attach without a paired host veth + network_type = dict_search(f'{network_name}.type', container['network']) + if dict_search('macvlan', network_type) is None: + host_ifname = get_container_host_ifname(name) + if host_ifname in net_dict['host_ifname']: + raise ConfigError( + f'Container "{name}" and "{net_dict["host_ifname"][host_ifname]}" ' + f'both generate the host interface name "{host_ifname}" - please ' + f'use less similar container names!') + net_dict['host_ifname'][host_ifname] = name + if 'name_server' in container_config and 'no_name_server' not in container['network'][network_name]: raise ConfigError(f'Setting name server has no effect when attached container network has DNS enabled!') @@ -361,7 +376,7 @@ def verify(container): return None -def generate_run_arguments(name, container_config, host_ident): +def generate_run_arguments(name, container_config, host_ident, network_config): image = container_config['image'] cpu_quota = container_config['cpu_quota'] memory = container_config['memory'] @@ -511,9 +526,19 @@ def generate_run_arguments(name, container_config, host_ident): else: ip_param = '' addr_info = '' - networks = ",".join(container_config['network']) + network_opts = [] for network in container_config['network']: network_name = network + # T7736: give the host-side veth a name that can never collide + # with a VyOS "virtual-ethernet vethN" interface. + type_config = dict_search(f'{network}.type', network_config) + is_macvlan = dict_search('macvlan', type_config) is not None + net_opt = network + if not is_macvlan: + ifname = get_container_host_ifname(name) + net_opt += f':host_interface_name={ifname}' + network_opts.append(net_opt) + if 'address' not in container_config['network'][network]: continue for address in container_config['network'][network]['address']: @@ -524,6 +549,8 @@ def generate_run_arguments(name, container_config, host_ident): addr_info = ''.join(container_config['network'][network]['address']) + networks = ' '.join(f'--network {opt}' for opt in network_opts) + get_mac = dict_search(f'network.{network_name}.mac', container_config) if get_mac == 'auto' or get_mac is None: mac_add = gen_mac(name, addr_info, host_ident) @@ -546,7 +573,7 @@ def generate_run_arguments(name, container_config, host_ident): delete_cli_node(mac_config_path) add_cli_node(mac_config_path, value=mac_add) - net = f'--net {networks} {ip_param} {mac_address}' + net = f'{networks} {ip_param} {mac_address}' return f'{container_base_cmd} {healthcheck} {net} {entrypoint} {image} {command} {command_arguments}'.strip() @@ -626,12 +653,13 @@ def generate(container): if 'name' in container: host_ident = get_host_identity() + network_config = container.get('network', {}) for name, container_config in container['name'].items(): if 'disable' in container_config: continue file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service') - run_args = generate_run_arguments(name, container_config, host_ident) + run_args = generate_run_arguments(name, container_config, host_ident, network_config) render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args, }, formatter=lambda _: _.replace(""", '"').replace("'", "'")) diff --git a/src/op_mode/container.py b/src/op_mode/container.py index 0948a0d84..6b733e8d6 100755 --- a/src/op_mode/container.py +++ b/src/op_mode/container.py @@ -21,6 +21,8 @@ import sys import subprocess from pathlib import Path +from tabulate import tabulate + from vyos.defaults import directories from vyos.utils.process import cmdl from vyos.utils.process import rc_cmd @@ -163,6 +165,43 @@ def show_network(raw: bool): else: return cmdl(command.split()) +def show_interface(raw: bool): + """ Show the deterministic host-side veth interface name (T7736) VyOS + assigns to each configured container's network attachment """ + from vyos.configquery import ConfigTreeQuery + from vyos.container import get_container_host_ifname + from vyos.utils.dict import dict_search + + conf = ConfigTreeQuery() + container = conf.get_config_dict(['container'], key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + data = [] + for name, container_config in container.get('name', {}).items(): + if 'allow_host_networks' in container_config: + data.append({'name': name, 'network': None, 'interface': None}) + continue + if 'network' not in container_config: + continue + + network_name = list(container_config['network'])[0] + network_type = dict_search(f'network.{network_name}.type', container) + is_macvlan = dict_search('macvlan', network_type) is not None + interface = None if is_macvlan else get_container_host_ifname(name) + data.append({'name': name, 'network': network_name, 'interface': interface}) + + if raw: + return data + + if not data: + return 'No containers configured!' + + headers = ['Container', 'Network', 'Host Interface'] + rows = [[d['name'], d['network'] or 'host', d['interface'] or 'n/a'] for d in data] + return tabulate(rows, headers) + def restart(name: str): from vyos.utils.process import rc_cmd from vyos.config import Config |
