From 7d11ab55ce140cb03ffebb55856c0a766853d83e Mon Sep 17 00:00:00 2001 From: Stanislav Brabec 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 --- 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 + * + * 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 + * + * 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 +#include +#include +#include +#include +#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 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 + * + * 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 +#include +#include +#include +#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 + * + * 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 +#include +#include +#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 + +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