2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-31 14:35:26 +00:00

PROXYv2 over UDP transport

This commit adds a new transport that supports PROXYv2 over UDP. It is
built on top of PROXYv2 handling code (just like PROXY Stream). It
works by processing and stripping the PROXYv2 headers at the beginning
of a datagram (when accepting a datagram) or by placing a PROXYv2
header to the beginning of an outgoing datagram.

The transport is built in such a way that incoming datagrams are being
handled with minimal memory allocations and copying.
This commit is contained in:
Artem Boldariev
2023-07-12 15:25:38 +03:00
parent 69995bc7b7
commit 4a88fc9d5b
6 changed files with 1004 additions and 8 deletions

View File

@@ -107,6 +107,7 @@ libisc_la_SOURCES = \
netmgr/netmgr-int.h \
netmgr/netmgr.c \
netmgr/proxystream.c \
netmgr/proxyudp.c \
netmgr/socket.c \
netmgr/streamdns.c \
netmgr/tcp.c \

View File

@@ -326,6 +326,26 @@ isc_nm_routeconnect(isc_nm_t *mgr, isc_nm_cb_t cb, void *cbarg);
* are not supported.
*/
isc_result_t
isc_nm_listenproxyudp(isc_nm_t *mgr, uint32_t workers, isc_sockaddr_t *iface,
isc_nm_recv_cb_t cb, void *cbarg, isc_nmsocket_t **sockp);
/*%<
* The same as `isc_nm_listenudp()`, but PROXYv2 headers are
* expected at the beginning of the received datagrams.
*/
void
isc_nm_proxyudpconnect(isc_nm_t *mgr, isc_sockaddr_t *local,
isc_sockaddr_t *peer, isc_nm_cb_t cb, void *cbarg,
unsigned int timeout,
isc_nm_proxyheader_info_t *proxy_info);
/*%<
* The same as `isc_nm_udpconnect()`, but PROXYv2 headers are added
* at the beginning of each sent datagram. The PROXYv2 headers are
* created using the data from the `proxy_info` object. If the
* object is omitted, then LOCAL PROXYv2 headers are used.
*/
void
isc_nm_stoplistening(isc_nmsocket_t *sock);
/*%<
@@ -598,7 +618,7 @@ isc_nm_streamdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local,
unsigned int timeout, isc_tlsctx_t *tlsctx,
isc_tlsctx_client_session_cache_t *client_sess_cache,
isc_nm_proxy_type_t proxy_type,
isc_nm_proxyheader_info_t *proxy_info);
isc_nm_proxyheader_info_t *proxy_info);
/*%<
* Establish a DNS client connection via a TCP or TLS connection, bound to
* the address 'local' and connected to the address 'peer'.

View File

@@ -99,6 +99,7 @@ typedef enum isc_nmsocket_type {
isc_nm_httpsocket = 1 << 4,
isc_nm_streamdnssocket = 1 << 5,
isc_nm_proxystreamsocket = 1 << 6,
isc_nm_proxyudpsocket = 1 << 7,
isc_nm_maxsocket,
isc_nm_udplistener, /* Aggregate of nm_udpsocks */
@@ -106,7 +107,8 @@ typedef enum isc_nmsocket_type {
isc_nm_tlslistener,
isc_nm_httplistener,
isc_nm_streamdnslistener,
isc_nm_proxystreamlistener
isc_nm_proxystreamlistener,
isc_nm_proxyudplistener
} isc_nmsocket_type;
typedef isc_nmsocket_type isc_nmsocket_type_t;

View File

