File util-linux-lib-netlink.patch of Package util-linux
From 7d11ab55ce140cb03ffebb55856c0a766853d83e Mon Sep 17 00:00:00 2001
From: Stanislav Brabec <sbrabec@suse.cz>
Date: Wed, 9 Jul 2025 14:29:10 +0200
Subject: [PATCH 1/2] New netlink library
References: https://github.com/util-linux/util-linux/pull/3649
To support netlink and IP address processing, two new library files were
added:
netlink: Generic netlink message processing code converting netlink
messages to calls of callbacks with a pre-processed data.
netaddrq: A code that gets and maintains linked list of the current
interfaces and assigned IP addresses. It also provides a rating of IP
addresses based on its "quality", i. e. type of address, validity, lifetime
etc.
Signed-off-by: Stanislav Brabec <sbrabec@suse.cz>
---
include/Makemodule.am | 2 +
include/netaddrq.h | 124 +++++++
include/netlink.h | 171 ++++++++++
lib/Makemodule.am | 11 +
lib/meson.build | 2 +
lib/netaddrq.c | 729 ++++++++++++++++++++++++++++++++++++++++++
lib/netlink.c | 465 +++++++++++++++++++++++++++
7 files changed, 1504 insertions(+)
create mode 100644 include/netaddrq.h
create mode 100644 include/netlink.h
create mode 100644 lib/netaddrq.c
create mode 100644 lib/netlink.c
diff --git a/include/Makemodule.am b/include/Makemodule.am
index bdf87e221..4e310f0c4 100644
--- a/include/Makemodule.am
+++ b/include/Makemodule.am
@@ -48,6 +48,8 @@ dist_noinst_HEADERS += \
include/monotonic.h \
include/mount-api-utils.h \
include/namespace.h \
+ include/netaddrq.h \
+ include/netlink.h \
include/nls.h \
include/optutils.h \
include/pager.h \
diff --git a/include/netaddrq.h b/include/netaddrq.h
new file mode 100644
index 000000000..6d5e655f5
--- /dev/null
+++ b/include/netaddrq.h
@@ -0,0 +1,124 @@
+/*
+ * Netlink address quality rating list builder
+ *
+ * Copyright (C) 2025 Stanislav Brabec <sbrabec@suse.com>
+ *
+ * This program is freely distributable.
+ *
+ * This set of netlink callbacks kernel and creates
+ * and/or maintains a linked list of requested type. Using callback fuctions
+ * and custom data, it could be used for arbitraty purpose.
+ *
+ */
+
+#ifndef UTIL_LINUX_NETADDRQ_H
+#define UTIL_LINUX_NETADDRQ_H
+
+#include "netlink.h"
+
+/* Specific return code */
+#define UL_NL_IFACES_MAX 64 /* ADDR: Too many interfaces */
+
+/* Network address "quality". Higher means worse. */
+enum ul_netaddrq_ip_rating {
+ ULNETLINK_RATING_SCOPE_UNIVERSE,
+ ULNETLINK_RATING_SCOPE_SITE,
+ ULNETLINK_RATING_F_TEMPORARY,
+ ULNETLINK_RATING_SCOPE_LINK,
+ ULNETLINK_RATING_BAD,
+ __ULNETLINK_RATING_MAX
+};
+
+/* Data structure in ul_nl_data You can use callback_pre for filtering events
+ * you want to get into the list, callback_post to check the processed data or
+ * use the list after processing
+ */
+struct ul_netaddrq_data {
+ ul_nl_callback callback_pre; /* Function to process ul_netaddrq_data */
+ ul_nl_callback callback_post; /* Function to process ul_netaddrq_data */
+ void *callback_data; /* Arbitrary data for callback */
+ struct list_head ifaces; /* The intefaces list */
+ /* ifaces_change_* has to be changed by userspace when processed. */
+ bool ifaces_change_4; /* Any changes in the IPv4 list? */
+ bool ifaces_change_6; /* Any changes in the IPv6 list? */
+ int nifaces; /* interface count */
+ bool overflow; /* Too many interfaces? */
+};
+
+/* List item for particular interface contains interface specific data and
+ * heads of two lists, one per each address family */
+struct ul_netaddrq_iface {
+ struct list_head entry;
+ uint32_t ifa_index;
+ char *ifname;
+ struct list_head ip_quality_list_4;
+ struct list_head ip_quality_list_6;
+};
+
+/* Macro casting generic ul_nl_data->data_addr to struct ul_netaddrq_data */
+#define UL_NETADDRQ_DATA(nl) ((struct ul_netaddrq_data*)((nl)->data_addr))
+
+/* list_for_each macro for intercaces */
+#define list_for_each_netaddrq_iface(li, nl) list_for_each(li, &(UL_NETADDRQ_DATA(nl)->ifaces))
+
+/* List item for for a particular address contains information for IP quality
+ * evaluation and a copy of generic ul_nl_addr data */
+struct ul_netaddrq_ip {
+ struct list_head entry;
+ enum ul_netaddrq_ip_rating quality;
+ struct ul_nl_addr *addr;
+};
+
+/* Initialize ul_nl_data for use with netlink-addr-quality
+ * callback: Process the data after updating the tree. If NULL, it just
+ * updates the tree and everything has to be processed outside.
+ */
+int ul_netaddrq_init(struct ul_nl_data *nl, ul_nl_callback callback_pre,
+ ul_nl_callback callback_post, void *data);
+
+/* Get best rating value from the ul_netaddrq_ip list
+ * ipq_list: List of IP addresses of a particular interface and family
+ * returns:
+ * best array: best ifa_valid lifetime seen per quality rating
+ * return value: best rating seen
+ * Note: It can be needed to call it twice: once for ip_quality_list_4, once
+ * for ip_quality_list_6.
+ */
+enum ul_netaddrq_ip_rating
+ul_netaddrq_iface_bestaddr(struct list_head *ipq_list,
+ struct ul_netaddrq_ip *(*best)[__ULNETLINK_RATING_MAX]);
+
+/* Get best rating value from the ifaces list (i. e. best address of all
+ * interfaces)
+ * returns:
+ * best_iface: interface where the best address was seen
+ * best array: best ifa_valid lifetime seen per quality rating
+ * return value: best rating seen
+ * Note: It can be needed to call it twice: once for ip_quality_list_4, once
+ * for ip_quality_list_6.
+ */
+enum ul_netaddrq_ip_rating
+ul_netaddrq_bestaddr(struct ul_nl_data *nl,
+ struct ul_netaddrq_iface **best_iface,
+ struct ul_netaddrq_ip *(*best)[__ULNETLINK_RATING_MAX],
+ uint8_t ifa_family);
+
+/* Get best rating value from the ul_netaddrq_ip list as a string
+ * ipq_list: List of IP addresses of a particular interface and family
+ * returns:
+ * return value: The best address as a string
+ * threshold: The best rating ever seen.
+ * best_ifaceq: The best rated interfece ever seen.
+ * Note: It can be needed to call it twice: once for AF_INET, once
+ * for AF_INET6.
+ */
+const char *ul_netaddrq_get_best_ipp(struct ul_nl_data *nl,
+ uint8_t ifa_family,
+ enum ul_netaddrq_ip_rating *threshold,
+ struct ul_netaddrq_iface **best_ifaceq);
+
+/* Find interface by name */
+struct ul_netaddrq_iface *ul_netaddrq_iface_by_name(const struct ul_nl_data *nl,
+ const char *ifname);
+
+#endif /* UTIL_LINUX_NETADDRQ_H */
diff --git a/include/netlink.h b/include/netlink.h
new file mode 100644
index 000000000..3d7c3da04
--- /dev/null
+++ b/include/netlink.h
@@ -0,0 +1,171 @@
+/*
+ * Netlink message processing
+ *
+ * Copyright (C) 2025 Stanislav Brabec <sbrabec@suse.com>
+ *
+ * This program is freely distributable.
+ *
+ * This set of functions processes netlink messages from the kernel socket,
+ * joins message parts into a single structure and calls callback.
+ *
+ * To do something useful, callback for a selected message type has to be
+ * defined. Using callback fuctions and custom data, it could be used for
+ * arbitraty purpose.
+ *
+ * The code is incomplete. More could be implemented as needed by its use
+ * cases.
+ *
+ */
+
+#ifndef UTIL_LINUX_NETLINK_H
+#define UTIL_LINUX_NETLINK_H
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <net/if.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include "list.h"
+
+/* Return codes */
+/* 0 means OK.
+ * Negative return codes indicate fatal errors.
+ */
+
+#define UL_NL_WOULDBLOCK 1 /* no data are ready (for asynchronous mode) */
+#define UL_NL_DONE 2 /* processing reached NLMSG_DONE (for
+ * ul_nl_request_dump() */
+#define UL_NL_RETURN 3 /* callback initiated immediate return; if you use
+ * it, keep in mind that further processing could
+ * reach unprocessed NLMSG_DONE */
+#define UL_NL_SOFT_ERROR 4 /* soft error, indicating a race condition or
+ * message relating to events before program
+ * start); could be optionally ignored */
+
+struct ul_nl_data;
+
+/* The callback of the netlink message header.
+ * Return code: Normally returns UL_NL_OK. In other cases,
+ * ul_nl_process() immediately exits with an error.
+ * Special return codes:
+ * UL_NL_RETURN: stopping further processing that does not mean an error
+ * (example: There was found interface or IP we were waiting for.)
+ * See <linux/netlink.h> nlmsghdr to see, what you can process here.
+ */
+typedef int (*ul_nl_callback)(struct ul_nl_data *nl);
+
+/* Structure for ADDR messages collects information from a single ifaddsmsg
+ * structure and all optional rtattr structures into a single structure
+ * containing all useful data. */
+struct ul_nl_addr {
+/* values from ifaddrmsg or rtattr */
+ uint8_t ifa_family;
+ uint8_t ifa_scope;
+ uint8_t ifa_index;
+ uint32_t ifa_flags;
+ void *ifa_address; /* IFA_ADDRESS */
+ int ifa_address_len; /* size of IFA_ADDRESS data */
+ void *ifa_local; /* IFA_LOCAL */
+ int ifa_local_len; /* size of IFA_LOCAL data */
+ char *ifname; /* interface from ifa_index as string */
+ void *address; /* IFA_LOCAL, if defined, otherwise
+ * IFA_ADDRESS. This is what you want it most
+ * cases. See comment in linux/if_addr.h. */
+ int address_len; /* size of address data */
+ uint32_t ifa_prefered; /* ifa_prefered from IFA_CACHEINFO */
+ uint32_t ifa_valid; /* ifa_valid from IFA_CACHEINFO */
+ /* More can be implemented in future. */
+};
+
+/* Values for rtm_event */
+#define UL_NL_RTM_DEL false /* processing RTM_DEL_* */
+#define UL_NL_RTM_NEW true /* processing RTM_NEW_* */
+/* Checks for rtm_event */
+#define UL_NL_IS_RTM_DEL(nl) (!(nl->rtm_event)) /* is it RTM_DEL_*? */
+#define UL_NL_IS_RTM_NEW(nl) (nl->rtm_event) /* is it RTM_NEW_*? */
+
+struct ul_nl_data {
+ /* "static" part of the structure, filled once and kept */
+ ul_nl_callback callback_addr; /* Function to process ul_nl_addr */
+ void *data_addr; /* Arbitrary data of callback_addr */
+ int fd; /* netlink socket FD, may be used externally
+ * for select() */
+
+ /* volatile part of the structure, filled by the current message */
+ bool rtm_event; /* UL_NL_RTM_DEL or UL_NL_RTM_NEW */
+ bool dumping; /* Dump in progress */
+
+ /* volatile part of the structure that depends on message typ */
+ union {
+ /* ADDR */
+ struct ul_nl_addr addr;
+ /* More can be implemented in future (LINK, ROUTE etc.). */
+ };
+};
+
+/* Initialize ul_nl_data structure */
+void ul_nl_init(struct ul_nl_data *nl);
+
+/* Open a netlink connection.
+ * nl_groups: Applies for monitoring. In case of ul_nl_request_dump(),
+ * use its argument to select one.
+ *
+ * Close and open vs. initial open with parameters?
+ *
+ * If we use single open with parameters, we can get mixed output due to race
+ * window between opening the socket and sending dump request.
+ *
+ * If we use close/open, we get a race window that could contain unprocessed
+ * events.
+ */
+int ul_nl_open(struct ul_nl_data *nl, uint32_t nl_groups);
+
+/* Close a netlink connection. */
+int ul_nl_close(struct ul_nl_data *nl);
+
+/* Synchronously sends dump request of a selected nlmsg_type. It does not
+ * perform any further actions. The result is returned through the callback.
+ *
+ * Under normal conditions, use ul_nl_process(nl, false, true); for processing
+ * the reply
+ */
+int ul_nl_request_dump(struct ul_nl_data *nl, uint16_t nlmsg_type);
+
+/* Values for async */
+#define UL_NL_SYNC false /* synchronous mode */
+#define UL_NL_ASYNC true /* asynchronous mode */
+#define UL_NL_ONESHOT false /* return after processing message */
+#define UL_NL_LOOP true /* wait for NLMSG_DONE */
+/* Process netlink messages.
+ * async: If true, return UL_NL_WOULDBLOCK immediately if there is no data
+ * ready. If false, wait for a message.
+ * loop: If true, run in a loop until NLMSG_DONE is received. Returns after
+ * finishing a reply from ul_nl_request_dump(), otherwise it acts as an
+ * infinite loop. If false, it returns after processing one message.
+ */
+int ul_nl_process(struct ul_nl_data *nl, bool async, bool loop);
+
+/* Duplicate ul_nl_addr structure to a newly allocated memory */
+struct ul_nl_addr *ul_nl_addr_dup (struct ul_nl_addr *addr);
+
+/* Deallocate ul_nl_addr structure */
+void ul_nl_addr_free (struct ul_nl_addr *addr);
+
+/* Convert ul_nl_addr to string.
+ addr: ul_nl_addr structure
+ id: Which of 3 possible addresses should be converted?
+ * Returns static string, valid to next call.
+ */
+#define UL_NL_ADDR_ADDRESS offsetof(struct ul_nl_addr, address)
+#define UL_NL_ADDR_IFA_ADDRESS offsetof(struct ul_nl_addr, ifa_address)
+#define UL_NL_ADDR_IFA_LOCAL offsetof(struct ul_nl_addr, ifa_local)
+/* Warning: id must be one of above. No checks are performed */
+const char *ul_nl_addr_ntop (const struct ul_nl_addr *addr, int addrid);
+#define ul_nl_addr_ntop_address(addr)\
+ ul_nl_addr_ntop(addr, UL_NL_ADDR_ADDRESS)
+#define ul_nl_addr_ntop_ifa_address(addr)\
+ ul_nl_addr_ntop(addr, UL_NL_ADDR_IFA_ADDRESS)
+ #define ul_nl_addr_ntop_ifa_local(addr)\
+ ul_nl_addr_ntop(addr, UL_NL_ADDR_IFA_LOCAL)
+
+#endif /* UTIL_LINUX_NETLINK_H */
diff --git a/lib/Makemodule.am b/lib/Makemodule.am
index bf24b6bee..4af6589d1 100644
--- a/lib/Makemodule.am
+++ b/lib/Makemodule.am
@@ -1,4 +1,5 @@
#
+
# Use only LGPL or Public domain (preferred) code in libcommon, otherwise add
# your lib/file.c directly to the _SOURCES= of the target binary.
#
@@ -30,6 +31,8 @@ libcommon_la_SOURCES = \
lib/mbsalign.c \
lib/mbsedit.c\
lib/md5.c \
+ lib/netaddrq.c \
+ lib/netlink.c \
lib/pwdutils.c \
lib/randutils.c \
lib/sha1.c \
@@ -91,6 +94,8 @@ check_PROGRAMS += \
test_ismounted \
test_pwdutils \
test_mangle \
+ test_netlink \
+ test_netaddrq \
test_randutils \
test_remove_env \
test_strutils \
@@ -138,6 +143,12 @@ test_ismounted_LDADD = libcommon.la $(LDADD)
test_mangle_SOURCES = lib/mangle.c
test_mangle_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_MANGLE
+test_netlink_SOURCES = lib/netlink.c
+test_netlink_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_NETLINK
+
+test_netaddrq_SOURCES = lib/netaddrq.c lib/netlink.c
+test_netaddrq_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_NETADDRQ
+
test_strutils_SOURCES = lib/strutils.c
test_strutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_STRUTILS
diff --git a/lib/meson.build b/lib/meson.build
index 25febbc19..8734108a3 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -17,6 +17,8 @@ lib_common_sources = '''
mbsalign.c
mbsedit.c
md5.c
+ netaddrq.c
+ netlink.c
procfs.c
pwdutils.c
randutils.c
diff --git a/lib/netaddrq.c b/lib/netaddrq.c
new file mode 100644
index 000000000..11730cf07
--- /dev/null
+++ b/lib/netaddrq.c
@@ -0,0 +1,729 @@
+/*
+ * Netlink address quality rating list builder
+ *
+ * Copyright (C) 2025 Stanislav Brabec <sbrabec@suse.com>
+ *
+ * This program is freely distributable.
+ *
+ * This set of netlink callbacks kernel and creates
+ * and/or maintains a linked list of requested type. Using callback fuctions
+ * and custom data, it could be used for arbitraty purpose.
+ *
+ */
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_addr.h>
+#include "netaddrq.h"
+#include "list.h"
+#include "debug.h"
+
+/* Maximal number of interfaces. The algorithm has a quadratic complexity,
+ * don't overflood it. */
+const int max_ifaces = 12;
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+#define ULNETADDRQ_DEBUG_HELP (1 << 0)
+#define ULNETADDRQ_DEBUG_INIT (1 << 1)
+#define ULNETADDRQ_DEBUG_ADDRQ (1 << 2)
+#define ULNETADDRQ_DEBUG_LIST (1 << 3)
+#define ULNETADDRQ_DEBUG_BEST (1 << 4)
+
+#define ULNETADDRQ_DEBUG_ALL 0x1F
+
+static UL_DEBUG_DEFINE_MASK(netaddrq);
+UL_DEBUG_DEFINE_MASKNAMES(netaddrq) =
+{
+ { "all", ULNETADDRQ_DEBUG_ALL, "complete adddress processing" },
+ { "help", ULNETADDRQ_DEBUG_HELP, "this help" },
+ { "addrq", ULNETADDRQ_DEBUG_ADDRQ, "address rating" },
+ { "list", ULNETADDRQ_DEBUG_LIST, "list processing" },
+ { "best", ULNETADDRQ_DEBUG_BEST, "searching best address" },
+
+ { NULL, 0 }
+};
+
+#define DBG(m, x) __UL_DBG(netaddrq, ULNETADDRQ_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(netaddrq, ULNETADDRQ_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(netaddrq)
+#include "debugobj.h"
+
+static void netaddrq_init_debug(void)
+{
+ if (netaddrq_debug_mask)
+ return;
+
+ __UL_INIT_DEBUG_FROM_ENV(netaddrq, ULNETADDRQ_DEBUG_, 0,
+ ULNETADDRQ_DEBUG);
+
+ ON_DBG(HELP, ul_debug_print_masks("ULNETADDRQ_DEBUG",
+ UL_DEBUG_MASKNAMES(netaddrq)));
+}
+
+static inline enum ul_netaddrq_ip_rating
+evaluate_ip_quality(struct ul_nl_addr *addr) {
+ enum ul_netaddrq_ip_rating quality;
+
+ switch (addr->ifa_scope) {
+ case RT_SCOPE_UNIVERSE:
+ quality = ULNETLINK_RATING_SCOPE_UNIVERSE;
+ break;
+ case RT_SCOPE_LINK:
+ quality = ULNETLINK_RATING_SCOPE_LINK;
+ break;
+ case RT_SCOPE_SITE:
+ quality = ULNETLINK_RATING_SCOPE_SITE;
+ break;
+ default:
+ quality = ULNETLINK_RATING_BAD;
+ break;
+ }
+ if (addr->ifa_flags & IFA_F_TEMPORARY) {
+ if (quality <= ULNETLINK_RATING_F_TEMPORARY)
+ quality = ULNETLINK_RATING_F_TEMPORARY;
+ }
+ return quality;
+}
+
+#define DBG_CASE(x) case x: str = #x; break
+#define DBG_CASE_DEF8(x) default: snprintf(strx+2, 3, "%02hhx", x); str = strx; break
+static char *ip_rating(enum ul_netaddrq_ip_rating q)
+{
+ char *str;
+ static char strx[5] = "0x";
+ switch (q) {
+ DBG_CASE(ULNETLINK_RATING_SCOPE_UNIVERSE);
+ DBG_CASE(ULNETLINK_RATING_SCOPE_SITE);
+ DBG_CASE(ULNETLINK_RATING_F_TEMPORARY);
+ DBG_CASE(ULNETLINK_RATING_SCOPE_LINK);
+ DBG_CASE(ULNETLINK_RATING_BAD);
+ DBG_CASE_DEF8(q);
+ }
+ return str;
+}
+
+/* Netlink callback evaluating the address quality and building the list of
+ * interface lists */
+static int callback_addrq(struct ul_nl_data *nl) {
+ struct ul_netaddrq_data *addrq = UL_NETADDRQ_DATA(nl);
+ struct list_head *li, *ipq_list;
+ struct ul_netaddrq_iface *ifaceq = NULL;
+ struct ul_netaddrq_ip *ipq = NULL;
+ int rc;
+ bool *ifaces_change;
+
+ DBG(LIST, ul_debugobj(addrq, "callback_addrq() for %s on %s",
+ ul_nl_addr_ntop_address(&(nl->addr)),
+ nl->addr.ifname));
+ if (addrq->callback_pre)
+ {
+ DBG(LIST, ul_debugobj(addrq, "callback_pre"));
+ if ((rc = (*(addrq->callback_pre))(nl)))
+ DBG(LIST, ul_debugobj(nl, "callback_pre rc != 0"));
+ }
+
+ /* Search for interface in ifaces */
+ addrq->nifaces = 0;
+
+ list_for_each(li, &(addrq->ifaces)) {
+ struct ul_netaddrq_iface *ifaceqq;
+ ifaceqq = list_entry(li, struct ul_netaddrq_iface, entry);
+ if (ifaceqq->ifa_index == nl->addr.ifa_index) {
+ ifaceq = ifaceqq;
+ DBG(LIST, ul_debugobj(ifaceq,
+ "%s found in addrq",
+ nl->addr.ifname));
+ break;
+ }
+ addrq->nifaces++;
+ }
+
+ if (ifaceq == NULL) {
+ if (nl->rtm_event) {
+ if (addrq->nifaces >= max_ifaces) {
+ DBG(LIST, ul_debugobj(addrq,
+ "too many interfaces"));
+ addrq->overflow = true;
+ return UL_NL_IFACES_MAX;
+ }
+ DBG(LIST, ul_debugobj(addrq,
+ "new ifa_index in addrq"));
+ if (!(ifaceq = malloc(sizeof(struct ul_netaddrq_iface))))
+ {
+ DBG(LIST, ul_debugobj(addrq,
+ "malloc() 1 failed"));
+ return -1;
+ }
+ INIT_LIST_HEAD(&(ifaceq->ip_quality_list_4));
+ INIT_LIST_HEAD(&(ifaceq->ip_quality_list_6));
+ ifaceq->ifa_index = nl->addr.ifa_index;
+ if (!(ifaceq->ifname = strdup(nl->addr.ifname)))
+ {
+ DBG(LIST, ul_debugobj(addrq,
+ "malloc() 2 failed"));
+ free(ifaceq);
+ return -1;
+ }
+ list_add_tail(&(ifaceq->entry), &(addrq->ifaces));
+ DBG(LIST, ul_debugobj(ifaceq,
+ "new interface"));
+ } else {
+ /* Should never happen. */
+ DBG(LIST, ul_debugobj(ifaceq,
+ "interface not found"));
+ return UL_NL_SOFT_ERROR;
+ }
+ }
+ if (nl->addr.ifa_family == AF_INET) {
+ ipq_list = &(ifaceq->ip_quality_list_4);
+ ifaces_change = &(addrq->ifaces_change_4);
+ } else {
+ /* if (nl->addr.ifa_family == AF_INET6) */
+ ipq_list = &(ifaceq->ip_quality_list_6);
+ ifaces_change = &(addrq->ifaces_change_6);
+ }
+
+ list_for_each(li, ipq_list) {
+ ipq = list_entry(li, struct ul_netaddrq_ip, entry);
+ if (ipq->addr->address_len == nl->addr.address_len)
+ if (memcmp(ipq->addr->address, nl->addr.address,
+ nl->addr.address_len))
+ break;
+ }
+ if (ipq == NULL) {
+ DBG(LIST, ul_debugobj(ipq_list,
+ "address not found in the list"));
+ }
+
+ /* From now on, rc is return code */
+ rc = 0;
+ if (UL_NL_IS_RTM_NEW(nl)) {
+ struct ul_nl_addr *addr;
+
+ addr = ul_nl_addr_dup(&(nl->addr));
+ if (!addr) {
+ DBG(LIST, ul_debugobj(addrq,
+ "ul_nl_addr_dup() failed"));
+ rc = -1;
+ goto error;
+ }
+ if (ipq == NULL) {
+ if (!(ipq = malloc(sizeof(struct ul_netaddrq_ip))))
+ {
+ DBG(LIST, ul_debugobj(addrq,
+ "malloc() 3 failed"));
+ rc = -1;
+ ul_nl_addr_free(addr);
+ goto error;
+ }
+ ipq->addr = addr;
+ list_add_tail(&(ipq->entry), ipq_list);
+ DBG(LIST, ul_debugobj(ipq, "new address"));
+ *ifaces_change = true;
+ } else {
+ DBG(LIST, ul_debugobj(addrq, "updating address data"));
+ ul_nl_addr_free(ipq->addr);
+ ipq->addr = addr;
+ }
+ ipq->quality = evaluate_ip_quality(addr);
+ DBG(ADDRQ,
+ ul_debugobj(addrq, "%s rating: %s",
+ ul_nl_addr_ntop_address(&(nl->addr)),
+ ip_rating(ipq->quality)));
+ } else {
+ /* UL_NL_RTM_DEL */
+ if (ipq == NULL)
+ {
+ /* Should not happen. */
+ DBG(LIST, ul_debugobj(nl,
+ "UL_NL_RTM_DEL: unknown address"));
+ return UL_NL_SOFT_ERROR;
+ }
+ /* Delist the address */
+ DBG(LIST, ul_debugobj(ipq, "removing address"));
+ *ifaces_change = true;
+ list_del(&(ipq->entry));
+ ul_nl_addr_free(ipq->addr);
+ free(ipq);
+ error:
+ if (list_empty(&(ifaceq->ip_quality_list_4)) &&
+ list_empty(&(ifaceq->ip_quality_list_6))) {
+ DBG(LIST,
+ ul_debugobj(ifaceq,
+ "deleted last address, removing interface"));
+ list_del(&(ifaceq->entry));
+ addrq->nifaces--;
+ free(ifaceq->ifname);
+ free(ifaceq);
+ }
+ }
+ if (!rc && addrq->callback_post)
+ {
+ DBG(LIST, ul_debugobj(addrq, "callback_post"));
+ if ((rc = (*(addrq->callback_post))(nl)))
+ DBG(LIST, ul_debugobj(nl, "callback_post rc != 0"));
+ }
+ return rc;
+}
+
+/* Initialize ul_nl_data for use with netlink-addr-quality */
+int ul_netaddrq_init(struct ul_nl_data *nl, ul_nl_callback callback_pre,
+ ul_nl_callback callback_post, void *data)
+{
+ struct ul_netaddrq_data *addrq;
+
+ netaddrq_init_debug();
+ if (!(nl->data_addr = malloc(sizeof(struct ul_netaddrq_data))))
+ return -1;
+ nl->callback_addr = callback_addrq;
+ addrq = UL_NETADDRQ_DATA(nl);
+ addrq->callback_pre = callback_pre;
+ addrq->callback_post = callback_post;
+ addrq->callback_data = data;
+ addrq->nifaces = 0;
+ addrq->overflow = false;
+ INIT_LIST_HEAD(&(addrq->ifaces));
+ addrq->ifaces_change_4 = false;
+ addrq->ifaces_change_6 = false;
+ DBG(LIST, ul_debugobj(addrq, "callback initialized"));
+ return 0;
+}
+
+enum ul_netaddrq_ip_rating
+ul_netaddrq_iface_bestaddr(struct list_head *ipq_list,
+ struct ul_netaddrq_ip *(*best)[__ULNETLINK_RATING_MAX])
+{
+ struct list_head *li;
+ struct ul_netaddrq_ip *ipq;
+ enum ul_netaddrq_ip_rating threshold;
+
+ threshold = ULNETLINK_RATING_BAD;
+ list_for_each(li, ipq_list)
+ {
+ ipq = list_entry(li, struct ul_netaddrq_ip, entry);
+
+ if (!(*best)[ipq->quality] ||
+ ipq->addr->ifa_valid >
+ (*best)[ipq->quality]->addr->ifa_valid)
+ {
+ DBG(BEST,
+ ul_debugobj((*best), "%s -> best[%s]",
+ ul_nl_addr_ntop_address(ipq->addr),
+ ip_rating(ipq->quality)));
+ (*best)[ipq->quality] = ipq;
+ }
+
+ if (ipq->quality < threshold)
+ {
+ threshold = ipq->quality;
+ DBG(BEST,
+ ul_debug("threshold %s", ip_rating(threshold)));
+
+ }
+ }
+ return threshold;
+}
+
+enum ul_netaddrq_ip_rating
+ul_netaddrq_bestaddr(struct ul_nl_data *nl,
+ struct ul_netaddrq_iface **best_ifaceq,
+ struct ul_netaddrq_ip *(*best)[__ULNETLINK_RATING_MAX],
+ uint8_t ifa_family)
+{
+ struct ul_netaddrq_data *addrq = UL_NETADDRQ_DATA(nl);
+ struct list_head *li;
+ struct ul_netaddrq_iface *ifaceq;
+ size_t ipqo;
+ enum ul_netaddrq_ip_rating threshold;
+
+ if (ifa_family == AF_INET) {
+ ipqo = offsetof(struct ul_netaddrq_iface, ip_quality_list_4);
+ } else {
+ /* if (ifa_family == AF_INET6) */
+ ipqo = offsetof(struct ul_netaddrq_iface, ip_quality_list_6);
+ }
+
+ threshold = ULNETLINK_RATING_BAD;
+ list_for_each(li, &(addrq->ifaces))
+ {
+ struct list_head *ipq_list;
+ enum ul_netaddrq_ip_rating t;
+
+ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry);
+
+ ipq_list = (struct list_head*)((char*)ifaceq + ipqo);
+
+ t = ul_netaddrq_iface_bestaddr(ipq_list, best);
+ if (t < threshold)
+ {
+ DBG(BEST,
+ ul_debugobj(*best, "best iface %s, threshold %hhd",
+ ifaceq->ifname, t));
+ *best_ifaceq = ifaceq;
+ threshold = t;
+ }
+ }
+ return threshold;
+}
+
+const char *ul_netaddrq_get_best_ipp(struct ul_nl_data *nl,
+ uint8_t ifa_family,
+ enum ul_netaddrq_ip_rating *threshold,
+ struct ul_netaddrq_iface **best_ifaceq)
+{
+ struct ul_netaddrq_ip *best[__ULNETLINK_RATING_MAX];
+
+ memset(best, 0, sizeof(best));
+ *threshold = ul_netaddrq_bestaddr(nl, best_ifaceq, &best, ifa_family);
+ if (best[*threshold])
+ return ul_nl_addr_ntop_address(best[*threshold]->addr);
+ else
+ return NULL;
+}
+
+struct ul_netaddrq_iface *ul_netaddrq_iface_by_name(const struct ul_nl_data *nl,
+ const char *ifname)
+{
+ struct list_head *li;
+ struct ul_netaddrq_iface *ifaceq;
+
+ list_for_each_netaddrq_iface(li, nl)
+ {
+ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry);
+
+ if (!strcmp(ifaceq->ifname, ifname))
+ return ifaceq;
+ }
+ return NULL;
+}
+
+#ifdef TEST_PROGRAM_NETADDRQ
+/* This test program shows several possibilities for cherry-picking of IP
+ * addresses based on its rating. But there are many more possibilities in the
+ * criteria selection. ADDRQ_MODE_GOOD is the most smart one. */
+enum addrq_print_mode {
+ ADDRQ_MODE_BESTOFALL, /* Best address of all interfaces */
+ ADDRQ_MODE_BEST, /* Best address per interface */
+ ADDRQ_MODE_GOOD, /* All global or site addresses, if none, the
+ * longest living temporary, if none, link */
+ ADDRQ_MODE_ALL /* All available addresses */
+};
+
+/* In our example addrq->callback_data is a simple FILE *. In more complex
+ * programs it could be a pointer to an arbitrary struct */
+#define netout (FILE *)(UL_NETADDRQ_DATA(nl)->callback_data)
+
+/* This example uses separate threshold for IPv4 and IPv6, so the best IPv4 and
+ * best IPv6 addresses are printed. */
+static void dump_iface_best(struct ul_nl_data *nl,
+ struct ul_netaddrq_iface *ifaceq)
+{
+ struct ul_netaddrq_ip *best[__ULNETLINK_RATING_MAX];
+ enum ul_netaddrq_ip_rating threshold;
+ bool first = true;
+
+ memset(best, 0, sizeof(best));
+ threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_4),
+ &best);
+ if (best[threshold])
+ {
+ fprintf(netout, "%s IPv4: %s", (first ? "best address" : " "),
+ ul_nl_addr_ntop_address(best[threshold]->addr));
+ first = false;
+ }
+ memset(best, 0, sizeof(best));
+ threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_6),
+ &best);
+ if (best[threshold])
+ {
+ fprintf(netout, "%s IPv6: %s", (first ? "best address" : " "),
+ ul_nl_addr_ntop_address(best[threshold]->addr));
+ first = false;
+ }
+ if (!first)
+ fprintf(netout, " on interface %s\n",
+ ifaceq->ifname);
+}
+
+/* This example uses common threshold for IPv4 and IPv6, so e. g. worse rated
+ * IPv6 are completely ignored. */
+static void dump_iface_good(struct ul_nl_data *nl,
+ struct ul_netaddrq_iface *ifaceq)
+{
+ struct ul_netaddrq_ip *best4[__ULNETLINK_RATING_MAX];
+ struct ul_netaddrq_ip *best6[__ULNETLINK_RATING_MAX];
+ struct list_head *li;
+ enum ul_netaddrq_ip_rating threshold = __ULNETLINK_RATING_MAX - 1;
+ enum ul_netaddrq_ip_rating fthreshold; /* per family threshold */
+ bool first = true;
+
+ memset(best4, 0, sizeof(best4));
+ threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_4),
+ &best4);
+ memset(best6, 0, sizeof(best6));
+ fthreshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_6),
+ &best6);
+ if (fthreshold < threshold)
+ threshold = fthreshold;
+
+ list_for_each(li, &(ifaceq->ip_quality_list_4))
+ {
+ struct ul_netaddrq_ip *ipq;
+
+ ipq = list_entry(li, struct ul_netaddrq_ip, entry);
+ if (threshold <= ULNETLINK_RATING_SCOPE_LINK &&
+ ( ipq->quality <= threshold ||
+ /* Consider site addresses equally good as global */
+ ipq->quality == ULNETLINK_RATING_SCOPE_SITE) &&
+ best4[threshold])
+ {
+ if (first)
+ {
+ fprintf(netout, "%s: ", ifaceq->ifname);
+ first = false;
+ }
+ else
+ fprintf(netout, " ");
+ /* Write only the longest living temporary address */
+ if (threshold == ULNETLINK_RATING_F_TEMPORARY)
+ {
+ fputs(ul_nl_addr_ntop_address(best4[ULNETLINK_RATING_F_TEMPORARY]->addr),
+ netout);
+ goto temp_cont4;
+ }
+ else
+ fputs(ul_nl_addr_ntop_address(ipq->addr),
+ netout);
+ }
+ temp_cont4:;
+ }
+
+ list_for_each(li, &(ifaceq->ip_quality_list_6))
+ {
+ struct ul_netaddrq_ip *ipq;
+
+ ipq = list_entry(li, struct ul_netaddrq_ip, entry);
+ if (threshold <= ULNETLINK_RATING_SCOPE_LINK &&
+ ( ipq->quality <= threshold ||
+ /* Consider site addresses equally good as global */
+ ipq->quality == ULNETLINK_RATING_SCOPE_SITE) &&
+ best6[threshold])
+ {
+ if (first)
+ {
+ fprintf(netout, "%s: ", ifaceq->ifname);
+ first = false;
+ }
+ else
+ fprintf(netout, " ");
+ /* Write only the longest living temporary address */
+ if (threshold == ULNETLINK_RATING_F_TEMPORARY)
+ {
+ fputs(ul_nl_addr_ntop_address(best6[ULNETLINK_RATING_F_TEMPORARY]->addr),
+ netout);
+ goto temp_cont6;
+ }
+ else
+ fputs(ul_nl_addr_ntop_address(ipq->addr),
+ netout);
+ }
+ temp_cont6:;
+ }
+ if (!first)
+ fputs("\n", netout);
+}
+
+static void dump_iface_all(struct ul_nl_data *nl,
+ struct ul_netaddrq_iface *ifaceq)
+{
+ struct list_head *li;
+ struct ul_netaddrq_ip *ipq;
+ bool first = true;
+
+ list_for_each(li, &(ifaceq->ip_quality_list_4))
+ {
+ ipq = list_entry(li, struct ul_netaddrq_ip, entry);
+ if (first)
+ {
+ fprintf(netout, "%s: ", ifaceq->ifname);
+ first = false;
+ }
+ else
+ fprintf(netout, " ");
+ fputs(ul_nl_addr_ntop_address(ipq->addr), netout);
+ }
+ list_for_each(li, &(ifaceq->ip_quality_list_6))
+ {
+ ipq = list_entry(li, struct ul_netaddrq_ip, entry);
+ if (first)
+ {
+ fprintf(netout, "%s: ", ifaceq->ifname);
+ first = false;
+ }
+ else
+ fprintf(netout, " ");
+ fputs(ul_nl_addr_ntop_address(ipq->addr), netout);
+ }
+ if (!first)
+ fputs("\n", netout);
+}
+
+static void dump_addrq(struct ul_nl_data *nl, enum addrq_print_mode c) {
+ struct list_head *li;
+ struct ul_netaddrq_iface *ifaceq;
+ enum ul_netaddrq_ip_rating threshold;
+
+ switch(c)
+ {
+ case ADDRQ_MODE_BESTOFALL:
+ {
+ struct ul_netaddrq_iface *best_ifaceq;
+ const char *best_ipp;
+
+ best_ipp = ul_netaddrq_get_best_ipp(nl, AF_INET,
+ &threshold, &best_ifaceq);
+ if (best_ipp)
+ fprintf(netout, "best IPv4 address: %s on %s\n",
+ best_ipp, best_ifaceq->ifname);
+ best_ipp = ul_netaddrq_get_best_ipp(nl, AF_INET6,
+ &threshold, &best_ifaceq);
+ if (best_ipp)
+ fprintf(netout, "best IPv6 address: %s on %s\n",
+ best_ipp, best_ifaceq->ifname);
+ }
+ break;
+ case ADDRQ_MODE_BEST:
+ {
+ list_for_each_netaddrq_iface(li, nl)
+ {
+ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry);
+
+ dump_iface_best(nl, ifaceq);
+ }
+ }
+ break;
+ case ADDRQ_MODE_GOOD:
+ {
+ list_for_each_netaddrq_iface(li, nl)
+ {
+ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry);
+
+ dump_iface_good(nl, ifaceq);
+ }
+ }
+ break;
+ case ADDRQ_MODE_ALL:
+ list_for_each_netaddrq_iface(li, nl)
+ {
+ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry);
+
+ dump_iface_all(nl,ifaceq);
+ }
+ break;
+ }
+}
+
+static int callback_post(struct ul_nl_data *nl)
+{
+ /* If not processing dump, process the change immediatelly by the
+ * callback. */
+ if (!nl->dumping)
+ {
+ /* If there is no change in the list, do nothing */
+ if (!(UL_NETADDRQ_DATA(nl)->ifaces_change_4 ||
+ UL_NETADDRQ_DATA(nl)->ifaces_change_6))
+ {
+ fputs("\n\nNo changes in the address list.\n", netout);
+ return 0;
+ }
+ UL_NETADDRQ_DATA(nl)->ifaces_change_4 = false;
+ UL_NETADDRQ_DATA(nl)->ifaces_change_6 = false;
+ fputs("\n\nNetwork change detected:\n", netout);
+ fputs("\nbest address:\n", netout);
+ dump_addrq(nl, ADDRQ_MODE_BESTOFALL);
+
+ fputs("\nbest addresses dump:\n", netout);
+ dump_addrq(nl, ADDRQ_MODE_BEST);
+
+ fputs("\ngood addresses dump:\n", netout);
+ dump_addrq(nl, ADDRQ_MODE_GOOD);
+
+ fputs("\nall addresses dump:\n", netout);
+ dump_addrq(nl, ADDRQ_MODE_ALL);
+ }
+ return 0;
+}
+
+int main(int argc __attribute__((__unused__)), char *argv[] __attribute__((__unused__)))
+{
+ int rc = 1;
+ int ulrc;
+ struct ul_nl_data nl;
+ FILE *out = stdout;
+ struct ul_netaddrq_iface *ifaceq;
+ const char *ifname = "eth0";
+
+ /* Prepare netlink. */
+ ul_nl_init(&nl);
+ if ((ul_netaddrq_init(&nl, NULL, callback_post, (void *)out)))
+ return -1;
+
+ /* Dump addresses */
+ if (ul_nl_open(&nl,
+ RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR))
+ return -1;
+ if (ul_nl_request_dump(&nl, RTM_GETADDR))
+ goto error;
+ if (ul_nl_process(&nl, UL_NL_SYNC, UL_NL_LOOP) != UL_NL_DONE)
+ goto error;
+ fputs("RTM_GETADDR dump finished.", out);
+
+ /* Example of several types of dump */
+ fputs("\nbest address:\n", out);
+ dump_addrq(&nl, ADDRQ_MODE_BESTOFALL);
+
+ fputs("\nbest addresses dump:\n", out);
+ dump_addrq(&nl, ADDRQ_MODE_BEST);
+
+ fputs("\ngood addresses dump:\n", out);
+ dump_addrq(&nl, ADDRQ_MODE_GOOD);
+
+ fputs("\nall addresses dump:\n", out);
+ dump_addrq(&nl, ADDRQ_MODE_ALL);
+
+ fputs("\naddresses for interface ", out);
+ if ((ifaceq = ul_netaddrq_iface_by_name(&nl, ifname)))
+ dump_iface_all(&nl, ifaceq);
+ else
+ fprintf(out, "%s not found.", ifname);
+
+ /* Monitor further changes */
+ fputs("\nGoing to monitor mode.\n", out);
+
+ /* In this example UL_NL_RETURN never appears, as callback does
+ * not use it. */
+
+ /* There are two different ways to create the loop:
+ *
+ * 1) Use UL_NL_LOOP and process the result in addrq->callback_post
+ * (optionally excluding events with nl->dumping set. (We can
+ * process dump output in the callback as well, but in many cases,
+ * single run after finishing the dump is a better solution than
+ * processing it after each message.
+ *
+ * 2) Make a loop, use UL_NL_ONESHOT, keep addrq->callback_post empty
+ * and process the result in the loop.
+ */
+ ulrc = ul_nl_process(&nl, UL_NL_SYNC, UL_NL_LOOP);
+ if (!ulrc || ulrc == UL_NL_RETURN)
+ rc = 0;
+error:
+ if ((ul_nl_close(&nl)))
+ rc = 1;
+ return rc;
+}
+#endif /* TEST_PROGRAM_NETADDRQ */
diff --git a/lib/netlink.c b/lib/netlink.c
new file mode 100644
index 000000000..c57be4ce0
--- /dev/null
+++ b/lib/netlink.c
@@ -0,0 +1,465 @@
+/*
+ * Netlink message processing
+ *
+ * Copyright (C) 2025 Stanislav Brabec <sbrabec@suse.com>
+ *
+ * This program is freely distributable.
+ *
+ * This set of functions processes netlink messages from kernel and creates
+ * and/or maintains a linked list of requested type. Using callback fuctions
+ * and custom data, it could be used for arbitraty purpose.
+ *
+ * The code here just processes the netlink stream. To do something useful,
+ * callback for a selected message type has to be defined.
+ */
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include "netlink.h"
+#include "debug.h"
+#include "nls.h"
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+#define ULNETLINK_DEBUG_HELP (1 << 0)
+#define ULNETLINK_DEBUG_INIT (1 << 1)
+#define ULNETLINK_DEBUG_NLMSG (1 << 2)
+#define ULNETLINK_DEBUG_ADDR (1 << 3)
+
+#define ULNETLINK_DEBUG_ALL 0x0F
+
+static UL_DEBUG_DEFINE_MASK(netlink);
+UL_DEBUG_DEFINE_MASKNAMES(netlink) =
+{
+ { "all", ULNETLINK_DEBUG_ALL, "complete netlink debugging" },
+ { "help", ULNETLINK_DEBUG_HELP, "this help" },
+ { "nlmsg", ULNETLINK_DEBUG_NLMSG, "netlink message debugging" },
+ { "addr", ULNETLINK_DEBUG_ADDR, "netlink address processing" },
+
+ { NULL, 0 }
+};
+
+#define DBG(m, x) __UL_DBG(netlink, ULNETLINK_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(netlink, ULNETLINK_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(netlink)
+#include "debugobj.h"
+
+static void netlink_init_debug(void)
+{
+ if (netlink_debug_mask)
+ return;
+
+ __UL_INIT_DEBUG_FROM_ENV(netlink, ULNETLINK_DEBUG_, 0, ULNETLINK_DEBUG);
+
+ ON_DBG(HELP, ul_debug_print_masks("ULNETLINK_DEBUG",
+ UL_DEBUG_MASKNAMES(netlink)));
+}
+
+void ul_nl_init(struct ul_nl_data *nl) {
+ netlink_init_debug();
+ memset(nl, 0, sizeof(struct ul_nl_data));
+}
+
+int ul_nl_request_dump(struct ul_nl_data *nl, uint16_t nlmsg_type) {
+ struct {
+ struct nlmsghdr nh;
+ struct rtgenmsg g;
+ } req;
+
+ memset(&req, 0, sizeof(req));
+ req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.g));
+ req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ req.nh.nlmsg_type = nlmsg_type;
+ req.g.rtgen_family = AF_NETLINK;
+ nl->dumping = true;
+ DBG(NLMSG, ul_debugobj(nl, "sending dump request"));
+ if (send(nl->fd, &req, req.nh.nlmsg_len, 0) < 0)
+ return -1;
+ return 0;
+}
+
+#define DBG_CASE(x) case x: str = #x; break
+#define DBG_CASE_DEF8(x) default: snprintf(strx+2, 3, "%02hhx", x); str = strx; break
+static void dbg_addr(struct ul_nl_data *nl)
+{
+ char *str;
+ char strx[5] = "0x";
+ switch (nl->addr.ifa_family) {
+ DBG_CASE(AF_INET);
+ DBG_CASE(AF_INET6);
+ DBG_CASE_DEF8(nl->addr.ifa_family);
+ }
+ DBG(ADDR, ul_debug(" ifa_family: %s", str));
+ switch (nl->addr.ifa_scope) {
+ DBG_CASE(RT_SCOPE_UNIVERSE);
+ DBG_CASE(RT_SCOPE_SITE);
+ DBG_CASE(RT_SCOPE_LINK);
+ DBG_CASE(RT_SCOPE_HOST);
+ DBG_CASE(RT_SCOPE_NOWHERE);
+ DBG_CASE_DEF8(nl->addr.ifa_scope);
+ }
+ DBG(ADDR, ul_debug(" ifa_scope: %s", str));
+ DBG(ADDR, ul_debug(" interface: %s (ifa_index %u)",
+ nl->addr.ifname, nl->addr.ifa_index));
+ DBG(ADDR, ul_debug(" ifa_flags: 0x%02x", nl->addr.ifa_flags));
+}
+
+/* Expecting non-zero nl->callback_addr! */
+static int process_addr(struct ul_nl_data *nl, struct nlmsghdr *nh)
+{
+ struct ifaddrmsg *ifaddr;
+ struct rtattr *attr;
+ int len;
+ static char ifname[IF_NAMESIZE];
+ bool has_local_address = false;
+
+ DBG(ADDR, ul_debugobj(nh, "processing nlmsghdr"));
+ memset(&(nl->addr), 0, sizeof(struct ul_nl_addr));
+
+ /* Process ifaddrmsg. */
+ ifaddr = NLMSG_DATA(nh);
+
+ nl->addr.ifa_family = ifaddr->ifa_family;
+ nl->addr.ifa_scope = ifaddr->ifa_scope;
+ nl->addr.ifa_index = ifaddr->ifa_index;
+ if ((if_indextoname(ifaddr->ifa_index, ifname)))
+ nl->addr.ifname = ifname;
+ else
+ {
+ /* There can be race, we do not return error here */
+ /* FIXME I18N: *"unknown"* is too generic. Use context. */
+ /* TRANSLATORS: unknown network interface, maximum 15
+ * (IF_NAMESIZE-1) bytes */
+ nl->addr.ifname = _("unknown");
+ }
+ nl->addr.ifa_flags = (uint32_t)(ifaddr->ifa_flags);
+ /* If IFA_CACHEINFO is not present, suppose permanent addresses. */
+ nl->addr.ifa_valid = UINT32_MAX;
+ ON_DBG(ADDR, dbg_addr(nl));
+
+ /* Process rtattr. */
+ len = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifaddr));
+ for (attr = IFA_RTA(ifaddr); RTA_OK(attr, len);
+ attr = RTA_NEXT(attr, len)) {
+ /* Proces most common rta attributes */
+ DBG(ADDR, ul_debugobj(attr, "processing rtattr"));
+ switch (attr->rta_type) {
+ case IFA_ADDRESS:
+ nl->addr.ifa_address = RTA_DATA(attr);
+ nl->addr.ifa_address_len = RTA_PAYLOAD(attr);
+ if (!has_local_address) {
+ nl->addr.address = RTA_DATA(attr);
+ nl->addr.address_len = RTA_PAYLOAD(attr);
+ }
+ DBG(ADDR,
+ ul_debug(" IFA_ADDRESS%s: %s",
+ (has_local_address ? "" :
+ " (setting address)"),
+ ul_nl_addr_ntop_ifa_address(&(nl->addr))));
+ break;
+ case IFA_LOCAL:
+ /* Point to Point interface listens has local address
+ * and listens there */
+ has_local_address = true;
+ nl->addr.ifa_local = nl->addr.address = RTA_DATA(attr);
+ nl->addr.ifa_local_len =
+ nl->addr.address_len = RTA_PAYLOAD(attr);
+ DBG(ADDR,
+ ul_debug(" IFA_LOCAL (setting address): %s",
+ ul_nl_addr_ntop_ifa_local(&(nl->addr))));
+ break;
+ case IFA_CACHEINFO:
+ {
+ struct ifa_cacheinfo *ci =
+ (struct ifa_cacheinfo *)RTA_DATA(attr);
+ nl->addr.ifa_valid = ci->ifa_valid;
+ DBG(ADDR,
+ ul_debug(" IFA_CACHEINFO: ifa_prefered = %u,"
+ "ifa_valid = %u",
+ nl->addr.ifa_prefered,
+ nl->addr.ifa_valid));
+ }
+ break;
+ case IFA_FLAGS:
+ nl->addr.ifa_flags = *(uint32_t *)(RTA_DATA(attr));
+ DBG(ADDR, ul_debug(" IFA_FLAGS: 0x%08x",
+ nl->addr.ifa_flags));
+ break;
+ default:
+ DBG(ADDR, ul_debug(" rta_type = 0x%04x",
+ attr->rta_type));
+ break;
+ }
+ }
+ DBG(NLMSG, ul_debugobj(nl, "callback %p", nl->callback_addr));
+ return (*(nl->callback_addr))(nl);
+}
+
+static int process_msg(struct ul_nl_data *nl, struct nlmsghdr *nh)
+{
+ int rc = 0;
+
+ nl->rtm_event = UL_NL_RTM_DEL;
+ switch (nh->nlmsg_type) {
+ case RTM_NEWADDR:
+ nl->rtm_event = UL_NL_RTM_NEW;
+ /* fallthrough */
+ case RTM_DELADDR:
+ /* If callback_addr is not set, skip process_addr */
+ DBG(NLMSG, ul_debugobj(nl, "%s",
+ (UL_NL_IS_RTM_DEL(nl) ?
+ "RTM_DELADDR" : "RTM_NEWADDR")));
+ if (nl->callback_addr)
+ rc = process_addr(nl, nh);
+ break;
+ /* More can be implemented in future (RTM_NEWLINK, RTM_DELLINK etc.). */
+ default:
+ DBG(NLMSG, ul_debugobj(nl, "nlmsg_type = %hu", nh->nlmsg_type));
+ break;
+
+ }
+ return rc;
+}
+
+int ul_nl_process(struct ul_nl_data *nl, bool async, bool loop)
+{
+ char buf[4096];
+ struct sockaddr_nl snl;
+ struct nlmsghdr *nh;
+ int rc;
+ uint32_t len;
+
+ struct iovec iov = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf)
+ };
+ struct msghdr msg = {
+ .msg_name = &snl,
+ .msg_namelen = sizeof(snl),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = NULL,
+ .msg_controllen = 0,
+ .msg_flags = 0
+ };
+
+ while (1) {
+ DBG(NLMSG, ul_debugobj(nl, "waiting for message"));
+ rc = recvmsg(nl->fd, &msg, (loop ? 0 :
+ (async ? MSG_DONTWAIT : 0)));
+ DBG(NLMSG, ul_debugobj(nl, "got message"));
+ if (rc < 0) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ DBG(NLMSG,
+ ul_debugobj(nl, "no data"));
+ return UL_NL_WOULDBLOCK;
+ }
+ /* Failure, just stop listening for changes */
+ nl->dumping = false;
+ DBG(NLMSG, ul_debugobj(nl, "error"));
+ return rc;
+ }
+ else
+ len = rc;
+
+ for (nh = (struct nlmsghdr *)buf;
+ NLMSG_OK(nh, len);
+ nh = NLMSG_NEXT(nh, len)) {
+
+ if (nh->nlmsg_type == NLMSG_ERROR) {
+ DBG(NLMSG, ul_debugobj(nl, "NLMSG_ERROR"));
+ nl->dumping = false;
+ return -1;
+ }
+ if (nh->nlmsg_type == NLMSG_DONE) {
+ DBG(NLMSG,
+ ul_debugobj(nl, "NLMSG_DONE"));
+ nl->dumping = false;
+ return UL_NL_DONE;
+ }
+
+ rc = process_msg(nl, nh);
+ if (rc)
+ {
+ DBG(NLMSG,
+ ul_debugobj(nl,
+ "process_msg() returned %d",
+ rc));
+ return rc;
+ }
+ }
+ if (!loop)
+ return 0;
+ DBG(NLMSG, ul_debugobj(nl, "looping until NLMSG_DONE"));
+ }
+}
+
+int ul_nl_open(struct ul_nl_data *nl, uint32_t nl_groups)
+{
+ struct sockaddr_nl addr = { 0, };
+ int sock;
+ int rc;
+
+ DBG(NLMSG, ul_debugobj(nl, "opening socket"));
+ sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (sock < 0)
+ return sock;
+ addr.nl_family = AF_NETLINK;
+ addr.nl_pid = getpid();
+ addr.nl_groups = nl_groups;
+ if ((rc = bind(sock, (struct sockaddr *)&addr, sizeof(addr))) >= 0)
+ {
+ nl->fd = sock;
+ return 0;
+ }
+ else
+ {
+ close(sock);
+ return rc;
+ }
+}
+
+int ul_nl_close(struct ul_nl_data *nl) {
+ DBG(NLMSG, ul_debugobj(nl, "closing socket"));
+ return close(nl->fd);
+}
+
+struct ul_nl_addr *ul_nl_addr_dup (struct ul_nl_addr *addr) {
+ struct ul_nl_addr *newaddr;
+ newaddr = malloc(sizeof(struct ul_nl_addr));
+ if (!newaddr) goto error1;
+ memcpy(newaddr, addr, sizeof(struct ul_nl_addr));
+ if (addr->ifa_address_len) {
+ newaddr->ifa_address = malloc(addr->ifa_address_len);
+ if (!newaddr->ifa_address)
+ goto error2;
+ memcpy(newaddr->ifa_address, addr->ifa_address,
+ addr->ifa_address_len);
+ }
+ if (addr->ifa_local_len) {
+ newaddr->ifa_local = malloc(addr->ifa_local_len);
+ if (!newaddr->ifa_local)
+ goto error3;
+ memcpy(newaddr->ifa_local, addr->ifa_local,
+ addr->ifa_local_len);
+ }
+ if (&(addr->ifa_address) == &(addr->ifa_local))
+ newaddr->address = newaddr->ifa_local;
+ else
+ newaddr->address = newaddr->ifa_address;
+ if ((newaddr->ifname = strdup(addr->ifname)))
+ return newaddr;
+ free(newaddr->ifa_local);
+error3:
+ free(newaddr->ifa_address);
+error2:
+ free(newaddr);
+error1:
+ return NULL;
+}
+
+void ul_nl_addr_free (struct ul_nl_addr *addr) {
+ free(addr->ifa_address);
+ free(addr->ifa_local);
+ free(addr->ifname);
+ free(addr);
+}
+
+const char *ul_nl_addr_ntop (const struct ul_nl_addr *addr, int addrid) {
+ const void **ifa_addr = (const void **)((const char *)addr + addrid);
+ /* (INET6_ADDRSTRLEN-1) + (IF_NAMESIZE-1) + strlen("%") + 1 */
+ static char addr_str[INET6_ADDRSTRLEN+IF_NAMESIZE];
+
+ if (addr->ifa_family == AF_INET)
+ return inet_ntop(AF_INET,
+ *ifa_addr, addr_str, sizeof(addr_str));
+ else {
+ /* if (addr->ifa_family == AF_INET6) */
+ if (addr->ifa_scope == RT_SCOPE_LINK) {
+ char *p;
+
+ inet_ntop(AF_INET6,
+ *ifa_addr, addr_str, sizeof(addr_str));
+ p = addr_str;
+ while (*p) p++;
+ *p++ = '%';
+ strncpy(p, addr->ifname, IF_NAMESIZE);
+ return addr_str;
+ } else
+ return inet_ntop(AF_INET6,
+ *ifa_addr, addr_str, sizeof(addr_str));
+ }
+}
+
+#ifdef TEST_PROGRAM_NETLINK
+#include <stdio.h>
+
+static int callback_addr(struct ul_nl_data *nl) {
+ char *str;
+
+ printf("%s address:\n", ((nl->rtm_event ? "Add" : "Delete")));
+ printf(" interface: %s\n", nl->addr.ifname);
+ if (nl->addr.ifa_family == AF_INET)
+ printf(" IPv4 %s\n",
+ ul_nl_addr_ntop_address(&(nl->addr)));
+ else
+ /* if (nl->addr.ifa_family == AF_INET) */
+ printf(" IPv6 %s\n",
+ ul_nl_addr_ntop_address(&(nl->addr)));
+ switch (nl->addr.ifa_scope) {
+ case RT_SCOPE_UNIVERSE: str = "global"; break;
+ case RT_SCOPE_SITE: str = "site"; break;
+ case RT_SCOPE_LINK: str = "link"; break;
+ case RT_SCOPE_NOWHERE: str = "nowhere"; break;
+ default: str = "other"; break;
+ }
+ printf(" scope: %s\n", str);
+ if (nl->addr.ifa_valid != UINT32_MAX)
+ printf(" valid: %u\n", nl->addr.ifa_valid);
+ else
+ printf(" valid: forever\n");
+ return 0;
+}
+
+int main(int argc __attribute__((__unused__)),
+ char *argv[] __attribute__((__unused__)))
+{
+ int rc;
+ struct ul_nl_data nl;
+
+ /* Prepare netlink. */
+ ul_nl_init(&nl);
+ nl.callback_addr = callback_addr;
+
+ /* Dump addresses */
+ if ((rc = ul_nl_open(&nl, 0)))
+ return 1;
+ if (ul_nl_request_dump(&nl, RTM_GETADDR))
+ goto error;
+ if (ul_nl_process(&nl, UL_NL_SYNC, UL_NL_LOOP) != UL_NL_DONE)
+ goto error;
+ puts("RTM_GETADDR dump finished.");
+
+ /* Close and later open. See note in the ul_nl_open() docs. */
+ if ((rc = ul_nl_close(&nl)) < 0)
+ goto error;
+
+ /* Monitor further changes */
+ puts("Going to monitor mode.");
+ if ((rc = ul_nl_open(&nl, RTMGRP_LINK |
+ RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR)))
+ goto error;
+ /* In this example UL_NL_ABORT never appears, as callback does
+ * not use it. */
+ rc = ul_nl_process(&nl, UL_NL_SYNC, UL_NL_LOOP);
+ if (rc == UL_NL_RETURN)
+ rc = 0;
+error:
+ if ((ul_nl_close(&nl)))
+ rc = 1;
+ return rc;
+}
+#endif /* TEST_PROGRAM_NETLINK */
--
2.48.1