Repository: containers/crun
Pull request #1750: linux: add support for net devices
Author: login: giuseppe
Created: 2025-05-16T15:22:51Z | Merged: 2025-05-21T14:53:35Z

Closes: https://github.com/containers/crun/issues/1712

## Summary by Sourcery

Add support for moving network devices into the container network namespace based on the OCI spec

New Features:
- Add `linux.net_devices` configuration to specify host interfaces to move and rename into the container
- Implement `move_network_device` and `libcrun_move_network_devices` to use netlink for transferring IP settings and activating interfaces in the target namespace

Enhancements:
- Expose `netDevices` in the OCI features output
- Enable the net_devices flag and invoke the device-move step during container startup

Build:
- Include `net_device.c` and `net_device.h` in the build configuration (Makefile.am)


Diff:
diff --git a/Makefile.am b/Makefile.am
index bd3aca46f4..92e66708ea 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -69,6 +69,7 @@ libcrun_SOURCES = src/libcrun/utils.c \
 		src/libcrun/seccomp_notify.c \
 		src/libcrun/signals.c \
 		src/libcrun/status.c \
+		src/libcrun/net_device.c \
 		src/libcrun/terminal.c
 
 if HAVE_EMBEDDED_YAJL
@@ -157,6 +158,7 @@ EXTRA_DIST = COPYING COPYING.libcrun README.md NEWS SECURITY.md rpm/crun.spec au
 	src/libcrun/linux.h src/libcrun/utils.h src/libcrun/error.h src/libcrun/criu.h \
 	src/libcrun/scheduler.h src/libcrun/status.h src/libcrun/terminal.h \
 	src/libcrun/mount_flags.h src/libcrun/intelrdt.h src/libcrun/ring_buffer.h src/libcrun/string_map.h \
+	src/libcrun/net_device.h \
 	crun.1.md crun.1 libcrun.lds \
 	krun.1.md krun.1 \
 	lua/luacrun.rockspec
