2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-09-03 16:15:27 +00:00

netmgr: add isc_nm_routeconnect()

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.
This commit is contained in:
Evan Hunt
2021-10-02 14:52:46 -07:00
parent a9e35ea936
commit 8c51a32e5c
4 changed files with 270 additions and 8 deletions

View 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);
/*%<

View 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);

View 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);

View File

@@ -31,6 +31,32 @@
#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;
}
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