]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Examine RTM_NEWADDR, RTM_DELADDR messages contents
authorArtem Boldariev <artem@boldariev.com>
Tue, 14 Dec 2021 11:38:28 +0000 (13:38 +0200)
committerEvan Hunt <each@isc.org>
Wed, 15 Dec 2021 17:46:06 +0000 (09:46 -0800)
This commit modifies the NetLink handling code in such a way
that the contents of the messages we are interested in is checked
for the local addresses changes only. This helps to avoid spurious
interface re-scans.

The 'route_recv' log messages are also reduced from DEBUG(3) to
DEBUG(9).

lib/ns/interfacemgr.c

index 4c3eef92f9e4c8f20a53cabcc2c8155e310f7b84..69002375176661c8217049d55df3be55cc7cde7d 100644 (file)
@@ -40,6 +40,7 @@
 #endif /* ifdef HAVE_NET_ROUTE_H */
 
 #if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H)
+#define LINUX_NETLINK_AVAILABLE
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #if defined(RTM_NEWADDR) && defined(RTM_DELADDR)
@@ -102,14 +103,105 @@ scan_event(isc_task_t *task, isc_event_t *event) {
        isc_event_free(&event);
 }
 
+static bool
+need_rescan(ns_interfacemgr_t *mgr, struct MSGHDR *rtm, size_t len) {
+       if (rtm->MSGTYPE != RTM_NEWADDR && rtm->MSGTYPE != RTM_DELADDR) {
+               return (false);
+       }
+
+#ifndef LINUX_NETLINK_AVAILABLE
+       UNUSED(mgr);
+       UNUSED(len);
+       /* On most systems, any NEWADDR or DELADDR means we rescan */
+       return (true);
+#else  /* LINUX_NETLINK_AVAILABLE */
+       /* ...but on linux we need to check the messages more carefully */
+       for (struct MSGHDR *nlh = rtm;
+            NLMSG_OK(nlh, len) && nlh->nlmsg_type != NLMSG_DONE;
+            nlh = NLMSG_NEXT(nlh, len))
+       {
+               struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nlh);
+               struct rtattr *rth = IFA_RTA(ifa);
+               size_t rtl = IFA_PAYLOAD(nlh);
+
+               while (rtl > 0 && RTA_OK(rth, rtl)) {
+                       /*
+                        * Look for IFA_ADDRESS to detect IPv6 interface
+                        * state changes.
+                        */
+                       if (rth->rta_type == IFA_ADDRESS &&
+                           ifa->ifa_family == AF_INET6) {
+                               bool was_listening = false;
+                               isc_netaddr_t addr = { 0 };
+                               ns_interface_t *ifp = NULL;
+
+                               isc_netaddr_fromin6(&addr, RTA_DATA(rth));
+
+                               /*
+                                * Check whether we were listening on the
+                                * address. We need to do this as the
+                                * Linux kernel seems to issue messages
+                                * containing IFA_ADDRESS far more often
+                                * than the actual state changes (on
+                                * router advertisements?)
+                                */
+                               LOCK(&mgr->lock);
+                               for (ifp = ISC_LIST_HEAD(mgr->interfaces);
+                                    ifp != NULL;
+                                    ifp = ISC_LIST_NEXT(ifp, link))
+                               {
+                                       isc_netaddr_t tmp = { 0 };
+                                       isc_netaddr_fromsockaddr(&tmp,
+                                                                &ifp->addr);
+                                       if (isc_netaddr_equal(&tmp, &addr)) {
+                                               was_listening = true;
+                                               break;
+                                       }
+                               }
+                               UNLOCK(&mgr->lock);
+
+                               /*
+                                * Do rescan if the state of the interface
+                                * has changed.
+                                */
+                               if ((!was_listening &&
+                                    rtm->MSGTYPE == RTM_NEWADDR) ||
+                                   (was_listening &&
+                                    rtm->MSGTYPE == RTM_DELADDR))
+                               {
+                                       return (true);
+                               }
+                       } else if (rth->rta_type == IFA_ADDRESS &&
+                                  ifa->ifa_family == AF_INET) {
+                               /*
+                                * It seems that the IPv4 P2P link state
+                                * has changed.
+                                */
+                               return (true);
+                       } else if (rth->rta_type == IFA_LOCAL) {
+                               /*
+                                * Local address state has changed - do
+                                * rescan.
+                                */
+                               return (true);
+                       }
+                       rth = RTA_NEXT(rth, rtl);
+               }
+       }
+#endif /* LINUX_NETLINK_AVAILABLE */
+
+       return (false);
+}
+
 static void
 route_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
           void *arg) {
        ns_interfacemgr_t *mgr = (ns_interfacemgr_t *)arg;
        struct MSGHDR *rtm = NULL;
        bool done = true;
+       size_t rtmlen;
 
-       isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(3), "route_recv: %s",
+       isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(9), "route_recv: %s",
                      isc_result_totext(eresult));
 
        if (handle == NULL) {
@@ -137,6 +229,8 @@ route_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
        }
 
        rtm = (struct MSGHDR *)region->base;
+       rtmlen = region->length;
+
 #ifdef RTM_VERSION
        if (rtm->rtm_version != RTM_VERSION) {
                isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
@@ -150,19 +244,13 @@ route_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
        }
 #endif /* ifdef RTM_VERSION */
 
-       switch (rtm->MSGTYPE) {
-       case RTM_NEWADDR:
-       case RTM_DELADDR:
-               if (mgr->route != NULL && mgr->sctx->interface_auto) {
-                       isc_event_t *event = NULL;
-                       event = isc_event_allocate(mgr->mctx, mgr,
-                                                  NS_EVENT_IFSCAN, scan_event,
-                                                  mgr, sizeof(*event));
-                       isc_task_send(mgr->excl, &event);
-               }
-               break;
-       default:
-               break;
+       if (need_rescan(mgr, rtm, rtmlen) && mgr->route != NULL &&
+           mgr->sctx->interface_auto)
+       {
+               isc_event_t *event = NULL;
+               event = isc_event_allocate(mgr->mctx, mgr, NS_EVENT_IFSCAN,
+                                          scan_event, mgr, sizeof(*event));
+               isc_task_send(mgr->excl, &event);
        }
 
        LOCK(&mgr->lock);
@@ -183,7 +271,7 @@ static void
 route_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
        ns_interfacemgr_t *mgr = (ns_interfacemgr_t *)arg;
 
-       isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(3),
+       isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(9),
                      "route_connected: %s", isc_result_totext(eresult));
 
        if (eresult != ISC_R_SUCCESS) {