Merge pull request #695 from kernelkit/custom-phys-address

This commit is contained in:
Joachim Wiberg
2024-10-11 18:30:36 +02:00
committed by GitHub
20 changed files with 646 additions and 142 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 281 KiB

+89
View File
@@ -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.
![Linux Networking Blocks](img/lego.svg)
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
![Stacking order dependencies](img/lego-relations.svg)
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`.
+1 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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)
+235
View File
@@ -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
+2 -1
View File
@@ -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"
+2
View File
@@ -0,0 +1,2 @@
migratedir = $(pkgdatadir)/migrate/1.2
dist_migrate_DATA = 10-ietf-interfaces-phys-address.sh
+1 -1
View File
@@ -1,2 +1,2 @@
SUBDIRS = 1.0 1.1
SUBDIRS = 1.0 1.1 1.2
migratedir = $(pkgdatadir)/migrate
+108 -16
View File
@@ -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;
+1 -1
View File
@@ -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"
+36 -3
View File
@@ -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()