diff options
| author | l0crian1 <143656816+l0crian1@users.noreply.github.com> | 2025-10-02 17:52:06 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-02 23:52:06 +0200 |
| commit | 31d0ac632882e0faf8334823acc0a29147d22cc0 (patch) | |
| tree | ee02ae237354310e3dfd3e51bad8615c78579c09 | |
| parent | 3a5947fb673aaabcd79967ce08d04b6785f9f550 (diff) | |
| download | vyos-1x-31d0ac632882e0faf8334823acc0a29147d22cc0.tar.gz vyos-1x-31d0ac632882e0faf8334823acc0a29147d22cc0.zip | |
container: T7863: Add user-defined MAC option for containers (#4762)
| -rw-r--r-- | interface-definitions/container.xml.in | 18 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_container.py | 19 | ||||
| -rwxr-xr-x | src/conf_mode/container.py | 33 |
3 files changed, 69 insertions, 1 deletions
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index d4efc08be..c0afde48f 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -312,6 +312,24 @@ <multi/> </properties> </leafNode> + <leafNode name="mac"> + <properties> + <help>Media Access Control (MAC) address</help> + <valueHelp> + <format>macaddr</format> + <description>Hardware (MAC) address</description> + </valueHelp> + <valueHelp> + <format>auto</format> + <description>Generate a random MAC address for the container</description> + </valueHelp> + <constraint> + <validator name="mac-address"/> + <regex>(auto)</regex> + </constraint> + </properties> + <defaultValue>auto</defaultValue> + </leafNode> </children> </tagNode> <tagNode name="port"> diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py index cf12151ef..fd8272f44 100755 --- a/smoketest/scripts/cli/test_container.py +++ b/smoketest/scripts/cli/test_container.py @@ -224,6 +224,25 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): self.assertEqual(n['subnets'][0]['subnet'], '10.0.2.0/24') self.assertEqual(n['subnets'][0]['gateway'], '10.0.2.1') + def test_user_defined_mac(self): + # Bridge Network + self.cli_set(base_path + ['network', 'bridge1', 'prefix', '10.0.1.0/24']) + self.cli_set(base_path + ['network', 'bridge1', 'type', 'bridge']) + + self.cli_set(base_path + ['name', "test1", 'image', busybox_image]) + self.cli_set(base_path + ['name', "test1", 'network', 'bridge1', 'address', '10.0.1.11']) + self.cli_set(base_path + ['name', "test1", 'network', 'bridge1', 'mac', '02:00:00:00:00:01']) + + self.cli_set(base_path + ['name', "test2", 'image', busybox_image]) + self.cli_set(base_path + ['name', "test2", 'network', 'bridge1', 'address', '10.0.1.12']) + self.cli_set(base_path + ['name', "test2", 'network', 'bridge1', 'mac', '02:00:00:00:00:02']) + self.cli_commit() + + n = cmd_to_json(f'sudo podman container inspect test1') + self.assertEqual(n['NetworkSettings']['Networks']['bridge1']['MacAddress'], '02:00:00:00:00:01') + n = cmd_to_json(f'sudo podman container inspect test2') + self.assertEqual(n['NetworkSettings']['Networks']['bridge1']['MacAddress'], '02:00:00:00:00:02') + def test_ipv4_network(self): prefix = '192.0.2.0/24' base_name = 'ipv4' diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 4fe1e432c..b8c3d3d0c 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -30,6 +30,8 @@ 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.utils.configfs import delete_cli_node +from vyos.utils.configfs import add_cli_node from vyos.utils.cpu import get_core_count from vyos.utils.file import write_file from vyos.utils.dict import dict_search @@ -117,6 +119,10 @@ def verify(container): # Add new container if 'name' in container: + net_dict = {} + net_dict['mac'] = {} + net_dict['address'] = {} + for name, container_config in container['name'].items(): # Container image is a mandatory option if 'image' not in container_config: @@ -155,6 +161,13 @@ def verify(container): 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!') + mac = dict_search(f'network.{network_name}.mac', container_config) + if mac: + if mac in net_dict['mac'].keys(): + raise ConfigError(f'MAC address "{mac}" is already used by container "{net_dict["mac"][mac]}"!') + if mac != 'auto': + net_dict['mac'][mac] = name + if 'address' in container_config['network'][network_name]: cnt_ipv4 = 0 cnt_ipv6 = 0 @@ -181,6 +194,10 @@ def verify(container): 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 address in net_dict['address'].keys(): + raise ConfigError(f'IP address "{address}" is already used by container "{net_dict["address"][address]}"!') + net_dict['address'][address] = name if cnt_ipv4 > 1 or cnt_ipv6 > 1: raise ConfigError(f'Only one IP address per address family can be used for ' \ @@ -477,6 +494,7 @@ def generate_run_arguments(name, container_config, host_ident): addr_info = '' networks = ",".join(container_config['network']) for network in container_config['network']: + network_name = network if 'address' not in container_config['network'][network]: continue for address in container_config['network'][network]['address']: @@ -487,7 +505,20 @@ def generate_run_arguments(name, container_config, host_ident): addr_info = ''.join(container_config['network'][network]['address']) - mac_address = f'--mac-address {gen_mac(name, addr_info, host_ident)}' + 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) + else: + mac_add = get_mac + + mac_address = f'--mac-address {mac_add}' + + # Replace mac-auto with the generated mac address + if get_mac == 'auto': + mac_config_path = ['container', 'name', name, 'network', network_name, 'mac'] + + delete_cli_node(mac_config_path) + add_cli_node(mac_config_path, value=mac_add) return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {mac_address} {entrypoint} {image} {command} {command_arguments}'.strip() |
