summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-08-05 14:10:53 +0200
committerGitHub <noreply@github.com>2026-08-05 14:10:53 +0200
commit636f74e180a6481892fd87f7957b9b3758bb17e8 (patch)
tree5080ff64af8c4cd183ee374c6057a88b1c98cb6f /src
parent02ab78102df4f58c149d375de8a88494ee9e02df (diff)
parent1373026ff54e6496ae182ede56ada5eca120fb83 (diff)
downloadvyos-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-xsrc/conf_mode/container.py36
-rwxr-xr-xsrc/op_mode/container.py39
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("&quot;", '"').replace("&apos;", "'"))
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