From 45c810ac76af3c5045aadce4f79349c04e29bc23 Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Mon, 16 Dec 2024 10:35:52 +0100 Subject: [PATCH] net/linux: add net_netlink_addrs (#1232) This pull request adds support for obtaining network interface addresses using the RTNETLINK API on Linux systems. fixes baresip/baresip#3245 --- CMakeLists.txt | 1 + include/re_net.h | 1 + src/net/linux/addrs.c | 210 +++++++++++++++++++++++++++++++++++++++++ src/net/linux/macros.h | 14 +++ src/net/linux/rt.c | 13 +-- src/net/net.c | 4 +- 6 files changed, 230 insertions(+), 13 deletions(-) create mode 100644 src/net/linux/addrs.c create mode 100644 src/net/linux/macros.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5de10c117..78df6d25a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -606,6 +606,7 @@ elseif(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") list(APPEND SRCS src/net/linux/rt.c + src/net/linux/addrs.c ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Android") list(APPEND SRCS diff --git a/include/re_net.h b/include/re_net.h index 841f67460..7f16f7a2a 100644 --- a/include/re_net.h +++ b/include/re_net.h @@ -73,6 +73,7 @@ int net_if_getlinklocal(const char *ifname, int af, struct sa *ip); /* Net interface (ifaddrs.c) */ int net_getifaddrs(net_ifaddr_h *ifh, void *arg); +int net_netlink_addrs(net_ifaddr_h *ifh, void *arg); /* Net route */ diff --git a/src/net/linux/addrs.c b/src/net/linux/addrs.c new file mode 100644 index 000000000..c8a904eda --- /dev/null +++ b/src/net/linux/addrs.c @@ -0,0 +1,210 @@ +/** + * @file linux/addrs.c Get interface addresses (See rtnetlink(7)) + * + * Copyright (C) 2024 Sebastian Reimers + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "macros.h" + +#define DEBUG_MODULE "linuxaddrs" +#define DEBUG_LEVEL 5 +#include + +enum { BUFSZ = 8192, MAXIF = 255 }; + + +static void parse_rtattr(struct rtattr *tb[], struct rtattr *rta, int len) +{ + memset(tb, 0, sizeof(struct rtattr *) * (IFA_MAX + 1)); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= IFA_MAX) { + tb[rta->rta_type] = rta; + } + rta = RTA_NEXT(rta, len); + } +} + + +static bool is_ipv6_deprecated(uint32_t flags) +{ + if (flags & (IFA_F_TENTATIVE | IFA_F_OPTIMISTIC | IFA_F_DADFAILED | + IFA_F_DEPRECATED)) + return true; + + return false; +} + + +static bool parse_msg_link(struct nlmsghdr *msg, ssize_t len, int *iff_up) +{ + struct nlmsghdr *nlh; + struct ifinfomsg *ifi; + + for (nlh = msg; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { + if (nlh->nlmsg_type == NLMSG_DONE) { + return true; + } + if (nlh->nlmsg_type == NLMSG_ERROR) { + DEBUG_WARNING("netlink recv error\n"); + return true; + } + + ifi = NLMSG_DATA(nlh); + + if (ifi->ifi_index >= MAXIF) { + DEBUG_WARNING("Max interface index [%d] reached!\n", + MAXIF); + return false; + } + + iff_up[ifi->ifi_index] = ifi->ifi_flags & IFF_UP; + } + + return false; +} + + +static bool parse_msg_addr(struct nlmsghdr *msg, ssize_t len, + net_ifaddr_h *ifh, int *iff_up, void *arg) +{ + struct nlmsghdr *nlh; + for (nlh = msg; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { + struct sa sa; + uint32_t flags; + char if_name[IF_NAMESIZE]; + + if (nlh->nlmsg_type == NLMSG_DONE) { + return true; + } + if (nlh->nlmsg_type == NLMSG_ERROR) { + DEBUG_WARNING("netlink recv error\n"); + return true; + } + + struct ifaddrmsg *ifa = NLMSG_DATA(nlh); + struct rtattr *rta_tb[IFA_MAX + 1]; + + parse_rtattr(rta_tb, IFA_RTA(ifa), + nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa))); + + if (!rta_tb[IFA_ADDRESS]) + continue; + + if (ifa->ifa_index < MAXIF && !iff_up[ifa->ifa_index]) + continue; + + if (rta_tb[IFA_FLAGS] && ifa->ifa_family == AF_INET6) { + flags = *(uint32_t *)RTA_DATA(rta_tb[IFA_FLAGS]); + if (is_ipv6_deprecated(flags)) + continue; + } + + if (ifa->ifa_family == AF_INET) { + sa_init(&sa, AF_INET); + sa.u.in.sin_addr.s_addr = + *(uint32_t *)RTA_DATA(rta_tb[IFA_ADDRESS]); + } + else if (ifa->ifa_family == AF_INET6) { + sa_set_in6(&sa, RTA_DATA(rta_tb[IFA_ADDRESS]), 0); + sa_set_scopeid(&sa, ifa->ifa_index); + } + else + continue; + + if (!if_indextoname(ifa->ifa_index, if_name)) + continue; + + if (ifh(if_name, &sa, arg)) + return true; + } + + return false; +} + + +int net_netlink_addrs(net_ifaddr_h *ifh, void *arg) +{ + int err = 0; + char buffer[BUFSZ]; + re_sock_t sock; + ssize_t len; + int iff_up[MAXIF] = {0}; + + struct { + struct nlmsghdr nlh; + struct ifaddrmsg ifa; + } req; + + if (!ifh) + return EINVAL; + + if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) { + err = errno; + DEBUG_WARNING("socket failed %m\n", err); + return err; + } + + struct timeval timeout = {5, 0}; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + + /* GETLINK */ + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.nlh.nlmsg_type = RTM_GETLINK; + + if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) { + err = errno; + DEBUG_WARNING("GETLINK send failed %m\n", err); + goto out; + } + + while ((len = recv(sock, buffer, sizeof(buffer), 0)) > 0) { + if (parse_msg_link((struct nlmsghdr *)buffer, len, iff_up)) + break; + } + + if (len < 0) { + err = errno; + DEBUG_WARNING("GETLINK recv failed %m\n", err); + goto out; + } + + /* GETADDR */ + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.nlh.nlmsg_type = RTM_GETADDR; + + if (send(sock, &req, req.nlh.nlmsg_len, 0) < 0) { + err = errno; + DEBUG_WARNING("GETADDR send failed %m\n", err); + goto out; + } + + while ((len = recv(sock, buffer, sizeof(buffer), 0)) > 0) { + if (parse_msg_addr((struct nlmsghdr *)buffer, len, ifh, iff_up, + arg)) + break; + } + + if (len < 0) { + err = errno; + DEBUG_WARNING("GETADDR recv failed %m\n", err); + } + +out: + close(sock); + + return err; +} diff --git a/src/net/linux/macros.h b/src/net/linux/macros.h new file mode 100644 index 000000000..7b49d2ca9 --- /dev/null +++ b/src/net/linux/macros.h @@ -0,0 +1,14 @@ +/* Override macros to avoid casting alignment warning */ +#undef RTM_RTA +#define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))) +#undef RTA_NEXT +#define RTA_NEXT(rta, len) \ + ((len) -= RTA_ALIGN((rta)->rta_len), \ + (void *)(((char *)(rta)) + RTA_ALIGN((rta)->rta_len))) +#undef NLMSG_NEXT +#define NLMSG_NEXT(nlh, len) \ + ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ + (void *)(((char *)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) +#undef IFA_RTA +#define IFA_RTA(r) \ + ((void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) diff --git a/src/net/linux/rt.c b/src/net/linux/rt.c index 8992b1f48..04381d5fd 100644 --- a/src/net/linux/rt.c +++ b/src/net/linux/rt.c @@ -15,24 +15,13 @@ #include #include #include +#include "macros.h" #define DEBUG_MODULE "linuxrt" #define DEBUG_LEVEL 5 #include - -/* Override macros to avoid casting alignment warning */ -#undef RTM_RTA -#define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))) -#undef RTA_NEXT -#define RTA_NEXT(rta, len) ((len) -= RTA_ALIGN((rta)->rta_len), \ - (void *)(((char *)(rta)) + RTA_ALIGN((rta)->rta_len))) -#undef NLMSG_NEXT -#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ - (void*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) - - enum {BUFSIZE = 8192}; diff --git a/src/net/net.c b/src/net/net.c index db7bd1d37..29af3dacd 100644 --- a/src/net/net.c +++ b/src/net/net.c @@ -129,7 +129,9 @@ int net_default_source_addr_get(int af, struct sa *ip) */ int net_if_apply(net_ifaddr_h *ifh, void *arg) { -#ifdef HAVE_GETIFADDRS +#ifdef LINUX + return net_netlink_addrs(ifh, arg); +#elif HAVE_GETIFADDRS return net_getifaddrs(ifh, arg); #else return net_if_list(ifh, arg);