]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
netmgr: add isc_nm_routeconnect()
authorEvan Hunt <each@isc.org>
Sat, 2 Oct 2021 21:52:46 +0000 (14:52 -0700)
committerEvan Hunt <each@isc.org>
Fri, 15 Oct 2021 07:56:58 +0000 (00:56 -0700)
isc_nm_routeconnect() opens a route/netlink socket, then calls a
connect callback, much like isc_nm_udpconnect(), with a handle that
can then be monitored for network changes.

Internally the socket is treated as a UDP socket, since route/netlink
sockets follow the datagram contract.

lib/isc/include/isc/netmgr.h
lib/isc/netmgr/netmgr-int.h
lib/isc/netmgr/netmgr.c
lib/isc/netmgr/udp.c

index 980bff2a142ddbef60a3edd6effb81a87854e221..b517eaaec2f8e4760306b893eaf124b1be071368 100644 (file)
@@ -232,6 +232,18 @@ isc_nm_udpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
  * 'cb'.
  */
 
+isc_result_t
+isc_nm_routeconnect(isc_nm_t *mgr, isc_nm_cb_t cb, void *cbarg,
+                   size_t extrahandlesize);
+/*%<
+ * Open a route/netlink socket and call 'cb', so the caller can be
+ * begin listening for interface changes.  This behaves similarly to
+ * isc_nm_udpconnect().
+ *
+ * Returns ISC_R_NOTIMPLEMENTED on systems where route/netlink sockets
+ * are not supported.
+ */
+
 void
 isc_nm_stoplistening(isc_nmsocket_t *sock);
 /*%<
index c776aa08b3bb376dc7ddb9d8f12fe47e76d1b660..2c5305f37b0bd8b2d4f6f4c28d39c41f4a83b4bc 100644 (file)
@@ -240,6 +240,8 @@ typedef enum isc__netievent_type {
        netievent_udpread,
        netievent_udpcancel,
 
+       netievent_routeconnect,
+
        netievent_tcpconnect,
        netievent_tcpclose,
        netievent_tcpsend,
@@ -967,6 +969,8 @@ struct isc_nmsocket {
        atomic_bool active;
        atomic_bool destroying;
 
+       bool route_sock;
+
        /*%
         * Socket is closed if it's not active and all the possible
         * callbacks were fired, there are no active handles, etc.
@@ -1351,6 +1355,12 @@ isc__nm_async_udpclose(isc__networker_t *worker, isc__netievent_t *ev0);
  * Callback handlers for asynchronous UDP events (listen, stoplisten, send).
  */
 
