#include <isc/loop.h>
#include <isc/magic.h>
#include <isc/mem.h>
+#include <isc/netaddr.h>
#include <isc/netmgr.h>
#include <isc/quota.h>
#include <isc/random.h>
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;
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);
isc__nm_http_close(sock);
return;
#endif
+ case isc_nm_proxystreamsocket:
+ isc__nm_proxystream_close(sock);
+ return;
default:
break;
}
(*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);
}
FALLTHROUGH;
case isc_nm_tcpsocket:
case isc_nm_tlssocket:
+ case isc_nm_proxystreamsocket:
INSIST(sock->statichandle == NULL);
/*
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
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();
}
case isc_nm_streamdnssocket:
isc__nmsocket_streamdns_timer_restart(sock);
return;
+ case isc_nm_proxystreamsocket:
+ isc__nmsocket_proxystream_timer_restart(sock);
+ return;
default:
break;
}
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;
}
case isc_nm_streamdnssocket:
isc__nmsocket_streamdns_timer_stop(sock);
return;
+ case isc_nm_proxystreamsocket:
+ isc__nmsocket_proxystream_timer_stop(sock);
+ return;
default:
break;
}
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);
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;
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);
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.
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();
}
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();
}
isc__nm_http_read(handle, cb, cbarg);
break;
#endif
+ case isc_nm_proxystreamsocket:
+ isc__nm_proxystream_read(handle, cb, cbarg);
+ break;
default:
UNREACHABLE();
}
case isc_nm_tlssocket:
isc__nm_tls_read_stop(handle);
break;
+ case isc_nm_proxystreamsocket:
+ isc__nm_proxystream_read_stop(handle);
+ break;
default:
UNREACHABLE();
}
isc__nm_http_stoplistening(sock);
break;
#endif
+ case isc_nm_proxystreamlistener:
+ isc__nm_proxystream_stoplistening(sock);
+ break;
default:
UNREACHABLE();
}
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;
case isc_nm_streamdnssocket:
isc__nmsocket_streamdns_reset(sock);
return;
+ case isc_nm_proxystreamsocket:
+ isc__nmsocket_proxystream_reset(sock);
+ return;
default:
UNREACHABLE();
break;
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;
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;
break;
case isc_nm_tcpsocket:
case isc_nm_tlssocket:
+ case isc_nm_proxystreamsocket:
default:
UNREACHABLE();
break;
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));
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));
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;
};
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;
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
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();
}
--- /dev/null
+/*
+ * 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 <isc/netmgr.h>
+
+#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);
+}