Skip to content

Conversation

l0crian1
Copy link
Contributor

Change summary

This change adds the ability to configure macvlan as a network type for podman.

Syntax additions:

set container network <network name> gateway (ipv4-address|ipv6-address)
set container network <network name> type macvlan mode (bridge|private|vepa)
set container network <network name> type macvlan parent <host interface name>

Example network JSON:

{
  "name": "macvlan1",
  "id": "f284f716fb76eabbb8409c7c76fb7ad95df9472e5010c010a810cf0df08f0913",
  "driver": "macvlan",
  "network_interface": "eth0",
  "subnets": [
    {
      "subnet": "10.0.0.0/24",
      "gateway": "10.0.0.1"
    }
  ],
  "ipv6_enabled": false,
  "internal": false,
  "dns_enabled": true,
  "ipam_options": {
    "driver": "host-local"
  },
  "options": {
    "mode": "bridge",
    "mtu": "1500"
  }

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes)
  • Migration from an old Vyatta component to vyos-1x, please link to related PR inside obsoleted component
  • Other (please describe):

Related Task(s)

https://vyos.dev/T7186

Related PR(s)

How to test / Smoketest result

Configure physical interface to use as container network's parent (note that it doesn't need an IP):

set interfaces ethernet eth0 vif 100

Configure the macvlan network:

set container network macvlan1 prefix '10.0.101.0/24'
set container network macvlan1 type macvlan mode 'bridge'
set container network macvlan1 type macvlan parent 'eth0.101'
Optionally add a gateway. This is important since a physical network might have a gateway that isn't the first IP in the subnet:
set container network macvlan1 gateway '10.0.101.10'

Attach a container to the network. This will be the same syntax as it's always been:

set container name alpine1 image 'alpine'
set container name alpine1 network macvlan1 address 10.0.101.137

You should see the MAC address for the container in the FDB of VyOS:

vyos@PE2# run connect container alpine1
/ # ifconfig eth0 | grep HWaddr
eth0      Link encap:Ethernet  HWaddr 02:19:74:91:63:E4
/ # exit
vyos@PE2# sudo bridge fdb show dev eth0.101 | match 02:19:74:91:63:e4
02:19:74:91:63:e4 self permanent

The IP should be reachable on your physical network:

C:\WINDOWS\system32>fping 10.0.101.137 -n 1
Pinging 10.0.101.137 with 32 bytes of data every 1000 ms:

Reply[1] from 10.0.101.137: bytes=32 time=3.7 ms TTL=63

Ping statistics for 10.0.101.137:
        Packets: Sent = 1, Received = 1, Lost = 0 (0% loss)

Smoketest results:

test_api_socket (__main__.TestContainer.test_api_socket) ... ok
test_basic (__main__.TestContainer.test_basic) ... ok
test_cpu_limit (__main__.TestContainer.test_cpu_limit) ... ok
test_dual_stack_network (__main__.TestContainer.test_dual_stack_network) ... ok
test_ipv4_network (__main__.TestContainer.test_ipv4_network) ... ok
test_ipv6_network (__main__.TestContainer.test_ipv6_network) ... ok
test_name_server (__main__.TestContainer.test_name_server) ... ok
test_network_mtu (__main__.TestContainer.test_network_mtu) ... ok
test_network_types (__main__.TestContainer.test_network_types) ... ok
test_no_name_server (__main__.TestContainer.test_no_name_server) ... ok
test_uid_gid (__main__.TestContainer.test_uid_gid) ... ok

Checklist:

  • I have read the CONTRIBUTING document
  • I have linked this PR to one or more Phabricator Task(s)
  • I have run the components SMOKETESTS if applicable
  • My commit headlines contain a valid Task id
  • My change requires a change to the documentation
  • I have updated the documentation accordingly

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
Copy link

github-actions bot commented Aug 28, 2025

👍
No issues in PR Title / Commit Title

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for macvlan network type in VyOS container configuration, allowing containers to connect directly to physical networks with dedicated MAC addresses. This enables containers to appear as separate hosts on the physical network rather than being behind NAT.

  • Adds macvlan network type with configurable mode (bridge/private/vepa) and parent interface
  • Implements custom gateway configuration for container networks
  • Generates deterministic MAC addresses for containers using host identity and container details

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/conf_mode/container.py Core implementation for macvlan network support, gateway validation, and MAC address generation
smoketest/scripts/cli/test_container.py Test cases for macvlan and bridge network types
python/vyos/utils/network.py Utility functions for generating deterministic MAC addresses
interface-definitions/container.xml.in CLI configuration schema for gateway and network type options

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 484 to 486
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()
Copy link
Preview

