From 3b5c0248d740aa774f098a1faab45c8468cbf5b1 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 13 Feb 2024 14:39:55 +0100 Subject: [PATCH] confd: handle factory-default RPC better wrt. container networks Because sysrepo callbacks are threaded and factory-default RPC is called from a separate subscription (to prevent blocking), we cannot prevent ietf-intefaces.c from being called before infix-containers.c, regardless of the priority we set for our subscriptions. When assigning a physical network interface this becomes a bit of a pain during factory-default RPC since the physical interface is hidden from the host network namespace. So, when applying factory to running, we check each interface if it was a container-network previously, if so we call on the container script in the exit of the current dagger generation to move the interface back to the host netns. This affects all other functions that assume interfaces only live in the host netns. To that end a set of new helper functions have been added to wrap iproute2 commands in nsenter when the interface lives elsewhere. Signed-off-by: Joachim Wiberg --- board/common/rootfs/usr/sbin/container | 56 ++++++++++- src/confd/src/ietf-interfaces.c | 124 ++++++++++++++++++++++--- 2 files changed, 164 insertions(+), 16 deletions(-) diff --git a/board/common/rootfs/usr/sbin/container b/board/common/rootfs/usr/sbin/container index 75f8ff50..01e4531d 100755 --- a/board/common/rootfs/usr/sbin/container +++ b/board/common/rootfs/usr/sbin/container @@ -3,6 +3,7 @@ all="" env="" port="" +force= log() { @@ -100,6 +101,10 @@ create() log "Got networks: $network" if [ -n "$network" ]; then + for net in $network; do + args="$args --net=$net" + done + for srv in $dns; do args="$args --dns=$srv" done @@ -141,6 +146,20 @@ delete() log "Container $name has been removed." } +# Removes network $1 from all containers +netwrm() +{ + net=$1 + + for c in $(podman ps $all --format "{{.Names}}"); do + for n in $(podman inspect "$c" |jq -r '.[].NetworkSettings.Networks | keys[]'); do + if [ "$n" = "$net" ]; then + podman network disconnect $force "$n" "$c" >/dev/null + fi + done + done +} + usage() { cat </dev/null) + for n in $nets; do + if [ "$network" = "$n" ]; then + pid=$(echo "$json" | jq .[].State.Pid) + echo "$pid" + exit 0 + fi + done + done + fi + ;; help) usage ;; diff --git a/src/confd/src/ietf-interfaces.c b/src/confd/src/ietf-interfaces.c index ced6d025..87cc0097 100644 --- a/src/confd/src/ietf-interfaces.c +++ b/src/confd/src/ietf-interfaces.c @@ -28,6 +28,83 @@ #define IF_XPATH "/ietf-interfaces:interfaces/interface" +/* + * When an interface has been handed off to a container it is moved to + * another network namespace. This function asks podman for the PID of + * the container that currently hosts the interface. + */ +static pid_t find_in_container(const char *ifname) +{ + char buf[32] = { 0 }; + pid_t pid = 0; + FILE *pp; + + if (fexistf("/sys/class/net/%s", ifname)) + return 0; /* it's right here, not in a container */ + + pp = popenf("r", "container --net %s find", ifname); + if (!pp) + return 0; + + if (fgets(buf, sizeof(buf), pp)) { + chomp(buf); + pid = atoi(buf); + } + + pclose(pp); + + return pid; +} + +/* + * This function takes the PID from find_in_container() and figures out + * the name of our ifname inside the container. For CNI/podman they + * save the host's name as the interface's ifalias. + */ +static char *find_container_ifname(pid_t pid, const char *ifname) +{ + static char buf[IFNAMSIZ + 2]; + char *ptr = NULL; + FILE *pp; + + pp = popenf("r", "container --net %s find ifname %d", ifname, pid); + if (!pp) + return NULL; + + buf[0] = 0; + if (fgets(buf, sizeof(buf), pp)) { + chomp(buf); + ptr = buf; + } + pclose(pp); + + return ptr; +} + +/* + * Sometimes we need to perform some kind of 'ip link dev IFNAME' + * command to find buried interface data. This function handles + * the fact that interfaces sometimes are on vacation in another + * network namespace (container). + */ +static FILE *popen_ifcmd(const char *fmt, const char *ifname) +{ + char cmd[strlen(fmt) + 64]; + static char *ifalias; + pid_t pid; + + pid = find_in_container(ifname); + if (!pid) + return popenf("re", fmt, ifname); + + ifalias = find_container_ifname(pid, ifname); + if (!ifalias) + return NULL; + + snprintf(cmd, sizeof(cmd), fmt, ifalias); + return popenf("re", "nsenter -t %d -n %s", pid, cmd); +} + static bool iface_is_cni(const char *ifname, struct lyd_node *node, const char **type) { struct lyd_node *net = lydx_get_child(node, "container-network"); @@ -320,7 +397,7 @@ static bool iface_is_phys(const char *ifname) json_t *link; FILE *proc; - proc = popenf("re", "ip -d -j link show dev %s 2>/dev/null", ifname); + proc = popen_ifcmd("ip -d -j link show dev %s 2>/dev/null", ifname); if (!proc) goto out; @@ -787,7 +864,7 @@ static int netdag_gen_link_addr(FILE *ip, struct lyd_node *cif, struct lyd_node * Only physical interfaces support this, virtual ones * we remove, see netdag_must_del() for details. */ - fp = popenf("r", "ip -d -j link show dev %s |jq -rM .[].permaddr", ifname); + fp = popen_ifcmd("ip -d -j link show dev %s |jq -rM .[].permaddr", ifname); if (fp) { if (fgets(buf, sizeof(buf), fp)) mac = chomp(buf); @@ -809,7 +886,7 @@ static int netdag_gen_link_addr(FILE *ip, struct lyd_node *cif, struct lyd_node return 0; } -static int netdag_gen_ip_addrs(FILE *ip, const char *proto, +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); @@ -817,8 +894,15 @@ static int netdag_gen_ip_addrs(FILE *ip, const char *proto, const char *ifname = lydx_get_cattr(dif, "name"); if (!ipconf || !lydx_is_enabled(ipconf, "enabled")) { - if (if_nametoindex(ifname)) - systemf("ip -%c addr flush dev %s\n", proto[3], ifname); + if (!find_in_container(ifname) && if_nametoindex(ifname)) { + FILE *fp; + + fp = dagger_fopen_next(net, "init", ifname, 49, "flush.sh"); + if (fp) { + fprintf(fp, "ip -%c addr flush dev %s\n", proto[3], ifname); + fclose(fp); + } + } return 0; } @@ -1486,11 +1570,30 @@ static sr_error_t netdag_gen_iface(struct dagger *net, { const char *ifname = lydx_get_cattr(dif, "name"); enum lydx_op op = lydx_get_op(dif); - const char *attr; + const char *attr, *cni_type = NULL; int err = 0; bool fixed; FILE *ip; + if (iface_is_cni(ifname, cif, &cni_type)) { + err = iface_gen_cni(ifname, cif); + if (cni_type && !strcmp(cni_type, "bridge")) + goto err; /* CNI bridges are managed by podman */ + } else if (iface_is_cni(ifname, dif, &cni_type)) { + FILE *fp; + + /* No longer a container-network, clean up. */ + fp = dagger_fopen_current(net, "exit", ifname, 30, "cni.sh"); + if (!fp) + return -EIO; + + fprintf(fp, "container -a -f delete network %s >/dev/null\n", ifname); + fclose(fp); + + if (cni_type && !strcmp(cni_type, "bridge")) + goto err; /* CNI bridges are managed by podman */ + } + fixed = iface_is_phys(ifname) || !strcmp(ifname, "lo"); DEBUG("%s(%s) %s", ifname, fixed ? "fixed" : "dynamic", @@ -1524,11 +1627,6 @@ static sr_error_t netdag_gen_iface(struct dagger *net, op = LYDX_OP_CREATE; } - if (iface_is_cni(ifname, cif)) { - err = iface_gen_cni(ifname, cif); - goto err; - } - ip = dagger_fopen_next(net, "init", ifname, 50, "init.ip"); if (!ip) { err = -EIO; @@ -1565,8 +1663,8 @@ static sr_error_t netdag_gen_iface(struct dagger *net, /* Set Addresses */ err = err ? : netdag_gen_link_mtu(ip, dif); err = err ? : netdag_gen_link_addr(ip, cif, dif); - err = err ? : netdag_gen_ip_addrs(ip, "ipv4", cif, dif); - err = err ? : netdag_gen_ip_addrs(ip, "ipv6", cif, dif); + err = err ? : netdag_gen_ip_addrs(net, ip, "ipv4", cif, dif); + err = err ? : netdag_gen_ip_addrs(net, ip, "ipv6", cif, dif); if (err) goto err_close_ip;