mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-29 20:23:01 +02:00
Merge pull request #695 from kernelkit/custom-phys-address
This commit is contained in:
@@ -128,7 +128,7 @@ what provides a default, the same default MAC addresses to Linux:
|
||||
|
||||
This is important in case you want to run multiple R2S devices on the
|
||||
same LAN. Meaning you either have to change the MAC address in the
|
||||
U-Boot environment (below), or modify your `phys-address` setting in
|
||||
U-Boot environment (below), or use the `custom-phys-address` setting in
|
||||
Infix for the interface(s).
|
||||
|
||||
Break into U-Boot using Ctrl-C at power-on, preferably when the text
|
||||
|
||||
@@ -1,102 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Run .cfg migration jq scripts to backup and transform older .cfg files
|
||||
ident=$(basename "$0")
|
||||
|
||||
MIGRATIONS_DIR="/usr/share/confd/migrate"
|
||||
# Check if /cfg/startup-config.cfg needs to be migrated to new syntax.
|
||||
# Backup of the original is created in /cfg/backup/ for old versions,
|
||||
# the migrate tool inserts old version in name before .cfg extension.
|
||||
CONFIG_FILE="/cfg/startup-config.cfg"
|
||||
BACKUP_DIR="/cfg/backup"
|
||||
|
||||
note()
|
||||
{
|
||||
logger -I $$ -k -p user.notice -t "$ident" "$1"
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
logger -I $$ -k -p user.err -t "$ident" "$1"
|
||||
}
|
||||
|
||||
file_version()
|
||||
{
|
||||
jq -r '
|
||||
if .["infix-meta:meta"] | has("version") then
|
||||
.["infix-meta:meta"]["version"]
|
||||
else
|
||||
"0.0"
|
||||
end
|
||||
' "$1"
|
||||
}
|
||||
|
||||
atoi()
|
||||
{
|
||||
echo "$1" | awk -F. '{print $1 * 1000 + $2}'
|
||||
}
|
||||
BACKUP_FILE="/cfg/backup/startup-config.cfg"
|
||||
mkdir -p "$(dirname "$BACKUP_FILE")"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
# Nothing to migrate
|
||||
note "No $(basename "$CONFIG_FILE" .cfg) yet, likely factory reset."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cfg_version=$(file_version "$CONFIG_FILE")
|
||||
current_version=$(atoi "$cfg_version")
|
||||
|
||||
# Find the latest version by examining the highest numbered directory
|
||||
sys_version=$(find "$MIGRATIONS_DIR" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n1 | xargs -n1 basename)
|
||||
latest_version=$(atoi "$sys_version")
|
||||
|
||||
# Check for downgrade
|
||||
if [ "$current_version" -gt "$latest_version" ]; then
|
||||
err "Configuration file $CONFIG_FILE version ($cfg_version) is newer than the latest supported version ($sys_version). Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If the current version is already the latest, exit the script
|
||||
if [ "$current_version" -eq "$latest_version" ]; then
|
||||
note "Configuration is already at the latest version ($sys_version). No migration needed."
|
||||
elif migrate -cq "$CONFIG_FILE"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
note "Configuration file $CONFIG_FILE is of version $cfg_version, migrating ..."
|
||||
|
||||
# Create the backup directory if it doesn't exist
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Create a backup of the current configuration file
|
||||
nm=$(basename "$CONFIG_FILE" .cfg)
|
||||
BACKUP_FILE="$BACKUP_DIR/${nm}-${cfg_version}.cfg"
|
||||
if cp "$CONFIG_FILE" "$BACKUP_FILE"; then
|
||||
note "Backup created: $BACKUP_FILE"
|
||||
else
|
||||
err "Failed creating backup: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Apply the scripts for each version directory in sequence
|
||||
for version_dir in $(find "$MIGRATIONS_DIR" -mindepth 1 -maxdepth 1 -type d | sort -V); do
|
||||
dir=$(basename "$version_dir")
|
||||
version=$(atoi "$dir")
|
||||
|
||||
# Step by step upgrade file to latest version
|
||||
if [ "$current_version" -lt "$version" ]; then
|
||||
note "Applying migrations for version $dir ..."
|
||||
|
||||
# Apply all scripts in the version directory in order
|
||||
for script in $(find "$version_dir" -type f -name '*.sh' | sort -V); do
|
||||
note "Calling $script for $CONFIG_FILE ..."
|
||||
sh "$script" "$CONFIG_FILE"
|
||||
done
|
||||
|
||||
# File now at $version ...
|
||||
current_version="$version"
|
||||
fi
|
||||
done
|
||||
|
||||
# Update the JSON file to the latest version
|
||||
if jq --arg version "$sys_version" '.["infix-meta:meta"] = {"infix-meta:version": $version}' "$CONFIG_FILE" \
|
||||
> "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"; then
|
||||
note "Configuration updated to version $sys_version."
|
||||
else
|
||||
err "Failed updating configuration to version $sys_version!"
|
||||
exit 1
|
||||
fi
|
||||
migrate -i -b "$BACKUP_FILE" "$CONFIG_FILE"
|
||||
|
||||
+31
-4
@@ -5,14 +5,41 @@ All notable changes to the project are documented in this file.
|
||||
|
||||
[v24.10.0][] - UNRELEASED
|
||||
-------------------------
|
||||
|
||||
**News:** this release contains *breaking YANG changes* in custom MAC
|
||||
addresses for interfaces! For details, see below issue #680.
|
||||
|
||||
### Changes
|
||||
- OSPF: Add limitation to only allow one interface per area.
|
||||
- Update CONTRIBUTING.md for scaling core team and helping external
|
||||
contributors understand the development process, issue #672
|
||||
- OSPF: Add limitation to only allow one interface per area
|
||||
- The default builds now include the curiOS nftables container image,
|
||||
which can be used for advanced firewall setups. For an introduction
|
||||
see <https://kernelkit.org/posts/firewall-container/>
|
||||
|
||||
### Fixes
|
||||
- Fix #499 by adding a NACM rule to factory config, which by default
|
||||
deny everyone to read the user password hash.
|
||||
- Fix #499: add an NACM rule to factory config, which by default
|
||||
deny everyone to read user password hash(es)
|
||||
- Fix #663: internal Ethernet interfaces shown in CLI tab completion
|
||||
- Fix #674: CLI `show interfaces` display internal Ethernet interfaces,
|
||||
regression introduced late in v24.09 release cycle
|
||||
- Fix #676: port dropped from bridge when changing its VLAN membership
|
||||
from tagged to untagged
|
||||
- Fix #680: replace deviation for `phys-address` in ietf-interfaces.yang
|
||||
with `custom-phys-address` to allow for constructing more free-form
|
||||
MAC addresses based on the chassis MAC (a.k.a., base MAC) address.
|
||||
For more information, see the YANG model, a few examples are listed in
|
||||
the updated documentation.
|
||||
The syntax will be automatically updated in the `startup-config` and
|
||||
`factory-config` -- make sure to verify the changes and update any
|
||||
static `factory-config` used for your products
|
||||
- Fix #690: CLI `show ip route` command stops working after 24 hours,
|
||||
this includes all operational data in ietf-routing:/routing/ribs.
|
||||
- Fix #697: password is not always set for new users, bug introduced
|
||||
in v24.06.0 when replacing Augeas with native user handling
|
||||
- Fix BFD in OSPF, previously you could not enable BFD on a single
|
||||
interface without it was enabled on all interfaces.
|
||||
interface without it was enabled on all interfaces
|
||||
|
||||
|
||||
[v24.09.0][] - 2024-09-30
|
||||
-------------------------
|
||||
|
||||
+1
-1
@@ -420,7 +420,7 @@ line where we declare the `ntpd` end as a container network interface:
|
||||
admin@example:/config/interface/veth0/> end
|
||||
admin@example:/config/> edit interface ntpd
|
||||
admin@example:/config/interface/ntpd/> set ipv4 address 192.168.0.2 prefix-length 24
|
||||
admin@example:/config/interface/ntpd/> set phys-address 00:c0:ff:ee:00:01
|
||||
admin@example:/config/interface/ntpd/> set custom-phys-address static 00:c0:ff:ee:00:01
|
||||
admin@example:/config/interface/ntpd/> set container-network
|
||||
|
||||
> Notice how you can also set a custom MAC address at the same time.
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 339 KiB |
+1
-1
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 281 KiB |
@@ -14,8 +14,34 @@ Infix to exploit the unique features not available in IEEE models.
|
||||
|
||||
## Interface LEGO®
|
||||
|
||||
The network building blocks available in Linux are akin to the popular
|
||||
LEGO® bricks.
|
||||
|
||||

