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); +}