Copilot AI Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MAC address generation logic runs for every network loop iteration but only uses the last network's address. This will generate incorrect MAC addresses for containers with multiple networks and overwrite the mac_address variable on each iteration.

Suggested change
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()
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()

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

@l0crian1 l0crian1 Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one network can be applied to a container:

            if 'network' in container_config:
                if len(container_config['network']) > 1:
                    raise ConfigError(f'Only one network can be specified for container "{name}"!')

With that said, to prevent collisions in case that behavior changes later, I'll make some changes to move the gen_mac() function outside of that loop.

net_interface = dict_search('macvlan.parent', type_config)
driver = 'macvlan'
mode = dict_search('macvlan.mode', type_config)
elif dict_search('bridge', type_config) != None:
Copy link
Preview

Copilot AI Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using != None is unnecessary and not idiomatic. Use is not None instead, or simply check the truthiness with elif dict_search('bridge', type_config):.

Suggested change
elif dict_search('bridge', type_config) != None:
elif dict_search('bridge', type_config) is not None:

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dict_search will return either None if the path doesn't exist, or {} if it does. This would cause a simple if dict_search() to fail, so the None check is required. I'll make it is not None

Comment on lines +67 to +68
uuid = cmd(f"cat /sys/class/dmi/id/product_uuid").strip().replace("-", "").lower()
host = cmd("hostname").strip().lower()
Copy link
Preview

Copilot AI Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These commands are executed every time gen_mac() is called through get_host_identity(). Since host identity rarely changes during runtime, consider caching the result to avoid repeated subprocess calls.

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, there's no need to get that data per-container. I'll make that change.

ident = get_host_identity()
h = hashlib.sha256(f"{ident}:{name}:{addr}".encode()).hexdigest()
# 0x02 = locally-administered, unicast
b = [0x02] + [int(h[i:i+2], 16) for i in range(0, 10, 2)] # 5 bytes = 40 bits
Copy link
Preview

Copilot AI Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 10 should be extracted as a constant or calculated as 5 * 2 to make the relationship between 5 bytes and 10 hex characters explicit.

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 isn't a magic number, it's the number of digits I need to grab to equal 40bits. Each hex digit is 4 bits, so 10*4=40. I don't think changing this as Copilot suggests is an improvement.

 - Moved gen_mac function outside of 'for network' loop
 - Cached result of get_host_identity() to prevent multiple calls
 - Other minor improvements per Copilot suggestions
Copy link

CI integration ❌ failed!

Details

CI logs

  • CLI Smoketests (no interfaces) ❌ failed
  • CLI Smoketests VPP 👍 passed
  • CLI Smoketests (interfaces only) 👍 passed
  • Config tests 👍 passed
  • Config tests VPP 👍 passed
  • RAID1 tests 👍 passed
  • TPM tests 👍 passed

Copy link
Contributor

@jestabro jestabro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes suggested by copilot made; smoketest additions tested and passed.

Copy link
Member

@dmbaturin dmbaturin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. One note: macvlan interfaces are historically known as "pseudo-ethernet" in VyOS — a strange decision that has been carried over from Vyatta (set interfaces pseudo-ethernet pethX ...).

Now we will have both "MACVLAN" and "pseudo-ethernet" terms in the system, so I wonder if we should eventually rename interfaces pseudo-ethernet to interfaces macvlan.

@dmbaturin dmbaturin added the bp/circinus Create automatic backport for circinus label Sep 15, 2025
@dmbaturin dmbaturin merged commit 48b9f84 into vyos:current Sep 15, 2025
16 of 18 checks passed
@vyosbot vyosbot added mirror-initiated This PR initiated for mirror sync workflow mirror-completed and removed mirror-initiated This PR initiated for mirror sync workflow labels Sep 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bp/circinus Create automatic backport for circinus current mirror-completed rebase
Development

Successfully merging this pull request may close these issues.

4 participants