+void
+isc__nm_async_routeconnect(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Callback handler for route socket events.
+ */
+
 void
 isc__nm_tcp_send(isc_nmhandle_t *handle, const isc_region_t *region,
                 isc_nm_cb_t cb, void *cbarg);
@@ -1880,6 +1890,8 @@ NETIEVENT_SOCKET_TYPE(tcpstartread);
 NETIEVENT_SOCKET_REQ_TYPE(tlssend);
 NETIEVENT_SOCKET_REQ_TYPE(udpconnect);
 
+NETIEVENT_SOCKET_REQ_TYPE(routeconnect);
+
 NETIEVENT_SOCKET_REQ_RESULT_TYPE(connectcb);
 NETIEVENT_SOCKET_REQ_RESULT_TYPE(readcb);
 NETIEVENT_SOCKET_REQ_RESULT_TYPE(sendcb);
@@ -1946,6 +1958,8 @@ NETIEVENT_SOCKET_REQ_DECL(tcpsend);
 NETIEVENT_SOCKET_REQ_DECL(tlssend);
 NETIEVENT_SOCKET_REQ_DECL(udpconnect);
 
+NETIEVENT_SOCKET_REQ_DECL(routeconnect);
+
 NETIEVENT_SOCKET_REQ_RESULT_DECL(connectcb);
 NETIEVENT_SOCKET_REQ_RESULT_DECL(readcb);
 NETIEVENT_SOCKET_REQ_RESULT_DECL(sendcb);
index 259e097da267a660554c1be87d86be391b11ce58..e89bd3d48526e2cfb055967bf876d0c31937ced5 100644 (file)
@@ -913,6 +913,8 @@ process_netievent(isc__networker_t *worker, isc__netievent_t *ievent) {
                NETIEVENT_CASE(udpcancel);
                NETIEVENT_CASE(udpclose);
 
+               NETIEVENT_CASE(routeconnect);
+
                NETIEVENT_CASE(tcpaccept);
                NETIEVENT_CASE(tcpconnect);
                NETIEVENT_CASE(tcplisten);
@@ -1072,6 +1074,7 @@ NETIEVENT_SOCKET_REQ_DEF(tcpconnect);
 NETIEVENT_SOCKET_REQ_DEF(tcpsend);
 NETIEVENT_SOCKET_REQ_DEF(tlssend);
 NETIEVENT_SOCKET_REQ_DEF(udpconnect);
+NETIEVENT_SOCKET_REQ_DEF(routeconnect);
 NETIEVENT_SOCKET_REQ_RESULT_DEF(connectcb);
 NETIEVENT_SOCKET_REQ_RESULT_DEF(readcb);
 NETIEVENT_SOCKET_REQ_RESULT_DEF(sendcb);
@@ -1447,12 +1450,8 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
 
        REQUIRE(sock != NULL);
        REQUIRE(mgr != NULL);
-       REQUIRE(iface != NULL);
-
-       family = iface->type.sa.sa_family;
 
        *sock = (isc_nmsocket_t){ .type = type,
-                                 .iface = *iface,
                                  .fd = -1,
                                  .ah_size = 32,
                                  .inactivehandles = isc_astack_new(
@@ -1460,6 +1459,13 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
                                  .inactivereqs = isc_astack_new(
                                          mgr->mctx, ISC_NM_REQS_STACK_SIZE) };
 
+       if (iface != NULL) {
+               family = iface->type.sa.sa_family;
+               sock->iface = *iface;
+       } else {
+               family = AF_UNSPEC;
+       }
+
 #if NETMGR_TRACE
        sock->backtrace_size = isc_backtrace(sock->backtrace, TRACE_SIZE);
        ISC_LINK_INIT(sock, active_link);
@@ -1492,6 +1498,12 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
                case AF_INET6:
                        sock->statsindex = udp6statsindex;
                        break;
+               case AF_UNSPEC:
+                       /*
+                        * Route sockets are AF_UNSPEC, and don't
+                        * have stats counters.
+                        */
+                       break;
                default:
                        INSIST(0);
                        ISC_UNREACHABLE();
@@ -1521,6 +1533,10 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
                break;
        }
 
+       if (sock->statsindex != NULL) {
+               isc__nm_incstats(sock->mgr, sock->statsindex[STATID_ACTIVE]);
+       }
+
        isc_mutex_init(&sock->lock);
        isc_condition_init(&sock->cond);
        isc_condition_init(&sock->scond);
index 3982d49e7f87eed26a7ac29b44eb80ff108343d4..e4b4b8926d7683a0fc2f8af5d79dae053e062e04 100644 (file)
 #include "netmgr-int.h"
 #include "uv-compat.h"
 
+#ifdef HAVE_NET_ROUTE_H
+#include <net/route.h>
+#if defined(RTM_VERSION) && defined(RTM_NEWADDR) && defined(RTM_DELADDR)
+#define USE_ROUTE_SOCKET      1
+#define ROUTE_SOCKET_PF              PF_ROUTE
+#define ROUTE_SOCKET_PROTOCOL 0
+#define MSGHDR               rt_msghdr
+#define MSGTYPE                      rtm_type
+#endif /* if defined(RTM_VERSION) && defined(RTM_NEWADDR) && \
+       * defined(RTM_DELADDR) */
+#endif /* ifdef HAVE_NET_ROUTE_H */
+
+#if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H)
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#if defined(RTM_NEWADDR) && defined(RTM_DELADDR)
+#define USE_ROUTE_SOCKET      1
+#define USE_NETLINK          1
+#define ROUTE_SOCKET_PF              PF_NETLINK
+#define ROUTE_SOCKET_PROTOCOL NETLINK_ROUTE
+#define MSGHDR               nlmsghdr
+#define MSGTYPE                      nlmsg_type
+#endif /* if defined(RTM_NEWADDR) && defined(RTM_DELADDR) */
+#endif /* if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) \
+       */
+
 static isc_result_t
 udp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
                isc_sockaddr_t *peer);
@@ -189,6 +215,197 @@ isc_nm_listenudp(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nm_recv_cb_t cb,
        return (result);
 }
 
+#ifdef USE_ROUTE_SOCKET
+static isc_result_t
+route_socket(uv_os_sock_t *fdp) {
+       isc_result_t result;
+       uv_os_sock_t fd;
+#ifdef USE_NETLINK
+       struct sockaddr_nl sa;
+       int r;
+#endif
+
+       result = isc__nm_socket(ROUTE_SOCKET_PF, SOCK_RAW,
+                               ROUTE_SOCKET_PROTOCOL, &fd);
+       if (result != ISC_R_SUCCESS) {
+               return (result);
+       }
+
+#ifdef USE_NETLINK
+       sa.nl_family = PF_NETLINK;
+       sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
+       r = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
+       if (r < 0) {
+               isc__nm_closesocket(fd);
+               return (isc_errno_toresult(r));
+       }
+#endif
+
+       *fdp = fd;
+       return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+route_connect_direct(isc_nmsocket_t *sock) {
+       isc__networker_t *worker = NULL;
+       isc_result_t result = ISC_R_UNSET;
+       int r;
+
+       REQUIRE(isc__nm_in_netthread());
+       REQUIRE(sock->tid == isc_nm_tid());
+
+       worker = &sock->mgr->workers[isc_nm_tid()];
+
+       atomic_store(&sock->connecting, true);
+
+       r = uv_udp_init(&worker->loop, &sock->uv_handle.udp);
+       RUNTIME_CHECK(r == 0);
+       uv_handle_set_data(&sock->uv_handle.handle, sock);
+
+       r = uv_timer_init(&worker->loop, &sock->timer);
+       RUNTIME_CHECK(r == 0);
+       uv_handle_set_data((uv_handle_t *)&sock->timer, sock);
+
+       if (isc__nm_closing(sock)) {
+               result = ISC_R_SHUTTINGDOWN;
+               goto error;
+       }
+
+       r = uv_udp_open(&sock->uv_handle.udp, sock->fd);
+       if (r != 0) {
+               goto done;
+       }
+
+       isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+       atomic_store(&sock->connecting, false);
+       atomic_store(&sock->connected, true);
+
+done:
+       result = isc__nm_uverr2result(r);
+error:
+
+       LOCK(&sock->lock);
+       sock->result = result;
+       SIGNAL(&sock->cond);
+       if (!atomic_load(&sock->active)) {
+               WAIT(&sock->scond, &sock->lock);
+       }
+       INSIST(atomic_load(&sock->active));
+       UNLOCK(&sock->lock);
+
+       return (result);
+}
+
+/*
+ * Asynchronous 'udpconnect' call handler: open a new UDP socket and
+ * call the 'open' callback with a handle.
+ */
+void
+isc__nm_async_routeconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
+       isc__netievent_routeconnect_t *ievent =
+               (isc__netievent_routeconnect_t *)ev0;
+       isc_nmsocket_t *sock = ievent->sock;
+       isc__nm_uvreq_t *req = ievent->req;
+       isc_result_t result;
+
+       UNUSED(worker);
+
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(sock->type == isc_nm_udpsocket);
+       REQUIRE(sock->parent == NULL);
+       REQUIRE(sock->tid == isc_nm_tid());
+
+       result = route_connect_direct(sock);
+       if (result != ISC_R_SUCCESS) {
+               atomic_store(&sock->active, false);
+               isc__nm_udp_close(sock);
+               isc__nm_connectcb(sock, req, result, true);
+       } else {
+               /*
+                * The callback has to be called after the socket has been
+                * initialized
+                */
+               isc__nm_connectcb(sock, req, ISC_R_SUCCESS, true);
+       }
+
+       /*
+        * The sock is now attached to the handle.
+        */
+       isc__nmsocket_detach(&sock);
+}
+#endif /* USE_ROUTE_SOCKET */
+
+isc_result_t
+isc_nm_routeconnect(isc_nm_t *mgr, isc_nm_cb_t cb, void *cbarg,
+                   size_t extrahandlesize) {
+#ifdef USE_ROUTE_SOCKET
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *sock = NULL;
+       isc__netievent_udpconnect_t *event = NULL;
+       isc__nm_uvreq_t *req = NULL;
+
+       REQUIRE(VALID_NM(mgr));
+
+       sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+       isc__nmsocket_init(sock, mgr, isc_nm_udpsocket, NULL);
+
+       sock->connect_cb = cb;
+       sock->connect_cbarg = cbarg;
+       sock->extrahandlesize = extrahandlesize;
+       sock->result = ISC_R_UNSET;
+       atomic_init(&sock->client, true);
+       sock->route_sock = true;
+
+       req = isc__nm_uvreq_get(mgr, sock);
+       req->cb.connect = cb;
+       req->cbarg = cbarg;
+       req->handle = isc__nmhandle_get(sock, NULL, NULL);
+
+       result = route_socket(&sock->fd);
+       if (result != ISC_R_SUCCESS) {
+               if (isc__nm_in_netthread()) {
+                       sock->tid = isc_nm_tid();
+               }
+               isc__nmsocket_clearcb(sock);
+               isc__nm_connectcb(sock, req, result, true);
+               atomic_store(&sock->closed, true);
+               isc__nmsocket_detach(&sock);
+               return (result);
+       }
+
+       event = isc__nm_get_netievent_routeconnect(mgr, sock, req);
+
+       if (isc__nm_in_netthread()) {
+               atomic_store(&sock->active, true);
+               sock->tid = isc_nm_tid();
+               isc__nm_async_routeconnect(&mgr->workers[sock->tid],
+                                          (isc__netievent_t *)event);
+               isc__nm_put_netievent_routeconnect(mgr, event);
+       } else {
+               atomic_init(&sock->active, false);
+               sock->tid = 0;
+               isc__nm_enqueue_ievent(&mgr->workers[sock->tid],
+                                      (isc__netievent_t *)event);
+       }
+       LOCK(&sock->lock);
+       while (sock->result == ISC_R_UNSET) {
+               WAIT(&sock->cond, &sock->lock);
+       }
+       atomic_store(&sock->active, true);
+       BROADCAST(&sock->scond);
+       UNLOCK(&sock->lock);
+
+       return (sock->result);
+#else  /* USE_ROUTE_SOCKET */
+       UNUSED(mgr);
+       UNUSED(cb);
+       UNUSED(cbarg);
+       UNUSED(extrahandlesize);
+       return (ISC_R_NOTIMPLEMENTED);
+#endif /* USE_ROUTE_SOCKET */
+}
+
 /*
  * Asynchronous 'udplisten' call handler: start listening on a UDP socket.
  */
@@ -338,8 +555,8 @@ udp_recv_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
        isc__nm_uvreq_t *req = NULL;
        uint32_t maxudp;
        bool free_buf;
-       isc_sockaddr_t sockaddr;
        isc_result_t result;
+       isc_sockaddr_t sockaddr, *sa = NULL;
 
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(sock->tid == isc_nm_tid());
@@ -398,10 +615,13 @@ udp_recv_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
                goto free;
        }
 
-       result = isc_sockaddr_fromsockaddr(&sockaddr, addr);
-       RUNTIME_CHECK(result == ISC_R_SUCCESS);
+       if (!sock->route_sock) {
+               result = isc_sockaddr_fromsockaddr(&sockaddr, addr);
+               RUNTIME_CHECK(result == ISC_R_SUCCESS);
+               sa = &sockaddr;
+       }
 
-       req = isc__nm_get_read_req(sock, &sockaddr);
+       req = isc__nm_get_read_req(sock, sa);
 
        /*
         * The callback will be called synchronously, because result is