1616 lines
47 KiB
Diff
1616 lines
47 KiB
Diff
|
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
|
||
|
|