mirror of
https://github.com/isc-projects/bind9.git
synced 2026-02-25 10:59:35 -05:00
This commit makes it possible to use PROXY Stream not only over TCP, but also over TLS. That is, now PROXY Stream can work in two modes as far as TLS is involved: 1. PROXY over (plain) TCP - PROXYv2 headers are sent unencrypted before TLS handshake messages. That is the main mode as described in the PROXY protocol specification (as it is clearly stated there), and most of the software expects PROXYv2 support to be implemented that way (e.g. HAProxy); 2. PROXY over (encrypted) TLS - PROXYv2 headers are sent after the TLS handshake has happened. For example, this mode is being used (only ?) by "dnsdist". As far as I can see, that is, in fact, a deviation from the spec, but I can certainly see how PROXYv2 could end up being implemented this way elsewhere.
1166 lines
30 KiB
C
1166 lines
30 KiB
C
/*
|
|
* 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_tlsctx_t *tlsctx,
|
|
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;
|
|
|
|
if (tlsctx == NULL) {
|
|
result = isc_nm_listentcp(mgr, workers, iface,
|
|
proxystream_accept_cb, listener,
|
|
backlog, quota, &listener->outer);
|
|
} else {
|
|
result = isc_nm_listentls(
|
|
mgr, workers, iface, proxystream_accept_cb, listener,
|
|
backlog, quota, tlsctx, false, &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_tlsctx_t *tlsctx,
|
|
isc_tlsctx_client_session_cache_t *client_sess_cache,
|
|
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);
|
|
|
|
if (tlsctx == NULL) {
|
|
isc_nm_tcpconnect(mgr, local, peer, proxystream_connect_cb,
|
|
nsock, nsock->connect_timeout);
|
|
} else {
|
|
isc_nm_tlsconnect(mgr, local, peer, proxystream_connect_cb,
|
|
nsock, tlsctx, client_sess_cache,
|
|
nsock->connect_timeout, false, NULL);
|
|
}
|
|
}
|
|
|
|
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:
|
|
case isc_nm_tlssocket:
|
|
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);
|
|
}
|
|
|
|
void
|
|
isc__nm_proxystream_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx) {
|
|
REQUIRE(VALID_NMSOCK(listener));
|
|
REQUIRE(listener->type == isc_nm_proxystreamlistener);
|
|
|
|
if (listener->outer != NULL) {
|
|
INSIST(VALID_NMSOCK(listener->outer));
|
|
isc_nmsocket_set_tlsctx(listener->outer, tlsctx);
|
|
}
|
|
}
|
|
|
|
bool
|
|
isc__nm_proxystream_has_encryption(const 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));
|
|
return (isc_nm_has_encryption(sock->outerhandle));
|
|
}
|
|
|
|
return (false);
|
|
}
|
|
|
|
const char *
|
|
isc__nm_proxystream_verify_tls_peer_result_string(const 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));
|
|
return (isc_nm_verify_tls_peer_result_string(
|
|
sock->outerhandle));
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
void
|
|
isc__nmhandle_proxystream_get_selected_alpn(isc_nmhandle_t *handle,
|
|
const unsigned char **alpn,
|
|
unsigned int *alpnlen) {
|
|
isc_nmsocket_t *sock;
|
|
|
|
REQUIRE(VALID_NMHANDLE(handle));
|
|
sock = handle->sock;
|
|
REQUIRE(VALID_NMSOCK(sock));
|
|
REQUIRE(sock->type == isc_nm_proxystreamsocket);
|
|
REQUIRE(sock->tid == isc_tid());
|
|
|
|
isc__nmhandle_get_selected_alpn(sock->outerhandle, alpn, alpnlen);
|
|
}
|