@@ -249,6 +249,7 @@ struct isc_nmhandle {
isc_sockaddr_t peer;
isc_sockaddr_t local;
bool proxy_is_unspec;
struct isc_nmhandle *proxy_udphandle;
isc_nm_opaquecb_t doreset; /* reset extra callback, external */
isc_nm_opaquecb_t dofree; /* free extra callback, external */
#if ISC_NETMGR_TRACE
@@ -553,6 +554,8 @@ struct isc_nmsocket {
} proxy2;
bool header_processed;
bool extra_processed; /* data arrived past header processed */
isc_nmsocket_t **udp_server_socks; /* UDP sockets */
size_t udp_server_socks_num;
} proxy;
/*%
@@ -1237,6 +1240,45 @@ isc__nmhandle_proxystream_get_selected_alpn(isc_nmhandle_t *handle,
const unsigned char **alpn,
unsigned int *alpnlen);
void
isc__nm_proxyudp_failed_read_cb(isc_nmsocket_t *sock, const isc_result_t result,
const bool async);
void
isc__nm_proxyudp_stoplistening(isc_nmsocket_t *listener);
void
isc__nm_proxyudp_cleanup_data(isc_nmsocket_t *sock);
void
isc__nmhandle_proxyudp_cleartimeout(isc_nmhandle_t *handle);
void
isc__nmhandle_proxyudp_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
void
isc__nmhandle_proxyudp_setwritetimeout(isc_nmhandle_t *handle,
uint64_t write_timeout);
bool
isc__nmsocket_proxyudp_timer_running(isc_nmsocket_t *sock);
void
isc__nmsocket_proxyudp_timer_restart(isc_nmsocket_t *sock);
void
isc__nmsocket_proxyudp_timer_stop(isc_nmsocket_t *sock);
void
isc__nm_proxyudp_close(isc_nmsocket_t *sock);
void
isc__nm_proxyudp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
void
isc__nm_proxyudp_send(isc_nmhandle_t *handle, isc_region_t *region,
isc_nm_cb_t cb, void *cbarg);
void
isc__nm_incstats(isc_nmsocket_t *sock, isc__nm_statid_t id);
/*%<

View File

@@ -324,6 +324,9 @@ isc_nmhandle_setwritetimeout(isc_nmhandle_t *handle, uint64_t write_timeout) {
isc__nmhandle_proxystream_setwritetimeout(handle,
write_timeout);
break;
case isc_nm_proxyudpsocket:
isc__nmhandle_proxyudp_setwritetimeout(handle, write_timeout);
break;
default:
UNREACHABLE();
break;
@@ -475,6 +478,7 @@ nmsocket_cleanup(void *arg) {
#endif
isc__nm_streamdns_cleanup_data(sock);
isc__nm_proxystream_cleanup_data(sock);
isc__nm_proxyudp_cleanup_data(sock);
if (sock->barriers_initialised) {
isc_barrier_destroy(&sock->listen_barrier);
@@ -610,6 +614,9 @@ isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG) {
case isc_nm_proxystreamsocket:
isc__nm_proxystream_close(sock);
return;
case isc_nm_proxyudpsocket:
isc__nm_proxyudp_close(sock);
return;
default:
break;
}
@@ -655,7 +662,8 @@ isc_nmsocket_close(isc_nmsocket_t **sockp) {
(*sockp)->type == isc_nm_streamdnslistener ||
(*sockp)->type == isc_nm_tlslistener ||
(*sockp)->type == isc_nm_httplistener ||
(*sockp)->type == isc_nm_proxystreamlistener);
(*sockp)->type == isc_nm_proxystreamlistener ||
(*sockp)->type == isc_nm_proxyudplistener);
isc__nmsocket_detach(sockp);
}
@@ -848,6 +856,7 @@ isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t const *peer,
switch (sock->type) {
case isc_nm_udpsocket:
case isc_nm_proxyudpsocket:
if (!sock->client) {
break;
}
@@ -951,6 +960,10 @@ nmhandle_destroy(isc_nmhandle_t *handle) {
sock->statichandle = NULL;
}
if (handle->proxy_udphandle != NULL) {
isc_nmhandle_detach(&handle->proxy_udphandle);
}
ISC_LIST_UNLINK(sock->active_handles, handle, active_link);
if (sock->closehandle_cb == NULL) {
@@ -1045,6 +1058,9 @@ isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, bool async) {
case isc_nm_proxystreamsocket:
isc__nm_proxystream_failed_read_cb(sock, result, async);
return;
case isc_nm_proxyudpsocket:
isc__nm_proxyudp_failed_read_cb(sock, result, async);
return;
default:
UNREACHABLE();
}
@@ -1151,6 +1167,9 @@ isc__nmsocket_timer_restart(isc_nmsocket_t *sock) {
case isc_nm_proxystreamsocket:
isc__nmsocket_proxystream_timer_restart(sock);
return;
case isc_nm_proxyudpsocket:
isc__nmsocket_proxyudp_timer_restart(sock);
return;
default:
break;
}
@@ -1196,6 +1215,8 @@ isc__nmsocket_timer_running(isc_nmsocket_t *sock) {
return (isc__nmsocket_streamdns_timer_running(sock));
case isc_nm_proxystreamsocket:
return (isc__nmsocket_proxystream_timer_running(sock));
case isc_nm_proxyudpsocket:
return (isc__nmsocket_proxyudp_timer_running(sock));
default:
break;
}
@@ -1230,6 +1251,9 @@ isc__nmsocket_timer_stop(isc_nmsocket_t *sock) {
case isc_nm_proxystreamsocket:
isc__nmsocket_proxystream_timer_stop(sock);
return;
case isc_nm_proxyudpsocket:
isc__nmsocket_proxyudp_timer_stop(sock);
return;
default:
break;
}
@@ -1407,6 +1431,9 @@ isc_nmhandle_cleartimeout(isc_nmhandle_t *handle) {
case isc_nm_proxystreamsocket:
isc__nmhandle_proxystream_cleartimeout(handle);
return;
case isc_nm_proxyudpsocket:
isc__nmhandle_proxyudp_cleartimeout(handle);
return;
default:
handle->sock->read_timeout = 0;
@@ -1436,6 +1463,9 @@ isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
case isc_nm_proxystreamsocket:
isc__nmhandle_proxystream_settimeout(handle, timeout);
return;
case isc_nm_proxyudpsocket:
isc__nmhandle_proxyudp_settimeout(handle, timeout);
return;
default:
handle->sock->read_timeout = timeout;
isc__nmsocket_timer_restart(handle->sock);
@@ -1595,6 +1625,9 @@ isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb,
case isc_nm_proxystreamsocket:
isc__nm_proxystream_send(handle, region, cb, cbarg);
break;
case isc_nm_proxyudpsocket:
isc__nm_proxyudp_send(handle, region, cb, cbarg);
break;
default:
UNREACHABLE();
}
@@ -1645,6 +1678,9 @@ isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
case isc_nm_proxystreamsocket:
isc__nm_proxystream_read(handle, cb, cbarg);
break;
case isc_nm_proxyudpsocket:
isc__nm_proxyudp_read(handle, cb, cbarg);
break;
default:
UNREACHABLE();
}
@@ -1662,6 +1698,7 @@ cancelread_cb(void *arg) {
switch (handle->sock->type) {
case isc_nm_udpsocket:
case isc_nm_proxyudpsocket:
case isc_nm_streamdnssocket:
case isc_nm_httpsocket:
isc__nm_failed_read_cb(handle->sock, ISC_R_CANCELED, false);
@@ -1738,6 +1775,9 @@ isc_nm_stoplistening(isc_nmsocket_t *sock) {
case isc_nm_proxystreamlistener:
isc__nm_proxystream_stoplistening(sock);
break;
case isc_nm_proxyudplistener:
isc__nm_proxyudp_stoplistening(sock);
break;
default:
UNREACHABLE();
}
@@ -1751,7 +1791,8 @@ isc__nmsocket_stop(isc_nmsocket_t *listener) {
REQUIRE(listener->type == isc_nm_httplistener ||
listener->type == isc_nm_tlslistener ||
listener->type == isc_nm_streamdnslistener ||
listener->type == isc_nm_proxystreamlistener);
listener->type == isc_nm_proxystreamlistener ||
listener->type == isc_nm_proxyudplistener);
REQUIRE(!listener->closing);
listener->closing = true;
@@ -2090,6 +2131,7 @@ isc_nm_bad_request(isc_nmhandle_t *handle) {
switch (sock->type) {
case isc_nm_udpsocket:
case isc_nm_proxyudpsocket:
return;
case isc_nm_tcpsocket:
case isc_nm_streamdnssocket:
@@ -2146,6 +2188,7 @@ get_proxy_handle(isc_nmhandle_t *handle) {
switch (sock->type) {
case isc_nm_proxystreamsocket:
case isc_nm_proxyudpsocket:
return (handle);
#ifdef HAVE_LIBNGHTTP2
case isc_nm_httpsocket:
@@ -2205,8 +2248,8 @@ isc_nmhandle_real_peeraddr(isc_nmhandle_t *handle) {
if (isc_nmhandle_is_stream(proxyhandle)) {
addr = isc_nmhandle_peeraddr(proxyhandle->sock->outerhandle);
} else {
/* TODO: PROXY over UDP */
UNREACHABLE();
INSIST(proxyhandle->sock->type == isc_nm_proxyudpsocket);
addr = isc_nmhandle_peeraddr(proxyhandle->proxy_udphandle);
}
return (addr);
@@ -2228,8 +2271,8 @@ isc_nmhandle_real_localaddr(isc_nmhandle_t *handle) {
if (isc_nmhandle_is_stream(proxyhandle)) {
addr = isc_nmhandle_localaddr(proxyhandle->sock->outerhandle);
} else {
/* TODO: PROXY over UDP */
UNREACHABLE();
INSIST(proxyhandle->sock->type == isc_nm_proxyudpsocket);
addr = isc_nmhandle_localaddr(proxyhandle->proxy_udphandle);
}
return (addr);
@@ -2319,6 +2362,7 @@ isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) {
break;
#endif /* HAVE_LIBNGHTTP2 */
case isc_nm_udpsocket:
case isc_nm_proxyudpsocket:
case isc_nm_streamdnssocket:
return;
break;
@@ -2811,6 +2855,10 @@ nmsocket_type_totext(isc_nmsocket_type type) {
return ("isc_nm_proxystreamlistener");
case isc_nm_proxystreamsocket:
return ("isc_nm_proxystreamsocket");
case isc_nm_proxyudplistener:
return ("isc_nm_proxyudplistener");
case isc_nm_proxyudpsocket:
return ("isc_nm_proxyudpsocket");
default:
UNREACHABLE();
}

883
lib/isc/netmgr/proxyudp.c Normal file
View File

@@ -0,0 +1,883 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MP1 was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <isc/netmgr.h>
#include "netmgr-int.h"
typedef struct proxyudp_send_req {
isc_nm_cb_t cb; /* send callback */
void *cbarg; /* send callback argument */
isc_nmhandle_t *proxyhandle; /* socket handle */
isc_buffer_t *outbuf; /* PROXY header followed by data (client only) */
} proxyudp_send_req_t;
static bool
proxyudp_closing(isc_nmsocket_t *sock);
static void
proxyudp_stop_reading(isc_nmsocket_t *sock);
static void
proxyudp_on_header_data_cb(const isc_result_t result,
const isc_proxy2_command_t cmd, const int socktype,
const isc_sockaddr_t *restrict src_addr,
const isc_sockaddr_t *restrict dst_addr,
const isc_region_t *restrict tlvs,
const isc_region_t *restrict extra, void *cbarg);
static isc_nmsocket_t *
proxyudp_sock_new(isc__networker_t *worker, const isc_nmsocket_type_t type,
isc_sockaddr_t *addr, const bool is_server);
static void
proxyudp_read_cb(isc_nmhandle_t *handle, isc_result_t result,
isc_region_t *region, void *cbarg);
static void
proxyudp_call_connect_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle,
isc_result_t result);
static void
proxyudp_try_close_unused(isc_nmsocket_t *sock);
static void
proxyudp_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
static void
stop_proxyudp_child_job(void *arg);
static void
stop_proxyudp_child(isc_nmsocket_t *sock);
static void
proxyudp_clear_proxy_header_data(isc_nmsocket_t *sock);
static proxyudp_send_req_t *
proxyudp_get_send_req(isc_mem_t *mctx, isc_nmsocket_t *sock,
isc_nmhandle_t *proxyhandle, isc_region_t *client_data,
isc_nm_cb_t cb, void *cbarg);
static void
proxyudp_put_send_req(isc_mem_t *mctx, proxyudp_send_req_t *send_req,
const bool force_destroy);
static void
proxyudp_send_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
static bool
proxyudp_closing(isc_nmsocket_t *sock) {
return (isc__nmsocket_closing(sock) ||
(sock->client && sock->outerhandle == NULL) ||
(sock->outerhandle != NULL &&
isc__nmsocket_closing(sock->outerhandle->sock)));
}
static void
proxyudp_stop_reading(isc_nmsocket_t *sock) {
isc__nmsocket_timer_stop(sock);
if (sock->outerhandle != NULL) {
isc__nm_stop_reading(sock->outerhandle->sock);
}
}
void
isc__nm_proxyudp_failed_read_cb(isc_nmsocket_t *sock, const isc_result_t result,
const bool async) {
REQUIRE(VALID_NMSOCK(sock));
REQUIRE(result != ISC_R_SUCCESS);
REQUIRE(sock->tid == isc_tid());
/*
* For UDP server socket, we don't have child socket via
* "accept", so we:
* - we continue to read
* - we don't clear the callbacks
* - we don't destroy it (only stoplistening could do that)
*/
if (sock->client) {
proxyudp_stop_reading(sock);
}
if (sock->reading) {
sock->reading = false;
if (sock->recv_cb != NULL) {
isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
isc__nm_readcb(sock, req, result, async);
}
}
if (sock->client) {
isc__nmsocket_clearcb(sock);
isc__nmsocket_prep_destroy(sock);
}
}
static void
proxyudp_on_header_data_cb(const isc_result_t result,
const isc_proxy2_command_t cmd, const int socktype,
const isc_sockaddr_t *restrict src_addr,
const isc_sockaddr_t *restrict dst_addr,
const isc_region_t *restrict tlvs,
const isc_region_t *restrict extra, void *cbarg) {
isc_nmhandle_t *proxyhandle = (isc_nmhandle_t *)cbarg;
isc_nmsocket_t *proxysock = proxyhandle->sock;
if (result != ISC_R_SUCCESS) {
isc__nm_proxyudp_failed_read_cb(proxysock, result, false);
return;
} else if (extra == NULL) {
/* a PROXYv2 header with no data is unexpected */
goto unexpected;
}
/* Process header data */
if (cmd == ISC_PROXY2_CMD_LOCAL) {
proxyhandle->proxy_is_unspec = true;
} else if (cmd == ISC_PROXY2_CMD_PROXY) {
switch (socktype) {
case 0:
/*
* Treat unsupported addresses (aka AF_UNSPEC)
* as LOCAL.
*/
proxyhandle->proxy_is_unspec = true;
break;
case SOCK_STREAM:
/*
* In some cases proxies can do protocol conversion. In
* this case, the original request might have arrived
* over TCP-based transport and, thus, the PROXYv2
* header can contain SOCK_STREAM, while for UDP one
* would expect SOCK_DGRAM. That might be unexpected,
* but, as the main idea behind PROXYv2 is to carry the
* original endpoint information to back-ends, that is
* fine.
*/
case SOCK_DGRAM:
INSIST(isc_sockaddr_pf(src_addr) ==
isc_sockaddr_pf(dst_addr));
/* We will treat AF_UNIX as unspec */
if (isc_sockaddr_pf(src_addr) == AF_UNIX) {
proxyhandle->proxy_is_unspec = true;
} else {
if (!isc__nm_valid_proxy_addresses(src_addr,
dst_addr))
{
goto unexpected;
}
}
break;
default:
goto unexpected;
}
}
if (!proxyhandle->proxy_is_unspec) {
INSIST(src_addr != NULL);
INSIST(dst_addr != NULL);
proxyhandle->local = *dst_addr;
proxyhandle->peer = *src_addr;
}
isc__nm_received_proxy_header_log(proxyhandle, cmd, socktype, src_addr,
dst_addr, tlvs);
proxysock->recv_cb(proxyhandle, result, (isc_region_t *)extra,
proxysock->recv_cbarg);
return;
unexpected:
isc__nm_proxyudp_failed_read_cb(proxysock, ISC_R_UNEXPECTED, false);
};
static isc_nmsocket_t *
proxyudp_sock_new(isc__networker_t *worker, const isc_nmsocket_type_t type,
isc_sockaddr_t *addr, const bool is_server) {
isc_nmsocket_t *sock;
INSIST(type == isc_nm_proxyudpsocket ||
type == isc_nm_proxyudplistener);
sock = isc_mem_get(worker->mctx, sizeof(*sock));
isc__nmsocket_init(sock, worker, type, addr, NULL);
sock->result = ISC_R_UNSET;
if (type == isc_nm_proxyudpsocket) {
uint32_t initial = 0;
isc_nm_gettimeouts(worker->netmgr, &initial, NULL, NULL, NULL);
sock->read_timeout = initial;
sock->client = !is_server;
sock->connecting = !is_server;
if (!is_server) {
isc_buffer_allocate(worker->mctx,
&sock->proxy.proxy2.outbuf,
ISC_NM_PROXY2_DEFAULT_BUFFER_SIZE);
}
} else if (type == isc_nm_proxyudplistener) {
size_t nworkers = worker->netmgr->nloops;
sock->proxy.udp_server_socks_num = nworkers;
sock->proxy.udp_server_socks = isc_mem_cget(
worker->mctx, nworkers, sizeof(isc_nmsocket_t *));
}
return (sock);
}
static void
proxyudp_read_cb(isc_nmhandle_t *handle, isc_result_t result,
isc_region_t *region, void *cbarg) {
isc_nmsocket_t *sock = (isc_nmsocket_t *)cbarg;
isc_nmsocket_t *proxysock = NULL;
REQUIRE(VALID_NMSOCK(sock));
REQUIRE(VALID_NMHANDLE(handle));
if (sock->client) {
proxysock = sock;
} else {
INSIST(sock->type == isc_nm_proxyudplistener);
proxysock = sock->proxy.udp_server_socks[handle->sock->tid];
if (proxysock->outerhandle == NULL) {
isc_nmhandle_attach(handle, &proxysock->outerhandle);
}
proxysock->iface = isc_nmhandle_localaddr(handle);
proxysock->peer = isc_nmhandle_peeraddr(handle);
}
INSIST(proxysock->tid == isc_tid());
if (result != ISC_R_SUCCESS) {
if (!proxysock->client) {
goto failed;
}
if (result != ISC_R_TIMEDOUT) {
goto failed;
}
}
if (isc__nm_closing(proxysock->worker)) {
result = ISC_R_SHUTTINGDOWN;
goto failed;
} else if (proxyudp_closing(proxysock)) {
result = ISC_R_CANCELED;
goto failed;
}
/* Handle initial PROXY header data */
if (!proxysock->client) {
isc_nmhandle_t *proxyhandle = NULL;
proxysock->reading = false;
proxyhandle = isc__nmhandle_get(proxysock, &proxysock->peer,
&proxysock->iface);
isc_nmhandle_attach(handle, &proxyhandle->proxy_udphandle);
(void)isc_proxy2_header_handle_directly(
region, proxyudp_on_header_data_cb, proxyhandle);
isc_nmhandle_detach(&proxyhandle);
} else {
isc_nm_recv_cb_t recv_cb = NULL;
void *recv_cbarg = NULL;
recv_cb = proxysock->recv_cb;
recv_cbarg = proxysock->recv_cbarg;
if (result != ISC_R_TIMEDOUT) {
proxysock->reading = false;
proxyudp_stop_reading(proxysock);
}
recv_cb(proxysock->statichandle, result, region, recv_cbarg);
if (result == ISC_R_TIMEDOUT &&
!isc__nmsocket_timer_running(proxysock))
{
isc__nmsocket_clearcb(proxysock);
goto failed;
}
}
proxyudp_try_close_unused(proxysock);
return;
failed:
isc__nm_proxyudp_failed_read_cb(proxysock, result, false);
return;
}
isc_result_t
isc_nm_listenproxyudp(isc_nm_t *mgr, uint32_t workers, isc_sockaddr_t *iface,
isc_nm_recv_cb_t cb, void *cbarg,
isc_nmsocket_t **sockp) {
isc_result_t result;
isc_nmsocket_t *listener = NULL;
isc__networker_t *worker = &mgr->workers[isc_tid()];
REQUIRE(VALID_NM(mgr));
REQUIRE(isc_tid() == 0);
REQUIRE(sockp != NULL && *sockp == NULL);
if (isc__nm_closing(worker)) {
return (ISC_R_SHUTTINGDOWN);
}
listener = proxyudp_sock_new(worker, isc_nm_proxyudplistener, iface,
true);
listener->recv_cb = cb;
listener->recv_cbarg = cbarg;
for (size_t i = 0; i < listener->proxy.udp_server_socks_num; i++) {
listener->proxy.udp_server_socks[i] = proxyudp_sock_new(
&mgr->workers[i], isc_nm_proxyudpsocket, iface, true);
listener->proxy.udp_server_socks[i]->recv_cb =
listener->recv_cb;
listener->proxy.udp_server_socks[i]->recv_cbarg =
listener->recv_cbarg;
isc__nmsocket_attach(
listener,
&listener->proxy.udp_server_socks[i]->listener);
}
result = isc_nm_listenudp(mgr, workers, iface, proxyudp_read_cb,
listener, &listener->outer);
if (result == ISC_R_SUCCESS) {
listener->active = true;
listener->result = result;
listener->nchildren = listener->outer->nchildren;
*sockp = listener;
} else {
for (size_t i = 0; i < listener->proxy.udp_server_socks_num;
i++)
{
stop_proxyudp_child(
listener->proxy.udp_server_socks[i]);
}
listener->closed = true;
isc__nmsocket_detach(&listener);
}
return (result);
}
static void
proxyudp_call_connect_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle,
isc_result_t result) {
sock->connecting = false;
if (sock->connect_cb == NULL) {
return;
}
sock->connect_cb(handle, result, sock->connect_cbarg);
if (result != ISC_R_SUCCESS) {
isc__nmsocket_clearcb(handle->sock);
} else {
sock->connected = true;
}
}
static void
proxyudp_try_close_unused(isc_nmsocket_t *sock) {
/* try to close unused socket */
if (sock->statichandle == NULL && sock->proxy.nsending == 0) {
if (sock->client) {
isc__nmsocket_prep_destroy(sock);
} else if (sock->outerhandle) {
isc_nmhandle_detach(&sock->outerhandle);
}
}
}
static void
proxyudp_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
isc_nmsocket_t *sock = (isc_nmsocket_t *)cbarg;
isc_nmhandle_t *proxyhandle = NULL;
REQUIRE(VALID_NMSOCK(sock));
sock->tid = isc_tid();
if (result != ISC_R_SUCCESS) {
goto error;
}
INSIST(VALID_NMHANDLE(handle));
sock->iface = isc_nmhandle_localaddr(handle);
sock->peer = isc_nmhandle_peeraddr(handle);
isc_nmhandle_attach(handle, &sock->outerhandle);
handle->sock->proxy.sock = sock;
sock->active = true;
sock->connected = true;
sock->connecting = false;
proxyhandle = isc__nmhandle_get(sock, &sock->peer, &sock->iface);
proxyudp_call_connect_cb(sock, proxyhandle, ISC_R_SUCCESS);
isc_nmhandle_detach(&proxyhandle);
proxyudp_try_close_unused(sock);
isc__nmsocket_detach(&handle->sock->proxy.sock);
return;
error:
proxyhandle = isc__nmhandle_get(sock, NULL, NULL);
sock->closed = true;
proxyudp_call_connect_cb(sock, proxyhandle, result);
isc_nmhandle_detach(&proxyhandle);
isc__nmsocket_detach(&sock);
}
void
isc_nm_proxyudpconnect(isc_nm_t *mgr, isc_sockaddr_t *local,
isc_sockaddr_t *peer, isc_nm_cb_t cb, void *cbarg,
unsigned int timeout,
isc_nm_proxyheader_info_t *proxy_info) {
isc_result_t result = ISC_R_FAILURE;
isc_nmsocket_t *nsock = NULL;
isc__networker_t *worker = &mgr->workers[isc_tid()];
REQUIRE(VALID_NM(mgr));
if (isc__nm_closing(worker)) {
cb(NULL, ISC_R_SHUTTINGDOWN, cbarg);
return;
}
nsock = proxyudp_sock_new(worker, isc_nm_proxyudpsocket, local, false);
nsock->connect_cb = cb;
nsock->connect_cbarg = cbarg;
nsock->read_timeout = timeout;
nsock->connecting = true;
if (proxy_info == NULL) {
result = isc_proxy2_make_header(nsock->proxy.proxy2.outbuf,
ISC_PROXY2_CMD_LOCAL, 0, NULL,
NULL, NULL);
} else if (proxy_info->complete) {
isc_buffer_putmem(nsock->proxy.proxy2.outbuf,
proxy_info->complete_header.base,
proxy_info->complete_header.length);
result = ISC_R_SUCCESS;
} else if (!proxy_info->complete) {
result = isc_proxy2_make_header(
nsock->proxy.proxy2.outbuf, ISC_PROXY2_CMD_PROXY,
SOCK_DGRAM, &proxy_info->proxy_info.src_addr,
&proxy_info->proxy_info.dst_addr,
&proxy_info->proxy_info.tlv_data);
}
RUNTIME_CHECK(result == ISC_R_SUCCESS);
isc_nm_udpconnect(mgr, local, peer, proxyudp_connect_cb, nsock,
timeout);
}
/*
* Asynchronous 'udpstop' call handler: stop listening on a UDP socket.
*/
static void
stop_proxyudp_child_job(void *arg) {
isc_nmsocket_t *listener = NULL;
isc_nmsocket_t *sock = arg;
uint32_t tid = 0;
if (sock == NULL) {
return;
}
INSIST(VALID_NMSOCK(sock));
INSIST(sock->tid == isc_tid());
listener = sock->listener;
sock->listener = NULL;
INSIST(VALID_NMSOCK(listener));
INSIST(listener->type == isc_nm_proxyudplistener);
if (sock->outerhandle != NULL) {
proxyudp_stop_reading(sock);
isc_nmhandle_detach(&sock->outerhandle);
}
tid = sock->tid;
isc__nmsocket_prep_destroy(sock);
isc__nmsocket_detach(&listener->proxy.udp_server_socks[tid]);
isc__nmsocket_detach(&listener);
}
static void
stop_proxyudp_child(isc_nmsocket_t *sock) {
REQUIRE(VALID_NMSOCK(sock));
if (sock->tid == 0) {
stop_proxyudp_child_job(sock);
} else {
isc_async_run(sock->worker->loop, stop_proxyudp_child_job,
sock);
}
}
void
isc__nm_proxyudp_stoplistening(isc_nmsocket_t *listener) {
REQUIRE(VALID_NMSOCK(listener));
REQUIRE(listener->type == isc_nm_proxyudplistener);
REQUIRE(listener->proxy.sock == NULL);
isc__nmsocket_stop(listener);
listener->active = false;
for (size_t i = 1; i < listener->proxy.udp_server_socks_num; i++) {
stop_proxyudp_child(listener->proxy.udp_server_socks[i]);
}
stop_proxyudp_child(listener->proxy.udp_server_socks[0]);
}
static void
proxyudp_clear_proxy_header_data(isc_nmsocket_t *sock) {
if (sock->client && sock->proxy.proxy2.outbuf != NULL) {
isc_buffer_free(&sock->proxy.proxy2.outbuf);
}
}
void
isc__nm_proxyudp_cleanup_data(isc_nmsocket_t *sock) {
switch (sock->type) {
case isc_nm_proxyudpsocket:
if (sock->proxy.send_req != NULL) {
proxyudp_put_send_req(sock->worker->mctx,
sock->proxy.send_req, true);
}
proxyudp_clear_proxy_header_data(sock);
break;
case isc_nm_proxyudplistener:
isc_mem_cput(sock->worker->mctx, sock->proxy.udp_server_socks,
sock->proxy.udp_server_socks_num,
sizeof(isc_nmsocket_t *));
break;
case isc_nm_udpsocket:
INSIST(sock->proxy.sock == NULL);
break;
default:
break;
};
}
void
isc__nmhandle_proxyudp_cleartimeout(isc_nmhandle_t *handle) {
isc_nmsocket_t *sock = NULL;
REQUIRE(VALID_NMHANDLE(handle));
REQUIRE(VALID_NMSOCK(handle->sock));
REQUIRE(handle->sock->type == isc_nm_proxyudpsocket);
sock = handle->sock;
if (sock->outerhandle != NULL) {
INSIST(VALID_NMHANDLE(sock->outerhandle));
isc_nmhandle_cleartimeout(sock->outerhandle);
}
}
void
isc__nmhandle_proxyudp_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
isc_nmsocket_t *sock = NULL;
REQUIRE(VALID_NMHANDLE(handle));
REQUIRE(VALID_NMSOCK(handle->sock));
REQUIRE(handle->sock->type == isc_nm_proxyudpsocket);
sock = handle->sock;
if (sock->outerhandle != NULL) {
INSIST(VALID_NMHANDLE(sock->outerhandle));
isc_nmhandle_settimeout(sock->outerhandle, timeout);
}
}
void
isc__nmhandle_proxyudp_setwritetimeout(isc_nmhandle_t *handle,
uint64_t write_timeout) {
isc_nmsocket_t *sock = NULL;
REQUIRE(VALID_NMHANDLE(handle));
REQUIRE(VALID_NMSOCK(handle->sock));
REQUIRE(handle->sock->type == isc_nm_proxyudpsocket);
sock = handle->sock;
if (sock->outerhandle != NULL) {
INSIST(VALID_NMHANDLE(sock->outerhandle));
isc_nmhandle_setwritetimeout(sock->outerhandle, write_timeout);
}
}
bool
isc__nmsocket_proxyudp_timer_running(isc_nmsocket_t *sock) {
REQUIRE(VALID_NMSOCK(sock));
REQUIRE(sock->type == isc_nm_proxyudpsocket);
if (sock->outerhandle != NULL) {
INSIST(VALID_NMHANDLE(sock->outerhandle));
REQUIRE(VALID_NMSOCK(sock->outerhandle->sock));
return (isc__nmsocket_timer_running(sock->outerhandle->sock));
}
return (false);
}
void
isc__nmsocket_proxyudp_timer_restart(isc_nmsocket_t *sock) {
REQUIRE(VALID_NMSOCK(sock));
REQUIRE(sock->type == isc_nm_proxyudpsocket);
if (sock->outerhandle != NULL) {
INSIST(VALID_NMHANDLE(sock->outerhandle));
REQUIRE(VALID_NMSOCK(sock->outerhandle->sock));
isc__nmsocket_timer_restart(sock->outerhandle->sock);
}
}
void
isc__nmsocket_proxyudp_timer_stop(isc_nmsocket_t *sock) {
REQUIRE(VALID_NMSOCK(sock));
REQUIRE(sock->type == isc_nm_proxyudpsocket);
if (sock->outerhandle != NULL) {
INSIST(VALID_NMHANDLE(sock->outerhandle));
REQUIRE(VALID_NMSOCK(sock->outerhandle->sock));
isc__nmsocket_timer_stop(sock->outerhandle->sock);
}
}
void
isc__nm_proxyudp_close(isc_nmsocket_t *sock) {
REQUIRE(VALID_NMSOCK(sock));
REQUIRE(sock->type == isc_nm_proxyudpsocket);
REQUIRE(sock->tid == isc_tid());
sock->closing = true;
/*
* At this point we're certain that there are no
* external references, we can close everything.
*/
proxyudp_stop_reading(sock);
sock->reading = false;
if (sock->outerhandle != NULL) {
isc_nmhandle_close(sock->outerhandle);
isc_nmhandle_detach(&sock->outerhandle);
}
if (sock->proxy.sock != NULL) {
isc__nmsocket_detach(&sock->proxy.sock);
}
/* Further cleanup performed in isc__nm_proxyudp_cleanup_data() */
sock->closed = true;
sock->active = false;
}
void
isc__nm_proxyudp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb,
void *cbarg) {
isc_nmsocket_t *sock = NULL;
REQUIRE(VALID_NMHANDLE(handle));
sock = handle->sock;
REQUIRE(VALID_NMSOCK(sock));
REQUIRE(sock->type == isc_nm_proxyudpsocket);
REQUIRE(sock->recv_handle == NULL);
REQUIRE(sock->tid == isc_tid());
sock->recv_cb = cb;
sock->recv_cbarg = cbarg;
sock->reading = true;
if (isc__nm_closing(sock->worker)) {
isc__nm_proxyudp_failed_read_cb(sock, ISC_R_SHUTTINGDOWN,
false);
return;
} else if (proxyudp_closing(sock)) {
isc__nm_proxyudp_failed_read_cb(sock, ISC_R_CANCELED, true);
return;
}
isc_nm_read(sock->outerhandle, proxyudp_read_cb, sock);
}
static proxyudp_send_req_t *
proxyudp_get_send_req(isc_mem_t *mctx, isc_nmsocket_t *sock,
isc_nmhandle_t *proxyhandle, isc_region_t *client_data,
isc_nm_cb_t cb, void *cbarg) {
proxyudp_send_req_t *send_req = NULL;
if (sock->proxy.send_req != NULL) {
/*
* We have a previously allocated object - let's use that.
* That should help reducing stress on the memory allocator.
*/
send_req = (proxyudp_send_req_t *)sock->proxy.send_req;
sock->proxy.send_req = NULL;
} else {
/* Allocate a new object. */
send_req = isc_mem_get(mctx, sizeof(*send_req));
*send_req = (proxyudp_send_req_t){ 0 };
}
/* Initialise the send request object */
send_req->cb = cb;
send_req->cbarg = cbarg;
isc_nmhandle_attach(proxyhandle, &send_req->proxyhandle);
if (client_data != NULL) {
isc_region_t header_region = { 0 };
INSIST(sock->client);
INSIST(sock->proxy.proxy2.outbuf != NULL);
isc_buffer_usedregion(sock->proxy.proxy2.outbuf,
&header_region);
INSIST(header_region.length > 0);
/* allocate the buffer if it has not been allocated yet */
if (send_req->outbuf == NULL) {
isc_buffer_allocate(mctx, &send_req->outbuf,
client_data->length +
header_region.length);
}
isc_buffer_putmem(send_req->outbuf, header_region.base,
header_region.length);
isc_buffer_putmem(send_req->outbuf, client_data->base,
client_data->length);
}
sock->proxy.nsending++;
return (send_req);
}
static void
proxyudp_put_send_req(isc_mem_t *mctx, proxyudp_send_req_t *send_req,
const bool force_destroy) {
if (send_req->outbuf != NULL) {
/* clear the buffer to reuse it further */
isc_buffer_clear(send_req->outbuf);
}
/*
* Attempt to put the object for reuse later if we are not
* wrapping up.
*/
if (!force_destroy) {
isc_nmsocket_t *sock = send_req->proxyhandle->sock;
sock->proxy.nsending--;
isc_nmhandle_detach(&send_req->proxyhandle);
if (sock->proxy.send_req == NULL) {
sock->proxy.send_req = send_req;
/*
* An object has been recycled,
* if not - we are going to destroy it.
*/
return;
}
} else {
if (send_req->outbuf != NULL) {
isc_buffer_free(&send_req->outbuf);
}
}
isc_mem_put(mctx, send_req, sizeof(*send_req));
}
static void
proxyudp_send_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
proxyudp_send_req_t *send_req = (proxyudp_send_req_t *)cbarg;
isc_mem_t *mctx;
isc_nm_cb_t cb;
void *send_cbarg;
isc_nmhandle_t *proxyhandle = NULL;
isc_nmsocket_t *sock = NULL;
REQUIRE(VALID_NMHANDLE(handle));
REQUIRE(VALID_NMHANDLE(send_req->proxyhandle));
REQUIRE(VALID_NMSOCK(send_req->proxyhandle->sock));
REQUIRE(send_req->proxyhandle->sock->tid == isc_tid());
mctx = send_req->proxyhandle->sock->worker->mctx;
cb = send_req->cb;
send_cbarg = send_req->cbarg;
isc_nmhandle_attach(send_req->proxyhandle, &proxyhandle);
isc__nmsocket_attach(proxyhandle->sock, &sock);
/* try to keep the send request object for reuse */
proxyudp_put_send_req(mctx, send_req, false);
cb(proxyhandle, result, send_cbarg);
isc_nmhandle_detach(&proxyhandle);
/*
* Try to close the client socket when we do not need it
* anymore. In the case of server socket - detach the underlying
* (UDP) handle when the socket is not being used anymore.
*/
proxyudp_try_close_unused(sock);
isc__nmsocket_detach(&sock);
}
void
isc__nm_proxyudp_send(isc_nmhandle_t *handle, isc_region_t *region,
isc_nm_cb_t cb, void *cbarg) {
isc_nmsocket_t *sock = NULL;
proxyudp_send_req_t *send_req = NULL;
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(VALID_NMHANDLE(handle));
REQUIRE(VALID_NMSOCK(handle->sock));
sock = handle->sock;
REQUIRE(sock->type == isc_nm_proxyudpsocket);
if (isc__nm_closing(sock->worker)) {
result = ISC_R_SHUTTINGDOWN;
} else if (proxyudp_closing(sock)) {
result = ISC_R_CANCELED;
}
if (result != ISC_R_SUCCESS) {
isc__nm_uvreq_t *uvreq = isc__nm_uvreq_get(sock);
isc_nmhandle_attach(handle, &uvreq->handle);
uvreq->cb.send = cb;
uvreq->cbarg = cbarg;
isc__nm_failed_send_cb(sock, uvreq, result, true);
return;
}
send_req = proxyudp_get_send_req(sock->worker->mctx, sock, handle,
(sock->client ? region : NULL), cb,
cbarg);
if (sock->client) {
isc_region_t send_data = { 0 };
isc_buffer_usedregion(send_req->outbuf, &send_data);
isc_nm_send(sock->outerhandle, &send_data, proxyudp_send_cb,
send_req);
} else {
isc_nm_send(handle->proxy_udphandle, region, proxyudp_send_cb,
send_req);
}
}