Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ISO_PATH := $(build_dir)/live-image-$(ARCH).hybrid.iso
# to their scripts via $(MAKECMDGOALS). Those extra words are also goals as
# far as make is concerned, so without this they'd fall through to the `%:`
# flavor rule below and run build-vyos-image with garbage arguments.
TEST_TARGETS := test test-no-interfaces test-no-interfaces-no-vpp test-interfaces test-vpp testc testcvpp testraid testsb testtpm test-ci-qcow2 test-image-update qemu-live
TEST_TARGETS := test test-no-interfaces test-no-interfaces-no-vpp test-interfaces test-vpp testc testcvpp testraid testsb testtpm testifname test-ci-qcow2 test-image-update qemu-live
ifneq ($(filter $(TEST_TARGETS),$(firstword $(MAKECMDGOALS))),)
$(eval $(filter-out $(firstword $(MAKECMDGOALS)),$(MAKECMDGOALS)):;@:)
endif
Expand Down Expand Up @@ -71,6 +71,11 @@ testsb:
testtpm:
scripts/check-qemu-install --debug --tpmtest --iso $(ISO_PATH) $(filter-out $@,$(MAKECMDGOALS))

.PHONY: testifname
.ONESHELL:
testifname:
scripts/check-qemu-install --debug --ifnametest --iso $(ISO_PATH) $(filter-out $@,$(MAKECMDGOALS))

.PHONY: test-ci-qcow2
.ONESHELL:
test-ci-qcow2:
Expand Down
94 changes: 85 additions & 9 deletions scripts/check-qemu-install
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ tpm_folder = '/tmp/vyos_tpm_test'
tpm_sock = f'{tpm_folder}/swtpm-sock'
qemu_name = 'VyOS-QEMU'

# RFC7042 section 2.1.2 MAC addresses used for documentation
macbase = '00:00:5E:00:53'

test_timeout = 5 *3600 # 5 hours (in seconds) to complete individual testcases

