Merge pull request #870 from kernelkit/confd-ifaces-split

confd: Split up ietf-interfaces.c
This commit is contained in:
Tobias Waldekranz
2024-12-13 13:08:38 +01:00
committed by GitHub
8 changed files with 1387 additions and 1278 deletions
+6 -1
View File
@@ -26,7 +26,12 @@ confd_plugin_la_SOURCES = \
base64.c base64.h \
core.c core.h \
dagger.c dagger.h \
ietf-interfaces.c \
ietf-interfaces.c ietf-interfaces.h \
ieee802-ethernet-interface.c \
ietf-ip.c \
infix-if-bridge.c \
infix-if-veth.c \
infix-if-vlan.c \
ietf-keystore.c \
ietf-system.c \
ietf-syslog.c \
+147
View File
@@ -0,0 +1,147 @@
/* 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"
static bool iface_uses_autoneg(struct lyd_node *cif)
{
struct lyd_node *aneg = lydx_get_descendant(lyd_child(cif), "ethernet",
"auto-negotiation", NULL);
/* Because `ieee802-ethernet-interface` declares
* `auto-negotiation` as a presence container, the `enabled`
* leaf, although `true` by default, is not set if the whole
* container is absent. Since auto-negotiation is the expected
* default behavior for most Ethernet links, we choose to
* enable it in these situations.
*/
return !aneg || lydx_get_bool(aneg, "enable");
}
/*
* XXX: always disable flow control, for now, until we've added
* configurable support for flow-control/pause/direction and
* flow-control/force-flow-control
*/
static int netdag_gen_ethtool_flow_control(struct dagger *net, struct lyd_node *cif)
{
const char *ifname = lydx_get_cattr(cif, "name");
FILE *fp;
fp = dagger_fopen_next(net, "init", ifname, 10, "ethtool-aneg.sh");
if (!fp)
return -EIO;
fprintf(fp, "ethtool --pause %s autoneg %s rx off tx off\n",
ifname, iface_uses_autoneg(cif) ? "on" : "off");
fclose(fp);
return 0;
}
static int netdag_gen_ethtool_autoneg(struct dagger *net, struct lyd_node *cif)
{
struct lyd_node *eth = lydx_get_child(cif, "ethernet");
const char *ifname = lydx_get_cattr(cif, "name");
const char *speed, *duplex;
int mbps, err = 0;
FILE *fp;
fp = dagger_fopen_next(net, "init", ifname, 10, "ethtool-aneg.sh");
if (!fp)
return -EIO;
fprintf(fp, "ethtool --change %s autoneg ", ifname);
if (iface_uses_autoneg(cif)) {
fputs("on\n", fp);
} else {
speed = lydx_get_cattr(eth, "speed");
if (!speed) {
sr_session_set_error_message(net->session, "%s: "
"\"speed\" must be specified "
"when auto-negotiation is disabled", ifname);
err = -EINVAL;
goto out;
}
mbps = (int)(atof(speed) * 1000.);
if (!((mbps == 10) || (mbps == 100))) {
sr_session_set_error_message(net->session, "%s: "
"\"speed\" must be either 0.01 or 0.1 "
"when auto-negotiation is disabled", ifname);
err = -EINVAL;
goto out;
}
duplex = lydx_get_cattr(eth, "duplex");
if (!duplex || (strcmp(duplex, "full") && strcmp(duplex, "half"))) {
sr_session_set_error_message(net->session, "%s: "
"\"duplex\" must be either "
"\"full\" or \"half\" "
"when auto-negotiation is disabled", ifname);
err = -EINVAL;
goto out;
}
fprintf(fp,"off speed %d duplex %s\n", mbps, duplex);
}
out:
fclose(fp);
return err;
}
int netdag_gen_ethtool(struct dagger *net, struct lyd_node *cif, struct lyd_node *dif)
{
struct lyd_node *eth = lydx_get_child(dif, "ethernet");
const char *type = lydx_get_cattr(cif, "type");
int err;
/*
* Story time: when assigning a physical interface to a container, and then
* removing it, even though our type may be 'etherlike' we will
* get the following from sysrepo:
*
* "ieee802-ethernet-interface:ethernet": {
* "@": {
* "yang:operation": "delete"
* },
* "duplex": "full"
* },
*
* Hence this "redundant" check.
*/
if (strcmp(type, "infix-if-type:ethernet"))
return 0;
if (!eth)
return 0;
if (dagger_is_bootstrap(net) ||
lydx_get_descendant(lyd_child(eth), "auto-negotiation", "enable", NULL)) {
err = netdag_gen_ethtool_flow_control(net, cif);
if (err)
return err;
}
if (dagger_is_bootstrap(net) ||
lydx_get_descendant(lyd_child(eth), "auto-negotiation", "enable", NULL) ||
lydx_get_child(eth, "speed") ||
lydx_get_child(eth, "duplex")) {
err = netdag_gen_ethtool_autoneg(net, cif);
if (err)
return err;
}
return 0;
}
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef CONFD_IETF_INTERFACES_H_
#define CONFD_IETF_INTERFACES_H_
#include "core.h"
#include "cni.h"
#define IF_XPATH "/ietf-interfaces:interfaces/interface"
#define IF_VLAN_XPATH "%s/infix-interfaces:vlan"
#define ERR_IFACE(_iface, _err, _fmt, ...) \
({ \
ERROR("%s: " _fmt, lydx_get_cattr(_iface, "name"), \
##__VA_ARGS__); \
_err; \
})
#define DEBUG_IFACE(_iface, _fmt, ...) \
DEBUG("%s: " _fmt, lydx_get_cattr(_iface, "name"), ##__VA_ARGS__)
#define ONOFF(boolean) boolean ? "on" : "off"
static inline const char *bridge_tagtype2str(const char *type)
{
if (!strcmp(type, "ieee802-dot1q-types:c-vlan"))
return "802.1Q";
else if (!strcmp(type, "ieee802-dot1q-types:s-vlan"))
return "802.1ad";
return NULL;
}
static inline bool is_bridge_port(struct lyd_node *cif)
{
struct lyd_node *node = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL);
if (!node || !lydx_get_child(node, "bridge"))
return false;
return true;
}
/* ieee802-ethernet-interface.c */
int netdag_gen_ethtool(struct dagger *net, struct lyd_node *cif, struct lyd_node *dif);
/* ietf-interfaces.c */
char *get_phys_addr(struct lyd_node *parent, int *deleted);
int netdag_exit_reload(struct dagger *net);
/* ietf-ip.c */
int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif,
struct lyd_node *dif, FILE *ip);
int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif,
struct lyd_node *dif);
int netdag_gen_ip_addrs(struct dagger *net, FILE *ip, const char *proto,
struct lyd_node *cif, struct lyd_node *dif);
/* infix-if-bridge.c */
void mcast_querier(const char *ifname, int vid, int mode, int interval);
int bridge_gen_ports(struct dagger *net, struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
int netdag_gen_bridge(sr_session_ctx_t *session, struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip, int add);
/* infix-if-veth.c */
int ifchange_cand_infer_veth(sr_session_ctx_t *session, const char *path);
int netdag_gen_veth(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip);
/* infix-if-vlan.c */
int ifchange_cand_infer_vlan(sr_session_ctx_t *session, const char *path);
int netdag_gen_vlan(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip);
#endif /* CONFD_IETF_INTERFACES_H_ */
+270
View File
@@ -0,0 +1,270 @@
/* 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"
int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif,
struct lyd_node *dif, FILE *ip)
{
const char *preferred_lft = "86400", *valid_lft = "604800";
struct lyd_node *ipconf = lydx_get_child(cif, "ipv6");
const char *ifname = lydx_get_cattr(dif, "name");
int global = 0, random = 0;
struct lyd_node *node;
FILE *fp;
if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_bridge_port(cif)) {
fputs(" addrgenmode none", ip);
return 0;
}
node = lydx_get_child(ipconf, "autoconf");
if (node) {
global = lydx_is_enabled(node, "create-global-addresses");
random = lydx_is_enabled(node, "create-temporary-addresses");
preferred_lft = lydx_get_cattr(node, "temporary-preferred-lifetime");
valid_lft = lydx_get_cattr(node, "temporary-valid-lifetime");
}
/* 51: must run after interfaces have been created (think: bridge, veth) */
fp = dagger_fopen_next(net, "init", ifname, 51, "init.sysctl");
if (fp) {
/* Autoconfigure addresses using Prefix Information in Router Advertisements */
fprintf(fp, "net.ipv6.conf.%s.autoconf = %d\n", ifname, global);
/* The amount of Duplicate Address Detection probes to send. */
fprintf(fp, "net.ipv6.conf.%s.dad_transmits = %s\n", ifname,
lydx_get_cattr(ipconf, "dup-addr-detect-transmits"));
/* Preferred and valid lifetimes for temporary (random) addresses */
fprintf(fp, "net.ipv6.conf.%s.temp_prefered_lft = %s\n", ifname, preferred_lft);
fprintf(fp, "net.ipv6.conf.%s.temp_valid_lft = %s\n", ifname, valid_lft);
fclose(fp);
}
fprintf(ip, " addrgenmode %s", random ? "random" : "eui64");
return 0;
}
/*
* Check if ipv4 is enabled, only then can autoconf be enabled, in all
* other cases it must be disabled. Since we have multiple settings in
* autoconf, we check if either is modified (diff), in which case we not
* only enable, but also "touch" the Finit service for avahi-autoipd to
* ensure it is (re)started.
*
* Note: in Infix, regardless of the IPv4 configuration, any link-local
* link-local address is disabled when the interface is being used
* as a bridge port.
*
* Also, IPv4LL is not defined for loopback, so always skip there.
*/
int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif,
struct lyd_node *dif)
{
struct lyd_node *ipconf = lydx_get_child(cif, "ipv4");
struct lyd_node *ipdiff = lydx_get_child(dif, "ipv4");
const char *ifname = lydx_get_cattr(dif, "name");
struct lyd_node *zcip;
char defaults[64];
FILE *initctl;
int err = 0;
if (!strcmp(ifname, "lo"))
return 0;
/* client defults for this interface, needed in both cases */
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))
goto disable;
/*
* when enabled, we may have been enabled before, but skipped
* for various reasons: was bridge port, ipv4 was disabled...
*/
zcip = lydx_get_child(ipconf, "autoconf");
if (zcip && lydx_is_enabled(zcip, "enabled")) {
struct lyd_node *node;
const char *addr;
int diff = 0;
FILE *fp;
/* check for any changes in this container */
node = lydx_get_child(ipdiff, "autoconf");
if (node) {
const struct lyd_node *tmp;
tmp = lydx_get_child(node, "enabled");
if (tmp)
diff++;
tmp = lydx_get_child(node, "request-address");
if (tmp)
diff++;
}
fp = fopen(defaults, "w");
if (!fp) {
ERRNO("Failed creating %s, cannot enable IPv4LL on %s", defaults, ifname);
return -EIO;
}
fprintf(fp, "ZEROCONF_ARGS=\"--force-bind --syslog ");
addr = lydx_get_cattr(zcip, "request-address");
if (addr)
fprintf(fp, "--start=%s", addr);
fprintf(fp, "\"\n");
fclose(fp);
initctl = dagger_fopen_next(net, "init", ifname, 60, "zeroconf-up.sh");
if (!initctl)
return -EIO;
/* on enable, or reactivation, it is enough to ensure the service is enabled */
fprintf(initctl, "initctl -bnq enable zeroconf@%s.conf\n", ifname);
/* on changes to autoconf we must ensure Finit restarts the service */
if (diff)
fprintf(initctl, "initctl -bnq touch zeroconf@%s.conf\n", ifname);
} else {
disable:
initctl = dagger_fopen_current(net, "exit", ifname, 40, "zeroconf-down.sh");
if (!initctl) {
/* check if in bootstrap (pre gen 0) */
if (errno == EUNATCH)
return 0;
return -EIO;
}
fprintf(initctl, "initctl -bnq disable zeroconf@%s.conf\n", ifname);
fprintf(initctl, "rm -f %s\n", defaults);
err = netdag_exit_reload(net);
}
fclose(initctl);
return err;
}
static bool is_std_lo_addr(const char *ifname, const char *ip, const char *pf)
{
struct in6_addr in6, lo6;
struct in_addr in4;
if (strcmp(ifname, "lo"))
return false;
if (inet_pton(AF_INET, ip, &in4) == 1)
return (ntohl(in4.s_addr) == INADDR_LOOPBACK) && !strcmp(pf, "8");
if (inet_pton(AF_INET6, ip, &in6) == 1) {
inet_pton(AF_INET6, "::1", &lo6);
return !memcmp(&in6, &lo6, sizeof(in6))
&& !strcmp(pf, "128");
}
return false;
}
static int netdag_gen_diff_addr(FILE *ip, const char *ifname,
struct lyd_node *addr)
{
enum lydx_op op = lydx_get_op(addr);
struct lyd_node *adr, *pfx;
struct lydx_diff adrd, pfxd;
const char *addcmd = "add";
adr = lydx_get_child(addr, "ip");
pfx = lydx_get_child(addr, "prefix-length");
if (!adr || !pfx)
return -EINVAL;
lydx_get_diff(adr, &adrd);
lydx_get_diff(pfx, &pfxd);
if (op != LYDX_OP_CREATE) {
fprintf(ip, "address delete %s/%s dev %s\n",
adrd.old, pfxd.old, ifname);
if (op == LYDX_OP_DELETE)
return 0;
}
/* When bringing up loopback, the kernel will automatically
* add the standard addresses, so don't treat the existance of
* these as an error.
*/
if ((op == LYDX_OP_CREATE) &&
is_std_lo_addr(ifname, adrd.new, pfxd.new))
addcmd = "replace";
fprintf(ip, "address %s %s/%s dev %s proto 4\n", addcmd,
adrd.new, pfxd.new, ifname);
return 0;
}
static int netdag_gen_diff_addrs(FILE *ip, const char *ifname,
struct lyd_node *ipvx)
{
struct lyd_node *addr;
int err = 0;
LYX_LIST_FOR_EACH(lyd_child(ipvx), addr, "address") {
err = netdag_gen_diff_addr(ip, ifname, addr);
if (err)
break;
}
return err;
}
static int netdag_set_conf_addrs(FILE *ip, const char *ifname,
struct lyd_node *ipvx)
{
struct lyd_node *addr;
LYX_LIST_FOR_EACH(lyd_child(ipvx), addr, "address") {
fprintf(ip, "address add %s/%s dev %s\n",
lydx_get_cattr(addr, "ip"),
lydx_get_cattr(addr, "prefix-length"),
ifname);
}
return 0;
}
int netdag_gen_ip_addrs(struct dagger *net, FILE *ip, const char *proto,
struct lyd_node *cif, struct lyd_node *dif)
{
struct lyd_node *ipconf = lydx_get_child(cif, proto);
struct lyd_node *ipdiff = lydx_get_child(dif, proto);
const char *ifname = lydx_get_cattr(dif, "name");
if (!ipconf || !lydx_is_enabled(ipconf, "enabled")) {
if (!cni_find(ifname) && if_nametoindex(ifname)) {
FILE *fp;
fp = dagger_fopen_current(net, "exit", ifname, 49, "flush.sh");
if (fp) {
fprintf(fp, "ip -%c addr flush dev %s\n", proto[3], ifname);
fclose(fp);
}
}
return 0;
}
if (lydx_get_op(lydx_get_child(ipdiff, "enabled")) == LYDX_OP_REPLACE)
return netdag_set_conf_addrs(ip, ifname, ipconf);
return netdag_gen_diff_addrs(ip, ifname, ipdiff);
}
+589
View File
@@ -0,0 +1,589 @@
/* 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"
static void brport_pvid_adjust(FILE *br, struct lyd_node *vlan, int vid, const char *brport,
struct lydx_diff *pvidiff, int tagged)
{
const char *type = tagged ? "tagged" : "untagged";
struct lyd_node *port;
LYX_LIST_FOR_EACH(lyd_child(vlan), port, type) {
if (strcmp(brport, lyd_get_value(port)))
continue;
if (pvidiff->old && atoi(pvidiff->old) == vid)
fprintf(br, "vlan add vid %d dev %s %s\n", vid, brport, type);
if (pvidiff->new && atoi(pvidiff->new) == vid)
fprintf(br, "vlan add vid %d dev %s pvid %s\n", vid, brport, type);
}
}
/*
* Called when only pvid is changed for a bridge-port. Then we use the
* cif data to iterate over all known VLANS for the given port.
*/
static int bridge_port_vlans(struct dagger *net, struct lyd_node *cif, const char *brname,
const char *brport, struct lydx_diff *pvidiff)
{
struct lyd_node *bridge = lydx_find_by_name(lyd_parent(cif), "interface", brname);
struct lyd_node *vlan, *vlans;
int err = 0;
FILE *br;
vlans = lydx_get_descendant(lyd_child(bridge), "bridge", "vlans", NULL);
if (!vlans)
goto done;
br = dagger_fopen_next(net, "init", brname, 60, "init.bridge");
if (!br) {
err = -EIO;
goto done;
}
LYX_LIST_FOR_EACH(lyd_child(vlans), vlan, "vlan") {
int vid = atoi(lydx_get_cattr(vlan, "vid"));
brport_pvid_adjust(br, vlan, vid, brport, pvidiff, 0);
brport_pvid_adjust(br, vlan, vid, brport, pvidiff, 1);
}
fclose(br);
done:
return err;
}
static void bridge_remove_vlan_ports(struct dagger *net, FILE *br, const char *brname,
int vid, struct lyd_node *ports, int tagged)
{
struct lyd_node *port;
LYX_LIST_FOR_EACH(lyd_child(ports), port, tagged ? "tagged" : "untagged") {
enum lydx_op op = lydx_get_op(port);
const char *brport = lyd_get_value(port);
if (op != LYDX_OP_CREATE) {
fprintf(br, "vlan del vid %d dev %s\n", vid, brport);
}
}
}
static void bridge_add_vlan_ports(struct dagger *net, FILE *br, const char *brname,
int vid, struct lyd_node *ports, int tagged)
{
struct lyd_node *port;
LYX_LIST_FOR_EACH(lyd_child(ports), port, tagged ? "tagged" : "untagged") {
enum lydx_op op = lydx_get_op(port);
const char *brport = lyd_get_value(port);
if (op != LYDX_OP_DELETE) {
int pvid = 0;
srx_get_int(net->session, &pvid, SR_UINT16_T, IF_XPATH "[name='%s']/bridge-port/pvid", brport);
fprintf(br, "vlan add vid %d dev %s %s %s %s\n", vid, brport, vid == pvid ? "pvid" : "",
tagged ? "" : "untagged", strcmp(brname, brport) ? "" : "self");
}
}
}
static int bridge_diff_vlan_ports(struct dagger *net, FILE *br, const char *brname,
int vid, struct lyd_node *ports)
{
/* First remove all VLANs that should that should be removed, see #676 */
bridge_remove_vlan_ports(net, br, brname, vid, ports, 0);
bridge_remove_vlan_ports(net, br, brname, vid, ports, 1);
bridge_add_vlan_ports(net, br, brname, vid, ports, 0);
bridge_add_vlan_ports(net, br, brname, vid, ports, 1);
return 0;
}
static int bridge_vlan_settings(struct lyd_node *cif, const char **proto, int *vlan_mcast)
{
struct lyd_node *vlans, *vlan;
vlans = lydx_get_descendant(lyd_child(cif), "bridge", "vlans", NULL);
if (vlans) {
const char *type = lydx_get_cattr(vlans, "proto");
int num = 0;
*proto = bridge_tagtype2str(type);
LYX_LIST_FOR_EACH(lyd_child(vlans), vlan, "vlan") {
struct lyd_node *mcast;
mcast = lydx_get_descendant(lyd_child(vlan), "multicast", NULL);
if (mcast)
*vlan_mcast += lydx_is_enabled(mcast, "snooping");
num++;
}
return num;
}
return 0;
}
static void bridge_port_settings(FILE *next, const char *ifname, struct lyd_node *cif)
{
struct lyd_node *bp, *flood, *mcast;
int ucflood = 1; /* default: flood unknown unicast */
bp = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL);
if (!bp)
return;
fprintf(next, "link set %s type bridge_slave", ifname);
flood = lydx_get_child(bp, "flood");
if (flood) {
ucflood = lydx_is_enabled(flood, "unicast");
fprintf(next, " bcast_flood %s", ONOFF(lydx_is_enabled(flood, "broadcast")));
fprintf(next, " flood %s", ONOFF(ucflood));
fprintf(next, " mcast_flood %s", ONOFF(lydx_is_enabled(flood, "multicast")));
}
if (ucflood) {
/* proxy arp must be disabled while flood on, see man page */
fprintf(next, " proxy_arp off");
fprintf(next, " proxy_arp_wifi off");
} else {
/* XXX: proxy arp/wifi settings here */
}
mcast = lydx_get_child(bp, "multicast");
if (mcast) {
const char *router = lydx_get_cattr(mcast, "router");
struct { const char *str; int val; } xlate[] = {
{ "off", 0 },
{ "auto", 1 },
{ "permanent", 2 },
};
int mrouter = 1;
for (size_t i = 0; i < NELEMS(xlate); i++) {
if (strcmp(xlate[i].str, router))
continue;
mrouter = xlate[i].val;
break;
}
fprintf(next, " mcast_fast_leave %s mcast_router %d",
ONOFF(lydx_is_enabled(mcast, "fast-leave")),
mrouter);
}
fprintf(next, "\n");
}
int bridge_gen_ports(struct dagger *net, struct lyd_node *dif, struct lyd_node *cif, FILE *ip)
{
const char *ifname = lydx_get_cattr(cif, "name");
struct lyd_node *node, *bridge;
struct lydx_diff brdiff;
int err = 0;
node = lydx_get_descendant(lyd_child(dif), "bridge-port", NULL);
if (!node)
goto fail;
/*
* If bridge is not in dif, then we only have bridge-port
* settings and can use cif instead for any new settings
* since we always set *all* port settings anyway.
*/
bridge = lydx_get_child(node, "bridge");
if (!bridge) {
struct lyd_node *pvid = lydx_get_child(node, "pvid");
struct lydx_diff pvidiff;
const char *brname;
FILE *next;
node = lydx_get_descendant(lyd_child(cif), "bridge-port", NULL);
brname = lydx_get_cattr(node, "bridge");
if (!node || !brname)
goto fail;
next = dagger_fopen_next(net, "init", ifname, 56, "init.ip");
if (!next) {
err = -EIO;
goto fail;
}
bridge_port_settings(next, ifname, cif);
fclose(next);
/* Change in bridge port's PVID => change in VLAN port memberships */
if (lydx_get_diff(pvid, &pvidiff))
bridge_port_vlans(net, cif, brname, ifname, &pvidiff);
err = dagger_add_dep(net, brname, ifname);
if (err)
return ERR_IFACE(cif, err, "Unable to add dep \"%s\" to %s", ifname, brname);
goto fail;
}
if (lydx_get_diff(bridge, &brdiff) && brdiff.old) {
FILE *prev;
prev = dagger_fopen_current(net, "exit", brdiff.old, 55, "exit.ip");
if (!prev) {
err = -EIO;
goto fail;
}
fprintf(prev, "link set %s nomaster\n", ifname);
fclose(prev);
}
if (brdiff.new) {
FILE *next;
next = dagger_fopen_next(net, "init", brdiff.new, 55, "init.ip");
if (!next) {
err = -EIO;
goto fail;
}
fprintf(next, "link set %s master %s\n", ifname, brdiff.new);
bridge_port_settings(next, ifname, cif);
fclose(next);
err = dagger_add_dep(net, brdiff.new, ifname);
if (err)
return ERR_IFACE(cif, err, "Unable to add dep \"%s\" to %s", ifname, brdiff.new);
}
fail:
return err;
}
static int bridge_fwd_mask(struct lyd_node *cif)
{
struct lyd_node *node, *proto;
int fwd_mask = 0;
node = lydx_get_descendant(lyd_child(cif), "bridge", NULL);
if (!node)
goto fail;
LYX_LIST_FOR_EACH(lyd_child(node), proto, "ieee-group-forward") {
struct lyd_node_term *leaf = (struct lyd_node_term *)proto;
struct lysc_node_leaf *sleaf = (struct lysc_node_leaf *)leaf->schema;
if ((sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && (sleaf->type->basetype == LY_TYPE_UNION)) {
struct lyd_value *actual = &leaf->value.subvalue->value;
int val;
if (actual->realtype->basetype == LY_TYPE_UINT8)
val = actual->uint8;
else
val = actual->enum_item->value;
fwd_mask |= 1 << val;
}
}
fail:
return fwd_mask;
}
static int querier_mode(const char *mode)
{
struct { const char *mode; int val; } table[] = {
{ "off", 0 },
{ "proxy", 1 },
{ "auto", 2 },
};
for (size_t i = 0; i < NELEMS(table); i++) {
if (strcmp(table[i].mode, mode))
continue;
return table[i].val;
}
return 0; /* unknown: off */
}
void mcast_querier(const char *ifname, int vid, int mode, int interval)
{
FILE *fp;
DEBUG("mcast querier %s mode %d interval %d", ifname, mode, interval);
if (!mode) {
systemf("rm -f /etc/mc.d/%s-*.conf", ifname);
systemf("initctl -bnq disable mcd");
return;
}
fp = fopenf("w", "/etc/mc.d/%s-%d.conf", ifname, vid);
if (!fp) {
ERRNO("Failed creating querier configuration for %s", ifname);
return;
}
fprintf(fp, "iface %s", ifname);
if (vid > 0)
fprintf(fp, " vlan %d", vid);
fprintf(fp, " enable %sigmpv3 query-interval %d\n",
mode == 1 ? "proxy-queries " : "", interval);
fclose(fp);
systemf("initctl -bnq enable mcd");
systemf("initctl -bnq touch mcd");
}
static char *find_vlan_interface(sr_session_ctx_t *session, const char *brname, int vid)
{
const char *fmt = "/interfaces/interface/vlan[id=%d and lower-layer-if='%s']";
static char xpath[128];
struct lyd_node *iface;
sr_data_t *data;
int rc;
snprintf(xpath, sizeof(xpath), fmt, vid, brname);
rc = sr_get_data(session, xpath, 0, 0, 0, &data);
if (rc || !data) {
DEBUG("Skpping VLAN %d interface for %s", vid, brname);
return NULL;
}
/* On match we should not need the if(iface) checks */
iface = lydx_get_descendant(data->tree, "interfaces", "interface", NULL);
if (iface)
strlcpy(xpath, lydx_get_cattr(iface, "name"), sizeof(xpath));
sr_release_data(data);
if (iface)
return xpath;
return NULL;
}
static int vlan_mcast_settings(sr_session_ctx_t *session, FILE *br, const char *brname,
struct lyd_node *vlan, int vid)
{
int interval, querier, snooping;
struct lyd_node *mcast;
const char *ifname;
mcast = lydx_get_descendant(lyd_child(vlan), "multicast", NULL);
if (!mcast)
return 0;
snooping = lydx_is_enabled(mcast, "snooping");
querier = querier_mode(lydx_get_cattr(mcast, "querier"));
fprintf(br, "vlan global set vid %d dev %s mcast_snooping %d",
vid, brname, snooping);
fprintf(br, " mcast_igmp_version 3 mcast_mld_version 2\n");
interval = atoi(lydx_get_cattr(mcast, "query-interval"));
ifname = find_vlan_interface(session, brname, vid);
if (ifname)
mcast_querier(ifname, 0, querier, interval);
else
mcast_querier(brname, vid, querier, interval);
return 0;
}
static int bridge_mcast_settings(FILE *ip, const char *brname, struct lyd_node *cif, int vlan_mcast)
{
int interval, querier, snooping;
struct lyd_node *mcast;
mcast = lydx_get_descendant(lyd_child(cif), "bridge", "multicast", NULL);
if (!mcast) {
mcast_querier(brname, 0, 0, 0);
interval = snooping = querier = 0;
} else {
snooping = lydx_is_enabled(mcast, "snooping");
querier = querier_mode(lydx_get_cattr(mcast, "querier"));
interval = atoi(lydx_get_cattr(mcast, "query-interval"));
}
fprintf(ip, " mcast_vlan_snooping %d", vlan_mcast ? 1 : 0);
fprintf(ip, " mcast_snooping %d mcast_querier 0", vlan_mcast ? 1 : snooping);
if (snooping)
fprintf(ip, " mcast_igmp_version 3 mcast_mld_version 2");
if (interval)
fprintf(ip, " mcast_query_interval %d", interval * 100);
if (!vlan_mcast)
mcast_querier(brname, 0, querier, interval);
else
mcast_querier(brname, 0, 0, 0);
return 0;
}
static int netdag_gen_multicast_filter(FILE *current, FILE *prev, const char *brname,
struct lyd_node *multicast_filter, int vid)
{
const char *group = lydx_get_cattr(multicast_filter, "group");
enum lydx_op op = lydx_get_op(multicast_filter);
struct lyd_node * port;
LYX_LIST_FOR_EACH(lyd_child(multicast_filter), port, "ports") {
enum lydx_op port_op = lydx_get_op(port);
if (op == LYDX_OP_DELETE) {
fprintf(prev, "mdb del dev %s port %s ", brname, lydx_get_cattr(port, "port"));
fprintf(prev, " grp %s ", group);
if (vid)
fprintf(prev, " vid %d ", vid);
fputs("\n", prev);
} else {
fprintf(current, "mdb replace dev %s ", brname);
if (port_op != LYDX_OP_DELETE)
fprintf(current, " port %s ", lydx_get_cattr(port, "port"));
fprintf(current, " grp %s ", group);
if (vid)
fprintf(current, " vid %d ", vid);
fprintf(current, " %s\n", lydx_get_cattr(port, "state"));
}
}
return 0;
}
static int netdag_gen_multicast_filters(struct dagger *net, FILE *current, const char *brname,
struct lyd_node *multicast_filters, int vid) {
struct lyd_node *multicast_filter;
FILE *prev = NULL;
int err = 0;
prev = dagger_fopen_current(net, "exit", brname, 50, "exit.bridge");
if (!prev) {
/* check if in bootstrap (pre gen 0) */
if (errno != EUNATCH) {
err = -EIO;
goto err;
}
}
LYX_LIST_FOR_EACH(lyd_child(multicast_filters), multicast_filter, "multicast-filter") {
netdag_gen_multicast_filter(current, prev, brname, multicast_filter, vid);
}
if(prev)
fclose(prev);
err:
return err;
}
int netdag_gen_bridge(sr_session_ctx_t *session, struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip, int add)
{
struct lyd_node *vlans, *vlan, *multicast_filters;
const char *brname = lydx_get_cattr(cif, "name");
int vlan_filtering, fwd_mask, vlan_mcast = 0;
const char *op = add ? "add" : "set";
const char *proto;
FILE *br = NULL;
int err = 0;
vlan_filtering = bridge_vlan_settings(cif, &proto, &vlan_mcast);
fwd_mask = bridge_fwd_mask(cif);
fprintf(ip, "link %s dev %s", op, brname);
/*
* Must set base mac on add to prevent kernel from seeding ipv6
* addrgenmode eui64 with random mac, issue #357.
*/
if (add) {
const char *mac = get_phys_addr(cif, NULL);
if (!mac) {
struct json_t *j;
j = json_object_get(confd.root, "mac-address");
if (j)
mac = json_string_value(j);
}
if (mac)
fprintf(ip, " address %s", mac);
/* on failure, fall back to kernel's random mac */
}
/*
* Issue #198: we require explicit VLAN assignment for ports
* when VLAN filtering is enabled. We strongly
* believe this is the only sane way of doing it.
* Issue #310: malplaced 'vlan_default_pvid 0'
*/
fprintf(ip, " type bridge group_fwd_mask %d mcast_flood_always 1"
" vlan_filtering %d vlan_default_pvid 0",
fwd_mask, vlan_filtering ? 1 : 0);
if ((err = bridge_mcast_settings(ip, brname, cif, vlan_mcast)))
goto out;
br = dagger_fopen_next(net, "init", brname, 60, "init.bridge");
if (!br) {
err = -EIO;
goto out;
}
if (!vlan_filtering) {
fputc('\n', ip);
multicast_filters = lydx_get_descendant(lyd_child(dif), "bridge", "multicast-filters", NULL);
if (multicast_filters)
err = netdag_gen_multicast_filters(net, br, brname, multicast_filters, 0);
goto out_close_br;
} else if (!proto) {
fputc('\n', ip);
ERROR("%s: unsupported bridge proto", brname);
err = -ENOSYS;
goto out_close_br;
}
fprintf(ip, " vlan_protocol %s\n", proto);
vlans = lydx_get_descendant(lyd_child(dif), "bridge", "vlans", NULL);
if (!vlans)
goto out_close_br;
LYX_LIST_FOR_EACH(lyd_child(vlans), vlan, "vlan") {
int vid = atoi(lydx_get_cattr(vlan, "vid"));
err = bridge_diff_vlan_ports(net, br, brname, vid, vlan);
if (err)
break;
/* MDB static groups */
multicast_filters = lydx_get_child(vlan, "multicast-filters");
if (multicast_filters) {
if ((err = netdag_gen_multicast_filters(net, br, brname, multicast_filters, vid)))
break;
}
}
/* need the vlans created before we can set features on them */
vlans = lydx_get_descendant(lyd_child(cif), "bridge", "vlans", NULL);
if (vlans) {
LYX_LIST_FOR_EACH(lyd_child(vlans), vlan, "vlan") {
int vid = atoi(lydx_get_cattr(vlan, "vid"));
err = vlan_mcast_settings(session, br, brname, vlan, vid);
if (err)
break;
}
}
out_close_br:
fclose(br);
out:
return err;
}
+102
View File
@@ -0,0 +1,102 @@
/* 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"
int ifchange_cand_infer_veth(sr_session_ctx_t *session, const char *path)
{
char *ifname, *type, *peer, *xpath, *val;
sr_error_t err = SR_ERR_OK;
size_t cnt = 0;
xpath = xpath_base(path);
if (!xpath)
return SR_ERR_SYS;
type = srx_get_str(session, "%s/type", xpath);
if (!type)
goto out;
if (strcmp(type, "infix-if-type:veth"))
goto out_free_type;
ifname = srx_get_str(session, "%s/name", xpath);
if (!ifname)
goto out_free_type;
peer = srx_get_str(session, "%s/veth/peer", xpath);
if (!peer)
goto out_free_ifname;
err = srx_nitems(session, &cnt, "/interfaces/interface[name='%s']/name", peer);
if (err || cnt)
goto out_free_peer;
val = "infix-if-type:veth";
err = srx_set_str(session, val, 0, IF_XPATH "[name='%s']/type", peer);
if (err) {
ERROR("failed setting iface %s type %s, err %d", peer, val, err);
goto out_free_peer;
}
err = srx_set_str(session, ifname, 0, IF_XPATH "[name='%s']/infix-interfaces:veth/peer", peer);
if (err)
ERROR("failed setting iface %s peer %s, err %d", peer, ifname, err);
out_free_peer:
free(peer);
out_free_ifname:
free(ifname);
out_free_type:
free(type);
out:
free(xpath);
return err;
}
int netdag_gen_veth(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip)
{
const char *ifname = lydx_get_cattr(cif, "name");
struct lyd_node *node;
const char *peer;
int err;
node = lydx_get_descendant(lyd_child(cif), "veth", NULL);
if (!node)
return -EINVAL;
peer = lydx_get_cattr(node, "peer");
if (dagger_should_skip(net, ifname)) {
err = dagger_add_dep(net, ifname, peer);
if (err)
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);
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 && (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);
}
return 0;
}
+192
View File
@@ -0,0 +1,192 @@
/* 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"
int ifchange_cand_infer_vlan(sr_session_ctx_t *session, const char *path)
{
sr_val_t inferred = { .type = SR_STRING_T };
char *ifname, *type, *xpath, *lower;
sr_error_t err = SR_ERR_OK;
size_t cnt = 0;
int vid;
xpath = xpath_base(path);
if (!xpath)
return SR_ERR_SYS;
type = srx_get_str(session, "%s/type", xpath);
if (!type)
goto out;
if (strcmp(type, "infix-if-type:vlan"))
goto out_free_type;
ifname = srx_get_str(session, "%s/name", xpath);
if (!ifname)
goto out_free_type;
if (!fnmatch("*.+([0-9])", ifname, FNM_EXTMATCH)) {
char *ptr = rindex(ifname, '.');
if (!ptr)
goto out_free_ifname;
*ptr++ = '\0';
vid = strtol(ptr, NULL, 10);
lower = ifname;
} else if (!fnmatch("vlan+([0-9])", ifname, FNM_EXTMATCH)) {
if (sscanf(ifname, "vlan%d", &vid) != 1)
goto out_free_ifname;
/* Avoid setting lower-layer-if to vlanN */
lower = NULL;
} else {
goto out_free_ifname;
}
if (vid < 1 || vid > 4094)
goto out_free_ifname;
if (lower) {
err = srx_nitems(session, &cnt, "/interfaces/interface[name='%s']/name", lower);
if (err || !cnt)
goto out_free_ifname;
err = srx_nitems(session, &cnt, IF_VLAN_XPATH "/lower-layer-if", xpath);
if (!err && !cnt) {
inferred.data.string_val = lower;
err = srx_set_item(session, &inferred, 0, IF_VLAN_XPATH "/lower-layer-if", xpath);
if (err)
goto out_free_ifname;
}
}
err = srx_nitems(session, &cnt, IF_VLAN_XPATH "/tag-type", xpath);
if (!err && !cnt) {
inferred.data.string_val = "ieee802-dot1q-types:c-vlan";
err = srx_set_item(session, &inferred, 0, IF_VLAN_XPATH "/tag-type", xpath);
if (err)
goto out_free_ifname;
}
err = srx_nitems(session, &cnt, IF_VLAN_XPATH "/id", xpath);
if (!err && !cnt) {
inferred.type = SR_INT32_T;
inferred.data.int32_val = vid;
err = srx_set_item(session, &inferred, 0, IF_VLAN_XPATH "/id", xpath);
if (err)
goto out_free_ifname;
}
out_free_ifname:
free(ifname);
out_free_type:
free(type);
out:
free(xpath);
return err;
}
static int netdag_gen_vlan_ingress_qos(struct lyd_node *cif, FILE *ip)
{
const char *prio;
prio = lyd_get_value(lydx_get_descendant(lyd_child(cif),
"vlan", "ingress-qos", "priority", NULL));
if (prio[0] >= '0' && prio[0] <= '7' && prio[1] == '\0') {
fprintf(ip, " ingress-qos-map 0:%c 1:%c 2:%c 3:%c 4:%c 5:%c 6:%c 7:%c",
prio[0], prio[0], prio[0], prio[0], prio[0], prio[0], prio[0], prio[0]);
return 0;
} else if (!strcmp(prio, "from-pcp")) {
fputs(" ingress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7", ip);
return 0;
}
return ERR_IFACE(cif, -EINVAL, "Unsupported ingress priority mode \"%s\"", prio);
}
static int netdag_gen_vlan_egress_qos(struct lyd_node *cif, FILE *ip)
{
const char *pcp;
pcp = lyd_get_value(lydx_get_descendant(lyd_child(cif),
"vlan", "egress-qos", "pcp", NULL));
if (pcp[0] >= '0' && pcp[0] <= '7' && pcp[1] == '\0') {
fprintf(ip, " egress-qos-map 0:%c 1:%c 2:%c 3:%c 4:%c 5:%c 6:%c 7:%c",
pcp[0], pcp[0], pcp[0], pcp[0], pcp[0], pcp[0], pcp[0], pcp[0]);
return 0;
} else if (!strcmp(pcp, "from-priority")) {
fputs(" egress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7", ip);
return 0;
}
return ERR_IFACE(cif, -EINVAL, "Unsupported egress priority mode \"%s\"", pcp);
}
int netdag_gen_vlan(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip)
{
const char *ifname = lydx_get_cattr(cif, "name");
struct lydx_diff typed, vidd;
struct lyd_node *vlan;
const char *lower_if;
const char *proto;
int err;
vlan = lydx_get_descendant(lyd_child(dif ? : cif), "vlan", NULL);
if (!vlan) {
/*
* Note: this is only an error if vlan subcontext is missing
* from cif, otherwise it just means the interface had a
* a change that was not related to the VLAN config.
*/
if (!dif)
ERROR("%s: missing mandatory vlan", ifname);
return 0;
}
lower_if = lydx_get_cattr(vlan, "lower-layer-if");
DEBUG("ifname %s lower if %s\n", ifname, lower_if);
err = dagger_add_dep(net, ifname, lower_if);
if (err)
return ERR_IFACE(cif, err, "Unable to add dep \"%s\"", lower_if);
fprintf(ip, "link add dev %s down link %s type vlan", ifname, lower_if);
if (lydx_get_diff(lydx_get_child(vlan, "tag-type"), &typed)) {
proto = bridge_tagtype2str(typed.new);
if (!proto)
return ERR_IFACE(cif, -ENOSYS, "Unsupported tag type \"%s\"", typed.new);
fprintf(ip, " proto %s", proto);
}
if (lydx_get_diff(lydx_get_child(vlan, "id"), &vidd))
fprintf(ip, " id %s", vidd.new);
err = netdag_gen_vlan_ingress_qos(cif, ip);
if (err)
return err;
err = netdag_gen_vlan_egress_qos(cif, ip);
if (err)
return err;
fputc('\n', ip);
return 0;
}