From d119d666b3e694e532f287424516715d47a1ec79 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Thu, 16 Mar 2023 12:50:04 +0200 Subject: [PATCH] PROXY Stream transport This commit adds a new stream-based transport with an interface compatible with TCP. The transport is built on top of TCP transport and the new PROXYv2 handling code. Despite being built on top of TCP, it can be easily extended to work on top of any TCP-like stream-based transport. The intention of having this transport is to add PROXYv2 support into all existing stream-based DNS transport (DNS over TCP, DNS over TLS, DNS over HTTP) by making the work on top of this new transport. The idea behind the transport is simple after accepting the connection or connecting to a remote server it enters PROXYv2 handling mode: that is, it either attempts to read (when accepting the connection) or send (when establishing a connection) a PROXYv2 header. After that it works like a mere wrapper on top of the underlying stream-based transport (TCP). --- lib/isc/Makefile.am | 1 + lib/isc/include/isc/netmgr.h | 114 ++++ lib/isc/include/isc/types.h | 4 +- lib/isc/netmgr/netmgr-int.h | 92 +++ lib/isc/netmgr/netmgr.c | 351 ++++++++++- lib/isc/netmgr/proxystream.c | 1089 ++++++++++++++++++++++++++++++++++ 6 files changed, 1647 insertions(+), 4 deletions(-) create mode 100644 lib/isc/netmgr/proxystream.c diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am index 033b0f4435..19101bb563 100644 --- a/lib/isc/Makefile.am +++ b/lib/isc/Makefile.am @@ -106,6 +106,7 @@ libisc_la_SOURCES = \ $(libisc_la_HEADERS) \ netmgr/netmgr-int.h \ netmgr/netmgr.c \ + netmgr/proxystream.c \ netmgr/socket.c \ netmgr/streamdns.c \ netmgr/tcp.c \ diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h index 8bcbb1da7f..2bc154bc5d 100644 --- a/lib/isc/include/isc/netmgr.h +++ b/lib/isc/include/isc/netmgr.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -87,6 +88,21 @@ typedef void (*isc_nm_opaquecb_t)(void *arg); * callbacks. */ +typedef struct isc_nm_proxyheader_info { + bool complete; + union { + isc_region_t complete_header; /* complete header data */ + struct { + isc_sockaddr_t src_addr; + isc_sockaddr_t dst_addr; + isc_region_t tlv_data; + } proxy_info; /* information to put into the new header */ + }; +} isc_nm_proxyheader_info_t; +/*%< + * Information to put into the PROXYv2 header when establishing a connection. + */ + void isc_netmgr_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, isc_nm_t **netgmrp); /*%< @@ -229,6 +245,29 @@ isc_nmhandle_localaddr(isc_nmhandle_t *handle); * Return the local address for the given handle. */ +isc_sockaddr_t +isc_nmhandle_real_peeraddr(isc_nmhandle_t *handle); +/*%< + * Return the real (as seen by the OS) peer address for the given + * handle even when PROXY protocol is used. + * + * NOTE: This function is intended mostly for a) implementing PROXYv2 + * access control facilities and b) logging. Using it for anything + * else WILL break PROXYv2 support. Please consider using + * 'isc_nmhandle_peeraddr()' instead. + */ +isc_sockaddr_t +isc_nmhandle_real_localaddr(isc_nmhandle_t *handle); +/*%< + * Return the real (as seen by the OS) local address for the given + * handle even when PROXY protocol is used. + * + * NOTE: This function is intended mostly for a) implementing PROXYv2 + * access control facilities and b) logging. Using it for anything + * else WILL break PROXYv2 support. Please consider using + * 'isc_nmhandle_localaddr()' instead. + */ + isc_nm_t * isc_nmhandle_netmgr(isc_nmhandle_t *handle); /*%< @@ -391,6 +430,68 @@ isc_nm_listenstreamdns(isc_nm_t *mgr, uint32_t workers, isc_sockaddr_t *iface, * 'quota' is passed to isc_nm_listentcp() when opening the raw TCP socket. */ +isc_result_t +isc_nm_listenproxystream(isc_nm_t *mgr, uint32_t workers, isc_sockaddr_t *iface, + isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + int backlog, isc_quota_t *quota, + isc_nmsocket_t **sockp); +/*%< + * Start listening for data preceded by a PROXYv2 header over the + * TCP on interface 'iface', using net manager 'mgr'. + * + * On success, 'sockp' will be updated to contain a new listening TCP + * socket. + * + * When connection is accepted on the socket, 'accept_cb' will be called with + * 'accept_cbarg' as its argument. The callback is expected to start a read. + * + * If 'quota' is not NULL, then the socket is attached to the specified + * quota. This allows us to enforce TCP client quota limits. + */ + +void +isc_nm_proxystreamconnect(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); +/*%< + * Create a TCP socket using netmgr 'mgr', bind it to the address + * 'local', and connect it to the address 'peer'. Right after the + * connection has been established, send PROXYv2 header using the + * information provided via the 'proxy_info' to the remote peer. Then + * the connection is considered established. + * + * If 'proxy_info' is omitted, then a LOCAL PROXYv2 header is sent. + * + * When the connection is established or has timed out, call 'cb' with + * argument 'cbarg'. + * + * 'timeout' specifies the timeout interval in milliseconds. + * + * The connected socket can only be accessed via the handle passed to + * 'cb'. + */ + +void +isc_nm_proxyheader_info_init(isc_nm_proxyheader_info_t *restrict info, + isc_sockaddr_t *restrict src_addr, + isc_sockaddr_t *restrict dst_addr, + isc_region_t *restrict tlv_data); +/*%< + * Initialize a 'isc_nm_proxyheader_info_t' object with user + * provided addresses and a TLVs blob, that can be omitted (the rest + * of the data is REQUIRE()d). + */ + +void +isc_nm_proxyheader_info_init_complete(isc_nm_proxyheader_info_t *restrict info, + isc_region_t *restrict header_data); +/*%< + * Initialize a 'isc_nm_proxyheader_info_t' with user provided data + * blob (e.g. a pre-rendered PROXYv2 header for forwarding or + * testing). + */ + void isc_nm_settimeouts(isc_nm_t *mgr, uint32_t init, uint32_t idle, uint32_t keepalive, uint32_t advertised); @@ -499,6 +600,19 @@ isc_nm_is_http_handle(isc_nmhandle_t *handle); * 'isc_nm_httpsocket'. */ +bool +isc_nm_is_proxy_unspec(isc_nmhandle_t *handle); +/*%< + * Returns 'true' iff 'handle' is associated with a peer who send + * a PROXYv2 header with unsupported address type. + */ + +bool +isc_nm_is_proxy_handle(isc_nmhandle_t *handle); +/*%< Returns 'true' iff 'handle' is associated is with a PROXYv2 + * connection. + */ + isc_result_t isc_nm_listentls(isc_nm_t *mgr, uint32_t workers, isc_sockaddr_t *iface, isc_nm_accept_cb_t accept_cb, void *accept_cbarg, int backlog, diff --git a/lib/isc/include/isc/types.h b/lib/isc/include/isc/types.h index 09c5527b33..1600054f8c 100644 --- a/lib/isc/include/isc/types.h +++ b/lib/isc/include/isc/types.h @@ -98,13 +98,15 @@ typedef enum isc_nmsocket_type { isc_nm_tlssocket = 1 << 3, isc_nm_httpsocket = 1 << 4, isc_nm_streamdnssocket = 1 << 5, + isc_nm_proxystreamsocket = 1 << 6, isc_nm_maxsocket, isc_nm_udplistener, /* Aggregate of nm_udpsocks */ isc_nm_tcplistener, isc_nm_tlslistener, isc_nm_httplistener, - isc_nm_streamdnslistener + isc_nm_streamdnslistener, + isc_nm_proxystreamlistener } isc_nmsocket_type; typedef isc_nmsocket_type isc_nmsocket_type_t; diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index e525814f3d..5011d0b20b 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -111,6 +112,9 @@ STATIC_ASSERT(ISC_NETMGR_TCP_RECVBUF_SIZE <= ISC_NETMGR_RECVBUF_SIZE, #define ISC_NM_NMHANDLES_MAX 64 #define ISC_NM_UVREQS_MAX 64 +/*% ISC_PROXY2_MIN_AF_UNIX_SIZE is the largest type when TLVs are not used */ +#define ISC_NM_PROXY2_DEFAULT_BUFFER_SIZE (ISC_PROXY2_MIN_AF_UNIX_SIZE) + /* * Define ISC_NETMGR_TRACE to activate tracing of handles and sockets. * This will impair performance but enables us to quickly determine, @@ -244,6 +248,7 @@ struct isc_nmhandle { isc_sockaddr_t peer; isc_sockaddr_t local; + bool proxy_is_unspec; isc_nm_opaquecb_t doreset; /* reset extra callback, external */ isc_nm_opaquecb_t dofree; /* free extra callback, external */ #if ISC_NETMGR_TRACE @@ -536,6 +541,20 @@ struct isc_nmsocket { bool dot_alpn_negotiated; const char *tls_verify_error; } streamdns; + + struct { + isc_nmsocket_t *sock; + bool reading; + size_t nsending; + void *send_req; + union { + isc_proxy2_handler_t *handler; /* server */ + isc_buffer_t *outbuf; /* client */ + } proxy2; + bool header_processed; + bool extra_processed; /* data arrived past header processed */ + } proxy; + /*% * pquota is a non-attached pointer to the TCP client quota, stored in * listening sockets. @@ -1136,6 +1155,71 @@ void isc__nm_streamdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, bool async); +bool +isc__nm_valid_proxy_addresses(const isc_sockaddr_t *src, + const isc_sockaddr_t *dst); + +void +isc__nm_proxystream_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, + bool async); + +void +isc__nm_proxystream_stoplistening(isc_nmsocket_t *sock); + +void +isc__nm_proxystream_cleanup_data(isc_nmsocket_t *sock); + +void +isc__nmhandle_proxystream_cleartimeout(isc_nmhandle_t *handle); + +void +isc__nmhandle_proxystream_settimeout(isc_nmhandle_t *handle, uint32_t timeout); + +void +isc__nmhandle_proxystream_keepalive(isc_nmhandle_t *handle, bool value); + +void +isc__nmhandle_proxystream_setwritetimeout(isc_nmhandle_t *handle, + uint64_t write_timeout); + +void +isc__nmsocket_proxystream_reset(isc_nmsocket_t *sock); + +bool +isc__nmsocket_proxystream_timer_running(isc_nmsocket_t *sock); + +void +isc__nmsocket_proxystream_timer_restart(isc_nmsocket_t *sock); + +void +isc__nmsocket_proxystream_timer_stop(isc_nmsocket_t *sock); + +void +isc__nmhandle_proxystream_set_manual_timer(isc_nmhandle_t *handle, + const bool manual); + +isc_result_t +isc__nmhandle_proxystream_set_tcp_nodelay(isc_nmhandle_t *handle, + const bool value); + +void +isc__nm_proxystream_read_stop(isc_nmhandle_t *handle); + +void +isc__nm_proxystream_close(isc_nmsocket_t *sock); + +void +isc__nm_proxystream_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, + void *cbarg); + +void +isc__nm_proxystream_send(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); + +void +isc__nm_proxystream_senddns(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); /*%< @@ -1323,6 +1407,14 @@ void isc__nmhandle_log(const isc_nmhandle_t *handle, int level, const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); +void +isc__nm_received_proxy_header_log(isc_nmhandle_t *handle, + 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); + void isc__nmhandle_set_manual_timer(isc_nmhandle_t *handle, const bool manual); /* diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index df9b687c7c..97176c9336 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -319,6 +320,10 @@ isc_nmhandle_setwritetimeout(isc_nmhandle_t *handle, uint64_t write_timeout) { case isc_nm_streamdnssocket: isc__nmhandle_streamdns_setwritetimeout(handle, write_timeout); break; + case isc_nm_proxystreamsocket: + isc__nmhandle_proxystream_setwritetimeout(handle, + write_timeout); + break; default: UNREACHABLE(); break; @@ -469,6 +474,7 @@ nmsocket_cleanup(void *arg) { isc__nm_http_cleanup_data(sock); #endif isc__nm_streamdns_cleanup_data(sock); + isc__nm_proxystream_cleanup_data(sock); if (sock->barriers_initialised) { isc_barrier_destroy(&sock->listen_barrier); @@ -601,6 +607,9 @@ isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG) { isc__nm_http_close(sock); return; #endif + case isc_nm_proxystreamsocket: + isc__nm_proxystream_close(sock); + return; default: break; } @@ -645,7 +654,8 @@ isc_nmsocket_close(isc_nmsocket_t **sockp) { (*sockp)->type == isc_nm_tcplistener || (*sockp)->type == isc_nm_streamdnslistener || (*sockp)->type == isc_nm_tlslistener || - (*sockp)->type == isc_nm_httplistener); + (*sockp)->type == isc_nm_httplistener || + (*sockp)->type == isc_nm_proxystreamlistener); isc__nmsocket_detach(sockp); } @@ -844,6 +854,7 @@ isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t const *peer, FALLTHROUGH; case isc_nm_tcpsocket: case isc_nm_tlssocket: + case isc_nm_proxystreamsocket: INSIST(sock->statichandle == NULL); /* @@ -875,7 +886,8 @@ isc_nmhandle_is_stream(isc_nmhandle_t *handle) { return (handle->sock->type == isc_nm_tcpsocket || handle->sock->type == isc_nm_tlssocket || handle->sock->type == isc_nm_httpsocket || - handle->sock->type == isc_nm_streamdnssocket); + handle->sock->type == isc_nm_streamdnssocket || + handle->sock->type == isc_nm_proxystreamsocket); } static void @@ -1030,6 +1042,9 @@ isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, bool async) { case isc_nm_streamdnssocket: isc__nm_streamdns_failed_read_cb(sock, result, async); return; + case isc_nm_proxystreamsocket: + isc__nm_proxystream_failed_read_cb(sock, result, async); + return; default: UNREACHABLE(); } @@ -1133,6 +1148,9 @@ isc__nmsocket_timer_restart(isc_nmsocket_t *sock) { case isc_nm_streamdnssocket: isc__nmsocket_streamdns_timer_restart(sock); return; + case isc_nm_proxystreamsocket: + isc__nmsocket_proxystream_timer_restart(sock); + return; default: break; } @@ -1176,6 +1194,8 @@ isc__nmsocket_timer_running(isc_nmsocket_t *sock) { return (isc__nmsocket_tls_timer_running(sock)); case isc_nm_streamdnssocket: return (isc__nmsocket_streamdns_timer_running(sock)); + case isc_nm_proxystreamsocket: + return (isc__nmsocket_proxystream_timer_running(sock)); default: break; } @@ -1207,6 +1227,9 @@ isc__nmsocket_timer_stop(isc_nmsocket_t *sock) { case isc_nm_streamdnssocket: isc__nmsocket_streamdns_timer_stop(sock); return; + case isc_nm_proxystreamsocket: + isc__nmsocket_proxystream_timer_stop(sock); + return; default: break; } @@ -1228,6 +1251,7 @@ isc___nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr FLARG) { switch (sock->type) { case isc_nm_tcpsocket: case isc_nm_tlssocket: + case isc_nm_proxystreamsocket: #if ISC_NETMGR_TRACE isc_nmhandle__attach(sock->statichandle, &req->handle FLARG_PASS); @@ -1380,6 +1404,9 @@ isc_nmhandle_cleartimeout(isc_nmhandle_t *handle) { case isc_nm_streamdnssocket: isc__nmhandle_streamdns_cleartimeout(handle); return; + case isc_nm_proxystreamsocket: + isc__nmhandle_proxystream_cleartimeout(handle); + return; default: handle->sock->read_timeout = 0; @@ -1406,6 +1433,9 @@ isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) { case isc_nm_streamdnssocket: isc__nmhandle_streamdns_settimeout(handle, timeout); return; + case isc_nm_proxystreamsocket: + isc__nmhandle_proxystream_settimeout(handle, timeout); + return; default: handle->sock->read_timeout = timeout; isc__nmsocket_timer_restart(handle->sock); @@ -1446,6 +1476,9 @@ isc_nmhandle_keepalive(isc_nmhandle_t *handle, bool value) { isc__nmhandle_http_keepalive(handle, value); break; #endif /* HAVE_LIBNGHTTP2 */ + case isc_nm_proxystreamsocket: + isc__nmhandle_proxystream_keepalive(handle, value); + break; default: /* * For any other protocol, this is a no-op. @@ -1559,6 +1592,9 @@ isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, isc__nm_http_send(handle, region, cb, cbarg); break; #endif + case isc_nm_proxystreamsocket: + isc__nm_proxystream_send(handle, region, cb, cbarg); + break; default: UNREACHABLE(); } @@ -1576,6 +1612,9 @@ isc__nm_senddns(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, case isc_nm_tlssocket: isc__nm_tls_senddns(handle, region, cb, cbarg); break; + case isc_nm_proxystreamsocket: + isc__nm_proxystream_senddns(handle, region, cb, cbarg); + break; default: UNREACHABLE(); } @@ -1603,6 +1642,9 @@ isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { isc__nm_http_read(handle, cb, cbarg); break; #endif + case isc_nm_proxystreamsocket: + isc__nm_proxystream_read(handle, cb, cbarg); + break; default: UNREACHABLE(); } @@ -1654,6 +1696,9 @@ isc_nm_read_stop(isc_nmhandle_t *handle) { case isc_nm_tlssocket: isc__nm_tls_read_stop(handle); break; + case isc_nm_proxystreamsocket: + isc__nm_proxystream_read_stop(handle); + break; default: UNREACHABLE(); } @@ -1690,6 +1735,9 @@ isc_nm_stoplistening(isc_nmsocket_t *sock) { isc__nm_http_stoplistening(sock); break; #endif + case isc_nm_proxystreamlistener: + isc__nm_proxystream_stoplistening(sock); + break; default: UNREACHABLE(); } @@ -1702,7 +1750,8 @@ isc__nmsocket_stop(isc_nmsocket_t *listener) { REQUIRE(listener->tid == 0); REQUIRE(listener->type == isc_nm_httplistener || listener->type == isc_nm_tlslistener || - listener->type == isc_nm_streamdnslistener); + listener->type == isc_nm_streamdnslistener || + listener->type == isc_nm_proxystreamlistener); REQUIRE(!listener->closing); listener->closing = true; @@ -1833,6 +1882,9 @@ isc__nmsocket_reset(isc_nmsocket_t *sock) { case isc_nm_streamdnssocket: isc__nmsocket_streamdns_reset(sock); return; + case isc_nm_proxystreamsocket: + isc__nmsocket_proxystream_reset(sock); + return; default: UNREACHABLE(); break; @@ -2042,6 +2094,7 @@ isc_nm_bad_request(isc_nmhandle_t *handle) { case isc_nm_tcpsocket: case isc_nm_streamdnssocket: case isc_nm_tlssocket: + case isc_nm_proxystreamsocket: REQUIRE(sock->parent == NULL); isc__nmsocket_reset(sock); return; @@ -2085,6 +2138,162 @@ isc_nm_is_http_handle(isc_nmhandle_t *handle) { return (handle->sock->type == isc_nm_httpsocket); } +static isc_nmhandle_t * +get_proxy_handle(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + sock = handle->sock; + + switch (sock->type) { + case isc_nm_proxystreamsocket: + return (handle); + default: + break; + } + + if (sock->outerhandle == NULL) { + return NULL; + } + + return (get_proxy_handle(sock->outerhandle)); +} + +bool +isc_nm_is_proxy_handle(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + return (get_proxy_handle(handle) != NULL); +} + +bool +isc_nm_is_proxy_unspec(isc_nmhandle_t *handle) { + isc_nmhandle_t *proxyhandle; + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + if (handle->sock->client) { + return (false); + } + + proxyhandle = get_proxy_handle(handle); + if (proxyhandle == NULL) { + return (false); + } + + return (proxyhandle->proxy_is_unspec); +} + +isc_sockaddr_t +isc_nmhandle_real_peeraddr(isc_nmhandle_t *handle) { + isc_sockaddr_t addr = { 0 }; + isc_nmhandle_t *proxyhandle; + REQUIRE(VALID_NMHANDLE(handle)); + + proxyhandle = get_proxy_handle(handle); + if (proxyhandle == NULL) { + return (isc_nmhandle_peeraddr(handle)); + } + + INSIST(VALID_NMSOCK(proxyhandle->sock)); + + if (isc_nmhandle_is_stream(proxyhandle)) { + addr = isc_nmhandle_peeraddr(proxyhandle->sock->outerhandle); + } else { + /* TODO: PROXY over UDP */ + UNREACHABLE(); + } + + return (addr); +} + +isc_sockaddr_t +isc_nmhandle_real_localaddr(isc_nmhandle_t *handle) { + isc_sockaddr_t addr = { 0 }; + isc_nmhandle_t *proxyhandle; + REQUIRE(VALID_NMHANDLE(handle)); + + proxyhandle = get_proxy_handle(handle); + if (proxyhandle == NULL) { + return (isc_nmhandle_localaddr(handle)); + } + + INSIST(VALID_NMSOCK(proxyhandle->sock)); + + if (isc_nmhandle_is_stream(proxyhandle)) { + addr = isc_nmhandle_localaddr(proxyhandle->sock->outerhandle); + } else { + /* TODO: PROXY over UDP */ + UNREACHABLE(); + } + + return (addr); +} + +bool +isc__nm_valid_proxy_addresses(const isc_sockaddr_t *src, + const isc_sockaddr_t *dst) { + struct in_addr inv4 = { 0 }; + struct in6_addr inv6 = { 0 }; + isc_netaddr_t zerov4 = { 0 }, zerov6 = { 0 }; + isc_netaddr_t src_addr = { 0 }, dst_addr = { 0 }; + + if (src == NULL || dst == NULL) { + return (false); + } + + /* + * We should not allow using 0 in source addresses as well, but we + * have a precedent of a tool that issues port 0 in the source + * addresses (kdig). + */ + if (isc_sockaddr_getport(dst) == 0) { + return (false); + } + + /* + * Anybody using zeroes in source or destination addresses is not + * a friend. Considering that most of the upper level code is + * written with consideration that bot source and destination + * addresses are returned by the OS and should be valid, we should + * discard so suspicious addresses. Also, keep in mind that both + * "0.0.0.0" and "::" match all interfaces when using as listener + * addresses. + */ + isc_netaddr_fromin(&zerov4, &inv4); + isc_netaddr_fromin6(&zerov6, &inv6); + + isc_netaddr_fromsockaddr(&src_addr, src); + isc_netaddr_fromsockaddr(&dst_addr, dst); + + INSIST(isc_sockaddr_pf(src) == isc_sockaddr_pf(dst)); + + switch (isc_sockaddr_pf(src)) { + case AF_INET: + if (isc_netaddr_equal(&src_addr, &zerov4)) { + return (false); + } + + if (isc_netaddr_equal(&dst_addr, &zerov4)) { + return (false); + } + break; + case AF_INET6: + if (isc_netaddr_equal(&src_addr, &zerov6)) { + return (false); + } + + if (isc_netaddr_equal(&dst_addr, &zerov6)) { + return (false); + } + break; + default: + UNREACHABLE(); + } + + return (true); +} + void isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) { isc_nmsocket_t *sock = NULL; @@ -2110,6 +2319,7 @@ isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) { break; case isc_nm_tcpsocket: case isc_nm_tlssocket: + case isc_nm_proxystreamsocket: default: UNREACHABLE(); break; @@ -2157,6 +2367,10 @@ isc_nm_verify_tls_peer_result_string(const isc_nmhandle_t *handle) { case isc_nm_tlssocket: return (isc__nm_tls_verify_tls_peer_result_string(handle)); break; + case isc_nm_proxystreamsocket: + return (isc__nm_proxystream_verify_tls_peer_result_string( + handle)); + break; #if HAVE_LIBNGHTTP2 case isc_nm_httpsocket: return (isc__nm_http_verify_tls_peer_result_string(handle)); @@ -2351,6 +2565,96 @@ isc__nmhandle_log(const isc_nmhandle_t *handle, int level, const char *fmt, isc__nmsocket_log(handle->sock, level, "handle %p: %s", handle, msgbuf); } +void +isc__nm_received_proxy_header_log(isc_nmhandle_t *handle, + 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 int log_level = ISC_LOG_DEBUG(1); + isc_sockaddr_t real_local, real_peer; + char real_local_fmt[ISC_SOCKADDR_FORMATSIZE] = { 0 }; + char real_peer_fmt[ISC_SOCKADDR_FORMATSIZE] = { 0 }; + char common_msg[512] = { 0 }; + const char *proto = NULL; + const char *real_addresses_msg = + "real source and destination addresses are used"; + + if (!isc_log_wouldlog(isc_lctx, log_level)) { + return; + } + + if (isc_nmhandle_is_stream(handle)) { + proto = isc_nm_has_encryption(handle) ? "TLS" : "TCP"; + } else { + proto = "UDP"; + } + + real_local = isc_nmhandle_real_localaddr(handle); + real_peer = isc_nmhandle_real_peeraddr(handle); + + isc_sockaddr_format(&real_local, real_local_fmt, + sizeof(real_local_fmt)); + + isc_sockaddr_format(&real_peer, real_peer_fmt, sizeof(real_peer_fmt)); + + (void)snprintf(common_msg, sizeof(common_msg), + "Received a PROXYv2 header from %s on %s over %s", + real_peer_fmt, real_local_fmt, proto); + + if (cmd == ISC_PROXY2_CMD_LOCAL) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_DEFAULT, + ISC_LOGMODULE_NETMGR, log_level, + "%s: command: LOCAL (%s)", common_msg, + real_addresses_msg); + return; + } else if (cmd == ISC_PROXY2_CMD_PROXY) { + const char *tlvs_msg = tlvs == NULL ? "no" : "yes"; + const char *socktype_name = NULL; + const char *src_addr_msg = "(none)", *dst_addr_msg = "(none)"; + char src_addr_fmt[ISC_SOCKADDR_FORMATSIZE] = { 0 }; + char dst_addr_fmt[ISC_SOCKADDR_FORMATSIZE] = { 0 }; + + switch (socktype) { + case 0: + isc_log_write(isc_lctx, ISC_LOGCATEGORY_DEFAULT, + ISC_LOGMODULE_NETMGR, log_level, + "%s: command: PROXY (unspecified address " + "and socket type, %s)", + common_msg, real_addresses_msg); + return; + case SOCK_STREAM: + socktype_name = "SOCK_STREAM"; + break; + case SOCK_DGRAM: + socktype_name = "SOCK_DGRAM"; + break; + default: + UNREACHABLE(); + } + + if (src_addr) { + isc_sockaddr_format(src_addr, src_addr_fmt, + sizeof(src_addr_fmt)); + src_addr_msg = src_addr_fmt; + } + + if (dst_addr) { + isc_sockaddr_format(dst_addr, dst_addr_fmt, + sizeof(dst_addr_fmt)); + dst_addr_msg = dst_addr_fmt; + } + + isc_log_write(isc_lctx, ISC_LOGCATEGORY_DEFAULT, + ISC_LOGMODULE_NETMGR, log_level, + "%s: command: PROXY, socket type: %s, source: " + "%s, destination: %s, TLVs: %s", + common_msg, socktype_name, src_addr_msg, + dst_addr_msg, tlvs_msg); + } +} + void isc__nmhandle_set_manual_timer(isc_nmhandle_t *handle, const bool manual) { REQUIRE(VALID_NMHANDLE(handle)); @@ -2365,6 +2669,9 @@ isc__nmhandle_set_manual_timer(isc_nmhandle_t *handle, const bool manual) { case isc_nm_tlssocket: isc__nmhandle_tls_set_manual_timer(handle, manual); return; + case isc_nm_proxystreamsocket: + isc__nmhandle_proxystream_set_manual_timer(handle, manual); + return; default: break; }; @@ -2409,6 +2716,10 @@ isc_nmhandle_set_tcp_nodelay(isc_nmhandle_t *handle, const bool value) { case isc_nm_tlssocket: result = isc__nmhandle_tls_set_tcp_nodelay(handle, value); break; + case isc_nm_proxystreamsocket: + result = isc__nmhandle_proxystream_set_tcp_nodelay(handle, + value); + break; default: UNREACHABLE(); break; @@ -2423,6 +2734,36 @@ isc_nmsocket_getaddr(isc_nmsocket_t *sock) { return (sock->iface); } +void +isc_nm_proxyheader_info_init(isc_nm_proxyheader_info_t *restrict info, + isc_sockaddr_t *restrict src_addr, + isc_sockaddr_t *restrict dst_addr, + isc_region_t *restrict tlv_data) { + REQUIRE(info != NULL); + REQUIRE(src_addr != NULL); + REQUIRE(dst_addr != NULL); + REQUIRE(tlv_data == NULL || + (tlv_data->length > 0 && tlv_data->base != NULL)); + + *info = (isc_nm_proxyheader_info_t){ .proxy_info.src_addr = *src_addr, + .proxy_info.dst_addr = *dst_addr }; + if (tlv_data != NULL) { + info->proxy_info.tlv_data = *tlv_data; + } +} + +void +isc_nm_proxyheader_info_init_complete(isc_nm_proxyheader_info_t *restrict info, + isc_region_t *restrict header_data) { + REQUIRE(info != NULL); + REQUIRE(header_data != NULL); + REQUIRE(header_data->base != NULL && + header_data->length >= ISC_PROXY2_HEADER_SIZE); + + *info = (isc_nm_proxyheader_info_t){ .complete = true, + .complete_header = *header_data }; +} + #if ISC_NETMGR_TRACE /* * Dump all active sockets in netmgr. We output to stderr @@ -2452,6 +2793,10 @@ nmsocket_type_totext(isc_nmsocket_type type) { return ("isc_nm_streamdnslistener"); case isc_nm_streamdnssocket: return ("isc_nm_streamdnssocket"); + case isc_nm_proxystreamlistener: + return ("isc_nm_proxystreamlistener"); + case isc_nm_proxystreamsocket: + return ("isc_nm_proxystreamsocket"); default: UNREACHABLE(); } diff --git a/lib/isc/netmgr/proxystream.c b/lib/isc/netmgr/proxystream.c new file mode 100644 index 0000000000..39b154d06f --- /dev/null +++ b/lib/isc/netmgr/proxystream.c @@ -0,0 +1,1089 @@ +/* + * 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 MPL 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 + +#include "netmgr-int.h" + +/* + * The idea behind the transport is simple after accepting the + * connection or connecting to a remote server it enters PROXYv2 + * handling mode: that is, it either attempts to read (when accepting + * the connection) or send (when establishing a connection) a PROXYv2 + * header. After that it works like a mere wrapper on top of the + * underlying stream-based transport (TCP). + */ + +typedef struct proxystream_send_req { + isc_nm_cb_t cb; /* send callback */ + void *cbarg; /* send callback argument */ + isc_nmhandle_t *proxyhandle; /* PROXY Stream socket handle */ +} proxystream_send_req_t; + +static void +proxystream_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 tlv_blob, + const isc_region_t *restrict extra, void *cbarg); + +static isc_nmsocket_t * +proxystream_sock_new(isc__networker_t *worker, const isc_nmsocket_type_t type, + isc_sockaddr_t *addr, const bool is_server); + +static isc_result_t +proxystream_accept_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg); + +static void +proxystream_connect_cb(isc_nmhandle_t *handle, isc_result_t result, + void *cbarg); + +static void +proxystream_failed_read_cb_async(void *arg); + +static void +proxystream_clear_proxy_header_data(isc_nmsocket_t *sock); + +static void +proxystream_read_start(isc_nmsocket_t *sock); + +static void +proxystream_read_stop(isc_nmsocket_t *sock); + +static void +proxystream_try_close_unused(isc_nmsocket_t *sock); + +static void +proxystream_call_connect_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle, + isc_result_t result); + +static bool +proxystream_closing(isc_nmsocket_t *sock); + +static void +proxystream_failed_read_cb(isc_nmsocket_t *sock, const isc_result_t result); + +static void +proxystream_read_cb(isc_nmhandle_t *handle, isc_result_t result, + isc_region_t *region, void *cbarg); + +static void +proxystream_read_extra_cb(void *arg); + +static proxystream_send_req_t * +proxystream_get_send_req(isc_mem_t *mctx, isc_nmsocket_t *sock, + isc_nmhandle_t *proxyhandle, isc_nm_cb_t cb, + void *cbarg); + +static void +proxystream_put_send_req(isc_mem_t *mctx, proxystream_send_req_t *send_req, + const bool force_destroy); + +static void +proxystream_send_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg); + +static void +proxystream_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, + void *cbarg, const bool dnsmsg); + +static void +proxystream_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_nmsocket_t *sock = (isc_nmsocket_t *)cbarg; + + switch (result) { + case ISC_R_SUCCESS: { + isc_nmhandle_t *proxyhandle = NULL; + isc_result_t accept_result = ISC_R_FAILURE; + bool call_accept = false; + bool is_unspec = false; + + /* + * After header has been processed - stop reading (thus, + * stopping the timer) and disable manual timer control as in + * the case of TCP it is disabled by default + */ + proxystream_read_stop(sock); + isc__nmhandle_set_manual_timer(sock->outerhandle, false); + + sock->proxy.header_processed = true; + if (extra == NULL) { + sock->proxy.extra_processed = true; + } + + /* Process header data */ + if (cmd == ISC_PROXY2_CMD_LOCAL) { + is_unspec = true; + call_accept = true; + } else if (cmd == ISC_PROXY2_CMD_PROXY) { + switch (socktype) { + case 0: + /* + * Treat unsupported addresses (aka AF_UNSPEC) + * as LOCAL. + */ + is_unspec = true; + call_accept = true; + break; + case SOCK_DGRAM: + /* + * In some cases proxies can do protocol + * conversion. In this case, the original + * request might have arrived over UDP-based + * transport and, thus, the PROXYv2 header can + * contain SOCK_DGRAM, while for TCP one would + * expect SOCK_STREAM. That might be unexpected, + * but, as the main idea behind PROXYv2 is to + * carry the original endpoint information to + * back-ends, that is fine. + * + * At least "dnsdist" does that when redirecting + * a UDP request to a TCP or TLS-only server. + */ + case SOCK_STREAM: + 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) { + is_unspec = true; + } + + if (!is_unspec && + !isc__nm_valid_proxy_addresses(src_addr, + dst_addr)) + { + break; + } + + call_accept = true; + break; + default: + break; + } + } + + if (call_accept) { + if (is_unspec) { + proxyhandle = isc__nmhandle_get( + sock, &sock->peer, &sock->iface); + } else { + INSIST(src_addr != NULL); + INSIST(dst_addr != NULL); + proxyhandle = isc__nmhandle_get(sock, src_addr, + dst_addr); + } + proxyhandle->proxy_is_unspec = is_unspec; + isc__nm_received_proxy_header_log(proxyhandle, cmd, + socktype, src_addr, + dst_addr, tlvs); + accept_result = sock->accept_cb(proxyhandle, result, + sock->accept_cbarg); + isc_nmhandle_detach(&proxyhandle); + } + + if (accept_result != ISC_R_SUCCESS) { + isc__nmsocket_detach(&sock->listener); + isc_nmhandle_detach(&sock->outerhandle); + sock->closed = true; + } + + sock->accepting = false; + + proxystream_try_close_unused(sock); + } break; + case ISC_R_NOMORE: + /* + * That is fine, wait for more data to complete the PROXY + * header + */ + break; + default: + proxystream_failed_read_cb(sock, result); + break; + }; +}; + +static void +proxystream_handle_incoming_header_data(isc_nmsocket_t *sock, + isc_region_t *restrict data) { + isc_proxy2_handler_t *restrict handler = sock->proxy.proxy2.handler; + + (void)isc_proxy2_handler_push(handler, data); + proxystream_try_close_unused(sock); +} + +static isc_nmsocket_t * +proxystream_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_proxystreamsocket || + type == isc_nm_proxystreamlistener); + + 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_proxystreamsocket) { + 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) { + /* + * Smallest TCP (over IPv6) segment size we required to + * support. An adequate value for both IPv4 and IPv6. + */ + sock->proxy.proxy2.handler = isc_proxy2_handler_new( + worker->mctx, NM_MAXSEG, + proxystream_on_header_data_cb, sock); + } else { + isc_buffer_allocate(worker->mctx, + &sock->proxy.proxy2.outbuf, + ISC_NM_PROXY2_DEFAULT_BUFFER_SIZE); + } + } + + return (sock); +} + +static isc_result_t +proxystream_accept_cb(isc_nmhandle_t *handle, isc_result_t result, + void *cbarg) { + isc_nmsocket_t *listensock = (isc_nmsocket_t *)cbarg; + isc_nmsocket_t *nsock = NULL; + isc_sockaddr_t iface; + + if (result != ISC_R_SUCCESS) { + return (result); + } + + INSIST(VALID_NMHANDLE(handle)); + INSIST(VALID_NMSOCK(handle->sock)); + INSIST(VALID_NMSOCK(listensock)); + INSIST(listensock->type == isc_nm_proxystreamlistener); + + if (isc__nm_closing(handle->sock->worker)) { + return (ISC_R_SHUTTINGDOWN); + } else if (isc__nmsocket_closing(handle->sock)) { + return (ISC_R_CANCELED); + } + + iface = isc_nmhandle_localaddr(handle); + nsock = proxystream_sock_new(handle->sock->worker, + isc_nm_proxystreamsocket, &iface, true); + INSIST(listensock->accept_cb != NULL); + nsock->accept_cb = listensock->accept_cb; + nsock->accept_cbarg = listensock->accept_cbarg; + + nsock->peer = isc_nmhandle_peeraddr(handle); + nsock->tid = isc_tid(); + nsock->accepting = true; + nsock->active = true; + + isc__nmsocket_attach(listensock, &nsock->listener); + isc_nmhandle_attach(handle, &nsock->outerhandle); + handle->sock->proxy.sock = nsock; + + /* + * We need to control the timer manually as we do *not* want it to + * be reset on partial header data reads. + */ + isc__nmhandle_set_manual_timer(nsock->outerhandle, true); + isc__nmsocket_timer_restart(nsock); + + proxystream_read_start(nsock); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_nm_listenproxystream(isc_nm_t *mgr, uint32_t workers, isc_sockaddr_t *iface, + isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + int backlog, isc_quota_t *quota, + 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 = proxystream_sock_new(worker, isc_nm_proxystreamlistener, + iface, true); + listener->accept_cb = accept_cb; + listener->accept_cbarg = accept_cbarg; + + result = isc_nm_listentcp(mgr, workers, iface, proxystream_accept_cb, + listener, backlog, quota, &listener->outer); + + if (result != ISC_R_SUCCESS) { + listener->closed = true; + isc__nmsocket_detach(&listener); + return (result); + } + + listener->active = true; + listener->result = result; + listener->nchildren = listener->outer->nchildren; + + *sockp = listener; + + return (result); +} + +static void +proxystream_try_close_unused(isc_nmsocket_t *sock) { + /* try to close unused socket */ + if (sock->statichandle == NULL && sock->proxy.nsending == 0) { + isc__nmsocket_prep_destroy(sock); + } +} + +static void +proxystream_call_connect_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle, + isc_result_t result) { + sock->connecting = false; + if (sock->connect_cb == NULL) { + return; + } + + if (result == ISC_R_SUCCESS) { + sock->connected = true; + } + + sock->connect_cb(handle, result, sock->connect_cbarg); + if (result != ISC_R_SUCCESS) { + isc__nmsocket_clearcb(handle->sock); + } +} + +static void +proxystream_send_header_cb(isc_nmhandle_t *transphandle, isc_result_t result, + void *cbarg) { + isc_nmsocket_t *sock = (isc_nmsocket_t *)cbarg; + isc_nmhandle_t *proxyhandle = NULL; + + REQUIRE(VALID_NMHANDLE(transphandle)); + REQUIRE(VALID_NMSOCK(sock)); + + sock->proxy.nsending--; + sock->proxy.header_processed = true; + + if (isc__nm_closing(transphandle->sock->worker)) { + result = ISC_R_SHUTTINGDOWN; + } + + proxyhandle = isc__nmhandle_get(sock, &sock->peer, &sock->iface); + proxystream_call_connect_cb(sock, proxyhandle, result); + isc_nmhandle_detach(&proxyhandle); + + proxystream_try_close_unused(sock); +} + +static void +proxystream_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; + isc_region_t header = { 0 }; + + 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); + if (isc__nm_closing(handle->sock->worker)) { + result = ISC_R_SHUTTINGDOWN; + goto error; + } else if (isc__nmsocket_closing(handle->sock)) { + result = ISC_R_CANCELED; + goto error; + } + + isc_nmhandle_attach(handle, &sock->outerhandle); + handle->sock->proxy.sock = sock; + sock->active = true; + + isc_buffer_usedregion(sock->proxy.proxy2.outbuf, &header); + sock->proxy.nsending++; + isc_nm_send(handle, &header, proxystream_send_header_cb, sock); + + proxystream_try_close_unused(sock); + + return; +error: + proxyhandle = isc__nmhandle_get(sock, NULL, NULL); + sock->closed = true; + proxystream_call_connect_cb(sock, proxyhandle, result); + isc_nmhandle_detach(&proxyhandle); + isc__nmsocket_detach(&sock); +} + +void +isc_nm_proxystreamconnect(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 = proxystream_sock_new(worker, isc_nm_proxystreamsocket, local, + false); + nsock->connect_cb = cb; + nsock->connect_cbarg = cbarg; + nsock->connect_timeout = timeout; + + 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_STREAM, &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_tcpconnect(mgr, local, peer, proxystream_connect_cb, nsock, + nsock->connect_timeout); +} + +static void +proxystream_failed_read_cb_async(void *arg) { + isc__nm_uvreq_t *req = (isc__nm_uvreq_t *)arg; + + proxystream_failed_read_cb(req->sock, req->result); + isc__nm_uvreq_put(&req); +} + +void +isc__nm_proxystream_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, + bool async) { + proxystream_read_stop(sock); + + if (!async) { + proxystream_failed_read_cb(sock, result); + } else { + isc__nm_uvreq_t *req = isc__nm_uvreq_get(sock); + req->result = result; + req->cbarg = sock; + isc_job_run(sock->worker->loop, &req->job, + proxystream_failed_read_cb_async, req); + } +} + +void +isc__nm_proxystream_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_proxystreamlistener); + REQUIRE(sock->proxy.sock == NULL); + + isc__nmsocket_stop(sock); +} + +static void +proxystream_clear_proxy_header_data(isc_nmsocket_t *sock) { + if (!sock->client && sock->proxy.proxy2.handler != NULL) { + isc_proxy2_handler_free(&sock->proxy.proxy2.handler); + } else if (sock->client && sock->proxy.proxy2.outbuf != NULL) { + isc_buffer_free(&sock->proxy.proxy2.outbuf); + } +} + +void +isc__nm_proxystream_cleanup_data(isc_nmsocket_t *sock) { + switch (sock->type) { + case isc_nm_tcpsocket: + if (sock->proxy.sock != NULL) { + isc__nmsocket_detach(&sock->proxy.sock); + } + break; + case isc_nm_proxystreamsocket: + if (sock->proxy.send_req != NULL) { + proxystream_put_send_req( + sock->worker->mctx, + (proxystream_send_req_t *)sock->proxy.send_req, + true); + } + + proxystream_clear_proxy_header_data(sock); + break; + default: + break; + }; +} + +void +isc__nmhandle_proxystream_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_proxystreamsocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + isc_nmhandle_cleartimeout(sock->outerhandle); + } +} + +void +isc__nmhandle_proxystream_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_proxystreamsocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + isc_nmhandle_settimeout(sock->outerhandle, timeout); + } +} + +void +isc__nmhandle_proxystream_keepalive(isc_nmhandle_t *handle, bool value) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_proxystreamsocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + isc_nmhandle_keepalive(sock->outerhandle, value); + } +} + +void +isc__nmhandle_proxystream_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_proxystreamsocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + isc_nmhandle_setwritetimeout(sock->outerhandle, write_timeout); + } +} + +void +isc__nmsocket_proxystream_reset(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_proxystreamsocket); + + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + REQUIRE(VALID_NMSOCK(sock->outerhandle->sock)); + isc__nmsocket_reset(sock->outerhandle->sock); + } +} + +bool +isc__nmsocket_proxystream_timer_running(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_proxystreamsocket); + + 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_proxystream_timer_restart(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_proxystreamsocket); + + 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_proxystream_timer_stop(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_proxystreamsocket); + + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + REQUIRE(VALID_NMSOCK(sock->outerhandle->sock)); + isc__nmsocket_timer_stop(sock->outerhandle->sock); + } +} + +void +isc__nmhandle_proxystream_set_manual_timer(isc_nmhandle_t *handle, + const bool manual) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_proxystreamsocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + isc__nmhandle_set_manual_timer(sock->outerhandle, manual); + } +} + +isc_result_t +isc__nmhandle_proxystream_set_tcp_nodelay(isc_nmhandle_t *handle, + const bool value) { + isc_nmsocket_t *sock = NULL; + isc_result_t result = ISC_R_FAILURE; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_proxystreamsocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + result = isc_nmhandle_set_tcp_nodelay(sock->outerhandle, value); + } + + return (result); +} + +static void +proxystream_read_start(isc_nmsocket_t *sock) { + if (sock->proxy.reading == true) { + return; + } + + sock->proxy.reading = true; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + isc_nm_read(sock->outerhandle, proxystream_read_cb, sock); + } +} + +static void +proxystream_read_stop(isc_nmsocket_t *sock) { + if (sock->proxy.reading == false) { + return; + } + + sock->proxy.reading = false; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + isc_nm_read_stop(sock->outerhandle); + } +} + +void +isc__nm_proxystream_read_stop(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_proxystreamsocket); + + handle->sock->reading = false; + proxystream_read_stop(handle->sock); +} + +void +isc__nm_proxystream_close(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_proxystreamsocket); + REQUIRE(sock->tid == isc_tid()); + + sock->closing = true; + + /* + * At this point we're certain that there are no + * external references, we can close everything. + */ + proxystream_read_stop(sock); + if (sock->outerhandle != NULL) { + sock->reading = false; + isc_nm_read_stop(sock->outerhandle); + isc_nmhandle_close(sock->outerhandle); + isc_nmhandle_detach(&sock->outerhandle); + } + + if (sock->listener != NULL) { + isc__nmsocket_detach(&sock->listener); + } + + /* Further cleanup performed in isc__nm_proxystream_cleanup_data() */ + sock->closed = true; + sock->active = false; +} + +static bool +proxystream_closing(isc_nmsocket_t *sock) { + return (isc__nmsocket_closing(sock) || sock->outerhandle == NULL || + (sock->outerhandle != NULL && + isc__nmsocket_closing(sock->outerhandle->sock))); +} + +static void +proxystream_failed_read_cb(isc_nmsocket_t *sock, const isc_result_t result) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(result != ISC_R_SUCCESS); + + if (sock->client && sock->connect_cb != NULL && !sock->connected) { + isc_nmhandle_t *handle = NULL; + INSIST(sock->statichandle == NULL); + handle = isc__nmhandle_get(sock, &sock->peer, &sock->iface); + proxystream_call_connect_cb(sock, handle, result); + isc__nmsocket_clearcb(sock); + isc_nmhandle_detach(&handle); + + isc__nmsocket_prep_destroy(sock); + return; + } + + isc__nmsocket_timer_stop(sock); + + if (sock->statichandle == NULL) { + isc__nmsocket_prep_destroy(sock); + return; + } + + /* See isc__nmsocket_readtimeout_cb() */ + if (sock->client && result == ISC_R_TIMEDOUT) { + if (sock->recv_cb != NULL) { + isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL); + isc__nm_readcb(sock, req, result, false); + } + + if (isc__nmsocket_timer_running(sock)) { + /* Timer was restarted, bail-out */ + return; + } + + isc__nmsocket_clearcb(sock); + + isc__nmsocket_prep_destroy(sock); + return; + } + + if (sock->recv_cb != NULL) { + isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL); + isc__nmsocket_clearcb(sock); + isc__nm_readcb(sock, req, result, false); + } + + isc__nmsocket_prep_destroy(sock); +} + +static void +proxystream_read_cb(isc_nmhandle_t *handle, isc_result_t result, + isc_region_t *region, void *cbarg) { + isc_nmsocket_t *proxysock = (isc_nmsocket_t *)cbarg; + + REQUIRE(VALID_NMSOCK(proxysock)); + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(proxysock->tid == isc_tid()); + + if (result != ISC_R_SUCCESS) { + goto failed; + + } else if (isc__nm_closing(proxysock->worker)) { + result = ISC_R_SHUTTINGDOWN; + goto failed; + } else if (isc__nmsocket_closing(handle->sock)) { + result = ISC_R_CANCELED; + goto failed; + } + + /* Handle initial PROXY header data */ + if (!proxysock->client && !proxysock->proxy.header_processed) { + proxystream_handle_incoming_header_data(proxysock, region); + return; + } + + proxysock->recv_cb(proxysock->statichandle, ISC_R_SUCCESS, region, + proxysock->recv_cbarg); + + proxystream_try_close_unused(proxysock); + + return; +failed: + proxystream_failed_read_cb(proxysock, result); +} + +static void +proxystream_read_extra_cb(void *arg) { + isc_result_t result = ISC_R_SUCCESS; + isc__nm_uvreq_t *req = arg; + isc_region_t extra_data = { 0 }; /* data past PROXY header */ + + REQUIRE(VALID_UVREQ(req)); + + isc_nmsocket_t *sock = req->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_tid()); + + sock->proxy.extra_processed = true; + + if (isc__nm_closing(sock->worker)) { + result = ISC_R_SHUTTINGDOWN; + } else if (proxystream_closing(sock)) { + result = ISC_R_CANCELED; + } + + if (result == ISC_R_SUCCESS) { + extra_data.base = (uint8_t *)req->uvbuf.base; + extra_data.length = req->uvbuf.len; + + INSIST(extra_data.length > 0); + + req->cb.recv(req->handle, result, &extra_data, req->cbarg); + + if (sock->reading) { + proxystream_read_start(sock); + } + } else { + isc__nm_proxystream_failed_read_cb(sock, result, false); + } + + isc__nm_uvreq_put(&req); +} + +void +isc__nm_proxystream_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, + void *cbarg) { + isc_nmsocket_t *sock = NULL; + isc_region_t extra_data = { 0 }; /* data past PROXY header */ + + REQUIRE(VALID_NMHANDLE(handle)); + sock = handle->sock; + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_proxystreamsocket); + 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_proxystream_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, + false); + return; + } else if (proxystream_closing(sock)) { + isc__nm_proxystream_failed_read_cb(sock, ISC_R_CANCELED, true); + return; + } + + /* check if there is extra data on the server */ + if (!sock->client && sock->proxy.header_processed && + !sock->proxy.extra_processed && + isc_proxy2_handler_extra(sock->proxy.proxy2.handler, &extra_data) > + 0) + { + isc__nm_uvreq_t *req = isc__nm_uvreq_get(sock); + isc_nmhandle_attach(handle, &req->handle); + req->cb.recv = sock->recv_cb; + req->cbarg = sock->recv_cbarg; + + req->uvbuf.base = (char *)extra_data.base; + req->uvbuf.len = extra_data.length; + + isc_job_run(sock->worker->loop, &req->job, + proxystream_read_extra_cb, req); + return; + } + + proxystream_read_start(sock); +} + +static proxystream_send_req_t * +proxystream_get_send_req(isc_mem_t *mctx, isc_nmsocket_t *sock, + isc_nmhandle_t *proxyhandle, isc_nm_cb_t cb, + void *cbarg) { + proxystream_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 = (proxystream_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 = (proxystream_send_req_t){ 0 }; + } + + /* Initialise the send request object */ + send_req->cb = cb; + send_req->cbarg = cbarg; + isc_nmhandle_attach(proxyhandle, &send_req->proxyhandle); + + sock->proxy.nsending++; + + return (send_req); +} + +static void +proxystream_put_send_req(isc_mem_t *mctx, proxystream_send_req_t *send_req, + const bool force_destroy) { + /* + * 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; + } + } + + isc_mem_put(mctx, send_req, sizeof(*send_req)); +} + +static void +proxystream_send_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + proxystream_send_req_t *send_req = (proxystream_send_req_t *)cbarg; + isc_mem_t *mctx; + isc_nm_cb_t cb; + void *send_cbarg; + isc_nmhandle_t *proxyhandle = 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); + /* try to keep the send request object for reuse */ + proxystream_put_send_req(mctx, send_req, false); + cb(proxyhandle, result, send_cbarg); + proxystream_try_close_unused(proxyhandle->sock); + isc_nmhandle_detach(&proxyhandle); +} + +static void +proxystream_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, + void *cbarg, const bool dnsmsg) { + isc_nmsocket_t *sock = NULL; + proxystream_send_req_t *send_req = NULL; + isc_result_t result = ISC_R_SUCCESS; + bool fail_async = true; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + + REQUIRE(sock->type == isc_nm_proxystreamsocket); + + if (isc__nm_closing(sock->worker)) { + result = ISC_R_SHUTTINGDOWN; + fail_async = false; + } else if (proxystream_closing(sock)) { + result = ISC_R_CANCELED; + fail_async = true; + } + + 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, fail_async); + return; + } + + send_req = proxystream_get_send_req(sock->worker->mctx, sock, handle, + cb, cbarg); + if (dnsmsg) { + isc__nm_senddns(sock->outerhandle, region, proxystream_send_cb, + send_req); + } else { + isc_nm_send(sock->outerhandle, region, proxystream_send_cb, + send_req); + } +} + +void +isc__nm_proxystream_send(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + proxystream_send(handle, region, cb, cbarg, false); +} + +void +isc__nm_proxystream_senddns(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + proxystream_send(handle, region, cb, cbarg, true); +}