op_mode_prompt = r'vyos@vyos:~\$'
Expand Down Expand Up @@ -137,6 +140,8 @@ parser.add_argument('--configtest', help='Execute load/commit config tests',
action='store_true', default=False)
parser.add_argument('--tpmtest', help='Execute TPM encrypted config tests',
action='store_true', default=False)
parser.add_argument('--ifnametest', help='Execute interface naming/hw-id persistence tests',
action='store_true', default=False)
parser.add_argument('--sbtest', help='Execute Secure Boot tests',
action='store_true', default=False)
parser.add_argument('--cloud-init', help='Execute cloud-init tests',
Expand Down Expand Up @@ -268,9 +273,6 @@ def get_qemu_cmd(name, enable_uefi, disk_img, raid=None, iso_img=None, tpm=False
else:
nested_cdrom = f'{nested_cdrom} -device ide-cd,bus=achi0.1,{drive_settings}'

# RFC7042 section 2.1.2 MAC addresses used for documentation
macbase = '00:00:5E:00:53'

# Set QEmu disk image format - this differs if VyOS was installed via smoketest
# or we use an already ewxisting image
disk_format = 'qcow2' if args.disk.endswith('.qcow2') else 'raw'
Expand All @@ -290,10 +292,10 @@ def get_qemu_cmd(name, enable_uefi, disk_img, raid=None, iso_img=None, tpm=False
-netdev user,id=n1 -device virtio-net-pci,netdev=n1,mac={macbase}:01,romfile="",host_mtu=1500 \
-netdev user,id=n2 -device virtio-net-pci,netdev=n2,mac={macbase}:02,romfile="",host_mtu=1500 \
-netdev user,id=n3 -device virtio-net-pci,netdev=n3,mac={macbase}:03,romfile="",host_mtu=1500 \
-netdev user,id=n4 -device virtio-net-pci,netdev=n4,mac={macbase}:04,romfile="" \
-netdev user,id=n5 -device virtio-net-pci,netdev=n5,mac={macbase}:05,romfile="" \
-netdev user,id=n6 -device virtio-net-pci,netdev=n6,mac={macbase}:06,romfile="" \
-netdev user,id=n7 -device virtio-net-pci,netdev=n7,mac={macbase}:07,romfile="" \
-netdev user,id=n4 -device e1000e,netdev=n4,mac={macbase}:04,romfile="" \
-netdev user,id=n5 -device e1000e,netdev=n5,mac={macbase}:05,romfile="" \
-netdev user,id=n6 -device vmxnet3,netdev=n6,mac={macbase}:06,romfile="" \
-netdev user,id=n7 -device vmxnet3,netdev=n7,mac={macbase}:07,romfile="" \
-device virtio-scsi-pci,id=scsi0 \
{cdrom}{nested_cdrom} \
-drive format={disk_format},file={disk_img},if=none,media=disk,id=drive-hd1,readonly=off \
Expand Down Expand Up @@ -387,6 +389,8 @@ if args.test_image_update:
_primary_modes.append('--test-image-update')
if args.tpmtest:
_primary_modes.append('--tpmtest')
if args.ifnametest:
_primary_modes.append('--ifnametest')
if args.raid:
_primary_modes.append('--raid')
if args.smoketest:
Expand All @@ -397,8 +401,8 @@ if args.sbtest:
_primary_modes.append('--sbtest')
if len(_primary_modes) > 1:
log.error('Incompatible combination of testcase flags (%s): only one of '
'--cloud-init, --test-image-update, --tpmtest, --raid, --smoketest, '
'--configtest, --sbtest may be set.', ', '.join(_primary_modes))
'--cloud-init, --test-image-update, --tpmtest, --ifnametest, --raid, '
'--smoketest, --configtest, --sbtest may be set.', ', '.join(_primary_modes))
sys.exit(1)

if args.no_interfaces and not args.smoketest:
Expand Down Expand Up @@ -687,6 +691,31 @@ def basic_cli_tests(c):
c.expect(f'set console_type="{console_type}"')
c.expect(op_mode_prompt)

def verify_eth_mac_mapping(c, log):
""" NICs are attached with a mix of drivers (virtio/e1000e/vmxnet3, see
get_qemu_cmd()) to cover different naming schemes. Regardless of
driver, udev must always enumerate them in ascending order: eth0
carries mac0, eth1 mac1, ... up to eth7 mac7 - never scrambled. """
log.info('Verify eth0..eth7 are enumerated in ascending MAC order')
c.sendline('ip -json link show | jq -r \'.[] | select(.ifname|test("^eth[0-9]+$")) | "\(.ifname) \(.address)"\'')
c.expect(op_mode_prompt)
lines = [l.strip() for l in c.before.decode(errors='replace').splitlines() if l.strip()]

macs = {}
for line in lines:
parts = line.split()
if len(parts) == 2 and re.fullmatch(r'eth\d+', parts[0]):
macs[parts[0]] = parts[1].lower()

for i in range(8):
ifname = f'eth{i}'
expected_mac = f'{macbase}:{i:02x}'.lower()
if ifname not in macs:
raise Exception(f'Interface {ifname} not found on installed system')
if macs[ifname] != expected_mac:
raise Exception(f'Interface {ifname} has MAC {macs[ifname]}, expected {expected_mac} - naming race?')
log.info('eth0..eth7 MAC mapping verified')

def _image_update_cli_sequence(c, log, new_image_name, server_bind_host='127.0.0.1', use_vrf=False):
"""One add-system-image/delete cycle for nested ISO over HTTP (optional Linux VRF + VyOS vrf arg)."""
url = f'http://{server_bind_host}:{NESTED_HTTP_SERV_PORT}/{NESTED_INNER_ISO_NAME}'
Expand Down Expand Up @@ -1067,6 +1096,11 @@ try:
log.info('Basic CLI configuration mode test')
basic_cli_tests(c)

#################################################
# Verify NIC driver mix did not scramble interface naming
#################################################
verify_eth_mac_mapping(c, log)

Comment thread
coderabbitai[bot] marked this conversation as resolved.
#################################################
# Verify /etc/os-release via lsb_release
#################################################
Expand Down Expand Up @@ -1274,6 +1308,48 @@ try:
c.sendline('exit')
c.expect(op_mode_prompt)

elif args.ifnametest:
# A missing/deleted hw-id binding, or a fully deleted interface
# config, must not change the eth0..eth7 <-> MAC mapping after
# the next reboot (regression check for the boot-time naming race).
log.info('Running interface naming/hw-id persistence tests')
del_idx, hwid_idx = random.sample(range(8), 2)
log.info(f'Deleting eth{del_idx} entirely, removing hw-id only on eth{hwid_idx}')

c.sendline('configure')
c.expect(cfg_mode_prompt)
c.sendline(f'delete interfaces ethernet eth{del_idx}')
c.expect(cfg_mode_prompt)
c.sendline(f'delete interfaces ethernet eth{hwid_idx} hw-id')
c.expect(cfg_mode_prompt)
c.sendline('commit')
c.expect(cfg_mode_prompt)
c.sendline('save')
c.expect(cfg_mode_prompt)
Comment on lines +1319 to +1328

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect interface configuration setup and the ifnametest mutation path.
rg -n -C 8 \
  'args\.ifnametest|set interfaces ethernet|delete interfaces ethernet|hw-id|show configuration commands' \
  scripts/check-qemu-install

# Find other image/bootstrap code that creates Ethernet hw-id bindings.
rg -n -C 6 \
  'set interfaces ethernet eth[0-7]|interfaces ethernet.*hw-id' \
  . -g '!scripts/check-qemu-install'

Repository: vyos/vyos-build

Length of output: 5657


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- interface test function and reboot assertions ---'
sed -n '1180,1385p' scripts/check-qemu-install

printf '%s\n' '--- all interface configuration commands in this script ---'
rg -n -C 5 \
  'interfaces ethernet|hw-id|ifname|eth0\.\.eth7|eth0\.\.eth7|random\.sample|reboot' \
  scripts/check-qemu-install || true

printf '%s\n' '--- repository references to interface bootstrap configuration ---'
rg -n -C 4 \
  'set interfaces ethernet|hw-id' . \
  -g '!scripts/check-qemu-install' \
  -g '!*.iso' -g '!*.qcow2' -g '!*.img' || true

Repository: vyos/vyos-build

Length of output: 15033


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- interface test function and reboot assertions ---'
sed -n '1180,1385p' scripts/check-qemu-install

printf '%s\n' '--- all interface configuration commands in this script ---'
rg -n -C 5 \
  'interfaces ethernet|hw-id|ifname|eth0\.\.eth7|random\.sample|reboot' \
  scripts/check-qemu-install || true

printf '%s\n' '--- repository references to interface bootstrap configuration ---'
rg -n -C 4 \
  'set interfaces ethernet|hw-id' . \
  -g '!scripts/check-qemu-install' \
  -g '!*.iso' -g '!*.qcow2' -g '!*.img' || true

Repository: vyos/vyos-build

Length of output: 15033


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- interface and image setup ---'
sed -n '430,760p' scripts/check-qemu-install

printf '%s\n' '--- installation and initial boot flow ---'
sed -n '900,1125p' scripts/check-qemu-install

printf '%s\n' '--- configuration and interface-related symbols ---'
rg -n \
  'macbase|mac[0-9]|get_qemu_cmd|install|config\.boot|verify_eth_mac_mapping|interfaces ethernet|hw-id' \
  scripts/check-qemu-install

printf '%s\n' '--- current change summary ---'
git diff --stat
git status --short

Repository: vyos/vyos-build

Length of output: 26493


🌐 Web query:

VyOS install image automatically creates interfaces ethernet eth0 hw-id config.boot

💡 Result:

In VyOS, the hw-id parameter within the interfaces ethernet configuration is not automatically generated or injected into config.boot by the standard install image process [1]. The hw-id (Hardware ID) serves as a persistent mapping mechanism that ties a specific logical interface (e.g., eth0) to a physical network card identified by its MAC address [2][3]. This is particularly useful in virtualized or dynamic environments to ensure that network interface names remain consistent across reboots, even if the system's hardware discovery order changes [4][3]. Key points regarding hw-id and configuration: * Manual Configuration: Administrators typically define hw-id manually using CLI commands such as set interfaces ethernet eth0 hw-id 'XX:XX:XX:XX:XX:XX' [5][6]. * Automation Scenarios: While the standard installation does not auto-populate this, automated provisioning tools like cloud-init can be configured to inject hw-id settings into the configuration during the initial deployment [7]. * Renaming Consequences: If an hw-id is configured incorrectly or points to a non-existent MAC address, the system may fail to initialize the interface, often leading to interface renumbering or configuration load errors upon reboot [2][3]. * Distinction from MAC Spoofing: It is important to distinguish hw-id from the mac command; hw-id is for interface-to-hardware mapping [3], whereas mac (where supported) is used to change the MAC address presented by the interface [3]. If you find hw-id entries in your config.boot that you did not manually add, they may have been introduced by an automated provisioning script, cloud-init configuration, or a previous system setup that included these parameters in the configuration template [7][8][9].

Citations:


🌐 Web query:

site:github.com/vyos/vyos-1x "install image" "hw-id"

💡 Result:

The term "hw-id" in the context of VyOS is not a command, but rather a configuration node used to associate a physical network interface (Ethernet or wireless) with its specific hardware MAC address [1][2][3]. By defining the hw-id under an interface configuration (e.g., set interfaces ethernet eth0 hw-id 'xx:xx:xx:xx:xx:xx'), you ensure that the system consistently assigns the name (e.g., eth0) to the physical network card with that specific MAC address, regardless of the boot order or the presence of other hardware [3][4]. This mechanism is crucial in VyOS for predictable network interface naming [4]. If an interface is renamed or replaced, updating or deleting the hw-id entry is a common administrative task to allow the system to re-identify and correctly assign the interface name [3][4]. There is no "install image hw-id" command; if you encountered such a phrase, it likely refers to setting the hw-id during the initial installation or configuration of a network interface within a VyOS system image [5][2].

Citations:


🌐 Web query:

site:github.com/vyos/vyos-1x "hw-id" "config.boot"

💡 Result:

In VyOS, the hw-id configuration node is used to associate a specific network interface (such as an ethernet or wireless interface) with a particular Media Access Control (MAC) address [1]. This configuration, found within the config.boot file, ensures that the system assigns the desired interface name (e.g., eth0) to the physical hardware that possesses the specified MAC address, regardless of the order in which the kernel detects the interfaces at boot time [2][3][4]. When you define an interface with an hw-id, the system uses this identifier to perform interface renaming reliably [4]. This prevents issues where interface names might swap on reboot if multiple network interfaces are present or if hardware is added or removed [4]. Example of the hw-id configuration in config.boot: interfaces { ethernet eth0 { hw-id "00:11:22:33:44:55" } } The system processes these entries during the boot sequence—specifically, mechanisms like vyos-net-name-resolve or similar helper scripts read this configuration to map the MAC address to the intended interface name [2][4]. If an interface is not explicitly configured with an hw-id, the system uses other methods for naming, which may be less predictable across different hardware configurations [4].

Citations:


🔍 Additional research query:

Search vyos.dev documentation and issue discussions for whether install imagecreatesinterfaces ethernet ethNnodes orhw-identries inconfig.boot, and whether VyOS delete is a no-op when the requested node or leaf is absent.

💡 Result:

Findings

install image and config.boot

  • VyOS documentation shows installed systems mapping logical interfaces such as eth0, eth1, etc. to MAC addresses, but does not explicitly state that install image writes those entries to config.boot.
  • VyOS issue T577 explicitly reports that a fresh installation discovers Ethernet interfaces and adds them to the configuration. A follow-up states that, with an empty config.boot, each boot added one new Ethernet interface until all were discovered.
  • T1499 states that the interface-renaming logic stores the NIC-to-MAC mapping in hw-id tags immediately before VyOS loads the configuration.

Conclusion: The documented issue evidence indicates that the discovery process can create both:

interfaces {
    ethernet ethN {
        hw-id <MAC>
    }
}

That is, interfaces ethernet ethN nodes and their hw-id leaves—not just one or the other. The evidence is historical and does not guarantee identical behavior in every current release.

delete when the target is absent

  • Current VyOS documentation says REST API strict mode rejects a delete when the target path does not exist; without strict mode, such deletes can succeed silently.
  • The documentation explicitly describes this behavior for the HTTPS /configure API, not necessarily for the interactive CLI.
  • T5991 shows a separate boot-verification issue where missing interfaces caused generated delete operations and entire configuration sections to be omitted. This is not evidence that an interactive CLI delete is inherently destructive or that it behaves identically to REST API deletion.

Conclusion: For the REST API, an absent-path delete is effectively a no-op by default; enable REST strict mode to reject it. The retrieved sources do not conclusively establish the exact absent-target behavior of the interactive CLI.


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

for url in \
  'https://raw.githubusercontent.com/vyos/vyos-1x/f01423cdf32be5ce5344c00d570c80bdbce6eda9/src/helpers/vyos_net_name' \
  'https://raw.githubusercontent.com/vyos/vyos-1x/a5654ac187681f802a356090390d4b66355ee8a5/src/op_mode/image_installer.py'
do
  echo "--- $url ---"
  curl -fsSL "$url" | rg -n -C 6 'hw-id|config\.boot|interfaces|rename|load' || true
done

printf '%s\n' '--- issue references ---'
for url in \
  'https://vyos.dev/T577' \
  'https://vyos.dev/T1499' \
  'https://vyos.dev/T5991'
do
  echo "--- $url ---"
  curl -fsSL "$url" | rg -n -i -C 3 'hw-id|config\.boot|interface|delete|discover' || true
done

Length of output: 50344


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

url='https://raw.githubusercontent.com/vyos/vyos-1x/f01423cdf32be5ce5344c00d570c80bdbce6eda9/src/helpers/vyos_net_name'
echo "--- $url ---"
curl -fsSL "$url" | grep -n -E -C 8 'hw-id|config\.boot|interfaces|rename' | head -n 160

url='https://raw.githubusercontent.com/vyos/vyos-1x/a5654ac187681f802a356090390d4b66355ee8a5/src/op_mode/image_installer.py'
echo "--- $url ---"
curl -fsSL "$url" | grep -n -E -C 8 'hw-id|config\.boot|interfaces|rename|load' | head -n 240

Length of output: 4874


🏁 Script executed (no clone):

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- vyos_net_name assignment and persistence paths ---'
curl -fsSL \
  'https://raw.githubusercontent.com/vyos/vyos-1x/f01423cdf32be5ce5344c00d570c80bdbce6eda9/src/helpers/vyos_net_name' |
  sed -n '70,245p'

printf '%s\n' '--- installer file locations ---'
curl -fsSL \
  'https://api.github.com/repos/vyos/vyos-1x/git/trees/current?recursive=1' |
  jq -r '.tree[]?.path' |
  grep -E 'image_installer|install.*image|installer' |
  head -n 80

Length of output: 5964


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

path = Path("scripts/check-qemu-install")
lines = path.read_text().splitlines()

start = next(i for i, line in enumerate(lines) if "elif args.ifnametest:" in line)
end = next(i for i in range(start + 1, len(lines))
           if lines[i].startswith("    elif args.") or lines[i].startswith("    else:"))
block = lines[start:end]

commands = [
    line.strip()
    for line in block
    if "c.sendline(" in line
]
assertions = [
    line.strip()
    for line in block
    if any(token in line for token in (
        "show configuration", "compare", "config.boot", "assert", "raise"
    ))
]

print("ifnametest commands:")
print("\n".join(commands))
print("\nconfiguration-state checks:")
print("\n".join(assertions) or "<none>")

assert any("delete interfaces ethernet" in line for line in commands)
assert any("'save'" in line for line in commands)
assert not assertions
assert commands[-1].startswith("c.sendline('reboot now')")
PY

Repository: vyos/vyos-build

Length of output: 435


Assert the interface configuration mutations before reboot. At scripts/check-qemu-install:1321-1327, assert that the selected interface nodes and hw-id leaves exist before deletion, then assert that the candidate and /config/config.boot contain the expected removals. verify_eth_mac_mapping checks only runtime MACs, so no-op deletes can make the reboot test pass without testing persistence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-qemu-install` around lines 1319 - 1328, Update the interface
mutation flow around the c.sendline deletion and commit/save commands to assert
the selected interface nodes and hw-id leaves exist before deletion. After
applying the changes, assert both the candidate configuration and
/config/config.boot show the expected interface and hw-id removals before
reboot, rather than relying only on verify_eth_mac_mapping.

Source: MCP tools

c.sendline('exit')
c.expect(op_mode_prompt)

log.info('Rebooting to verify interface naming survives across reboot')
c.sendline('reboot now')
waitForLogin(c, log)
loginVM(c, log)

log.info('Collecting interface naming diagnostics')
c.sendline('show configuration commands | match "hw-id"')
c.expect(op_mode_prompt)
c.sendline('show interfaces ethernet')
c.expect(op_mode_prompt)
c.sendline('ip link show')
c.expect(op_mode_prompt)
c.sendline('show log | match "hw-id"')
c.expect(op_mode_prompt)
c.sendline('cat /run/vyos-net-name-resolve.json 2>/dev/null || true')
c.expect(op_mode_prompt)
c.sendline('show log kernel | match "eth"')
c.expect(op_mode_prompt)

verify_eth_mac_mapping(c, log)

elif args.raid:
# Verify RAID subsystem - by deleting a disk and re-create the array
# from scratch
Expand Down
Loading