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 <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2024-02-25 19:49:27 +01:00
parent 168fb24957
commit 3b5c0248d7
2 changed files with 164 additions and 16 deletions
+53 -3
View File
@@ -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 <<EOF
@@ -169,7 +188,7 @@ options:
commands:
create NAME IMAGE NET Create container NAME using IMAGE with networks NET
delete NAME Kill and remove container NAME
delete [network] NAME Remove container NAME or network NAME from all containers
exec NAME CMD Run a command inside a container
help Show this help text
list [image | oci] List names (only) of containers, images, or OCI archives
@@ -231,7 +250,11 @@ while [ "$1" != "" ]; do
;;
--net)
shift
network="$network --net $1"
if [ -n "$network" ]; then
network="$network $1"
else
network=$1
fi
;;
--privileged)
privileged="--privileged=true"
@@ -273,11 +296,38 @@ case $cmd in
create "$@"
;;
delete)
delete "$@"
cmd=$1
name=$2
if [ "$cmd" = "network" ] && [ -n "$name" ]; then
netwrm "$name"
else
delete "$@"
fi
;;
exec)
podman exec -it "$@"
;;
find)
cmd=$1
pid=$2
if [ "$cmd" = "ifname" ] && [ -n "$pid" ]; then
nsenter -t "$pid" -n ip -d -j link | \
jq --arg ifname "$network" -r '.[] | select(.ifalias==$ifname) | .ifname'
else
containers=$(podman ps $all --format "{{.Names}}")
for c in $containers; do
json=$(podman inspect "$c")
nets=$(echo "$json" |jq -r '.[].NetworkSettings.Networks | keys[]' 2>/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
;;
+111 -13
View File
@@ -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;