mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 04:33:00 +02:00
confd,statd: add support for link aggregates
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
committed by
Tobias Waldekranz
parent
ec2e161649
commit
78499757b0
@@ -30,6 +30,7 @@ confd_plugin_la_SOURCES = \
|
||||
ieee802-ethernet-interface.c \
|
||||
ietf-ip.c \
|
||||
infix-if-bridge.c \
|
||||
infix-if-lag.c \
|
||||
infix-if-bridge-mcd.c \
|
||||
infix-if-bridge-port.c \
|
||||
infix-if-veth.c \
|
||||
|
||||
@@ -73,6 +73,10 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path)
|
||||
inferred.data.string_val = "infix-if-type:ethernet";
|
||||
else if (!fnmatch("br+([0-9])", ifname, FNM_EXTMATCH))
|
||||
inferred.data.string_val = "infix-if-type:bridge";
|
||||
else if (!fnmatch("bond+([0-9])", ifname, FNM_EXTMATCH))
|
||||
inferred.data.string_val = "infix-if-type:lag";
|
||||
else if (!fnmatch("lag+([0-9])", ifname, FNM_EXTMATCH))
|
||||
inferred.data.string_val = "infix-if-type:lag";
|
||||
else if (!fnmatch("docker+([0-9])", ifname, FNM_EXTMATCH))
|
||||
inferred.data.string_val = "infix-if-type:bridge";
|
||||
else if (!fnmatch("dummy+([0-9])", ifname, FNM_EXTMATCH))
|
||||
@@ -299,6 +303,7 @@ static int netdag_gen_sysctl_setting(struct dagger *net, const char *ifname, FIL
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netdag_gen_sysctl(struct dagger *net,
|
||||
struct lyd_node *cif,
|
||||
struct lyd_node *dif)
|
||||
@@ -355,6 +360,8 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net,
|
||||
case IFT_GRE:
|
||||
case IFT_GRETAP:
|
||||
return gre_gen(NULL, cif, ip);
|
||||
case IFT_LAG:
|
||||
return lag_gen(dif, cif, ip, 1);
|
||||
case IFT_VETH:
|
||||
return veth_gen(NULL, cif, ip);
|
||||
case IFT_VLAN:
|
||||
@@ -368,7 +375,6 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net,
|
||||
case IFT_UNKNOWN:
|
||||
sr_session_set_error_message(net->session, "%s: unsupported interface type \"%s\"",
|
||||
ifname, lydx_get_cattr(cif, "type"));
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
__builtin_unreachable();
|
||||
@@ -383,6 +389,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
|
||||
switch (iftype_from_iface(cif)) {
|
||||
case IFT_BRIDGE:
|
||||
return bridge_gen(dif, cif, ip, 0);
|
||||
case IFT_LAG:
|
||||
return lag_gen(dif, cif, ip, 0);
|
||||
case IFT_VLAN:
|
||||
return vlan_gen(dif, cif, ip);
|
||||
|
||||
@@ -420,6 +428,9 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif)
|
||||
case IFT_GRE:
|
||||
case IFT_GRETAP:
|
||||
return lydx_get_descendant(lyd_child(dif), "gre", NULL);
|
||||
case IFT_LAG:
|
||||
return lydx_get_child(dif, "custom-phys-address") ||
|
||||
lydx_get_descendant(lyd_child(dif), "lag", "mode", NULL);
|
||||
case IFT_VLAN:
|
||||
return lydx_get_descendant(lyd_child(dif), "vlan", NULL);
|
||||
case IFT_VETH:
|
||||
@@ -505,6 +516,7 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif,
|
||||
case IFT_DUMMY:
|
||||
case IFT_GRE:
|
||||
case IFT_GRETAP:
|
||||
case IFT_LAG:
|
||||
case IFT_VLAN:
|
||||
case IFT_VXLAN:
|
||||
case IFT_UNKNOWN:
|
||||
@@ -593,6 +605,10 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net
|
||||
if (err)
|
||||
goto err_close_ip;
|
||||
|
||||
err = lag_port_gen(dif, cif);
|
||||
if (err)
|
||||
goto err_close_ip;
|
||||
|
||||
/* Set type specific attributes */
|
||||
if (!fixed && op != LYDX_OP_CREATE) {
|
||||
err = netdag_gen_afspec_set(session, net, dif, cif, ip);
|
||||
@@ -613,8 +629,7 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net
|
||||
fprintf(ip, "link set alias \"%s\" dev %s\n", attr ?: "", ifname);
|
||||
|
||||
/* Bring interface back up, if enabled */
|
||||
attr = lydx_get_cattr(cif, "enabled");
|
||||
if (!attr || !strcmp(attr, "true"))
|
||||
if (lydx_is_enabled(cif, "enabled"))
|
||||
fprintf(ip, "link set dev %s up state up\n", ifname);
|
||||
|
||||
err = err ? : netdag_gen_sysctl(net, cif, dif);
|
||||
@@ -641,8 +656,8 @@ static int netdag_init_iface(struct lyd_node *cif)
|
||||
switch (iftype_from_iface(cif)) {
|
||||
case IFT_BRIDGE:
|
||||
return bridge_add_deps(cif);
|
||||
/* case IFT_LAG: */
|
||||
/* return lag_add_deps(cif); */
|
||||
case IFT_LAG:
|
||||
return lag_add_deps(cif);
|
||||
case IFT_VLAN:
|
||||
return vlan_add_deps(cif);
|
||||
case IFT_VETH:
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
_map(IFT_ETHISH, "infix-if-type:etherlike") \
|
||||
_map(IFT_GRE, "infix-if-type:gre") \
|
||||
_map(IFT_GRETAP, "infix-if-type:gretap") \
|
||||
_map(IFT_LAG, "infix-if-type:lag") \
|
||||
_map(IFT_LO, "infix-if-type:loopback") \
|
||||
_map(IFT_VETH, "infix-if-type:veth") \
|
||||
_map(IFT_VLAN, "infix-if-type:vlan") \
|
||||
@@ -70,14 +71,26 @@ static inline const char *bridge_tagtype2str(const char *type)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline bool is_bridge_port(struct lyd_node *cif)
|
||||
static inline struct lyd_node *get_master(struct lyd_node *cif)
|
||||
{
|
||||
struct lyd_node *node = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL);
|
||||
struct lyd_node *node;
|
||||
|
||||
if (!node || !lydx_get_child(node, "bridge"))
|
||||
return false;
|
||||
node = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL);
|
||||
if (node)
|
||||
return lydx_get_child(node, "bridge");
|
||||
|
||||
return true;
|
||||
node = lydx_get_descendant(lyd_child(cif), "lag-port", NULL);
|
||||
if (node)
|
||||
return lydx_get_child(node, "lag");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline bool is_member_port(struct lyd_node *cif)
|
||||
{
|
||||
if (get_master(cif))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +121,11 @@ int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||
/* infix-if-gre.c */
|
||||
int gre_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||
|
||||
/* infix-if-lag.c */
|
||||
int lag_port_gen(struct lyd_node *dif, struct lyd_node *cif);
|
||||
int lag_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, int add);
|
||||
int lag_add_deps(struct lyd_node *cif);
|
||||
|
||||
/* infix-if-veth.c */
|
||||
bool veth_is_primary(struct lyd_node *cif);
|
||||
int ifchange_cand_infer_veth(sr_session_ctx_t *session, const char *path);
|
||||
|
||||
@@ -22,7 +22,7 @@ int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif,
|
||||
struct lyd_node *node;
|
||||
FILE *fp;
|
||||
|
||||
if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_bridge_port(cif)) {
|
||||
if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_member_port(cif)) {
|
||||
fputs(" addrgenmode none", ip);
|
||||
return 0;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif,
|
||||
snprintf(defaults, sizeof(defaults), "/etc/default/zeroconf-%s", ifname);
|
||||
|
||||
/* no ipv4 at all, ipv4 selectively disabled, or interface is a bridge port */
|
||||
if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_bridge_port(cif))
|
||||
if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_member_port(cif))
|
||||
goto disable;
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
#include <fnmatch.h>
|
||||
#include <stdbool.h>
|
||||
#include <jansson.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <srx/common.h>
|
||||
#include <srx/lyx.h>
|
||||
#include <srx/srx_val.h>
|
||||
|
||||
#include "ietf-interfaces.h"
|
||||
|
||||
/*
|
||||
* The lag-port noode is only in the dif at initial creation, or removal.
|
||||
* When a lag is recreated, e.g., when changing mode, the lag-port is only
|
||||
* in the cif.
|
||||
*
|
||||
* To simplify confd dependency handling we always call this function, so
|
||||
* a lag member always has its lag master reset. This is cruicial in the
|
||||
* case described above when the the lag is removed to change mode.
|
||||
*/
|
||||
int lag_port_gen(struct lyd_node *dif, struct lyd_node *cif)
|
||||
{
|
||||
const char *ifname = lydx_get_cattr(cif, "name");
|
||||
struct lyd_node *node;
|
||||
const char *lagname;
|
||||
int err = 0;
|
||||
FILE *fp;
|
||||
|
||||
node = lydx_get_descendant(lyd_child(dif), "lag-port", NULL);
|
||||
if (node) {
|
||||
struct lydx_diff lagdiff = { 0 };
|
||||
|
||||
if (!lydx_get_diff(lydx_get_child(node, "lag"), &lagdiff))
|
||||
goto fail;
|
||||
|
||||
if (lagdiff.old) {
|
||||
fp = dagger_fopen_net_exit(&confd.netdag, lagdiff.old,
|
||||
NETDAG_EXIT_LOWERS, "delete-ports.ip");
|
||||
if (!fp) {
|
||||
err = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fprintf(fp, "link set %s nomaster\n", ifname);
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
lagname = lagdiff.new;
|
||||
} else {
|
||||
node = lydx_get_descendant(lyd_child(cif), "lag-port", NULL);
|
||||
lagname = lydx_get_cattr(node, "lag");
|
||||
if (!node || !lagname)
|
||||
goto fail; /* done, nothing to do */
|
||||
}
|
||||
|
||||
fp = dagger_fopen_net_init(&confd.netdag, lagname, NETDAG_INIT_LOWERS,
|
||||
"add-ports.ip");
|
||||
if (!fp)
|
||||
return -EIO;
|
||||
|
||||
fprintf(fp, "link set %s down master %s\n", ifname, lagname);
|
||||
if (lydx_is_enabled(cif, "enabled"))
|
||||
fprintf(fp, "link set dev %s up state up\n", ifname);
|
||||
fclose(fp);
|
||||
|
||||
err = dagger_add_dep(&confd.netdag, lagname, ifname);
|
||||
if (err)
|
||||
ERROR("%s: unable to add dep to %s", ifname, lagname);
|
||||
fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
int lag_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, int add)
|
||||
{
|
||||
const char *lagname = lydx_get_cattr(cif, "name");
|
||||
const char *op = add ? "add" : "set";
|
||||
struct lyd_node *lag, *mon;
|
||||
const char *mode;
|
||||
int err = 0;
|
||||
|
||||
lag = lydx_get_descendant(lyd_child(cif), "lag", NULL);
|
||||
if (!lag)
|
||||
return -EINVAL;
|
||||
|
||||
/* Must take lag down if changing mode */
|
||||
if (!add)
|
||||
fprintf(ip, "link set %s down\n", lagname);
|
||||
|
||||
fprintf(ip, "link %s dev %s", op, lagname);
|
||||
|
||||
if (add)
|
||||
link_gen_address(cif, ip);
|
||||
|
||||
fprintf(ip, " type bond min_links 1");
|
||||
|
||||
mode = lydx_get_cattr(lag, "mode");
|
||||
if (!strcmp(mode, "lacp")) {
|
||||
struct lyd_node *lacp = lydx_get_child(lag, "lacp");
|
||||
|
||||
if (add)
|
||||
fprintf(ip, " mode 802.3ad ad_select bandwidth");
|
||||
|
||||
mode = lydx_get_cattr(lacp, "mode");
|
||||
if (!strcmp(mode, "active"))
|
||||
mode = "on";
|
||||
else
|
||||
mode = "off";
|
||||
|
||||
fprintf(ip, " lacp_rate %s lacp_active %s",
|
||||
lydx_get_cattr(lacp, "rate"), mode);
|
||||
} else {
|
||||
/* XXX: mode hard-coded for now for mv88e6xxxx operation */
|
||||
if (add)
|
||||
fprintf(ip, " mode balance-xor");
|
||||
}
|
||||
|
||||
/*
|
||||
* Required in lacp mode, we rely on it also in static mode.
|
||||
* A previous attempt supported arp-monitor, but it does not
|
||||
* work with mv88e6xxx link aggregates, unfortunately so was
|
||||
* dropped for the final version.
|
||||
*/
|
||||
fprintf(ip, " miimon 100 use_carrier 1");
|
||||
|
||||
mon = lydx_get_descendant(lyd_child(lag), "link-monitor", NULL);
|
||||
if (mon) {
|
||||
const struct lyd_node *debounce;
|
||||
|
||||
debounce = lydx_get_descendant(lyd_child(mon), "debounce", NULL);
|
||||
if (debounce) {
|
||||
const char *msec;
|
||||
|
||||
msec = lydx_get_cattr(mon, "up");
|
||||
if (msec)
|
||||
fprintf(ip, " updelay %s", msec);
|
||||
msec = lydx_get_cattr(mon, "down");
|
||||
if (msec)
|
||||
fprintf(ip, " downdelay %s", msec);
|
||||
}
|
||||
}
|
||||
|
||||
fputs("\n", ip);
|
||||
|
||||
/*
|
||||
* When netdag_must_del() is triggered, this is when we reattach
|
||||
* unmodified ports when recreating the lag.
|
||||
*/
|
||||
if (add) {
|
||||
struct lyd_node *node, *cifs;
|
||||
|
||||
cifs = lydx_get_descendant(lyd_parent(cif), "interfaces", "interface", NULL);
|
||||
LYX_LIST_FOR_EACH(cifs, node, "interface")
|
||||
err += lag_port_gen(NULL, node);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int lag_add_deps(struct lyd_node *cif)
|
||||
{
|
||||
const char *lagname = lydx_get_cattr(cif, "name");
|
||||
struct ly_set *ports;
|
||||
const char *portname;
|
||||
int err = 0;
|
||||
uint32_t i;
|
||||
|
||||
ports = lydx_find_xpathf(cif, "../interface[lag-port/lag='%s']", lagname);
|
||||
if (!ports)
|
||||
return ERR_IFACE(cif, -ENOENT, "Unable to fetch lag ports");
|
||||
|
||||
|
||||
for (i = 0; i < ports->count; i++) {
|
||||
portname = lydx_get_cattr(ports->dnodes[i], "name");
|
||||
|
||||
err = dagger_add_dep(&confd.netdag, lagname, portname);
|
||||
if (err) {
|
||||
ERR_IFACE(cif, err, "Unable to depend on \"%s\"", portname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ly_set_free(ports, NULL);
|
||||
return err;
|
||||
}
|
||||
+204
-100
@@ -10,6 +10,12 @@ submodule infix-if-lag {
|
||||
import iana-if-type {
|
||||
prefix ianaift;
|
||||
}
|
||||
import ietf-inet-types {
|
||||
prefix inet;
|
||||
}
|
||||
import ietf-yang-types {
|
||||
prefix yang;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
@@ -24,35 +30,93 @@ submodule infix-if-lag {
|
||||
* Typedefs
|
||||
*/
|
||||
|
||||
typedef lag-type {
|
||||
description "Mode values for link aggregates.";
|
||||
typedef lag-mode {
|
||||
description "Mode values for link aggregates.";
|
||||
|
||||
/* Temporarily limited to 802.1AX and Balanced XOR */
|
||||
type enumeration {
|
||||
enum static {
|
||||
description "Static mode (Balanced XOR).";
|
||||
value 2;
|
||||
}
|
||||
enum lacp {
|
||||
description "IEEE 802.3ad LACP mode.";
|
||||
value 4;
|
||||
}
|
||||
type enumeration {
|
||||
enum static {
|
||||
description "Static mode (Balanced XOR).";
|
||||
}
|
||||
enum lacp {
|
||||
description "IEEE 802.3ad LACP mode.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef lag-type {
|
||||
description "Static mode type of distribution (driver modes).";
|
||||
|
||||
type enumeration {
|
||||
enum balance-rr;
|
||||
enum active-backup;
|
||||
enum balance-xor;
|
||||
enum broadcast;
|
||||
enum balance-tlb;
|
||||
enum balance-alb;
|
||||
}
|
||||
}
|
||||
|
||||
typedef hash-policy {
|
||||
description "Egress hash policy, note: offloading limitations!";
|
||||
|
||||
type enumeration {
|
||||
enum layer2;
|
||||
enum layer3-4;
|
||||
enum layer2-3;
|
||||
enum encap2-3;
|
||||
enum encap3-4;
|
||||
enum vlan-srcmac;
|
||||
}
|
||||
}
|
||||
|
||||
typedef member-state {
|
||||
description "Lag port membership state, taking active part or backup.";
|
||||
|
||||
type enumeration {
|
||||
enum backup;
|
||||
enum active;
|
||||
}
|
||||
}
|
||||
|
||||
typedef lacp-mode {
|
||||
description "LACP mode values.";
|
||||
description "LACP mode values.";
|
||||
|
||||
type enumeration {
|
||||
enum passive {
|
||||
description "LACP active mode";
|
||||
value 0;
|
||||
}
|
||||
enum active {
|
||||
description "LACP passive mode.";
|
||||
value 1;
|
||||
}
|
||||
type enumeration {
|
||||
enum passive {
|
||||
description "LACP active mode";
|
||||
}
|
||||
enum active {
|
||||
description "LACP passive mode.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef lacp-rate {
|
||||
description "LACP rate values.";
|
||||
|
||||
type enumeration {
|
||||
enum slow {
|
||||
description "Send LACPDUs every 30 seconds (90-second timeout).";
|
||||
}
|
||||
enum fast {
|
||||
description "Send LACPDUs every 1 second (3-second timeout).";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef lacp-state {
|
||||
description "LACP port state flags.";
|
||||
|
||||
type enumeration {
|
||||
enum active;
|
||||
enum short_timeout;
|
||||
enum aggregating;
|
||||
enum in_sync;
|
||||
enum collecting;
|
||||
enum distributing;
|
||||
enum defaulted;
|
||||
enum expired;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -62,78 +126,8 @@ submodule infix-if-lag {
|
||||
grouping hash {
|
||||
leaf hash {
|
||||
description "Transmit hash policy.";
|
||||
type hash-policy;
|
||||
config false; // For now, staically set to layer2 only
|
||||
type string;
|
||||
}
|
||||
}
|
||||
|
||||
grouping lacp-settings {
|
||||
description "LACP mode settings.";
|
||||
|
||||
container lacp {
|
||||
description "Settings specific for LACP mode.";
|
||||
|
||||
uses hash;
|
||||
|
||||
leaf mode {
|
||||
description "Operational mode of LACP, default: active.
|
||||
|
||||
- Active: initiates negotiation by sending LACPDUs.
|
||||
- Passive: waits for the peer to initiate.
|
||||
|
||||
At least one end of the link must be in active mode. When both ends
|
||||
are active, there is slightly more traffic, but the default ensures
|
||||
fail-safe operation.
|
||||
|
||||
Passive mode is typically used for troubleshooting, in dynamic
|
||||
setups (e.g., MLAG), or to minimize the risk of unintended
|
||||
aggregation.
|
||||
|
||||
For most production scenarios, active mode is preferred to ensure
|
||||
faster and more predictable link aggregation.";
|
||||
type lacp-mode;
|
||||
default "active";
|
||||
}
|
||||
|
||||
leaf rate {
|
||||
description "Rate of LACP keep-alives, default: slow.
|
||||
|
||||
Determines the frequency of LACPDU transmission and the associated
|
||||
timeout for link failure detection.
|
||||
|
||||
- slow: Sends LACPDUs every 30 seconds. The associated timeout is 90
|
||||
seconds, meaning a link is considered failed after 3 consecutive
|
||||
missed LACPDUs.
|
||||
|
||||
- fast: Sends LACPDUs every 1 second. The associated timeout is 3
|
||||
seconds, meaning a link is considered failed after 3 consecutive
|
||||
missed LACPDUs.
|
||||
|
||||
The selected rate affects the responsiveness of link failure
|
||||
detection and the amount of control traffic.";
|
||||
type enumeration {
|
||||
enum slow {
|
||||
description "Send LACPDUs every 30 seconds (90-second timeout).";
|
||||
}
|
||||
enum fast {
|
||||
description "Send LACPDUs every 1 second (3-second timeout).";
|
||||
}
|
||||
}
|
||||
default "slow";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grouping static-settings {
|
||||
container static {
|
||||
config false; // For now, we need to read out mode and other status
|
||||
|
||||
uses hash;
|
||||
|
||||
leaf mode {
|
||||
description "Active mode for static aggregates.";
|
||||
type string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,12 +147,100 @@ submodule infix-if-lag {
|
||||
|
||||
leaf mode {
|
||||
description "Link aggregation mode.";
|
||||
type lag-type;
|
||||
type lag-mode;
|
||||
mandatory true;
|
||||
}
|
||||
|
||||
uses lacp-settings;
|
||||
uses static-settings;
|
||||
container lacp {
|
||||
description "Settings specific for LACP mode.";
|
||||
|
||||
uses hash;
|
||||
|
||||
leaf mode {
|
||||
description "Operational mode of LACP, default: active.
|
||||
|
||||
- Active: initiates negotiation by sending LACPDUs.
|
||||
- Passive: waits for the peer to initiate.
|
||||
|
||||
At least one end of the link must be in active mode. When both ends
|
||||
are active, there is slightly more traffic, but the default ensures
|
||||
fail-safe operation.
|
||||
|
||||
Passive mode is typically used for troubleshooting, in dynamic
|
||||
setups (e.g., MLAG), or to minimize the risk of unintended
|
||||
aggregation.
|
||||
|
||||
For most production scenarios, active mode is preferred to ensure
|
||||
faster and more predictable link aggregation.";
|
||||
type lacp-mode;
|
||||
default active;
|
||||
}
|
||||
|
||||
leaf rate {
|
||||
description "Rate of LACP keep-alives, default: slow.
|
||||
|
||||
Determines the frequency of LACPDU transmission and the associated
|
||||
timeout for link failure detection.
|
||||
|
||||
- slow: Sends LACPDUs every 30 seconds. The associated timeout is 90
|
||||
seconds, meaning a link is considered failed after 3 consecutive
|
||||
missed LACPDUs.
|
||||
|
||||
- fast: Sends LACPDUs every 1 second. The associated timeout is 3
|
||||
seconds, meaning a link is considered failed after 3 consecutive
|
||||
missed LACPDUs.
|
||||
|
||||
The selected rate affects the responsiveness of link failure
|
||||
detection and the amount of control traffic.";
|
||||
type lacp-rate;
|
||||
default slow;
|
||||
}
|
||||
|
||||
leaf system-priority {
|
||||
description "Sytem priority used by the node on this LAG interface.
|
||||
|
||||
Lower value is higher priority for determining which node
|
||||
is the controlling system.";
|
||||
type uint16 {
|
||||
range "1 .. 65535";
|
||||
}
|
||||
}
|
||||
|
||||
leaf aggregator-id {
|
||||
description "Aggregator ID.";
|
||||
config false;
|
||||
type uint16;
|
||||
}
|
||||
|
||||
leaf actor-key {
|
||||
description "Actor key.";
|
||||
config false;
|
||||
type uint16;
|
||||
}
|
||||
|
||||
leaf partner-key {
|
||||
description "Partner key.";
|
||||
config false;
|
||||
type uint16;
|
||||
}
|
||||
|
||||
leaf partner-mac {
|
||||
description "Partner MAC address.";
|
||||
config false;
|
||||
type yang:phys-address;
|
||||
}
|
||||
}
|
||||
|
||||
container static {
|
||||
config false; // For now, we need to read out mode and other status
|
||||
|
||||
leaf mode {
|
||||
description "Active mode for static aggregates.";
|
||||
type lag-type;
|
||||
}
|
||||
|
||||
uses hash;
|
||||
}
|
||||
|
||||
container link-monitor {
|
||||
description "Link monitor properties.";
|
||||
@@ -183,6 +265,16 @@ submodule infix-if-lag {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
must "not(./mode = 'lacp' and ./monitor/arp-monitor/interval > 0)" {
|
||||
error-message "ARP monitor cannot be enabled in LACP mode.";
|
||||
description "The Linux bond driver disables miimon when arpmon != 0.";
|
||||
}
|
||||
|
||||
must "not(./mode = 'lacp' and ./monitor/link-monitor/interval = 0)" {
|
||||
error-message "Link monitor must be enabled in LACP mode.";
|
||||
description "The Linux bond driver requires miimon in 802.3ad mode.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,24 +298,36 @@ submodule infix-if-lag {
|
||||
error-message "Must refer to a valid LAG interface.";
|
||||
}
|
||||
}
|
||||
|
||||
leaf state {
|
||||
description "Port state, active or backup member.";
|
||||
description "Link state, active or backup member.";
|
||||
type member-state;
|
||||
config false;
|
||||
}
|
||||
|
||||
leaf link-failures {
|
||||
description "Link failure counter, cannot be reset.";
|
||||
type uint32;
|
||||
config false;
|
||||
type string;
|
||||
}
|
||||
|
||||
container lacp {
|
||||
description "LACP port state, ours and partner.";
|
||||
config false;
|
||||
|
||||
leaf aggregator-id {
|
||||
description "Aggregator ID.";
|
||||
type uint16;
|
||||
}
|
||||
|
||||
leaf-list actor-state {
|
||||
description "LACP state flags.";
|
||||
type string;
|
||||
type lacp-state;
|
||||
}
|
||||
|
||||
leaf-list partner-state {
|
||||
description "LACP state flags for link partner.";
|
||||
type string;
|
||||
type lacp-state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,6 +451,34 @@ class Iface:
|
||||
self.pvid = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'pvid')
|
||||
self.stp_state = get_json_data('', self.data, 'infix-interfaces:bridge-port',
|
||||
'stp', 'cist', 'state')
|
||||
|
||||
self.lag_mode = get_json_data('', self.data, 'infix-interfaces:lag', 'mode')
|
||||
if self.lag_mode:
|
||||
self.lag_type = get_json_data('', self.data, 'infix-interfaces:lag', 'static', 'mode')
|
||||
if self.lag_mode == "lacp":
|
||||
self.lag_hash = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'hash')
|
||||
self.lacp_id = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'aggregator-id')
|
||||
self.lacp_actor_key = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'actor-key')
|
||||
self.lacp_partner_key = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'partner-key')
|
||||
self.lacp_partner_mac = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'partner-mac')
|
||||
self.lacp_sys_prio = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'system-priority')
|
||||
else:
|
||||
self.lag_hash = get_json_data('', self.data, 'infix-interfaces:lag', 'static', 'hash')
|
||||
self.link_updelay = get_json_data('', self.data, 'infix-interfaces:lag', 'link-monitor', 'debounce', 'up')
|
||||
self.link_downdelay = get_json_data('', self.data, 'infix-interfaces:lag', 'link-monitor', 'debounce', 'down')
|
||||
|
||||
self.lacp_mode = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'mode')
|
||||
rate = get_json_data('', self.data, 'infix-interfaces:lag', 'lacp', 'rate')
|
||||
self.lacp_rate = "fast (1s)" if rate == "fast" else "slow (30 sec)"
|
||||
|
||||
self.lag = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lag')
|
||||
if self.lag:
|
||||
self.lag_state = get_json_data('', self.data, 'infix-interfaces:lag-port', 'state')
|
||||
self.lacp_id = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'aggregator-id')
|
||||
self.lacp_state = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'actor-state')
|
||||
self.lacp_pstate = get_json_data('', self.data, 'infix-interfaces:lag-port', 'lacp', 'partner-state')
|
||||
self.link_failures = get_json_data('', self.data, 'infix-interfaces:lag-port', 'link-failures')
|
||||
|
||||
self.containers = get_json_data('', self.data, 'infix-interfaces:container-network', 'containers')
|
||||
|
||||
|
||||
@@ -491,6 +519,9 @@ class Iface:
|
||||
def is_bridge(self):
|
||||
return self.type == "infix-if-type:bridge"
|
||||
|
||||
def is_lag(self):
|
||||
return self.type == "infix-if-type:lag"
|
||||
|
||||
def is_veth(self):
|
||||
return self.data['type'] == "infix-if-type:veth"
|
||||
|
||||
@@ -635,6 +666,59 @@ class Iface:
|
||||
self.pr_proto_ipv4()
|
||||
self.pr_proto_ipv6()
|
||||
|
||||
def pr_proto_lag(self, member=True):
|
||||
data_str = ""
|
||||
|
||||
row = f"{'lag':<{Pad.proto}}"
|
||||
if member:
|
||||
state = self.lag_state.upper()
|
||||
if self.oper() == "up":
|
||||
row += Decore.green(f"{state:<{Pad.state}}")
|
||||
else:
|
||||
row += Decore.yellow(f"{state:<{Pad.state}}")
|
||||
if self.lacp_state:
|
||||
lacp = ', '.join(self.lacp_state)
|
||||
data_str += lacp
|
||||
else:
|
||||
dec = Decore.green if self.oper() == "up" else Decore.yellow
|
||||
row += dec(f"{self.oper().upper():<{Pad.state}}")
|
||||
data_str += f"{self.lag_mode}"
|
||||
if self.lag_mode == "lacp":
|
||||
data_str += f": {self.lacp_mode}"
|
||||
data_str += f", rate: {self.lacp_rate}"
|
||||
data_str += f", hash: {self.lag_hash}"
|
||||
else:
|
||||
data_str += f": {self.lag_type}"
|
||||
data_str += f", hash: {self.lag_hash}"
|
||||
|
||||
if data_str:
|
||||
row += f"{data_str:<{Pad.data}}"
|
||||
|
||||
print(row)
|
||||
|
||||
def pr_lag(self, _ifaces):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_lag(member=False)
|
||||
|
||||
lowers = []
|
||||
for _iface in [Iface(data) for data in _ifaces]:
|
||||
if _iface.lag and _iface.lag == self.name:
|
||||
lowers.append(_iface)
|
||||
|
||||
if lowers:
|
||||
self.pr_proto_eth(pipe='│')
|
||||
self.pr_proto_ipv4(pipe='│')
|
||||
self.pr_proto_ipv6(pipe='│')
|
||||
else:
|
||||
self.pr_proto_eth(pipe=' ')
|
||||
self.pr_proto_ipv4()
|
||||
self.pr_proto_ipv6()
|
||||
|
||||
for i, lower in enumerate(lowers):
|
||||
pipe = '└ ' if (i == len(lowers) -1) else '├ '
|
||||
lower.pr_name(pipe)
|
||||
lower.pr_proto_lag()
|
||||
|
||||
def pr_veth(self, _ifaces):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_veth()
|
||||
@@ -716,6 +800,32 @@ class Iface:
|
||||
if self.phys_address:
|
||||
print(f"{'physical address':<{20}}: {self.phys_address}")
|
||||
|
||||
if self.lag_mode:
|
||||
print(f"{'lag mode':<{20}}: {self.lag_mode}")
|
||||
if self.lag_mode == "lacp":
|
||||
print(f"{'lag hash':<{20}}: {self.lag_hash}")
|
||||
print(f"{'lacp mode':<{20}}: {self.lacp_mode}")
|
||||
print(f"{'lacp rate':<{20}}: {self.lacp_rate}")
|
||||
print(f"{'lacp aggregate id':<{20}}: {self.lacp_id}")
|
||||
print(f"{'lacp system priority':<{20}}: {self.lacp_sys_prio}")
|
||||
print(f"{'lacp actor key':<{20}}: {self.lacp_actor_key}")
|
||||
print(f"{'lacp partner key':<{20}}: {self.lacp_partner_key}")
|
||||
print(f"{'lacp partner mac':<{20}}: {self.lacp_partner_mac}")
|
||||
else:
|
||||
print(f"{'lag type':<{20}}: {self.lag_type}")
|
||||
print(f"{'lag hash':<{20}}: {self.lag_hash}")
|
||||
print(f"{'link debounce up':<{20}}: {self.link_updelay} msec")
|
||||
print(f"{'link debounce down':<{20}}: {self.link_downdelay} msec")
|
||||
|
||||
if self.lag:
|
||||
print(f"{'lag member':<{20}}: {self.lag}")
|
||||
print(f"{'lag member state':<{20}}: {self.lag_state}")
|
||||
if self.lacp_state:
|
||||
print(f"{'lacp aggregate id':<{20}}: {self.lacp_id}")
|
||||
print(f"{'lacp actor state':<{20}}: {', '.join(self.lacp_state)}")
|
||||
print(f"{'lacp partner state':<{20}}: {', '.join(self.lacp_pstate)}")
|
||||
print(f"{'link failure count':<{20}}: {self.link_failures}")
|
||||
|
||||
if self.ipv4_addr:
|
||||
first = True
|
||||
for addr in self.ipv4_addr:
|
||||
@@ -872,6 +982,10 @@ def pr_interface_list(json):
|
||||
iface.pr_bridge(ifaces)
|
||||
continue
|
||||
|
||||
if iface.is_lag():
|
||||
iface.pr_lag(ifaces)
|
||||
continue
|
||||
|
||||
if iface.is_veth():
|
||||
iface.pr_veth(ifaces)
|
||||
continue
|
||||
@@ -892,11 +1006,14 @@ def pr_interface_list(json):
|
||||
iface.pr_vlan(ifaces)
|
||||
continue
|
||||
|
||||
# These interfaces are printed by there parent, such as bridge
|
||||
# These interfaces are printed by their parent, such as bridge
|
||||
if iface.lower_if:
|
||||
continue
|
||||
if iface.bridge:
|
||||
continue
|
||||
if iface.lag:
|
||||
continue
|
||||
|
||||
print_interface(iface)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""The lag/bond oper-status always follows the carrier"""
|
||||
|
||||
|
||||
def lower(iplink):
|
||||
"""Return a dictionary of the status of a lag member"""
|
||||
port = {
|
||||
"lag": iplink['master'],
|
||||
}
|
||||
|
||||
info = iplink['linkinfo']['info_slave_data']
|
||||
if info:
|
||||
# active or backup link
|
||||
port['state'] = info['state'].lower()
|
||||
port['link-failures'] = info['link_failure_count']
|
||||
|
||||
# On etherlike interfaces and tap interfaces oper-status lies
|
||||
# if info['mii_status'] == "DOWN":
|
||||
# iface['oper-status'] = "down"
|
||||
|
||||
# Initialize lacp dict only if we encounter LACP-related fields
|
||||
if 'ad_aggregator_id' in info:
|
||||
port['lacp'] = {}
|
||||
port['lacp']['aggregator-id'] = info['ad_aggregator_id']
|
||||
port['lacp']['actor-state'] = info['ad_actor_oper_port_state_str']
|
||||
port['lacp']['partner-state'] = info['ad_partner_oper_port_state_str']
|
||||
else:
|
||||
port['state'] = 'backup'
|
||||
port['link-failures'] = 0
|
||||
|
||||
return port
|
||||
|
||||
|
||||
def lag(iplink):
|
||||
"""Return a dictionary of the status of the lag"""
|
||||
mode = {
|
||||
"balance-xor": "static",
|
||||
"802.3ad": "lacp",
|
||||
}
|
||||
hash_policy = {
|
||||
"layer2": "layer2",
|
||||
"layer3+4": "layer3-4",
|
||||
"layer2+3": "layer2-3",
|
||||
"encap2+3": "encap2-3",
|
||||
"encap3+4": "encap3-4",
|
||||
"vlan+srcmac": "vlan-srcmac",
|
||||
}
|
||||
bond = {}
|
||||
|
||||
info = iplink["linkinfo"]["info_data"]
|
||||
if info:
|
||||
bond["mode"] = mode.get(info['mode'], "static")
|
||||
if bond["mode"] == "lacp":
|
||||
bond["lacp"] = {
|
||||
"mode": 'active' if info['ad_lacp_active'] == "on" else 'passive',
|
||||
"rate": info['ad_lacp_rate'],
|
||||
"hash": hash_policy.get(info['xmit_hash_policy'], "layer2"),
|
||||
}
|
||||
|
||||
if 'ad_info' in info:
|
||||
bond["lacp"]["aggregator-id"] = info['ad_info']['aggregator']
|
||||
bond["lacp"]["actor-key"] = info['ad_info']['actor_key']
|
||||
bond["lacp"]["partner-key"] = info['ad_info']['partner_key']
|
||||
bond["lacp"]["partner-mac"] = info['ad_info']['partner_mac']
|
||||
if 'ad_actor_sys_prio' in info:
|
||||
bond["lacp"]["system-priority"] = info['ad_actor_sys_prio']
|
||||
else:
|
||||
bond["static"] = {
|
||||
"mode": info['mode'],
|
||||
"hash": info['xmit_hash_policy']
|
||||
}
|
||||
|
||||
bond["link-monitor"] = {
|
||||
"debounce": {
|
||||
"up": info['updelay'],
|
||||
"down": info['downdelay']
|
||||
}
|
||||
}
|
||||
|
||||
return bond
|
||||
@@ -3,6 +3,7 @@ from . import common
|
||||
from . import bridge
|
||||
from . import ethernet
|
||||
from . import ip
|
||||
from . import lag
|
||||
from . import tun
|
||||
from . import veth
|
||||
from . import vlan
|
||||
@@ -117,16 +118,16 @@ def interface(iplink, ipaddr):
|
||||
interface["infix-interfaces:bridge"] = br
|
||||
if brport := bridge.lower(iplink):
|
||||
interface["infix-interfaces:bridge-port"] = brport
|
||||
# case "infix-if-type:lag":
|
||||
# if l := lag.lag(iplink):
|
||||
# interface["infix-interfaces:lag"] = l
|
||||
case "infix-if-type:lag":
|
||||
if lg := lag.lag(iplink):
|
||||
interface["infix-interfaces:lag"] = lg
|
||||
case "infix-if-type:ethernet":
|
||||
if eth := ethernet.ethernet(iplink):
|
||||
interface["ieee802-ethernet-interface:ethernet"] = eth
|
||||
case "infix-if-type:vxlan":
|
||||
if vxlan := tun.vxlan(iplink):
|
||||
interface["infix-interfaces:vxlan"] = vxlan
|
||||
case "infix-if-type:gre"|"infix-if-type:gretap":
|
||||
case "infix-if-type:gre" | "infix-if-type:gretap":
|
||||
if gre := tun.gre(iplink):
|
||||
interface["infix-interfaces:gre"] = gre
|
||||
case "infix-if-type:veth":
|
||||
@@ -140,9 +141,9 @@ def interface(iplink, ipaddr):
|
||||
case "infix-interfaces:bridge-port":
|
||||
if brport := bridge.lower(iplink):
|
||||
interface["infix-interfaces:bridge-port"] = brport
|
||||
# case "infix-interfaces:lag-port":
|
||||
# if lagport := lag.lower(iplink):
|
||||
# interface["infix-interfaces:lag-port"] = lagport
|
||||
case "infix-interfaces:lag-port":
|
||||
if lagport := lag.lower(iplink):
|
||||
interface["infix-interfaces:lag-port"] = lagport
|
||||
|
||||
return interface
|
||||
|
||||
|
||||
Reference in New Issue
Block a user