summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorl0crian1 <ryan.claridge13@gmail.com>2025-08-28 15:24:45 -0400
committerl0crian1 <ryan.claridge13@gmail.com>2025-08-28 15:24:45 -0400
commit86b3b035b8559ee5dd8da8d4a57a11c041b32882 (patch)
tree2e67aca37d6e1eb6d3b60e8ad8ed06b9bc1a99ec /src
parent133cb0c65d5aa2ca5a96feba6ab7adc3a8f1de58 (diff)
downloadvyos-1x-86b3b035b8559ee5dd8da8d4a57a11c041b32882.tar.gz
vyos-1x-86b3b035b8559ee5dd8da8d4a57a11c041b32882.zip
container: T7186: Add macvlan network type for containers
Modified: - interface-definitions/container.xml.in: - Add macvlan network type - Add gateway option - python/vyos/utils/network.py: - Add gen_mac function to generate mac address - smoketest/scripts/cli/test_container.py: - Add test for container network types - src/conf_mode/container.py: - Add support for macvlan network type - Add gateway option
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/container.py112
1 files changed, 92 insertions, 20 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 4ec9b8849..b58c722fc 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -32,9 +32,11 @@ from vyos.configverify import verify_vrf
from vyos.ifconfig import Interface
from vyos.utils.cpu import get_core_count
from vyos.utils.file import write_file
+from vyos.utils.dict import dict_search
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import run
+from vyos.utils.network import gen_mac
from vyos.utils.network import interface_exists
from vyos.template import bracketize_ipv6
from vyos.template import inc_ip
@@ -260,22 +262,59 @@ def verify(container):
# Add new network
if 'network' in container:
for network, network_config in container['network'].items():
- v4_prefix = 0
- v6_prefix = 0
+ net_dict = {'ipv4_pfx_len': 0, 'ipv6_pfx_len': 0, 'ipv4_gateway_len': 0, 'ipv6_gateway_len': 0}
+
# If ipv4-prefix not defined for user-defined network
if 'prefix' not in network_config:
raise ConfigError(f'prefix for network "{network}" must be defined!')
for prefix in network_config['prefix']:
if is_ipv4(prefix):
- v4_prefix += 1
+ net_dict['ipv4_pfx_len'] += 1
+ net_dict['ipv4_prefix'] = prefix
elif is_ipv6(prefix):
- v6_prefix += 1
-
- if v4_prefix > 1:
+ net_dict['ipv6_pfx_len'] += 1
+ net_dict['ipv6_prefix'] = prefix
+
+ for gateway in network_config.get('gateway', []):
+ if is_ipv4(gateway):
+ net_dict['ipv4_gateway_len'] += 1
+ net_dict['ipv4_gateway'] = gateway
+ elif is_ipv6(gateway):
+ net_dict['ipv6_gateway_len'] += 1
+ net_dict['ipv6_gateway'] = gateway
+
+ if net_dict['ipv4_pfx_len'] > 1:
raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
- if v6_prefix > 1:
+ if net_dict['ipv6_pfx_len'] > 1:
raise ConfigError(f'Only one IPv6 prefix can be defined for network "{network}"!')
+ if net_dict['ipv4_gateway_len'] > 1:
+ raise ConfigError(f'Only one IPv4 gateway can be defined for network "{network}"!')
+ if net_dict['ipv6_gateway_len'] > 1:
+ raise ConfigError(f'Only one IPv6 gateway can be defined for network "{network}"!')
+
+ if net_dict.get('ipv4_prefix') and net_dict.get('ipv4_gateway'):
+ if ip_address(net_dict['ipv4_gateway']) not in ip_network(net_dict['ipv4_prefix']):
+ raise ConfigError(f'IPv4 gateway "{net_dict["ipv4_gateway"]}" is not in the IPv4 prefix "{net_dict["ipv4_prefix"]}"!')
+ if net_dict.get('ipv6_prefix') and net_dict.get('ipv6_gateway'):
+ if ip_address(net_dict['ipv6_gateway']) not in ip_network(net_dict['ipv6_prefix']):
+ raise ConfigError(f'IPv6 gateway "{net_dict["ipv6_gateway"]}" is not in the IPv6 prefix "{net_dict["ipv6_prefix"]}"!')
+ if net_dict.get('ipv4_gateway') and not net_dict.get('ipv4_prefix'):
+ raise ConfigError(f'IPv4 gateway configured but no IPv4 prefix defined for network "{network}"!')
+ if net_dict.get('ipv6_gateway') and not net_dict.get('ipv6_prefix'):
+ raise ConfigError(f'IPv6 gateway configured but no IPv6 prefix defined for network "{network}"!')
+
+ type_config = dict_search('type', network_config)
+ if dict_search('macvlan', type_config):
+ parent = dict_search('macvlan.parent', type_config)
+ if not parent:
+ raise ConfigError(f'MACVLAN networks must have a parent interface!')
+ if not interface_exists(parent):
+ raise ConfigError(f'MACVLAN parent interface "{parent}" does not exist!')
+ if not dict_search('macvlan.mode', type_config):
+ raise ConfigError(f'MACVLAN networks must have a mode configured!')
+ if dict_search('vrf', network_config):
+ raise ConfigError(f'MACVLAN networks do not support direct VRF assignment!')
# Verify VRF exists
verify_vrf(network_config)
@@ -430,6 +469,7 @@ def generate_run_arguments(name, container_config):
return f'{container_base_cmd} --net host {entrypoint} {image} {command} {command_arguments}'.strip()
ip_param = ''
+ mac_address = ''
networks = ",".join(container_config['network'])
for network in container_config['network']:
if 'address' not in container_config['network'][network]:
@@ -440,7 +480,10 @@ def generate_run_arguments(name, container_config):
else:
ip_param += f' --ip {address}'
- return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
+ addr_info = ''.join(container_config['network'][network]['address'])
+ mac_address = f'--mac-address {gen_mac(name, addr_info)}'
+
+ return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {mac_address} {entrypoint} {image} {command} {command_arguments}'.strip()
def generate(container):
@@ -453,11 +496,22 @@ def generate(container):
if 'network' in container:
for network, network_config in container['network'].items():
+ type_config = dict_search('type', network_config)
+ if dict_search('macvlan', type_config):
+ net_interface = dict_search('macvlan.parent', type_config)
+ driver = 'macvlan'
+ mode = dict_search('macvlan.mode', type_config)
+ elif dict_search('bridge', type_config) != None:
+ net_interface = f'pod-{network}'
+ driver = 'bridge'
+ else:
+ net_interface = f'pod-{network}'
+ driver = 'bridge'
tmp = {
'name': network,
'id': sha256(f'{network}'.encode()).hexdigest(),
- 'driver': 'bridge',
- 'network_interface': f'pod-{network}',
+ 'driver': driver,
+ 'network_interface': net_interface,
'subnets': [],
'ipv6_enabled': False,
'internal': False,
@@ -466,6 +520,7 @@ def generate(container):
'driver': 'host-local'
},
'options': {
+ **({'mode': mode} if driver == 'macvlan' else {}),
'mtu': '1500'
}
}
@@ -477,11 +532,26 @@ def generate(container):
tmp['options']['mtu'] = network_config['mtu']
for prefix in network_config['prefix']:
- net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
- tmp['subnets'].append(net)
+ gateway4, gateway6 = None, None
+ if dict_search('gateway', network_config):
+ for gw in network_config['gateway']:
+ if is_ipv6(gw):
+ gateway6 = gw
+ else:
+ gateway4 = gw
+
+ if is_ipv6(prefix) and not gateway6:
+ gateway6 = inc_ip(prefix, 1)
+ elif not gateway4:
+ gateway4 = inc_ip(prefix, 1)
if is_ipv6(prefix):
tmp['ipv6_enabled'] = True
+ net = {'subnet': prefix, 'gateway': gateway6}
+ else:
+ net = {'subnet': prefix, 'gateway': gateway4}
+
+ tmp['subnets'].append(net)
write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2))
@@ -553,14 +623,16 @@ def apply(container):
# the network interface in advance
if 'network' in container:
for network, network_config in container['network'].items():
- 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.
- if interface_exists(network_name):
- tmp = Interface(network_name)
- tmp.set_vrf(network_config.get('vrf', ''))
- tmp.add_ipv6_eui64_address('fe80::/64')
+ type_config = dict_search('type', network_config)
+ if not dict_search('macvlan', type_config):
+ 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.
+ if interface_exists(network_name):
+ tmp = Interface(network_name)
+ tmp.set_vrf(network_config.get('vrf', ''))
+ tmp.add_ipv6_eui64_address('fe80::/64')
return None