@@ -361,7 +361,19 @@ def verify(container):
361361 return None
362362
363363
364- def generate_run_arguments (name , container_config , host_ident ):
364+ def get_container_host_ifname (name : str ) -> str :
365+ """
366+ Deterministic host-side veth interface name for a container's network
367+ attachment (verify() only allows one network per container). Kept
368+ within IFNAMSIZ (15 usable characters) and - thanks to the leading
369+ "veth-" (a hyphen can never appear in a VyOS "vethN" interface name) -
370+ guaranteed to never collide with the VyOS "virtual-ethernet" naming
371+ scheme - T7736.
372+ """
373+ return f'veth-{ name } ' [:15 ]
374+
375+
376+ def generate_run_arguments (name , container_config , host_ident , network_config ):
365377 image = container_config ['image' ]
366378 cpu_quota = container_config ['cpu_quota' ]
367379 memory = container_config ['memory' ]
@@ -511,9 +523,21 @@ def generate_run_arguments(name, container_config, host_ident):
511523 else :
512524 ip_param = ''
513525 addr_info = ''
514- networks = "," . join ( container_config [ 'network' ])
526+ network_opts = []
515527 for network in container_config ['network' ]:
516528 network_name = network
529+ # T7736: give the host-side veth a name that can never collide
530+ # with a VyOS "virtual-ethernet vethN" interface, instead of
531+ # leaving it to Podman's own "vethN" auto-naming. Not applicable
532+ # to macvlan networks - they attach without a paired host veth.
533+ type_config = dict_search (f'{ network } .type' , network_config )
534+ is_macvlan = dict_search ('macvlan' , type_config ) is not None
535+ net_opt = network
536+ if not is_macvlan :
537+ ifname = get_container_host_ifname (name )
538+ net_opt += f':host_interface_name={ ifname } '
539+ network_opts .append (net_opt )
540+
517541 if 'address' not in container_config ['network' ][network ]:
518542 continue
519543 for address in container_config ['network' ][network ]['address' ]:
@@ -524,6 +548,8 @@ def generate_run_arguments(name, container_config, host_ident):
524548
525549 addr_info = '' .join (container_config ['network' ][network ]['address' ])
526550
551+ networks = ' ' .join (f'--network { opt } ' for opt in network_opts )
552+
527553 get_mac = dict_search (f'network.{ network_name } .mac' , container_config )
528554 if get_mac == 'auto' or get_mac is None :
529555 mac_add = gen_mac (name , addr_info , host_ident )
@@ -546,7 +572,7 @@ def generate_run_arguments(name, container_config, host_ident):
546572 delete_cli_node (mac_config_path )
547573 add_cli_node (mac_config_path , value = mac_add )
548574
549- net = f'--net { networks } { ip_param } { mac_address } '
575+ net = f'{ networks } { ip_param } { mac_address } '
550576
551577 return f'{ container_base_cmd } { healthcheck } { net } { entrypoint } { image } { command } { command_arguments } ' .strip ()
552578
@@ -626,12 +652,13 @@ def generate(container):
626652
627653 if 'name' in container :
628654 host_ident = get_host_identity ()
655+ network_config = container .get ('network' , {})
629656 for name , container_config in container ['name' ].items ():
630657 if 'disable' in container_config :
631658 continue
632659
633660 file_path = os .path .join (systemd_unit_path , f'vyos-container-{ name } .service' )
634- run_args = generate_run_arguments (name , container_config , host_ident )
661+ run_args = generate_run_arguments (name , container_config , host_ident , network_config )
635662 render (file_path , 'container/systemd-unit.j2' , {'name' : name , 'run_args' : run_args , },
636663 formatter = lambda _ : _ .replace (""" , '"' ).replace ("'" , "'" ))
637664
0 commit comments