diff --git a/libocispec b/libocispec
index 75de7796ea..68397329bc 160000
--- a/libocispec
+++ b/libocispec
@@ -1 +1 @@
-Subproject commit 75de7796ea600aa38fba37e0c565bafed2649409
+Subproject commit 68397329bc51a66c56938fc4111fac751d6fd3b0
diff --git a/src/libcrun/container.c b/src/libcrun/container.c
index 4a4e7e9e57..6e391fd7a3 100644
--- a/src/libcrun/container.c
+++ b/src/libcrun/container.c
@@ -2633,6 +2633,10 @@ libcrun_container_run_internal (libcrun_container_t *container, libcrun_context_
   if (UNLIKELY (ret < 0))
     goto fail;
 
+  ret = libcrun_move_network_devices (container, pid, err);
+  if (UNLIKELY (ret < 0))
+    goto fail;
+
   /* sync send own pid.  */
   ret = TEMP_FAILURE_RETRY (write (sync_socket, &pid, sizeof (pid)));
   if (UNLIKELY (ret != sizeof (pid)))
@@ -4172,6 +4176,8 @@ libcrun_container_get_features (libcrun_context_t *context, struct features_info
 
   (*info)->linux.intel_rdt.enabled = true;
 
+  (*info)->linux.net_devices.enabled = true;
+
   // Put the values for mount extensions
   (*info)->linux.mount_ext.idmap.enabled = true;
 
diff --git a/src/libcrun/container.h b/src/libcrun/container.h
index ff4e71d6ae..baba9b95f0 100644
--- a/src/libcrun/container.h
+++ b/src/libcrun/container.h
@@ -139,6 +139,11 @@ struct intel_rdt_s
   bool enabled;
 };
 
+struct net_devices_s
+{
+  bool enabled;
+};
+
 struct mount_ext_info_s
 {
   struct idmap_info_s idmap;
@@ -154,6 +159,7 @@ struct linux_info_s
   struct selinux_info_s selinux;
   struct mount_ext_info_s mount_ext;
   struct intel_rdt_s intel_rdt;
+  struct net_devices_s net_devices;
 };
 
 struct annotations_info_s
diff --git a/src/libcrun/linux.c b/src/libcrun/linux.c
index 79a1421dfc..b5c1e0c93d 100644
--- a/src/libcrun/linux.c
+++ b/src/libcrun/linux.c
@@ -52,6 +52,7 @@
 #include "scheduler.h"
 #include "intelrdt.h"
 #include "io_priority.h"
+#include "net_device.h"
 
 #include <sys/socket.h>
 #include <libgen.h>
@@ -6059,3 +6060,33 @@ libcrun_destroy_runtime_mounts (libcrun_container_t *container arg_unused, libcr
 
   return run_in_container_namespace (status, do_umount_in_a_container, &args, err);
 }
+
+int
+libcrun_move_network_devices (libcrun_container_t *container, pid_t pid, libcrun_error_t *err)
+{
+  runtime_spec_schema_config_schema *def = container->container_def;
+  cleanup_close int netns_fd = -1;
+  char ns_file[64];
+  size_t i;
+  int ret;
+
+  if (def == NULL || def->linux == NULL || def->linux->net_devices == NULL)
+    return 0;
+
+  snprintf (ns_file, sizeof (ns_file), "/proc/%d/ns/net", pid);
+
+  netns_fd = open (ns_file, O_RDONLY);
+  if (UNLIKELY (netns_fd < 0))
+    return crun_make_error (err, errno, "open `%s`", ns_file);
+
+  for (i = 0; i < def->linux->net_devices->len; i++)
+    {
+      const char *new_name = def->linux->net_devices->values[i]->name ?: def->linux->net_devices->keys[i];
+
+      ret = move_network_device (def->linux->net_devices->keys[i], new_name, netns_fd, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+    }
+
+  return 0;
+}
diff --git a/src/libcrun/linux.h b/src/libcrun/linux.h
index ae56ca867f..6ded9ea177 100644
--- a/src/libcrun/linux.h
+++ b/src/libcrun/linux.h
@@ -141,6 +141,8 @@ const char *libcrun_get_intelrdt_name (const char *ctr_name, libcrun_container_t
 
 int libcrun_apply_intelrdt (const char *ctr_name, libcrun_container_t *container, pid_t pid, int actions, libcrun_error_t *err);
 
+int libcrun_move_network_devices (libcrun_container_t *container, pid_t pid, libcrun_error_t *err);
+
 int libcrun_destroy_intelrdt (const char *name, libcrun_error_t *err);
 
 int libcrun_update_intel_rdt (const char *ctr_name, libcrun_container_t *container, const char *l3_cache_schema, const char *mem_bw_schema, char *const *schemata, libcrun_error_t *err);
diff --git a/src/libcrun/net_device.c b/src/libcrun/net_device.c
new file mode 100644
index 0000000000..9231bc0813
--- /dev/null
+++ b/src/libcrun/net_device.c
@@ -0,0 +1,490 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  struct ifaddrmsg ifa;
+
+  int rta_len;
+  char *rta;
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static void
+cleanup_ip_addrsp (void *p)
+{
+  struct ip_addr **pp = (struct ip_addr **) p;
+  struct ip_addr *ip;
+  if (*pp == NULL)
+    return;
+
+  for (ip = *pp; ip->rta_len >= 0; ip++)
+    free (ip->rta);
+
+  free (*pp);
+}
+
+#define cleanup_ip_addrs __attribute__ ((cleanup (cleanup_ip_addrsp)))
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static void
+copy_ip_addr (struct nlmsghdr *nlh, struct ip_addr *ip)
+{
+  struct ifaddrmsg *ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+  memcpy (&ip->ifa, ifa, sizeof (struct ifaddrmsg));
+  ip->rta_len = IFA_PAYLOAD (nlh);
+  ip->rta = xmalloc (ip->rta_len);
+  memcpy (ip->rta, IFA_RTA (ifa), ip->rta_len);
+};
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, struct ip_addr **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_ip_addrs struct ip_addr *ips = NULL;
+  size_t ips_len = 0;
+  int optval = 1;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+#ifdef NETLINK_GET_STRICT_CHK
+  ret = setsockopt (sock, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &optval, sizeof (optval));
+  if (ret < 0)
+    {
+      if (errno != ENOPROTOOPT)
+        return crun_make_error (err, errno, "setsockopt (NETLINK_GET_STRICT_CHK)");
+
+      /* NETLINK_GET_STRICT_CHK not supported by this kernel, continue without strict checking.   */
+    }
+#endif
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+  req->ifa.ifa_index = ifindex;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct nlmsghdr *nlh;
+
+      for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, len); nlh = NLMSG_NEXT (nlh, len))
+        {
+          struct ifaddrmsg *ifa;
+
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            continue;
+
+          /* Copy only permanent, globally routable IP addresses.  */
+          if (! (ifa->ifa_flags & IFA_F_PERMANENT) || (ifa->ifa_scope != RT_SCOPE_UNIVERSE))
+            continue;
+
+          /* Always append an empty struct.  */
+          ips = xrealloc (ips, sizeof (struct ip_addr) * (++ips_len + 1));
+          /* Mark the end of the array.  */
+          ips[ips_len].rta_len = -1;
+
+          copy_ip_addr (nlh, &ips[ips_len - 1]);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ip_addresses (int sock, int ifindex, char *buffer, size_t buffer_size, const struct ip_addr *ips, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  const struct ip_addr *ip;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = ips; ip->rta_len >= 0; ip++)
+    {
+      /* RTA_NEXT modifies the argument, so use a copy.  */
+      int rta_len = ip->rta_len;
+      struct rtattr *rta;
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memcpy (&req->ifa, &ip->ifa, sizeof (struct ifaddrmsg));
+
+      req->ifa.ifa_index = ifindex;
+
+      for (rta = (struct rtattr *) ip->rta; RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+        {
+          ret = append_rtattr (&(req->nlh), buffer_size, rta->rta_type, RTA_DATA (rta), RTA_PAYLOAD (rta), err);
+          if (UNLIKELY (ret < 0))
+            return ret;
+        }
+
+      ret = send_request (sock, req, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+
+      ret = wait_for_ack (sock, seq, buffer, buffer_size, err);
+      if (ret < 0)
+        return ret;
+    }
+
+  return 0;
+}
+
+static int
+request_enable_interface_and_wait (int sock, char *buffer, size_t buffer_size, int index, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  uint32_t seq;
+  int ret;
+
+  seq = reset_request (req, RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, sizeof (struct ifinfomsg));
+
+  req->ifi.ifi_family = AF_UNSPEC;
+  req->ifi.ifi_index = index;
+
+  req->ifi.ifi_flags = IFF_UP;
+  req->ifi.ifi_change = IFF_UP;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  return wait_for_ack (sock, seq, buffer, buffer_size, err);
+}
+
+static int
+setup_network_device_in_ns_helper (char *buffer, size_t buffer_size, int netns_fd, const char *newifname,
+                                   struct ip_addr *ips, libcrun_error_t *err)
+{
+  cleanup_close int sock_in_ns = -1;
+  int new_ifindex;
+  int ret;
+
+  ret = setns (netns_fd, CLONE_NEWNET);
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "change network namespace");
+
+  sock_in_ns = open_netlink_fd (err);
+  if (sock_in_ns < 0)
+    return sock_in_ns;
+
+  /* we could ask for a specific index with IFLA_NEW_IFINDEX, and apparently the kernel tries anyway to
+     reuse the existing one, but asking for a specific index could cause conflicts if the
+     target network namespace already exists, so avoid doing it and lookup the device again.  */
+  new_ifindex = name_to_index (sock_in_ns, newifname, buffer, buffer_size, err);
+  if (UNLIKELY (new_ifindex < 0))
+    return new_ifindex;
+
+  ret = configure_ip_addresses (sock_in_ns, new_ifindex, buffer, buffer_size, ips, err);
+  if (ret < 0)
+    return ret;
+
+  return request_enable_interface_and_wait (sock_in_ns, buffer, buffer_size, new_ifindex, err);
+}
+
+static int
+do_move_link_to_ns_and_wait (int sock, char *buffer, size_t buffer_size, int ifindex, int netns_fd, const char *newifname, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  uint32_t seq;
+  int ret;
+
+  seq = reset_request (req, RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+  req->ifi.ifi_index = ifindex;
+
+  ret = append_rtattr (&req->nlh, buffer_size, IFLA_NET_NS_FD, &netns_fd, sizeof (netns_fd), err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = append_rtattr (&req->nlh, buffer_size, IFLA_IFNAME, newifname, strlen (newifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  return wait_for_ack (sock, seq, buffer, buffer_size, err);
+}
+
+int
+move_network_device (const char *ifname, const char *newifname, int netns_fd, libcrun_error_t *err)
+{
+  const size_t buffer_size = 8192;
+  cleanup_ip_addrs struct ip_addr *ips = NULL;
+  cleanup_free char *buffer = xmalloc (buffer_size);
+  cleanup_close int sock = -1;
+  int wait_status;
+  int ifindex;
+  pid_t pid;
+  int ret;
+
+  sock = open_netlink_fd (err);
+  if (sock < 0)
+    return sock;
+
+  ifindex = name_to_index (sock, ifname, buffer, buffer_size, err);
+  if (UNLIKELY (ifindex < 0))
+    return ifindex;
+
+  ret = get_ip_addresses (sock, ifindex, &ips, buffer, buffer_size, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  /* Move the device to the target network namespace.  */
+  ret = do_move_link_to_ns_and_wait (sock, buffer, buffer_size, ifindex, netns_fd, newifname, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  /* must be vfork to propagate the error from the child proc.  */
+  pid = vfork ();
+  if (UNLIKELY (pid < 0))
+    return crun_make_error (err, errno, "vfork");
+
+  if (pid == 0)
+    {
+      ret = setup_network_device_in_ns_helper (buffer, buffer_size, netns_fd, newifname, ips, err);
+      if (UNLIKELY (ret < 0))
+        _exit (-ret);
+
+      _exit (0);
+    }
+
+  ret = waitpid_ignore_stopped (pid, &wait_status, 0);
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "waitpid for exec child pid");
+
+  if (wait_status != 0)
+    return -get_process_exit_status (wait_status);
+
+  return 0;
+}
diff --git a/src/libcrun/net_device.h b/src/libcrun/net_device.h
new file mode 100644
index 0000000000..5505b29c32
--- /dev/null
+++ b/src/libcrun/net_device.h
@@ -0,0 +1,28 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NET_DEVICE_H
+#define NET_DEVICE_H
+
+#include <config.h>
+#include <ocispec/runtime_spec_schema_config_schema.h>
+#include "error.h"
+
+int move_network_device (const char *ifname, const char *newifname, int netns_fd, libcrun_error_t *err);
+
+#endif
diff --git a/src/oci_features.c b/src/oci_features.c
index 761b09f60a..4bae598a75 100644
--- a/src/oci_features.c
+++ b/src/oci_features.c
@@ -195,6 +195,15 @@ crun_features_add_intel_rdt (yajl_gen json_gen, const struct linux_info_s *linux
   yajl_gen_map_close (json_gen);
 }
 
+void
+crun_features_add_net_devices (yajl_gen json_gen, const struct linux_info_s *linux)
+{
+  yajl_gen_string (json_gen, (const unsigned char *) "netDevices", strlen ("netDevices"));
+  yajl_gen_map_open (json_gen);
+  add_bool_to_json (json_gen, "enabled", linux->net_devices.enabled);
+  yajl_gen_map_close (json_gen);
+}
+
 void
 crun_features_add_linux_info (yajl_gen json_gen, const struct linux_info_s *linux)
 {
@@ -209,6 +218,7 @@ crun_features_add_linux_info (yajl_gen json_gen, const struct linux_info_s *linu
   crun_features_add_selinux_info (json_gen, linux);
   crun_features_add_mount_ext_info (json_gen, linux);
   crun_features_add_intel_rdt (json_gen, linux);
+  crun_features_add_net_devices (json_gen, linux);
 
   yajl_gen_map_close (json_gen);
 }
diff --git a/tests/init.c b/tests/init.c
index 40a91b031d..27867a0900 100644
--- a/tests/init.c
+++ b/tests/init.c
@@ -38,6 +38,12 @@
 #include <sys/types.h>
 #include <sched.h>
 
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+
 #ifdef HAVE_LINUX_IOPRIO_H
 #  include <linux/ioprio.h>
 #endif
@@ -374,6 +380,99 @@ memhog (int megabytes)
   return 0;
 }
 
+#define BUFFER_SIZE 8192
+
+static void
+dump_net_interface (const char *ifname)
+{
+  struct sockaddr_nl sa;
+  struct nlmsghdr *nlh;
+  struct ifaddrmsg *ifa;
+  char *buffer;
+  int ifindex;
+  int sock;
+
+  ifindex = if_nametoindex (ifname);
+  if (ifindex == 0)
+    error (EXIT_FAILURE, errno, "if_nametoindex `%s`", ifname);
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    error (EXIT_FAILURE, errno, "socket");
+
+  buffer = malloc (BUFFER_SIZE);
+  if (buffer == NULL)
+    error (EXIT_FAILURE, errno, "malloc");
+
+  sa.nl_family = AF_NETLINK;
+
+  if (bind (sock, (struct sockaddr *) &sa, sizeof (sa)) < 0)
+    error (EXIT_FAILURE, errno, "bind");
+
+  nlh = (struct nlmsghdr *) buffer;
+
+  memset (buffer, 0, BUFFER_SIZE);
+  nlh->nlmsg_len = NLMSG_LENGTH (sizeof (struct ifaddrmsg));
+  nlh->nlmsg_type = RTM_GETADDR;
+  nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+  nlh->nlmsg_seq = 1;
+
+  ifa = NLMSG_DATA (nlh);
+  ifa->ifa_family = AF_INET;
+
+  if (send (sock, nlh, nlh->nlmsg_len, 0) < 0)
+    error (EXIT_FAILURE, errno, "send");
+
+  while (1)
+    {
+      ssize_t len = recv (sock, buffer, BUFFER_SIZE, 0);
+      if (len < 0)
+        error (EXIT_FAILURE, errno, "recv");
+
+      for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, len); nlh = NLMSG_NEXT (nlh, len))
+        {
+          struct rtattr *rta[IFA_MAX + 1] = {};
+          struct rtattr *rta_it;
+          int rta_len;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            goto done;
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            error (EXIT_FAILURE, 0, "netlink error");
+
+          ifa = NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            continue;
+
+          rta_it = IFA_RTA (ifa);
+          rta_len = IFA_PAYLOAD (nlh);
+
+          while (RTA_OK (rta_it, rta_len))
+            {
+              if (rta_it->rta_type <= IFA_MAX)
+                rta[rta_it->rta_type] = rta_it;
+              rta_it = RTA_NEXT (rta_it, rta_len);
+            }
+
+          if (rta[IFA_ADDRESS])
+            {
+              char addr[INET_ADDRSTRLEN];
+              inet_ntop (AF_INET, RTA_DATA (rta[IFA_LOCAL]), addr, sizeof (addr));
+              printf ("address: %s/%d\n", addr, ifa->ifa_prefixlen);
+            }
+          if (rta[IFA_BROADCAST])
+            {
+              char bcast[INET_ADDRSTRLEN];
+              inet_ntop (AF_INET, RTA_DATA (rta[IFA_BROADCAST]), bcast, sizeof (bcast));
+              printf ("broadcast: %s\n", bcast);
+            }
+        }
+    }
+done:
+  close (sock);
+  free (buffer);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -587,6 +686,14 @@ main (int argc, char **argv)
       return 0;
     }
 
+  if (strcmp (argv[1], "ip") == 0)
+    {
+      if (argc < 2)
+        error (EXIT_FAILURE, 0, "'ip' requires an argument");
+      dump_net_interface (argv[2]);
+      exit (EXIT_SUCCESS);
+    }
+
   if (strcmp (argv[1], "write") == 0)
     {
       if (argc < 3)
diff --git a/tests/test_devices.py b/tests/test_devices.py
index 9831537500..2f6f0f6bd8 100755
--- a/tests/test_devices.py
+++ b/tests/test_devices.py
@@ -16,6 +16,8 @@
 # along with crun.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
+import subprocess
+import shutil
 from tests_utils import *
 
 def test_mode_device():
@@ -228,6 +230,64 @@ def test_trailing_slash_mknod_device():
         return -1
     return 0
 
+def test_net_devices():
+    if is_rootless():
+        return 77
+
+    ip_path = shutil.which("ip")
+    if ip_path is None:
+        sys.stderr.write("ip command not found\n")
+        return 77
+
+    current_netns = os.open("/proc/self/ns/net", os.O_RDONLY)
+    try:
+        os.unshare(os.CLONE_NEWNET)
+
+        for specify_broadcast in [True, False]:
+            for specify_name in [True, False]:
+                subprocess.run(["ip", "link", "add", "testdevice", "type", "dummy"])
+                if specify_broadcast:
+                    subprocess.run(["ip", "addr", "add", "10.1.2.3/24", "brd", "10.1.2.254", "dev", "testdevice"])
+                else:
+                    subprocess.run(["ip", "addr", "add", "10.1.2.3/24", "dev", "testdevice"])
+
+                conf = base_config()
+                add_all_namespaces(conf)
+                if specify_name:
+                    conf['process']['args'] = ['/init', 'ip', 'newtestdevice']
+                    conf['linux']['netDevices'] = {
+                        "testdevice": {
+                            "name": "newtestdevice"
+                        }
+                    }
+                else:
+                    conf['process']['args'] = ['/init', 'ip', 'testdevice']
+                    conf['linux']['netDevices'] = {
+                        "testdevice": {
+                        }
+                    }
+
+                try:
+                    out = run_and_get_output(conf)
+                    if "address: 10.1.2.3" not in out[0]:
+                        sys.stderr.write("address not found in output\n")
+                        return 1
+                    if specify_broadcast:
+                        if "broadcast: 10.1.2.254" not in out[0]:
+                            sys.stderr.write("broadcast address not found in output\n")
+                            return 1
+                    else:
+                        if "broadcast" in out[0]:
+                            sys.stderr.write("broadcast address found in output\n")
+                            return 1
+                except Exception as e:
+                    return -1
+    finally:
+        os.setns(current_netns, os.CLONE_NEWNET)
+        os.close(current_netns)
+
+    return 0
+
 all_tests = {
     "owner-device" : test_owner_device,
     "deny-devices" : test_deny_devices,
@@ -237,6 +297,7 @@ def test_trailing_slash_mknod_device():
     "mode-device"  : test_mode_device,
     "create-or-bind-mount-device" : test_create_or_bind_mount_device,
     "handle-device-trailing-slash" : test_trailing_slash_mknod_device,
+    "net-devices" : test_net_devices,
 }
 
 if __name__ == "__main__":


Reviews:
user: login: Luap99
body: Not a proper review but just come across this by chance
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T16:08:58Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-18T12:25:56Z
user: login: sourcery-ai[bot]
body: Hey @giuseppe - I've reviewed your changes - here's some feedback:

- Using vfork to call setup_network_device_in_ns is unsafe because the child invokes non-async-signal-safe functions; switch to a regular fork or exec-based approach to avoid undefined behavior.
- net_device.c has a lot of repeated netlink setup/teardown code—consider refactoring common socket, seq, and message handling into helper functions to reduce duplication and improve readability.
- Relying on a fixed 8 KB buffer for all netlink messages may lead to truncation or overflow in complex scenarios; either validate message sizes more rigorously or switch to dynamically sizing the buffer.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 1 issue found
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: APPROVED
author_association: NONE
submitted_at: 2025-05-19T07:10:41Z
user: login: Luap99
body: Do you have a plan to expose this feature somehow in podman? Or do you know if docker will expose this? Of course spec wise it seems straight forward but I mostly wonder about the podman/docker UI. I guess I should create an issue on podman and we can talk about it there.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T11:16:18Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T11:40:18Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T11:56:53Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T12:02:24Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T12:07:11Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T12:13:12Z
user: login: aojea
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-19T23:59:45Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:51:07Z
user: login: sourcery-ai[bot]
body: Hey @giuseppe - I've reviewed your changes and they look great!

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 2 issues found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: APPROVED
author_association: NONE
submitted_at: 2025-05-20T11:07:56Z
user: login: Luap99
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T11:45:39Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T19:47:28Z
user: login: Luap99
body: I don't feel qualified to review c code especially with a bunch of buffer copies from/to netlink types but netlink logic wise this looks good to me.

Not sure how you handle testing here normally, I would assume it should be quite simple to
```
ip link add test1 type dummy
ip addr add 10.0.0.0/24 dev test1
```
And then move that into the namespace with crun and run `ip addr` in the container to check if the address was copied properly? Not sure if this is easy to do here.

state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T08:54:57Z
user: login: aojea
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T12:40:51Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:02:02Z
user: login: aojea
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T13:10:11Z
user: login: giuseppe
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T13:13:02Z
user: login: flouthoc
body: LGTM
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-21T14:53:27Z

Review comments:
diff_hunk: @@ -0,0 +1,451 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+    struct rtgenmsg gen;
+  };
+};
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req req;
+  struct sockaddr_nl sa = { 0 };
+  struct iovec iov = { buffer, buffer_size };
+  struct msghdr msg;
+  int index = 0;
+  int ret;
+
+  memset (&req, 0, sizeof (req));
+
+  req.nlh.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtgenmsg));
+  req.nlh.nlmsg_type = RTM_GETLINK;
+  req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
path: src/libcrun/net_device.c
user: login: Luap99
body: actually you don't have to dump all links to get a single one, you can request by name and that is what we are doing in netavark.
https://github.com/containers/netavark/blob/c9649e875a9d5932fad8293095bac2938288a93f/src/network/netlink.rs#L132

This makes things much simpler as you don't have to loop over all links and compare each name yourself. 

created_at: 2025-05-16T16:07:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,451 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+    struct rtgenmsg gen;
+  };
+};
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req req;
+  struct sockaddr_nl sa = { 0 };
+  struct iovec iov = { buffer, buffer_size };
+  struct msghdr msg;
+  int index = 0;
+  int ret;
+
+  memset (&req, 0, sizeof (req));
+
+  req.nlh.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtgenmsg));
+  req.nlh.nlmsg_type = RTM_GETLINK;
+  req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
path: src/libcrun/net_device.c
user: login: giuseppe
body: thanks, that was one of the things I wanted to address before moving it out of Draft, plus some other cleanups
created_at: 2025-05-18T12:25:55Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  const size_t strlenifname = strlen (ifname);
+  struct ifinfomsg *ifi;
+  struct rtattr *rta;
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq = get_next_seq ();
+  int index = 0;
+  ssize_t len;
+  int ret;
+  struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
+  struct iovec iov = { buffer, buffer_size };
+  struct msghdr msg = {
+    .msg_name = &sa,
+    .msg_namelen = sizeof (sa),
+    .msg_iov = &iov,
+    .msg_iovlen = 1,
+  };
+
+  req = (struct nl_req *) buffer;
+  memset (req, 0, sizeof (*req));
+
+  ifi = &req->ifi;
+  rta = (struct rtattr *) ((char *) ifi + NLMSG_ALIGN (sizeof (*ifi)));
+  nlh = &req->nlh;
+
+  nlh->nlmsg_len = NLMSG_LENGTH (sizeof (*ifi));
+  nlh->nlmsg_type = RTM_GETLINK;
+  nlh->nlmsg_flags = NLM_F_REQUEST;
+  nlh->nlmsg_seq = seq;
+
+  ifi->ifi_family = AF_UNSPEC;
+
+  rta->rta_type = IFLA_IFNAME;
+  rta->rta_len = RTA_LENGTH (strlenifname + 1);
+  memcpy (RTA_DATA (rta), ifname, strlenifname + 1);
+
+  nlh->nlmsg_len += RTA_LENGTH (strlenifname + 1);
+
+  ret = sendmsg (sock, &msg, 0);
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "sendmsg");
+
+  len = recvmsg (sock, &msg, 0);
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recvmsg");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = recv (sock, recv_buffer, recv_buffer_size, 0);
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t seq, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  ssize_t len;
+
+  while ((len = recv (sock, buffer, buffer_size, 0)) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t new_size = sizeof (*ip) + rta_len;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_len);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+addattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      req->nlh.nlmsg_len = NLMSG_LENGTH (sizeof (struct ifaddrmsg));
+      req->nlh.nlmsg_type = RTM_NEWADDR;
+      req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
+      req->nlh.nlmsg_seq = get_next_seq ();
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = addattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = addattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+
+      ret = send (sock, req, req->nlh.nlmsg_len, 0);
+      if (ret < 0)
+        return crun_make_error (err, errno, "send");
+
+      ret = wait_for_ack (sock, req->nlh.nlmsg_seq, buffer, buffer_size, err);
+      if (ret < 0)
+        return ret;
+    }
+
+  return 0;
+}
+
+static void
+req_set_link_state (struct nl_req *req, int index, bool up)
+{
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_len = NLMSG_LENGTH (sizeof (struct ifinfomsg));
+  req->nlh.nlmsg_type = RTM_NEWLINK;
+  req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+  req->ifi.ifi_family = AF_UNSPEC;
+  req->ifi.ifi_index = index;
+
+  req->ifi.ifi_flags = up ? IFF_UP : 0;
+  req->ifi.ifi_change = IFF_UP;
+}
+
+static int
+setup_network_device_in_ns (struct nl_req *req, const char *newifname, const char *ips, int netns_fd,
+                            char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  cleanup_close int sock_in_ns = -1;
+  int new_ifindex;
+  int ret;
+
+  ret = setns (netns_fd, CLONE_NEWNET);
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "setns to target pid");
+
+  sock_in_ns = open_netlink_fd (err);
+  if (sock_in_ns < 0)
+    return sock_in_ns;
+
+  /* we could ask for a specific index with IFLA_NEW_IFINDEX, and apparently the kernel tries anyway to
+     reuse the existing one, but asking for a specific index could cause conflicts if the
+     target network namespace already exists, so avoid doing it and lookup the device again.  */
+  new_ifindex = name_to_index (sock_in_ns, newifname, buffer, buffer_size, err);
+  if (UNLIKELY (new_ifindex < 0))
+    return new_ifindex;
+
+  ret = configure_ips (sock_in_ns, new_ifindex, buffer, buffer_size, ips, err);
+  if (ret < 0)
+    return ret;
+
+  req_set_link_state (req, new_ifindex, true);
+  req->nlh.nlmsg_seq = get_next_seq ();
+  ret = send (sock_in_ns, req, req->nlh.nlmsg_len, 0);
+  if (ret < 0)
+    return crun_make_error (err, errno, "send");
+
+  ret = wait_for_ack (sock_in_ns, req->nlh.nlmsg_seq, buffer, buffer_size, err);
+  if (ret < 0)
+    return ret;
+
+  return 0;
+}
+
+int
+move_network_device (const char *ifname, const char *newifname, int netns_fd, libcrun_error_t *err)
+{
+  const size_t buffer_size = 8192;
+  cleanup_free char *ips = NULL;
+  cleanup_free char *buffer = xmalloc (buffer_size);
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  int ifindex;
+  int ret;
+  cleanup_close int sock = -1;
+  int wait_status;
+  pid_t pid;
+
+  sock = open_netlink_fd (err);
+  if (sock < 0)
+    return sock;
+
+  ifindex = name_to_index (sock, ifname, buffer, buffer_size, err);
+  if (UNLIKELY (ifindex < 0))
+    return ifindex;
+
+  req = (struct nl_req *) buffer;
+  nlh = &req->nlh;
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_len = NLMSG_LENGTH (sizeof (struct ifaddrmsg));
+  req->nlh.nlmsg_type = RTM_GETADDR;
+  req->nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+  req->ifa.ifa_family = AF_UNSPEC;
+  req->nlh.nlmsg_seq = get_next_seq ();
+  ret = send (sock, req, req->nlh.nlmsg_len, 0);
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  ret = get_ip_addresses (sock, req->nlh.nlmsg_seq, ifindex, &ips, buffer, buffer_size, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  /* Bring device down.  */
+  req_set_link_state (req, ifindex, false);
+  req->nlh.nlmsg_seq = get_next_seq ();
+  ret = send (sock, req, req->nlh.nlmsg_len, 0);
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  ret = wait_for_ack (sock, req->nlh.nlmsg_seq, buffer, buffer_size, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  req->nlh.nlmsg_type = RTM_NEWLINK;
+  req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+  req->ifi.ifi_family = AF_UNSPEC;
+  req->ifi.ifi_index = ifindex;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (sizeof (struct ifinfomsg));
+  req->nlh.nlmsg_seq = get_next_seq ();
+
+  ret = addattr (nlh, buffer_size, IFLA_NET_NS_FD, &netns_fd, sizeof (netns_fd), err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = addattr (nlh, buffer_size, IFLA_IFNAME, newifname, strlen (newifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
path: src/libcrun/net_device.c
user: login: sourcery-ai[bot]
body: **suggestion (bug_risk):** No validation of newifname length before passing to addattr.

Validate newifname length against IFNAMSIZ before calling addattr to avoid potential overflow.

```suggestion
  /* validate interface name length (including NUL) */
  if (UNLIKELY (strlen (newifname) + 1 > IFNAMSIZ))
    return crun_make_error (err, EINVAL, "interface name too long: %s", newifname);

  ret = addattr (nlh, buffer_size, IFLA_IFNAME, newifname, strlen (newifname) + 1, err);
  if (UNLIKELY (ret < 0))
    return ret;
```
created_at: 2025-05-19T07:10:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
path: src/libcrun/net_device.c
user: login: Luap99
body: `strace ip addr show dev lo`

That we can also short circuit this by requesting address on the link only from the kernel instead of filtering them here userspace.
I assume something like `req->ifa.ifa_index = ifindex` on line 229?

created_at: 2025-05-19T10:55:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: Luap99
body: you only collect IFA_ADDRESS on the dump but then assume you can set both IFA_ADDRESS and IFA_LOCAL here

From include/uapi/linux/if_addr.h:
> * Important comment:
> * IFA_ADDRESS is prefix address, rather than local interface address.
> * It makes no difference for normally configured broadcast interfaces,
> * but for point-to-point IFA_ADDRESS is DESTINATION address,
> * local address is supplied in IFA_LOCAL attribute.

Also comparing against netavark we only set IFA_LOCAL but we do also set IFA_BROADCAST for ipv4 only.
Skipping the IFA_BROADCAST broadcast in netavark seems to create an interface where the broadcast address is not shown in ip addr. Not sure if this means one cannot use broadcast then but I guess such behavior would not be expected from this move interface feature?

created_at: 2025-05-19T11:12:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+
+      ret = send_request (sock, req, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+
+      ret = wait_for_ack (sock, seq, buffer, buffer_size, err);
+      if (ret < 0)
+        return ret;
+    }
+
+  return 0;
+}
+
+static int
+request_set_link_state_and_wait (int sock, char *buffer, size_t buffer_size, int index, bool up, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  uint32_t seq;
+  int ret;
+
+  seq = reset_request (req, RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, sizeof (struct ifinfomsg));
+
+  req->ifi.ifi_family = AF_UNSPEC;
+  req->ifi.ifi_index = index;
+
+  req->ifi.ifi_flags = up ? IFF_UP : 0;
+  req->ifi.ifi_change = IFF_UP;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  return wait_for_ack (sock, seq, buffer, buffer_size, err);
+}
+
+static int
+setup_network_device_in_ns_helper (char *buffer, size_t buffer_size, int netns_fd,
+                                   const char *newifname, const char *ips, libcrun_error_t *err)
+{
+  cleanup_close int sock_in_ns = -1;
+  int new_ifindex;
+  int ret;
+
+  ret = setns (netns_fd, CLONE_NEWNET);
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "change network namespace");
+
+  sock_in_ns = open_netlink_fd (err);
+  if (sock_in_ns < 0)
+    return sock_in_ns;
+
+  /* we could ask for a specific index with IFLA_NEW_IFINDEX, and apparently the kernel tries anyway to
+     reuse the existing one, but asking for a specific index could cause conflicts if the
+     target network namespace already exists, so avoid doing it and lookup the device again.  */
+  new_ifindex = name_to_index (sock_in_ns, newifname, buffer, buffer_size, err);
+  if (UNLIKELY (new_ifindex < 0))
+    return new_ifindex;
+
+  ret = configure_ips (sock_in_ns, new_ifindex, buffer, buffer_size, ips, err);
+  if (ret < 0)
+    return ret;
+
+  return request_set_link_state_and_wait (sock_in_ns, buffer, buffer_size, new_ifindex, true, err);
+}
+
+static int
+do_move_link_to_ns_and_wait (int sock, char *buffer, size_t buffer_size, int ifindex, int netns_fd, const char *newifname, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  uint32_t seq;
+  int ret;
+
+  seq = reset_request (req, RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+  req->ifi.ifi_index = ifindex;
+
+  ret = append_rtattr (&req->nlh, buffer_size, IFLA_NET_NS_FD, &netns_fd, sizeof (netns_fd), err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = append_rtattr (&req->nlh, buffer_size, IFLA_IFNAME, newifname, strlen (newifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  return wait_for_ack (sock, seq, buffer, buffer_size, err);
+}
+
+int
+move_network_device (const char *ifname, const char *newifname, int netns_fd, libcrun_error_t *err)
+{
+  const size_t buffer_size = 8192;
+  cleanup_free char *ips = NULL;
+  cleanup_free char *buffer = xmalloc (buffer_size);
+  int ifindex;
+  int ret;
+  cleanup_close int sock = -1;
+  int wait_status;
+  pid_t pid;
+
+  sock = open_netlink_fd (err);
+  if (sock < 0)
+    return sock;
+
+  ifindex = name_to_index (sock, ifname, buffer, buffer_size, err);
+  if (UNLIKELY (ifindex < 0))
+    return ifindex;
+
+  ret = get_ip_addresses (sock, ifindex, &ips, buffer, buffer_size, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  /* Bring device down.  */
+  ret = request_set_link_state_and_wait (sock, buffer, buffer_size, ifindex, false, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
path: src/libcrun/net_device.c
user: login: Luap99
body: Is there a need to do that kernel wise? AFAICT the device is moved to down in the new namespace no matter what? So I wonder if we could save one netlink roundtrip here.
created_at: 2025-05-19T11:13:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
path: src/libcrun/net_device.c
user: login: giuseppe
body: that doesn't seem to work, I always get the whole list, let me try again and if perhaps there is a different command to achieve it
created_at: 2025-05-19T11:40:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
path: src/libcrun/net_device.c
user: login: giuseppe
body: hm.. looks like I need to use NETLINK_GET_STRICT_CHK
created_at: 2025-05-19T11:56:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+
+      ret = send_request (sock, req, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+
+      ret = wait_for_ack (sock, seq, buffer, buffer_size, err);
+      if (ret < 0)
+        return ret;
+    }
+
+  return 0;
+}
+
+static int
+request_set_link_state_and_wait (int sock, char *buffer, size_t buffer_size, int index, bool up, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  uint32_t seq;
+  int ret;
+
+  seq = reset_request (req, RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, sizeof (struct ifinfomsg));
+
+  req->ifi.ifi_family = AF_UNSPEC;
+  req->ifi.ifi_index = index;
+
+  req->ifi.ifi_flags = up ? IFF_UP : 0;
+  req->ifi.ifi_change = IFF_UP;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  return wait_for_ack (sock, seq, buffer, buffer_size, err);
+}
+
+static int
+setup_network_device_in_ns_helper (char *buffer, size_t buffer_size, int netns_fd,
+                                   const char *newifname, const char *ips, libcrun_error_t *err)
+{
+  cleanup_close int sock_in_ns = -1;
+  int new_ifindex;
+  int ret;
+
+  ret = setns (netns_fd, CLONE_NEWNET);
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "change network namespace");
+
+  sock_in_ns = open_netlink_fd (err);
+  if (sock_in_ns < 0)
+    return sock_in_ns;
+
+  /* we could ask for a specific index with IFLA_NEW_IFINDEX, and apparently the kernel tries anyway to
+     reuse the existing one, but asking for a specific index could cause conflicts if the
+     target network namespace already exists, so avoid doing it and lookup the device again.  */
+  new_ifindex = name_to_index (sock_in_ns, newifname, buffer, buffer_size, err);
+  if (UNLIKELY (new_ifindex < 0))
+    return new_ifindex;
+
+  ret = configure_ips (sock_in_ns, new_ifindex, buffer, buffer_size, ips, err);
+  if (ret < 0)
+    return ret;
+
+  return request_set_link_state_and_wait (sock_in_ns, buffer, buffer_size, new_ifindex, true, err);
+}
+
+static int
+do_move_link_to_ns_and_wait (int sock, char *buffer, size_t buffer_size, int ifindex, int netns_fd, const char *newifname, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  uint32_t seq;
+  int ret;
+
+  seq = reset_request (req, RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+  req->ifi.ifi_index = ifindex;
+
+  ret = append_rtattr (&req->nlh, buffer_size, IFLA_NET_NS_FD, &netns_fd, sizeof (netns_fd), err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = append_rtattr (&req->nlh, buffer_size, IFLA_IFNAME, newifname, strlen (newifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  return wait_for_ack (sock, seq, buffer, buffer_size, err);
+}
+
+int
+move_network_device (const char *ifname, const char *newifname, int netns_fd, libcrun_error_t *err)
+{
+  const size_t buffer_size = 8192;
+  cleanup_free char *ips = NULL;
+  cleanup_free char *buffer = xmalloc (buffer_size);
+  int ifindex;
+  int ret;
+  cleanup_close int sock = -1;
+  int wait_status;
+  pid_t pid;
+
+  sock = open_netlink_fd (err);
+  if (sock < 0)
+    return sock;
+
+  ifindex = name_to_index (sock, ifname, buffer, buffer_size, err);
+  if (UNLIKELY (ifindex < 0))
+    return ifindex;
+
+  ret = get_ip_addresses (sock, ifindex, &ips, buffer, buffer_size, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  /* Bring device down.  */
+  ret = request_set_link_state_and_wait (sock, buffer, buffer_size, ifindex, false, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
path: src/libcrun/net_device.c
user: login: giuseppe
body: good point, I agree this can be removed
created_at: 2025-05-19T12:02:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: giuseppe
body: @aojea any suggestions here?  Shoul we restore IFA_BROADCAST?
created_at: 2025-05-19T12:07:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
path: src/libcrun/net_device.c
user: login: Luap99
body: Oh and I just fully read the spec

> The runtime MUST preserve existing network interface attributes, including all
permanent IP addresses (IFA_F_PERMANENT flag) of any family with global scope
(RT_SCOPE_UNIVERSE value) as defined in [`RFC 3549 Section 2.3.3.2`][rfc3549].
This ensures that only addresses intended for persistent, external communication
are transferred. 

You already apply IFA_F_PERMANENT to the address when you set them but this here doesn't seem to skip addresses without IFA_F_PERMANENT set? I guess we would not want to copy an ipv6 link local address.
created_at: 2025-05-19T12:13:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: aojea
body: this is the logic in the golang library https://github.com/vishvananda/netlink/blob/17daef607c6442d47b0565343cf8a69f985a4cb7/addr_linux.go#L128-L145 

It infers the broadcast from the IP address, that comes in CIDR format IP/MASK
created_at: 2025-05-19T23:59:45Z
author_association: NONE
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: giuseppe
body: should this be documented in the specs?

Added the same logic now
created_at: 2025-05-20T10:51:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,514 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  struct ifaddrmsg ifa;
+
+  int rta_len;
+  char *rta;
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static void
+cleanup_ip_addrsp (void *p)
+{
+  struct ip_addr **pp = (struct ip_addr **) p;
+  struct ip_addr *ip;
+  if (*pp == NULL)
+    return;
+
+  for (ip = *pp; ip->rta_len >= 0; ip++)
+    free (ip->rta);
+
+  free (*pp);
+}
+
+#define cleanup_ip_addrs __attribute__ ((cleanup (cleanup_ip_addrsp)))
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static void
+copy_ip_addr (struct nlmsghdr *nlh, struct ip_addr *ip)
+{
+  struct ifaddrmsg *ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+  memcpy (&ip->ifa, ifa, sizeof (struct ifaddrmsg));
+  ip->rta_len = IFA_PAYLOAD (nlh);
+  ip->rta = xmalloc (ip->rta_len);
+  memcpy (ip->rta, IFA_RTA (ifa), ip->rta_len);
+};
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, struct ip_addr **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_ip_addrs struct ip_addr *ips = NULL;
+  size_t ips_len = 0;
+  int optval = 1;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  ret = setsockopt (sock, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &optval, sizeof (optval));
+  if (ret < 0)
+    return crun_make_error (err, errno, "setsockopt (NETLINK_GET_STRICT_CHK)");
path: src/libcrun/net_device.c
user: login: sourcery-ai[bot]
body: **suggestion:** NETLINK_GET_STRICT_CHK may not be available on all kernels.

Wrap the setsockopt call in a feature check or catch ENOPROTOOPT, falling back gracefully when NETLINK_GET_STRICT_CHK isn’t supported.

```suggestion
#ifdef NETLINK_GET_STRICT_CHK
  ret = setsockopt (sock, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &optval, sizeof (optval));
  if (ret < 0)
    {
      if (errno != ENOPROTOOPT)
        return crun_make_error (err, errno, "setsockopt (NETLINK_GET_STRICT_CHK)");
      /* NETLINK_GET_STRICT_CHK not supported by this kernel, continue without strict checking */
    }
#endif
```
created_at: 2025-05-20T11:07:56Z
author_association: NONE
diff_hunk: @@ -0,0 +1,514 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  struct ifaddrmsg ifa;
+
+  int rta_len;
+  char *rta;
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static void
+cleanup_ip_addrsp (void *p)
+{
+  struct ip_addr **pp = (struct ip_addr **) p;
+  struct ip_addr *ip;
+  if (*pp == NULL)
+    return;
+
+  for (ip = *pp; ip->rta_len >= 0; ip++)
+    free (ip->rta);
+
+  free (*pp);
+}
+
+#define cleanup_ip_addrs __attribute__ ((cleanup (cleanup_ip_addrsp)))
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static void
+copy_ip_addr (struct nlmsghdr *nlh, struct ip_addr *ip)
+{
+  struct ifaddrmsg *ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+  memcpy (&ip->ifa, ifa, sizeof (struct ifaddrmsg));
+  ip->rta_len = IFA_PAYLOAD (nlh);
+  ip->rta = xmalloc (ip->rta_len);
+  memcpy (ip->rta, IFA_RTA (ifa), ip->rta_len);
+};
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, struct ip_addr **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_ip_addrs struct ip_addr *ips = NULL;
+  size_t ips_len = 0;
+  int optval = 1;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  ret = setsockopt (sock, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &optval, sizeof (optval));
+  if (ret < 0)
+    return crun_make_error (err, errno, "setsockopt (NETLINK_GET_STRICT_CHK)");
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+  req->ifa.ifa_index = ifindex;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct nlmsghdr *nlh;
+
+      for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, len); nlh = NLMSG_NEXT (nlh, len))
+        {
+          struct ifaddrmsg *ifa;
+
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            continue;
+
+          /* Copy only permanent, globally routable IP addresses.  */
+          if (! (ifa->ifa_flags & IFA_F_PERMANENT) || (ifa->ifa_scope != RT_SCOPE_UNIVERSE))
+            continue;
+
+          /* Always append an empty struct.  */
+          ips = xrealloc (ips, sizeof (struct ip_addr) * (++ips_len + 1));
+          /* Mark the end of the array.  */
+          ips[ips_len].rta_len = -1;
+
+          copy_ip_addr (nlh, &ips[ips_len - 1]);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ip_addresses (int sock, int ifindex, char *buffer, size_t buffer_size, const struct ip_addr *ips, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  const struct ip_addr *ip;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = ips; ip->rta_len >= 0; ip++)
+    {
+      struct in_addr ifa_addr = { 0 };
+      bool has_broadcast = false;
+      /* RTA_NEXT modifies the argument, so use a copy.  */
+      int rta_len = ip->rta_len;
+      struct rtattr *rta;
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memcpy (&req->ifa, &ip->ifa, sizeof (struct ifaddrmsg));
+
+      req->ifa.ifa_index = ifindex;
+
+      for (rta = (struct rtattr *) ip->rta; RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+        {
+          switch (rta->rta_type)
+            {
+            case IFA_ADDRESS:
+              if (ip->ifa.ifa_family == AF_INET)
+                {
+                  if (RTA_PAYLOAD (rta) != sizeof (ifa_addr.s_addr))
+                    return crun_make_error (err, 0, "internal error: wrong size type for AF_INET address");
+                  memcpy (&ifa_addr.s_addr, RTA_DATA (rta), sizeof (ifa_addr.s_addr));
+                }
+              break;
+            case IFA_BROADCAST:
+              has_broadcast = true;
+              break;
+
+            default:
+              break;
+            }
+
+          ret = append_rtattr (&(req->nlh), buffer_size, rta->rta_type, RTA_DATA (rta), RTA_PAYLOAD (rta), err);
+          if (UNLIKELY (ret < 0))
+            return ret;
+        }
+
+      if (ip->ifa.ifa_family == AF_INET && ! has_broadcast && ip->ifa.ifa_prefixlen < 31 && ifa_addr.s_addr != 0)
+        {
+          /* Guess the broadcast address when it is not specified.  */
+          uint32_t mask = htonl (~((1ULL << (32 - ip->ifa.ifa_prefixlen)) - 1));
path: src/libcrun/net_device.c
user: login: sourcery-ai[bot]
body: **issue (bug_risk):** Bitwise operations for mask calculation may have undefined behavior for prefixlen=0.

Guard against ip->ifa.ifa_prefixlen == 0 before computing the mask to avoid the undefined left shift by 32 bits.
created_at: 2025-05-20T11:07:56Z
author_association: NONE
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: Luap99
body: I agree relying on the behavior of one golang netlink lib seems odd as part of the spec?

The spec currently says

> The runtime MUST preserve existing network interface attributes, including all
permanent IP addresses (IFA_F_PERMANENT flag) of any family with global scope
(RT_SCOPE_UNIVERSE value) as defined in [`RFC 3549 Section 2.3.3.2`][rfc3549].
This ensures that only addresses intended for persistent, external communication
are transferred.

To me this means all netlink attributes should be preserved for all addresses that have IFA_F_PERMANENT and RT_SCOPE_UNIVERSE set not just just a few  IFA_LOCAL/IFA_ADDRESS/IFA_BROADCAST.

And always setting IFA_BROADCAST like that would mean if the host interface did not have IFA_BROADCAST broadcast set then the container interface all of the sudden has it which certainly feels wrong, I would expect the runtime to not add new attributes.
created_at: 2025-05-20T11:45:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: giuseppe
body: @Luap99 I agree, let's drop this mechanism for now, it is surprising that the broadcast is configured when it is not present.

The patch to add it back is easy in case we need it:

```diff
diff --git a/src/libcrun/net_device.c b/src/libcrun/net_device.c
index 9231bc08..b25d8ab9 100644
--- a/src/libcrun/net_device.c
+++ b/src/libcrun/net_device.c
@@ -328,6 +328,8 @@ configure_ip_addresses (int sock, int ifindex, char *buffer, size_t buffer_size,
 
   for (ip = ips; ip->rta_len >= 0; ip++)
     {
+      struct in_addr ifa_addr = { 0 };
+      bool has_broadcast = false;
       /* RTA_NEXT modifies the argument, so use a copy.  */
       int rta_len = ip->rta_len;
       struct rtattr *rta;
@@ -341,11 +343,41 @@ configure_ip_addresses (int sock, int ifindex, char *buffer, size_t buffer_size,
 
       for (rta = (struct rtattr *) ip->rta; RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
         {
+          switch (rta->rta_type)
+            {
+            case IFA_ADDRESS:
+              if (ip->ifa.ifa_family == AF_INET)
+                {
+                  if (RTA_PAYLOAD (rta) != sizeof (ifa_addr.s_addr))
+                    return crun_make_error (err, 0, "internal error: wrong size type for AF_INET address");
+                  memcpy (&ifa_addr.s_addr, RTA_DATA (rta), sizeof (ifa_addr.s_addr));
+                }
+              break;
+            case IFA_BROADCAST:
+              has_broadcast = true;
+              break;
+
+            default:
+              break;
+            }
+
           ret = append_rtattr (&(req->nlh), buffer_size, rta->rta_type, RTA_DATA (rta), RTA_PAYLOAD (rta), err);
           if (UNLIKELY (ret < 0))
             return ret;
         }
 
+      if (ip->ifa.ifa_family == AF_INET && ! has_broadcast && ip->ifa.ifa_prefixlen > 0
+          && ip->ifa.ifa_prefixlen < 31 && ifa_addr.s_addr != 0)
+        {
+          /* Guess the broadcast address when it is not specified.  */
+          uint32_t mask = htonl (~((1ULL << (32 - ip->ifa.ifa_prefixlen)) - 1));
+          ifa_addr.s_addr |= ~mask;
+
+          ret = append_rtattr (&(req->nlh), buffer_size, IFA_BROADCAST, &ifa_addr, sizeof (ifa_addr), err);
+          if (UNLIKELY (ret < 0))
+            return ret;
+        }
+
       ret = send_request (sock, req, err);
       if (UNLIKELY (ret < 0))
         return ret;

```
created_at: 2025-05-20T19:47:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: aojea
body: Sorry let me clarify, because I leaked some details of the implementation on the comment and make it more confusing, in the golang library the library translates the address characteristics to golang types and it changes the IFA_BROADCAST to a net.IPNet with ip/mask and when it recovers it recalculates the IFA_BROADCAST  field, so effectively it passes IFA_BROADCAST

so inet `inet 192.168.8.5/24` is IFA_LOCAL=192.168.8.5 and IFA_BROADCAST=192.168.8.255
```
recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[[{nlmsg_len=88, nlmsg_type=RTM_NEWADDR, nlmsg_flags=NLM_F_MULTI|NLM_F_DUMP_FILTERED, nlmsg_seq=1747830917, nlmsg_pid=45678}, {ifa_family=AF_INET, ifa_prefixlen=24, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_UNIVERSE, ifa_index=if_nametoindex("eth0")}, [[{nla_len=8, nla_type=IFA_ADDRESS}, inet_addr("192.168.8.5")], [{nla_len=8, nla_type=IFA_LOCAL}, inet_addr("192.168.8.5")], [{nla_len=8, nla_type=IFA_BROADCAST}, inet_addr("192.168.8.255")], [{nla_len=9, nla_type=IFA_LABEL}, "eth0"], [{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT], [{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=147250349, tstamp=147250349}]]], [{nlmsg_len=72, nlmsg_type=RTM_NEWADDR, nlmsg_flags=NLM_F_MULTI|NLM_F_DUMP_FILTERED, nlmsg_seq=1747830917, nlmsg_pid=45678}, {ifa_family=AF_INET6, ifa_prefixlen=64, ifa_flags=IFA_F_NODAD|IFA_F_PERMANENT, ifa_scope=RT_SCOPE_UNIVERSE, ifa_index=if_nametoindex("eth0")}, [[{nla_len=20, nla_type=IFA_ADDRESS}, inet_pton(AF_INET6, "fc00:f853:ccd:e793::5")], [{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=147250352, tstamp=147250352}], [{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_NODAD|IFA_F_PERMANENT]]], [{nlmsg_len=80, nlmsg_type=RTM_NEWADDR, nlmsg_flags=NLM_F_MULTI|NLM_F_DUMP_FILTERED, nlmsg_seq=1747830917, nlmsg_pid=45678}, {ifa_family=AF_INET6, ifa_prefixlen=64, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_LINK, ifa_index=if_nametoindex("eth0")}, [[{nla_len=20, nla_type=IFA_ADDRESS}, inet_pton(AF_INET6, "fe80::42:c0ff:fea8:805")], [{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=147250352, tstamp=147250352}], [{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT], [{nla_len=5, nla_type=IFA_PROTO}, "\x03"]]]], iov_len=32768}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 240

```

so I agree with @Luap99 interpretations, because if user has 192.168.8.5/24 configured and we do not move the broadcast address, it will have 192.168.8.5/32 on the container that will break applications that depend on that interface to cover the entire subnet range
created_at: 2025-05-21T12:40:51Z
author_association: NONE
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: giuseppe
body: @aojea thanks, so are you ok with the current implementation where the BROADCAST is propagated only when it is set (and not always forced to be present)?
created_at: 2025-05-21T13:02:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: aojea
body: yes, that is the expectation, scratch my first comment
created_at: 2025-05-21T13:10:11Z
author_association: NONE
diff_hunk: @@ -0,0 +1,479 @@
+/*
+ * crun - OCI runtime written in C
+ *
+ * Copyright (C) 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
+ * crun is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * crun is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with crun.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include "net_device.h"
+#include "utils.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#if HAVE_STDATOMIC_H
+#  include <stdatomic.h>
+#  ifndef HAVE_ATOMIC_INT
+#    define atomic_uint volatile uint
+#  endif
+#endif
+
+struct ip_addr
+{
+  int len;
+  int ifa_family;
+  int ifa_prefixlen;
+  char data[0];
+};
+
+struct nl_req
+{
+  struct nlmsghdr nlh;
+  union
+  {
+    struct ifaddrmsg ifa;
+    struct ifinfomsg ifi;
+  };
+};
+
+static atomic_uint nl_seq_counter;
+
+static uint32_t
+get_next_seq ()
+{
+  return (uint32_t) ++nl_seq_counter;
+}
+
+static uint32_t
+reset_request (struct nl_req *req, int type, int flags, int msg_len)
+{
+  uint32_t seq = get_next_seq ();
+
+  memset (req, 0, sizeof (*req));
+  req->nlh.nlmsg_type = type;
+  req->nlh.nlmsg_flags = flags;
+  req->nlh.nlmsg_len = NLMSG_LENGTH (msg_len);
+  req->nlh.nlmsg_seq = seq;
+
+  return seq;
+}
+
+static int
+open_netlink_fd (libcrun_error_t *err)
+{
+  cleanup_close int sock = -1;
+  struct sockaddr_nl local = {
+    .nl_family = AF_NETLINK
+  };
+  int fd;
+
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    return crun_make_error (err, errno, "netlink socket");
+
+  if (bind (sock, (struct sockaddr *) &local, sizeof (local)) < 0)
+    return crun_make_error (err, errno, "bind");
+
+  fd = sock;
+  sock = -1;
+  return fd;
+}
+
+static int
+append_rtattr (struct nlmsghdr *n, size_t maxlen, int type, const void *data, size_t data_len, libcrun_error_t *err)
+{
+  int len = RTA_LENGTH (data_len);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    return crun_make_error (err, E2BIG, "internal error: buffer too small");
+
+  rta = (struct rtattr *) (((char *) n) + NLMSG_ALIGN (n->nlmsg_len));
+  rta->rta_type = type;
+  rta->rta_len = len;
+  if (data_len)
+    memcpy (RTA_DATA (rta), data, data_len);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+  return 0;
+}
+
+static int
+send_request (int sock, struct nl_req *req, libcrun_error_t *err)
+{
+  int ret;
+
+  ret = TEMP_FAILURE_RETRY (send (sock, req, req->nlh.nlmsg_len, 0));
+  if (UNLIKELY (ret < 0))
+    return crun_make_error (err, errno, "send");
+
+  return 0;
+}
+
+/* if_nametoindex with an open netlink socket.  */
+static int
+name_to_index (int sock, const char *ifname, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh;
+  struct nl_req *req;
+  uint32_t seq;
+  int index = 0;
+  ssize_t len;
+  int ret;
+
+  req = (struct nl_req *) buffer;
+
+  nlh = &req->nlh;
+
+  seq = reset_request (req, RTM_GETLINK, NLM_F_REQUEST, sizeof (struct ifinfomsg));
+  req->ifi.ifi_family = AF_UNSPEC;
+
+  ret = append_rtattr (nlh, buffer_size, IFLA_IFNAME, ifname, strlen (ifname) + 1, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0));
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+
+  for (nlh = (struct nlmsghdr *) buffer; NLMSG_OK (nlh, (unsigned int) len); nlh = NLMSG_NEXT (nlh, len))
+    {
+      if (nlh->nlmsg_seq != seq)
+        continue;
+
+      if (nlh->nlmsg_type == NLMSG_ERROR)
+        {
+          struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+          if (err_data->error == 0)
+            continue;
+          return crun_make_error (err, -err_data->error, "netlink error while looking for interface `%s`", ifname);
+        }
+
+      if (nlh->nlmsg_type == RTM_NEWLINK)
+        {
+          struct ifinfomsg *ifi = NLMSG_DATA (nlh);
+          index = ifi->ifi_index;
+          return index;
+        }
+    }
+
+  if (index == 0)
+    return crun_make_error (err, 0, "could not find device `%s`", ifname);
+
+  return index;
+}
+
+static int
+wait_for_ack (int sock, uint32_t seq, char *recv_buffer, size_t recv_buffer_size, libcrun_error_t *err)
+{
+  struct nlmsghdr *nlh_recv = (struct nlmsghdr *) recv_buffer;
+  ssize_t len;
+
+  do
+    {
+      len = TEMP_FAILURE_RETRY (recv (sock, recv_buffer, recv_buffer_size, 0));
+      if (len < 0)
+        return crun_make_error (err, errno, "recv");
+  } while (nlh_recv->nlmsg_seq != seq);
+
+  if (! NLMSG_OK (nlh_recv, len))
+    return crun_make_error (err, 0, "received invalid packet");
+
+  if (nlh_recv->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh_recv);
+      if (err_data->error == 0)
+        return 0;
+      return crun_make_error (err, -err_data->error, "netlink error");
+    }
+
+  return crun_make_error (err, 0, "internal error: received unknown netlink packet type");
+}
+
+static int
+get_ip_addresses (int sock, uint32_t ifindex, char **out_ips, char *buffer, size_t buffer_size, libcrun_error_t *err)
+{
+  struct nl_req *req = (struct nl_req *) buffer;
+  cleanup_free char *ips = NULL;
+  size_t ips_len = 0;
+  struct ip_addr *ip;
+  uint32_t seq;
+  ssize_t len;
+  int ret;
+
+  seq = reset_request (req, RTM_GETADDR, NLM_F_DUMP | NLM_F_REQUEST, sizeof (struct ifaddrmsg));
+  req->ifa.ifa_family = AF_UNSPEC;
+
+  ret = send_request (sock, req, err);
+  if (UNLIKELY (ret < 0))
+    return ret;
+
+  while ((len = TEMP_FAILURE_RETRY (recv (sock, buffer, buffer_size, 0))) > 0)
+    {
+      struct ifaddrmsg *ifa;
+      struct nlmsghdr *nlh;
+      struct rtattr *rta;
+      int rta_len;
+
+      nlh = (struct nlmsghdr *) buffer;
+
+      while (NLMSG_OK (nlh, len))
+        {
+          if (nlh->nlmsg_seq != seq)
+            continue;
+
+          if (nlh->nlmsg_type == NLMSG_DONE)
+            {
+              *out_ips = ips;
+              ips = NULL;
+              return 0;
+            }
+
+          if (nlh->nlmsg_type == NLMSG_ERROR)
+            {
+              struct nlmsgerr *err_data = (struct nlmsgerr *) NLMSG_DATA (nlh);
+              if (err_data->error == 0)
+                continue;
+              return crun_make_error (err, -err_data->error, "netlink error reading ip addresses");
+            }
+
+          ifa = (struct ifaddrmsg *) NLMSG_DATA (nlh);
+          if (ifa->ifa_index != ifindex)
+            {
+              nlh = NLMSG_NEXT (nlh, len);
+              continue;
+            }
+
+          rta_len = IFA_PAYLOAD (nlh);
+
+          for (rta = IFA_RTA (ifa); RTA_OK (rta, rta_len); rta = RTA_NEXT (rta, rta_len))
+            {
+              if (rta->rta_type == IFA_ADDRESS)
+                {
+                  size_t rta_payload_size = RTA_PAYLOAD (rta);
+                  size_t new_size = sizeof (*ip) + rta_payload_size;
+
+                  /* Always append an empty struct.  */
+                  ips = xrealloc (ips, ips_len + new_size + sizeof (struct ip_addr));
+
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ips_len += new_size;
+
+                  ip->len = rta_len;
+                  ip->ifa_family = ifa->ifa_family;
+                  ip->ifa_prefixlen = ifa->ifa_prefixlen;
+                  memcpy (&(ip->data), RTA_DATA (rta), rta_payload_size);
+
+                  /* Mark the end of the array.  */
+                  ip = (struct ip_addr *) (ips + ips_len);
+                  ip->len = 0;
+                }
+            }
+          nlh = NLMSG_NEXT (nlh, len);
+        }
+    }
+  if (UNLIKELY (len < 0))
+    return crun_make_error (err, errno, "recv");
+  return 0;
+}
+
+static int
+configure_ips (int sock, int ifindex, char *buffer, size_t buffer_size, const char *ips, libcrun_error_t *err)
+{
+  struct ip_addr *ip;
+  struct nl_req *req = (struct nl_req *) buffer;
+  int ret;
+
+  if (ips == NULL)
+    return 0;
+
+  for (ip = (struct ip_addr *) ips; ip->len; ip = (struct ip_addr *) (((char *) ip) + sizeof (*ip) + ip->len))
+    {
+      uint32_t seq;
+
+      seq = reset_request (req, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK, sizeof (struct ifaddrmsg));
+
+      memset (&(req->ifa), 0, sizeof (struct ifaddrmsg));
+      req->ifa.ifa_family = ip->ifa_family;
+      req->ifa.ifa_prefixlen = ip->ifa_prefixlen;
+      req->ifa.ifa_index = ifindex;
+      req->ifa.ifa_scope = RT_SCOPE_UNIVERSE;
+      req->ifa.ifa_flags = IFA_F_PERMANENT;
+
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_LOCAL, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
+      ret = append_rtattr (&(req->nlh), buffer_size, IFA_ADDRESS, &ip->data, ip->len, err);
+      if (UNLIKELY (ret < 0))
+        return ret;
path: src/libcrun/net_device.c
user: login: giuseppe
body: wonderful, thanks for confirming it
created_at: 2025-05-21T13:13:02Z
author_association: MEMBER

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-05-16T15:22:59Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide

This PR introduces netlink-based support for moving, renaming, and configuring network interfaces inside container network namespaces, integrates a startup hook to apply user-specified net devices, advertises the netDevices capability in the OCI features JSON, and updates the build to include the new net_device code.

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Implement netlink-based network device management | <ul><li>Introduce net_device.c/.h with routines for moving, renaming, and configuring interfaces via netlink</li><li>Define nl_req and ip_addr structures and helpers (reset_request, append_rtattr, send_request)</li><li>Implement name_to_index, get_ip_addresses, configure_ip_addresses, and namespace helpers</li><li>Add atomic sequence counter and cleanup utilities</li></ul> | `src/libcrun/net_device.c`<br/>`src/libcrun/net_device.h` |
| Integrate network device movement hook into container startup | <ul><li>Include net_device.h in linux.c and implement libcrun_move_network_devices to process OCI spec entries</li><li>Invoke libcrun_move_network_devices in libcrun_container_run_internal before PID sync</li><li>Declare libcrun_move_network_devices in linux.h and extend struct linux_info_s with net_devices in container.h</li></ul> | `src/libcrun/linux.c`<br/>`src/libcrun/linux.h`<br/>`src/libcrun/container.c`<br/>`src/libcrun/container.h` |
| Advertise netDevices capability in OCI features JSON | <ul><li>Add crun_features_add_net_devices in oci_features.c to output netDevices.enabled</li><li>Invoke the new function in crun_features_add_linux_info</li><li>Enable net_devices flag in libcrun_container_get_features</li></ul> | `src/oci_features.c`<br/>`src/libcrun/container.c` |
| Update build system to include net_device sources | <ul><li>Add net_device.c to libcrun_SOURCES</li><li>Add net_device.h to EXTRA_DIST</li></ul> | `Makefile.am` |

### Possibly linked issues

- **#1712**: The PR implements OCI net_devices config for user-specified network devices, directly addressing the issue.

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: giuseppe
created_at: 2025-05-19T12:02:08Z
body: > Do you have a plan to expose this feature somehow in podman? Or do you know if docker will expose this? Of course spec wise it seems straight forward but I mostly wonder about the podman/docker UI. I guess I should create an issue on podman and we can talk about it there.

I am adding it only to be compatible with the spec.  We already have a tool to configure the network, would it be helpful for us to ask the OCI runtime to do the final move?  Would it improve anything?
author_association: MEMBER
user: login: Luap99
created_at: 2025-05-19T12:18:13Z
body: > would it be helpful for us to ask the OCI runtime to do the final move? Would it improve anything?

Not if we fully manage the netns with netavark already, but I know we had a few folks asking to just move an existing interface (https://github.com/containers/podman/issues/25511) and for them this would be the perfect fit I think. Obviously we could add such feature in netavark but if the runtime supports it we might as well use and expose that.

author_association: MEMBER
user: login: packit-as-a-service[bot]
created_at: 2025-05-19T14:41:41Z
body: Ephemeral COPR build failed. @containers/packit-build please check.
author_association: NONE
user: login: giuseppe
created_at: 2025-05-21T08:58:10Z
body: > I don't feel qualified to review c code especially with a bunch of buffer copies from/to netlink types but netlink logic wise this looks good to me.
> 
> Not sure how you handle testing here normally, I would assume it should be quite simple to
> 
> ```
> ip link add test1 type dummy
> ip addr add 10.0.0.0/24 dev test1
> ```
> 
> And then move that into the namespace with crun and run `ip addr` in the container to check if the address was copied properly? Not sure if this is easy to do here.

that would be easy to add, but so far I've tried to avoid adding tests that modify the host.  I'll check if I can do that from a new network namespace used only for the test
author_association: MEMBER
user: login: giuseppe
created_at: 2025-05-21T10:34:29Z
body: tests added
author_association: MEMBER
user: login: giuseppe
created_at: 2025-05-21T13:13:11Z
body: @flouthoc PTAL
author_association: MEMBER
user: login: aojea
created_at: 2025-05-22T06:55:31Z
body: 👏 
author_association: NONE