]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
New netlink library
authorStanislav Brabec <sbrabec@suse.cz>
Wed, 9 Jul 2025 12:29:10 +0000 (14:29 +0200)
committerStanislav Brabec <sbrabec@suse.cz>
Wed, 1 Oct 2025 19:58:57 +0000 (21:58 +0200)
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
include/netaddrq.h [new file with mode: 0644]
include/netlink.h [new file with mode: 0644]
lib/Makemodule.am
lib/meson.build
lib/netaddrq.c [new file with mode: 0644]
lib/netlink.c [new file with mode: 0644]

index bc2c73415f4294cc32457c2f2e62d76d4097949c..206f12034e8ef0f0bce2b2f4f1656e7b833b2ed7 100644 (file)
@@ -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 (file)
index 0000000..6d5e655
--- /dev/null
@@ -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 (file)
index 0000000..3d7c3da
--- /dev/null
@@ -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 */
index 84ab3e3ae9219595b8d3e2e2f496adab2eb86f7a..81ec4fe2fbb84fa8a4eaae7704f673b3a05aeb7c 100644 (file)
@@ -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/pidutils.c \
        lib/pwdutils.c \
        lib/randutils.c \
@@ -93,6 +96,8 @@ check_PROGRAMS += \
        test_ismounted \
        test_pwdutils \
        test_mangle \
+       test_netlink \
+       test_netaddrq \
        test_randutils \
        test_remove_env \
        test_strutils \
@@ -140,6 +145,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
 test_strutils_LDADD = $(LDADD) libcommon.la
index 70e2703d5444e1160503e8cbe36d76755c8c068b..0f94a9b99b3613a3ddbe205e515f968ae93db2b8 100644 (file)
@@ -17,6 +17,8 @@ lib_common_sources = '''
        mbsalign.c
        mbsedit.c
        md5.c
+       netaddrq.c
+       netlink.c
        pidutils.c
        procfs.c
        pwdutils.c
diff --git a/lib/netaddrq.c b/lib/netaddrq.c
new file mode 100644 (file)
index 0000000..34431d8
--- /dev/null
@@ -0,0 +1,723 @@
+/*
+ * 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_as_string(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))))
+                               return -ENOMEM;
+                       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() failed"));
+                               free(ifaceq);
+                               return -ENOMEM;
+                       }
+                       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) {
+               struct ul_netaddrq_ip *ipqq;
+
+               ipqq = list_entry(li, struct ul_netaddrq_ip, entry);
+               if (ipqq->addr->address_len == nl->addr.address_len)
+                       if (!memcmp(ipqq->addr->address, nl->addr.address,
+                                  nl->addr.address_len)) {
+                               ipq = ipqq;
+                               DBG(LIST, ul_debugobj(ipq,
+                                                     "address found in ipq_list"));
+                               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) {
+                       rc = -ENOMEM;
+                       goto error;
+               }
+               if (ipq == NULL) {
+                       if (!(ipq = malloc(sizeof(struct ul_netaddrq_ip))))
+                       {
+                               rc = -ENOMEM;
+                               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_as_string(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 = calloc(1, sizeof(struct ul_netaddrq_data))))
+               return -ENOMEM;
+       nl->callback_addr = callback_addrq;
+       addrq = UL_NETADDRQ_DATA(nl);
+       addrq->callback_pre = callback_pre;
+       addrq->callback_post = callback_post;
+       addrq->callback_data = data;
+       INIT_LIST_HEAD(&(addrq->ifaces));
+       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_as_string(ipq->quality)));
+                       (*best)[ipq->quality] = ipq;
+               }
+
+               if (ipq->quality < threshold)
+               {
+                       threshold = ipq->quality;
+                       DBG(BEST,
+                           ul_debug("threshold %s", ip_rating_as_string(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);
+       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 (file)
index 0000000..3def42e
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ * 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 "strutils.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[BUFSIZ];
+       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 = calloc(1, sizeof(struct ul_nl_addr));
+       if (!newaddr)
+               goto error;
+       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 error;
+               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 error;
+               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)))
+               goto error;
+       return newaddr;
+error:
+       ul_nl_addr_free(newaddr);
+       return NULL;
+}
+
+void ul_nl_addr_free (struct ul_nl_addr *addr) {
+       if (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++ = '%';
+                       xstrncpy(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 */