|
||||
|
||||
There are two types of relationships that can link two blocks together:
|
||||
|
||||
1. **Lower-to-upper**: Visually represented by an extruding square
|
||||
connected upwards to a square socket. An interface _can only have
|
||||
a single_ lower-to-upper relationship, i.e., it can be attached to
|
||||
a single upper interface like a bridge or a LAG. In `iproute2`
|
||||
parlance, this corresponds to the interface's `master` setting
|
||||
2. **Upper-to-lower**: Visually represented by an extruding semicircle
|
||||
connected downwards to a semicircle socket. The lower interface in
|
||||
these relationships _accepts multiple_ upper-to-lower relationships
|
||||
from different upper blocks. E.g., multiple VLANs and IP address
|
||||
blocks can be connected to the same lower interface
|
||||
|
||||

|
||||
|
||||
An interface may simultaneously have a _lower-to-upper_ relation to some
|
||||
other interface, and be the target of one or more _upper-to-lower_
|
||||
relationships. It is valid, for example, for a physical port to be
|
||||
attached to a bridge, but also have a VLAN interface stacked on top of
|
||||
it. In this example, traffic assigned to the VLAN in question would be
|
||||
diverted to the VLAN interface before entering the bridge, while all
|
||||
other traffic would be bridged as usual.
|
||||
|
||||
| **Type** | **Yang Model** | **Description** |
|
||||
| -------- | ----------------- | ------------------------------------------------------------- |
|
||||
| bridge | infix-if-bridge | SW implementation of an IEEE 802.1Q bridge |
|
||||
@@ -27,6 +53,7 @@ Infix to exploit the unique features not available in IEEE models.
|
||||
| | ieee802-ethernet-interface | |
|
||||
| veth | infix-if-veth | Virtual Ethernet pair, typically one end is in a container |
|
||||
|
||||
|
||||
## Data Plane
|
||||
|
||||
The blocks you choose, and how you connect them, defines your data plane.
|
||||
@@ -50,6 +77,65 @@ possible to share with a container. Meaning, all the building blocks
|
||||
used on the left hand side can also be used freely on the right hand
|
||||
side as well.
|
||||
|
||||
|
||||
### General
|
||||
|
||||
General interface settings include `type`, `enable`, custom MAC address,
|
||||
and text `description`. Other settings have their own sections, below.
|
||||
|
||||
The `type` is important to set when configuring devices remotely because
|
||||
unlike the CLI, a NETCONF or RESTCONF session cannot guess the interface
|
||||
type for you. The operating system provides an override of the
|
||||
available interface types.
|
||||
|
||||
An `enabled` interface can be inspected using the operational datastore,
|
||||
nodes `admin-state` and `oper-state` show the status, . Possible values
|
||||
are listed in the YANG model.
|
||||
|
||||
The `custom-phys-address` can be used to set an interface's MAC address.
|
||||
This is an extension to the ietf-interfaces YANG model, which defines
|
||||
`phys-address` as read-only[^4]. The following shows the different
|
||||
configuration options.
|
||||
|
||||
> **Note:** there is no validation or safety checks performed by the
|
||||
> system when using `custom-phys-address`. In particular the `offset`
|
||||
> variant can be dangerous to use -- pay attention to the meaning of
|
||||
> bits in the upper-most octet: local bit, multicast/group, etc.
|
||||
|
||||
#### Fixed custom MAC
|
||||
|
||||
```
|
||||
admin@example:/config/> edit interface veth0a
|
||||
admin@example:/config/interface/veth0a/> set custom-phys-address static 00:ab:00:11:22:33
|
||||
|
||||
=> 00:ab:00:11:22:33
|
||||
```
|
||||
|
||||
#### Chassis MAC
|
||||
|
||||
Chassis MAC, sometimes also referred to as base MAC. In these two
|
||||
examples it is `00:53:00:c0:ff:ee`.
|
||||
|
||||
```
|
||||
admin@example:/config/> edit interface veth0a
|
||||
admin@example:/config/interface/veth0a/> set custom-phys-address chassis
|
||||
|
||||
=> 00:53:00:c0:ff:ee
|
||||
```
|
||||
|
||||
#### Chassis MAC, with offset
|
||||
|
||||
When constructing a derived address it is recommended to set the locally
|
||||
administered bit. Same chassis MAC as before.
|
||||
|
||||
```
|
||||
admin@example:/config/> edit interface veth0a
|
||||
admin@example:/config/interface/veth0a/> set custom-phys-address chassis offset 02:00:00:00:00:02
|
||||
|
||||
=> 02:53:00:c0:ff:f0
|
||||
```
|
||||
|
||||
|
||||
### Bridging
|
||||
|
||||
This is the most central part of the system. A bridge is a switch, and
|
||||
@@ -1031,3 +1117,6 @@ currently supported, namely `ipv4` and `ipv6`.
|
||||
mapping the low-order 23-bits of the IP address in the low-order 23
|
||||
bits of the Ethernet address 01:00:5E:00:00:00. Meaning, more than
|
||||
one IP multicast group maps to the same MAC multicast group.
|
||||
[^4]: A YANG deviation was previously used to make it possible to set
|
||||
`phys-address`, but this has been replaced with the more flexible
|
||||
`custom-phys-address`.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
################################################################################
|
||||
|
||||
CONFD_VERSION = 1.1
|
||||
CONFD_VERSION = 1.2
|
||||
CONFD_SITE_METHOD = local
|
||||
CONFD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/confd
|
||||
CONFD_LICENSE = BSD-3-Clause
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pkglibexec_SCRIPTS = bootstrap error load gen-service gen-hostname \
|
||||
gen-interfaces gen-motd gen-hardware gen-version
|
||||
sbin_SCRIPTS = dagger
|
||||
sbin_SCRIPTS = dagger migrate
|
||||
|
||||
+23
-4
@@ -22,6 +22,17 @@
|
||||
########################################################################
|
||||
STATUS=""
|
||||
|
||||
# Log functions
|
||||
critical()
|
||||
{
|
||||
logger -i -p user.crit -t bootstrap "$1" 2>/dev/null || echo "$1"
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
logger -i -p user.err -t bootstrap "$1" 2>/dev/null || echo "$1"
|
||||
}
|
||||
|
||||
# When logging errors, generating /etc/issue* or /etc/banner (SSH)
|
||||
. /etc/os-release
|
||||
|
||||
@@ -33,8 +44,7 @@ if [ "$1" = "-f" ] && [ -f "$2" ]; then
|
||||
fi
|
||||
|
||||
if [ ! -f "$RC" ]; then
|
||||
logger -sik -p user.error -t bootstrap "Missing rc file $RC" 2>/dev/null \
|
||||
|| echo "Missing rc file $RC"
|
||||
err "Missing rc file $RC"
|
||||
exit 99
|
||||
fi
|
||||
|
||||
@@ -72,7 +82,7 @@ collate()
|
||||
# Report error on console, syslog, and set login banners for getty + ssh
|
||||
console_error()
|
||||
{
|
||||
logger -p user.crit -t bootstrap "$1"
|
||||
critical "$1"
|
||||
|
||||
# shellcheck disable=SC3037
|
||||
/bin/echo -e "\n\n\e[31mCRITICAL BOOTSTRAP ERROR\n$1\e[0m\n" > /dev/console
|
||||
@@ -157,11 +167,20 @@ else
|
||||
fi
|
||||
rc=$?
|
||||
|
||||
# Ensure 'admin' group users always have access
|
||||
chgrp wheel "$CFG_PATH_"
|
||||
chmod g+w "$CFG_PATH_"
|
||||
|
||||
# Ensure factory-config has correct syntax
|
||||
if ! migrate -cq "$INIT_DATA"; then
|
||||
if migrate -iq -b "${INIT_DATA%.*}.bak" "$INIT_DATA"; then
|
||||
err "${INIT_DATA}: found and fixed old syntax!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! sysrepoctl -z "$INIT_DATA"; then
|
||||
rc=$?
|
||||
logger -sik -p user.error "Failed loading factory-default datastore"
|
||||
err "Failed loading factory-default datastore"
|
||||
else
|
||||
# Clear running-config so we can load/create startup in the next step
|
||||
temp=$(mktemp)
|
||||
|
||||
Executable
+235
@@ -0,0 +1,235 @@
|
||||
#!/bin/sh
|
||||
# Run .cfg migration jq scripts to backup and transform older syntax.
|
||||
scripts="/usr/share/confd/migrate"
|
||||
ident=$(basename "$0")
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "usage: $0 [-chiq] [-b /path/backup/file.ext] [/path/to/config.cfg]"
|
||||
echo
|
||||
echo "options:"
|
||||
echo " -b FILE Create backup FILE, appends detected version before .ext"
|
||||
echo " -c Check only, returns false when migration is not needed"
|
||||
echo " -h This help text"
|
||||
echo " -i Edit file in-place instead of sending to stdout, like sed"
|
||||
echo " -q Quiet, skip normal log messages, only errors are logged"
|
||||
echo
|
||||
echo "By default, this script reads a .cfg file on stdin, or as the first"
|
||||
echo "non-option argument, then migrates it to a new syntax on stdout."
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2317
|
||||
cleanup()
|
||||
{
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
note()
|
||||
{
|
||||
if [ -n "$quiet" ]; then
|
||||
return
|
||||
fi
|
||||
logger -I $$ -k -p user.notice -t "$ident" "$1"
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
logger -I $$ -k -p user.err -t "$ident" "$1"
|
||||
}
|
||||
|
||||
# Convert human-readable version to integer level
|
||||
atoi()
|
||||
{
|
||||
echo "$1" | awk -F. '{print $1 * 1000 + $2}'
|
||||
}
|
||||
|
||||
file_version()
|
||||
{
|
||||
jq -r '
|
||||
if .["infix-meta:meta"] | has("infix-meta:version") then
|
||||
.["infix-meta:meta"]["infix-meta:version"]
|
||||
else
|
||||
"0.0"
|
||||
end
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# Find the latest version by examining the highest numbered directory
|
||||
confd_version()
|
||||
{
|
||||
find "$scripts" -mindepth 1 -maxdepth 1 -type d \
|
||||
| sort -V | tail -n1 | xargs -n1 basename
|
||||
}
|
||||
|
||||
# Update meta data with the latest version
|
||||
meta_version()
|
||||
{
|
||||
if jq --arg version "$sys_version" '.["infix-meta:meta"] = {"infix-meta:version": $version}' "$2" \
|
||||
> "${2}.tmp" && mv "${2}.tmp" "$2"; then
|
||||
note "$1: configuration updated to version $sys_version."
|
||||
return 0
|
||||
fi
|
||||
|
||||
err "$1: failed updating configuration to version $sys_version!"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Apply the scripts for each version directory in sequence
|
||||
migrate()
|
||||
{
|
||||
note "$1: migrating from version $cfg_version"
|
||||
|
||||
for version_dir in $(find "$scripts" -mindepth 1 -maxdepth 1 -type d | sort -V); do
|
||||
dir=$(basename "$version_dir")
|
||||
version=$(atoi "$dir")
|
||||
|
||||
# Step by step upgrade file to latest version
|
||||
if [ "$cfg_level" -lt "$version" ]; then
|
||||
note "Applying migrations for version $dir ..."
|
||||
|
||||
# Apply all scripts in the version directory in order
|
||||
for script in $(find "$version_dir" -type f -name '*.sh' | sort -V); do
|
||||
note "$1: calling $script ..."
|
||||
sh "$script" "$2"
|
||||
done
|
||||
|
||||
# File now at $version ...
|
||||
cfg_level="$version"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Try migrating a copy, then diff the files, for factory-config check
|
||||
diff()
|
||||
{
|
||||
CFG=$1
|
||||
TMP=$(mktemp)
|
||||
|
||||
cp -p "$CFG" "$TMP"
|
||||
quiet=1
|
||||
migrate "(diff)" "$TMP"
|
||||
cmp -s "$CFG" "$TMP"
|
||||
rc=$?
|
||||
rm -f "$TMP"
|
||||
|
||||
return $rc
|
||||
}
|
||||
|
||||
|
||||
tmp=$(mktemp)
|
||||
chmod 600 "$tmp"
|
||||
|
||||
trap cleanup INT HUP TERM EXIT
|
||||
|
||||
OPTS=$(getopt -o b:chiq -- "$@")
|
||||
eval set -- "$OPTS"
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
case $1 in
|
||||
-b)
|
||||
bak=$2
|
||||
shift
|
||||
;;
|
||||
-c)
|
||||
check=1
|
||||
;;
|
||||
-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-i)
|
||||
inplace=1
|
||||
;;
|
||||
-q)
|
||||
quiet=1
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
# Likely file argument
|
||||
break
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -n "$1" ] && [ -f "$1" ]; then
|
||||
# Copy to tempfile to allow user to > same file
|
||||
orig="$1"
|
||||
cp -p "$1" "$tmp"
|
||||
shift
|
||||
elif [ -t 0 ]; then
|
||||
orig="(stdin)"
|
||||
cat > "$tmp"
|
||||
else
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! jq empty "$tmp" 2>/dev/null; then
|
||||
err "$tmp invalid JSON format!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cfg_version=$(file_version "$tmp")
|
||||
sys_version=$(confd_version)
|
||||
|
||||
cfg_level=$(atoi "$cfg_version")
|
||||
sys_level=$(atoi "$sys_version")
|
||||
|
||||
# Check for downgrade
|
||||
if [ "$cfg_level" -gt "$sys_level" ]; then
|
||||
err "$orig: version is newer ($cfg_version) than supported ($sys_version). Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If the current version is already the latest, exit the script
|
||||
if [ "$cfg_level" -eq "$sys_level" ]; then
|
||||
exit 0
|
||||
else
|
||||
if [ -n "$check" ]; then
|
||||
# We may be called to check a file without meta:version (factory)
|
||||
if [ "$cfg_version" = "0.0" ]; then
|
||||
if diff "$tmp"; then
|
||||
# File is OK, despite lacking meta:version
|
||||
exit 0
|
||||
fi
|
||||
msg="$orig: has syntax error, requires migrating."
|
||||
else
|
||||
msg="$orig: version $cfg_version, requires migrating."
|
||||
fi
|
||||
if [ -t 0 ]; then
|
||||
echo "$msg"
|
||||
else
|
||||
note "$msg"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$bak" ]; then
|
||||
fil="${bak%.*}"
|
||||
ext="${bak##*.}"
|
||||
bak="${fil}-${cfg_version}.${ext}"
|
||||
if cp -p "$tmp" "$bak" 2>/dev/null; then
|
||||
note "$orig: backup created: $bak"
|
||||
else
|
||||
err "$orig: failed creating backup: $bak"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! migrate "$orig" "$tmp"; then
|
||||
exit 1
|
||||
fi
|
||||
meta_version "$orig" "$tmp"
|
||||
|
||||
if [ -n "$inplace" ] && [ "$orig" != "(stdin)" ]; then
|
||||
cp -p "$tmp" "$orig"
|
||||
else
|
||||
cat "$tmp"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,6 +1,6 @@
|
||||
AC_PREREQ(2.61)
|
||||
# confd version is same as system YANG model version, step on breaking changes
|
||||
AC_INIT([confd], [1.1], [https://github.com/kernelkit/infix/issues])
|
||||
AC_INIT([confd], [1.2], [https://github.com/kernelkit/infix/issues])
|
||||
AM_INIT_AUTOMAKE(1.11 foreign subdir-objects)
|
||||
AM_SILENT_RULES(yes)
|
||||
|
||||
@@ -15,6 +15,7 @@ AC_CONFIG_FILES([
|
||||
share/migrate/Makefile
|
||||
share/migrate/1.0/Makefile
|
||||
share/migrate/1.1/Makefile
|
||||
share/migrate/1.2/Makefile
|
||||
src/Makefile
|
||||
yang/Makefile
|
||||
])
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
# Migrate phys-address -> custom-phys-address with static option
|
||||
|
||||
file=$1
|
||||
temp=${file}.tmp
|
||||
|
||||
jq '(
|
||||
.["ietf-interfaces:interfaces"].interface[] |=
|
||||
if has("phys-address") then
|
||||
.["infix-interfaces:custom-phys-address"] = { "static": .["phys-address"] } | del(.["phys-address"])
|
||||
else
|
||||
.
|
||||
end
|
||||
)' "$file" > "$temp" &&
|
||||
mv "$temp" "$file"
|
||||
@@ -0,0 +1,2 @@
|
||||
migratedir = $(pkgdatadir)/migrate/1.2
|
||||
dist_migrate_DATA = 10-ietf-interfaces-phys-address.sh
|
||||
@@ -1,2 +1,2 @@
|
||||
SUBDIRS = 1.0 1.1
|
||||
SUBDIRS = 1.0 1.1 1.2
|
||||
migratedir = $(pkgdatadir)/migrate
|
||||
|
||||
+108
-16
@@ -431,15 +431,110 @@ static int netdag_gen_link_mtu(FILE *ip, struct lyd_node *dif)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void calc_mac(const char *base_mac, const char *mac_offset, char *buf, size_t len)
|
||||
{
|
||||
uint8_t base[6], offset[6], result[6];
|
||||
int carry = 0, i;
|
||||
|
||||
sscanf(base_mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
||||
&base[0], &base[1], &base[2], &base[3], &base[4], &base[5]);
|
||||
|
||||
sscanf(mac_offset, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
||||
&offset[0], &offset[1], &offset[2], &offset[3], &offset[4], &offset[5]);
|
||||
|
||||
for (i = 5; i >= 0; i--) {
|
||||
int sum = base[i] + offset[i] + carry;
|
||||
|
||||
result[i] = sum & 0xFF;
|
||||
carry = (sum > 0xFF) ? 1 : 0;
|
||||
}
|
||||
|
||||
snprintf(buf, len, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
result[0], result[1], result[2], result[3], result[4], result[5]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get child value from a diff parent, only returns value if not
|
||||
* deleted. In which case the deleted flag may be set.
|
||||
*/
|
||||
static const char *get_val(struct lyd_node *parent, char *name, int *deleted)
|
||||
{
|
||||
const char *value = NULL;
|
||||
struct lyd_node *node;
|
||||
|
||||
node = lydx_get_child(parent, name);
|
||||
if (node) {
|
||||
if (lydx_get_op(node) == LYDX_OP_DELETE) {
|
||||
if (deleted)
|
||||
*deleted = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
value = lyd_get_value(node);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Locate custom-phys-address, adjust for any offset, and return pointer
|
||||
* to a static string. (Which will be overwritten on subsequent calls.)
|
||||
*
|
||||
* The 'deleted' flag will be set if any of the nodes in the subtree are
|
||||
* deleted. Used when restoring permaddr and similar.
|
||||
*/
|
||||
static char *get_phys_addr(struct lyd_node *parent, int *deleted)
|
||||
{
|
||||
struct lyd_node *node, *cpa;
|
||||
static char mac[18];
|
||||
struct json_t *j;
|
||||
const char *ptr;
|
||||
|
||||
cpa = lydx_get_descendant(lyd_child(parent), "custom-phys-address", NULL);
|
||||
if (!cpa || lydx_get_op(cpa) == LYDX_OP_DELETE) {
|
||||
if (cpa && deleted)
|
||||
*deleted = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ptr = get_val(cpa, "static", deleted);
|
||||
if (ptr) {
|
||||
strlcpy(mac, ptr, sizeof(mac));
|
||||
return mac;
|
||||
}
|
||||
|
||||
node = lydx_get_child(cpa, "chassis");
|
||||
if (!node || lydx_get_op(node) == LYDX_OP_DELETE) {
|
||||
if (node && deleted)
|
||||
*deleted = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j = json_object_get(confd.root, "mac-address");
|
||||
if (!j) {
|
||||
WARN("cannot set chassis based MAC, not found.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ptr = json_string_value(j);
|
||||
strlcpy(mac, ptr, sizeof(mac));
|
||||
|
||||
ptr = get_val(node, "offset", deleted);
|
||||
if (ptr)
|
||||
calc_mac(mac, ptr, mac, sizeof(mac));
|
||||
|
||||
return mac;
|
||||
}
|
||||
|
||||
static int netdag_gen_link_addr(FILE *ip, struct lyd_node *cif, struct lyd_node *dif)
|
||||
{
|
||||
const char *ifname = lydx_get_cattr(dif, "name");
|
||||
const char *mac = NULL;
|
||||
struct lyd_node *node;
|
||||
const char *mac;
|
||||
int deleted = 0;
|
||||
char buf[32];
|
||||
|
||||
node = lydx_get_child(dif, "phys-address");
|
||||
if (lydx_get_op(node) == LYDX_OP_DELETE) {
|
||||
mac = get_phys_addr(dif, &deleted);
|
||||
if (!mac && deleted) {
|
||||
FILE *fp;
|
||||
|
||||
/*
|
||||
@@ -455,8 +550,6 @@ static int netdag_gen_link_addr(FILE *ip, struct lyd_node *cif, struct lyd_node
|
||||
if (mac && !strcmp(mac, "null"))
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
mac = lyd_get_value(node);
|
||||
}
|
||||
|
||||
if (!mac || !strlen(mac)) {
|
||||
@@ -1344,9 +1437,8 @@ static int netdag_gen_bridge(sr_session_ctx_t *session, struct dagger *net, stru
|
||||
* addrgenmode eui64 with random mac, issue #357.
|
||||
*/
|
||||
if (add) {
|
||||
const char *mac;
|
||||
const char *mac = get_phys_addr(cif, NULL);
|
||||
|
||||
mac = lydx_get_cattr(cif, "phys-address");
|
||||
if (!mac) {
|
||||
struct json_t *j;
|
||||
|
||||
@@ -1449,16 +1541,17 @@ static int netdag_gen_veth(struct dagger *net, struct lyd_node *dif,
|
||||
return ERR_IFACE(cif, err, "Unable to add dep \"%s\" to %s", peer, ifname);
|
||||
} else {
|
||||
char ifname_args[64] = "", peer_args[64] = "";
|
||||
const char *mac;
|
||||
|
||||
dagger_skip_iface(net, peer);
|
||||
|
||||
node = lydx_get_child(dif, "phys-address");
|
||||
if (node)
|
||||
snprintf(ifname_args, sizeof(ifname_args), "address %s", lyd_get_value(node));
|
||||
mac = get_phys_addr(dif, NULL);
|
||||
if (mac)
|
||||
snprintf(ifname_args, sizeof(ifname_args), "address %s", mac);
|
||||
|
||||
node = lydx_find_by_name(lyd_parent(cif), "interface", peer);
|
||||
if (node && (node = lydx_get_child(node, "phys-address")))
|
||||
snprintf(peer_args, sizeof(peer_args), "address %s", lyd_get_value(node));
|
||||
if (node && (mac = get_phys_addr(node, NULL)))
|
||||
snprintf(peer_args, sizeof(peer_args), "address %s", mac);
|
||||
|
||||
fprintf(ip, "link add dev %s %s type veth peer %s %s\n",
|
||||
ifname, ifname_args, peer, peer_args);
|
||||
@@ -1612,10 +1705,9 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
|
||||
|
||||
static bool is_phys_addr_deleted(struct lyd_node *dif)
|
||||
{
|
||||
struct lyd_node *node;
|
||||
int deleted = 0;
|
||||
|
||||
node = lydx_get_child(dif, "phys-address");
|
||||
if (node && lydx_get_op(node) == LYDX_OP_DELETE)
|
||||
if (!get_phys_addr(dif, &deleted) && deleted)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
@@ -37,7 +37,7 @@ MODULES=(
|
||||
"ieee802-ethernet-interface@2019-06-21.yang"
|
||||
"infix-ethernet-interface@2024-02-27.yang"
|
||||
"infix-factory-default@2023-06-28.yang"
|
||||
"infix-interfaces@2024-09-23.yang -e vlan-filtering"
|
||||
"infix-interfaces@2024-10-08.yang -e vlan-filtering"
|
||||
|
||||
# from rousette
|
||||
"ietf-restconf@2017-01-26.yang"
|
||||
|
||||
@@ -9,6 +9,9 @@ module infix-interfaces {
|
||||
import ietf-interfaces {
|
||||
prefix if;
|
||||
}
|
||||
import ietf-yang-types {
|
||||
prefix yang;
|
||||
}
|
||||
|
||||
include infix-if-base;
|
||||
include infix-if-bridge;
|
||||
@@ -20,6 +23,11 @@ module infix-interfaces {
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Linux bridge and lag extensions for ietf-interfaces.";
|
||||
|
||||
revision 2024-10-08 {
|
||||
description "Replace writable phy-address with custom-phys-address.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2024-09-23 {
|
||||
description "Drop interfaces-state deviation, already marked deprecated.";
|
||||
reference "internal";
|
||||
@@ -71,9 +79,34 @@ module infix-interfaces {
|
||||
}
|
||||
}
|
||||
|
||||
deviation "/if:interfaces/if:interface/if:phys-address" {
|
||||
deviate replace {
|
||||
config true;
|
||||
augment "/if:interfaces/if:interface" {
|
||||
description "Custom phys-address management, static or derived from chassis MAC.";
|
||||
|
||||
container custom-phys-address {
|
||||
description "Override the default physical address.";
|
||||
|
||||
choice type {
|
||||
description "Choose between static MAC address or chassis-derived MAC.";
|
||||
|
||||
case static {
|
||||
leaf static {
|
||||
description "Statically configured interface address on protocol sub-layer, e.g., MAC.";
|
||||
type yang:phys-address;
|
||||
}
|
||||
}
|
||||
|
||||
case chassis {
|
||||
container chassis {
|
||||
description "Derive physical address from chassis MAC address.";
|
||||
presence "Enable chassis-derived address.";
|
||||
|
||||
leaf offset {
|
||||
description "Static offset added to the chassis MAC address.";
|
||||
type yang:phys-address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,64 @@
|
||||
"""
|
||||
Custom MAC address on interface
|
||||
|
||||
Test possibility to set and remove custom mac address on interfaces
|
||||
Verify support for setting and removing a custom MAC address on interfaces.
|
||||
Both static MAC address and derived from the chassis MAC with, or without,
|
||||
an offset applied.
|
||||
"""
|
||||
import copy
|
||||
|
||||
import infamy
|
||||
import infamy.iface as iface
|
||||
|
||||
from infamy.util import until
|
||||
|
||||
|
||||
def calc_mac(base_mac, mac_offset):
|
||||
"""Add mac_offset to base_mac and return result."""
|
||||
base = [int(x, 16) for x in base_mac.split(':')]
|
||||
offset = [int(x, 16) for x in mac_offset.split(':')]
|
||||
result = [0] * 6
|
||||
carry = 0
|
||||
|
||||
for i in range(5, -1, -1):
|
||||
total = base[i] + offset[i] + carry
|
||||
result[i] = total & 0xFF
|
||||
carry = 1 if total > 0xFF else 0
|
||||
|
||||
return ':'.join(f'{x:02x}' for x in result)
|
||||
|
||||
|
||||
def reset_mac(tgt, port, mac):
|
||||
"""Reset DUT interface MAC address to default."""
|
||||
node = "infix-interfaces:custom-phys-address"
|
||||
xpath = iface.get_iface_xpath(port, node)
|
||||
tgt.delete_xpath(xpath)
|
||||
with test.step("Verify target:data MAC address is reset to default"):
|
||||
until(lambda: iface.get_phys_address(tgt, tport) == mac)
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
CMD = "jq -r '.[\"mac-address\"]' /run/system.json"
|
||||
|
||||
with test.step("Initialize"):
|
||||
env = infamy.Env()
|
||||
target = env.attach("target", "mgmt")
|
||||
tgtssh = env.attach("target", "mgmt", "ssh")
|
||||
_, tport = env.ltop.xlate("target", "data")
|
||||
pmac = iface.get_phys_address(target, tport)
|
||||
cmac = "02:01:00:c0:ff:ee"
|
||||
print(f"Target iface {tport} original mac {pmac}")
|
||||
cmac = tgtssh.runsh(CMD).stdout.strip()
|
||||
STATIC = "02:01:00:c0:ff:ee"
|
||||
OFFSET = "00:00:00:00:ff:aa"
|
||||
|
||||
with test.step("Set custom MAC address to '02:01:00:c0:ff:ee' on target:mgmt"):
|
||||
print(f"Intitial MAC address: {pmac}")
|
||||
print(f"Chassis MAC address target: {cmac}")
|
||||
print(f"Default MAC address of {tport} : {pmac}")
|
||||
|
||||
with test.step("Set target:data static MAC address '02:01:00:c0:ff:ee'"):
|
||||
config = {
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": f"{tport}",
|
||||
"phys-address": f"{cmac}"
|
||||
"custom-phys-address": {
|
||||
"static": f"{STATIC}"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -33,14 +67,53 @@ with infamy.Test() as test:
|
||||
|
||||
with test.step("Verify target:mgmt has MAC address '02:01:00:c0:ff:ee'"):
|
||||
mac = iface.get_phys_address(target, tport)
|
||||
print(f"Target iface {tport} current mac: {mac}")
|
||||
print(f"Current MAC: {mac}, should be: {STATIC}")
|
||||
assert mac == STATIC
|
||||
|
||||
with test.step("Reset target:mgmt MAC address to default"):
|
||||
reset_mac(target, tport, pmac)
|
||||
|
||||
with test.step("Set target:data to chassis MAC"):
|
||||
config = {
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": f"{tport}",
|
||||
"custom-phys-address": {
|
||||
"chassis": {}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
target.put_config_dict("ietf-interfaces", config)
|
||||
|
||||
with test.step("Verify target:data has chassis MAC"):
|
||||
mac = iface.get_phys_address(target, tport)
|
||||
print(f"Current MAC: {mac}, should be: {cmac}")
|
||||
assert mac == cmac
|
||||
|
||||
with test.step("Remove custom MAC address '02:01:00:c0:ff:ee'"):
|
||||
xpath=iface.get_iface_xpath(tport, "phys-address")
|
||||
target.delete_xpath(xpath)
|
||||
with test.step("Set target:data to chassis MAC + offset"):
|
||||
print(f"Setting chassis MAC {cmac} + offset {OFFSET}")
|
||||
config = {
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": f"{tport}",
|
||||
"custom-phys-address": {
|
||||
"chassis": {
|
||||
"offset": f"{OFFSET}"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
target.put_config_dict("ietf-interfaces", config)
|
||||
|
||||
with test.step("Verify that target:mgmt has the original MAC address again"):
|
||||
until(lambda: iface.get_phys_address(target, tport) == pmac)
|
||||
with test.step("Verify target:data has chassis MAC + offset"):
|
||||
mac = iface.get_phys_address(target, tport)
|
||||
BMAC = calc_mac(cmac, OFFSET)
|
||||
print(f"Current MAC: {mac}, should be: {BMAC} (calculated)")
|
||||
assert mac == BMAC
|
||||
|
||||
with test.step("Reset target:mgmt MAC address to default"):
|
||||
reset_mac(target, tport, pmac)
|
||||
|
||||
test.succeed()
|
||||
|
||||
Reference in New Issue
Block a user