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

openSUSE Build Service is sponsored by