From 5675f89d3724893b9f5a843241827fcd0e0404f0 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 13 Dec 2024 10:32:03 +0100 Subject: [PATCH] confd: Split up ietf-interfaces.c Fact: ietf-interface.c has become too unwieldy. Therefore, split it, roughly based on the YANG structure, into multiple files. There is much more to do here, like cleaning up the naming (which is all over the place), moving more system commands into dagger, etc. But I wanted to keep this change to a strict code reshuffle. --- src/confd/src/Makefile.am | 7 +- src/confd/src/ieee802-ethernet-interface.c | 147 +++ src/confd/src/ietf-interfaces.c | 1280 +------------------- src/confd/src/ietf-interfaces.h | 78 ++ src/confd/src/ietf-ip.c | 270 +++++ src/confd/src/infix-if-bridge.c | 589 +++++++++ src/confd/src/infix-if-veth.c | 102 ++ src/confd/src/infix-if-vlan.c | 192 +++ 8 files changed, 1387 insertions(+), 1278 deletions(-) create mode 100644 src/confd/src/ieee802-ethernet-interface.c create mode 100644 src/confd/src/ietf-interfaces.h create mode 100644 src/confd/src/ietf-ip.c create mode 100644 src/confd/src/infix-if-bridge.c create mode 100644 src/confd/src/infix-if-veth.c create mode 100644 src/confd/src/infix-if-vlan.c diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am index 9fd64c49..62d10786 100644 --- a/src/confd/src/Makefile.am +++ b/src/confd/src/Makefile.am @@ -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 \ diff --git a/src/confd/src/ieee802-ethernet-interface.c b/src/confd/src/ieee802-ethernet-interface.c new file mode 100644 index 00000000..2765ffde --- /dev/null +++ b/src/confd/src/ieee802-ethernet-interface.c @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; +} diff --git a/src/confd/src/ietf-interfaces.c b/src/confd/src/ietf-interfaces.c index ec914b94..ba051eca 100644 --- a/src/confd/src/ietf-interfaces.c +++ b/src/confd/src/ietf-interfaces.c @@ -10,23 +10,7 @@ #include #include -#include "core.h" -#include "cni.h" - -#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" - -#define IF_XPATH "/ietf-interfaces:interfaces/interface" -#define IF_VLAN_XPATH "%s/infix-interfaces:vlan" +#include "ietf-interfaces.h" static bool iface_is_phys(const char *ifname) { @@ -63,142 +47,6 @@ out: return is_phys; } -static 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; -} - -static 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 ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path) { sr_val_t inferred = { .type = SR_STRING_T }; @@ -300,7 +148,7 @@ static int ifchange_cand(sr_session_ctx_t *session, uint32_t sub_id, const char return SR_ERR_OK; } -static int netdag_exit_reload(struct dagger *net) +int netdag_exit_reload(struct dagger *net) { FILE *initctl; @@ -324,104 +172,6 @@ static int netdag_exit_reload(struct dagger *net) return 0; } -static 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; -} - -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; -} - static int netdag_gen_link_mtu(FILE *ip, struct lyd_node *dif) { const char *ifname = lydx_get_cattr(dif, "name"); @@ -490,7 +240,7 @@ static const char *get_val(struct lyd_node *parent, char *name, int *deleted) * 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) +char *get_phys_addr(struct lyd_node *parent, int *deleted) { struct lyd_node *node, *cpa; static char mac[18]; @@ -568,174 +318,6 @@ static int netdag_gen_link_addr(FILE *ip, struct lyd_node *cif, struct lyd_node return 0; } -static 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); -} - -static 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. - */ -static 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 int netdag_gen_sysctl_setting(struct dagger *net, const char *ifname, FILE **fpp, int isboolean, const char *fallback, struct lyd_node *node, const char *fmt, ...) @@ -805,730 +387,6 @@ skip_mtu: return err; } -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; - -} - -static 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; -} - -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 const char *bridge_tagtype2str(const char *type) -{ - const char *proto; - - if (!strcmp(type, "ieee802-dot1q-types:c-vlan")) - proto = "802.1Q"; - else if (!strcmp(type, "ieee802-dot1q-types:s-vlan")) - proto = "802.1ad"; - else - proto = NULL; - - return proto; -} - -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"); -} - -static 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 */ -} - -static 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; -} - -static 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; -} - static int netdag_gen_dummy(struct dagger *net, struct lyd_node *dif, struct lyd_node *cif, FILE *ip) { @@ -1539,138 +397,6 @@ static int netdag_gen_dummy(struct dagger *net, struct lyd_node *dif, return 0; } -static 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; -} - -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); -} - -static 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; -} - static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net, struct lyd_node *dif, struct lyd_node *cif, FILE *ip) { diff --git a/src/confd/src/ietf-interfaces.h b/src/confd/src/ietf-interfaces.h new file mode 100644 index 00000000..bf614e4c --- /dev/null +++ b/src/confd/src/ietf-interfaces.h @@ -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_ */ + + diff --git a/src/confd/src/ietf-ip.c b/src/confd/src/ietf-ip.c new file mode 100644 index 00000000..26ae243b --- /dev/null +++ b/src/confd/src/ietf-ip.c @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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); +} diff --git a/src/confd/src/infix-if-bridge.c b/src/confd/src/infix-if-bridge.c new file mode 100644 index 00000000..1a75fda8 --- /dev/null +++ b/src/confd/src/infix-if-bridge.c @@ -0,0 +1,589 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; +} diff --git a/src/confd/src/infix-if-veth.c b/src/confd/src/infix-if-veth.c new file mode 100644 index 00000000..b4d35868 --- /dev/null +++ b/src/confd/src/infix-if-veth.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; +} diff --git a/src/confd/src/infix-if-vlan.c b/src/confd/src/infix-if-vlan.c new file mode 100644 index 00000000..25eb0237 --- /dev/null +++ b/src/confd/src/infix-if-vlan.c @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; +}