You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
868 lines
27 KiB
868 lines
27 KiB
From b2929ff50a7676563177bc52a372ddcae48cb002 Mon Sep 17 00:00:00 2001 |
|
From: Petr Mensik <pemensik@redhat.com> |
|
Date: Wed, 24 Apr 2019 20:09:07 +0200 |
|
Subject: [PATCH] 5200. [security] tcp-clients settings could be |
|
exceeded in some cases, which could lead to |
|
exhaustion of file descriptors. (CVE-2018-5743) [GL |
|
#615] |
|
|
|
--- |
|
bin/named/client.c | 421 +++++++++++++++++++------ |
|
bin/named/include/named/client.h | 13 +- |
|
bin/named/include/named/interfacemgr.h | 13 +- |
|
bin/named/interfacemgr.c | 9 +- |
|
lib/isc/include/isc/quota.h | 7 + |
|
lib/isc/quota.c | 33 +- |
|
6 files changed, 385 insertions(+), 111 deletions(-) |
|
|
|
diff --git a/bin/named/client.c b/bin/named/client.c |
|
index b7d8a98..e1acaf1 100644 |
|
--- a/bin/named/client.c |
|
+++ b/bin/named/client.c |
|
@@ -243,7 +243,7 @@ static void ns_client_dumpmessage(ns_client_t *client, const char *reason); |
|
static isc_result_t get_client(ns_clientmgr_t *manager, ns_interface_t *ifp, |
|
dns_dispatch_t *disp, isc_boolean_t tcp); |
|
static isc_result_t get_worker(ns_clientmgr_t *manager, ns_interface_t *ifp, |
|
- isc_socket_t *sock); |
|
+ isc_socket_t *sock, ns_client_t *oldclient); |
|
static inline isc_boolean_t |
|
allowed(isc_netaddr_t *addr, dns_name_t *signer, isc_netaddr_t *ecs_addr, |
|
isc_uint8_t ecs_addrlen, isc_uint8_t *ecs_scope, dns_acl_t *acl); |
|
@@ -295,6 +295,119 @@ ns_client_settimeout(ns_client_t *client, unsigned int seconds) { |
|
} |
|
} |
|
|
|
+/*% |
|
+ * Allocate a reference-counted object that will maintain a single pointer to |
|
+ * the (also reference-counted) TCP client quota, shared between all the |
|
+ * clients processing queries on a single TCP connection, so that all |
|
+ * clients sharing the one socket will together consume only one slot in |
|
+ * the 'tcp-clients' quota. |
|
+ */ |
|
+static isc_result_t |
|
+tcpconn_init(ns_client_t *client, isc_boolean_t force) { |
|
+ isc_result_t result; |
|
+ isc_quota_t *quota = NULL; |
|
+ ns_tcpconn_t *tconn = NULL; |
|
+ |
|
+ REQUIRE(client->tcpconn == NULL); |
|
+ |
|
+ /* |
|
+ * Try to attach to the quota first, so we won't pointlessly |
|
+ * allocate memory for a tcpconn object if we can't get one. |
|
+ */ |
|
+ if (force) { |
|
+ result = isc_quota_force(&ns_g_server->tcpquota, "a); |
|
+ } else { |
|
+ result = isc_quota_attach(&ns_g_server->tcpquota, "a); |
|
+ } |
|
+ if (result != ISC_R_SUCCESS) { |
|
+ return (result); |
|
+ } |
|
+ |
|
+ /* |
|
+ * A global memory context is used for the allocation as different |
|
+ * client structures may have different memory contexts assigned and a |
|
+ * reference counter allocated here might need to be freed by a |
|
+ * different client. The performance impact caused by memory context |
|
+ * contention here is expected to be negligible, given that this code |
|
+ * is only executed for TCP connections. |
|
+ */ |
|
+ tconn = isc_mem_allocate(ns_g_mctx, sizeof(*tconn)); |
|
+ |
|
+ isc_refcount_init(&tconn->refs, 1); |
|
+ tconn->tcpquota = quota; |
|
+ quota = NULL; |
|
+ tconn->pipelined = ISC_FALSE; |
|
+ |
|
+ client->tcpconn = tconn; |
|
+ |
|
+ return (ISC_R_SUCCESS); |
|
+} |
|
+ |
|
+/*% |
|
+ * Increase the count of client structures sharing the TCP connection |
|
+ * that 'source' is associated with; add a pointer to the same tcpconn |
|
+ * to 'target', thus associating it with the same TCP connection. |
|
+ */ |
|
+static void |
|
+tcpconn_attach(ns_client_t *source, ns_client_t *target) { |
|
+ int refs; |
|
+ |
|
+ REQUIRE(source->tcpconn != NULL); |
|
+ REQUIRE(target->tcpconn == NULL); |
|
+ REQUIRE(source->tcpconn->pipelined); |
|
+ |
|
+ isc_refcount_increment(&source->tcpconn->refs, &refs); |
|
+ INSIST(refs > 1); |
|
+ target->tcpconn = source->tcpconn; |
|
+} |
|
+ |
|
+/*% |
|
+ * Decrease the count of client structures sharing the TCP connection that |
|
+ * 'client' is associated with. If this is the last client using this TCP |
|
+ * connection, we detach from the TCP quota and free the tcpconn |
|
+ * object. Either way, client->tcpconn is set to NULL. |
|
+ */ |
|
+static void |
|
+tcpconn_detach(ns_client_t *client) { |
|
+ ns_tcpconn_t *tconn = NULL; |
|
+ int refs; |
|
+ |
|
+ REQUIRE(client->tcpconn != NULL); |
|
+ |
|
+ tconn = client->tcpconn; |
|
+ client->tcpconn = NULL; |
|
+ |
|
+ isc_refcount_decrement(&tconn->refs, &refs); |
|
+ if (refs == 0) { |
|
+ isc_quota_detach(&tconn->tcpquota); |
|
+ isc_mem_free(ns_g_mctx, tconn); |
|
+ } |
|
+} |
|
+ |
|
+/*% |
|
+ * Mark a client as active and increment the interface's 'ntcpactive' |
|
+ * counter, as a signal that there is at least one client servicing |
|
+ * TCP queries for the interface. If we reach the TCP client quota at |
|
+ * some point, this will be used to determine whether a quota overrun |
|
+ * should be permitted. |
|
+ * |
|
+ * Marking the client active with the 'tcpactive' flag ensures proper |
|
+ * accounting, by preventing us from incrementing or decrementing |
|
+ * 'ntcpactive' more than once per client. |
|
+ */ |
|
+static void |
|
+mark_tcp_active(ns_client_t *client, isc_boolean_t active) { |
|
+ if (active && !client->tcpactive) { |
|
+ isc_atomic_xadd(&client->interface->ntcpactive, 1); |
|
+ client->tcpactive = active; |
|
+ } else if (!active && client->tcpactive) { |
|
+ uint32_t old = |
|
+ isc_atomic_xadd(&client->interface->ntcpactive, -1); |
|
+ INSIST(old > 0); |
|
+ client->tcpactive = active; |
|
+ } |
|
+} |
|
+ |
|
/*% |
|
* Check for a deactivation or shutdown request and take appropriate |
|
* action. Returns ISC_TRUE if either is in progress; in this case |
|
@@ -384,7 +497,8 @@ exit_check(ns_client_t *client) { |
|
INSIST(client->recursionquota == NULL); |
|
|
|
if (NS_CLIENTSTATE_READING == client->newstate) { |
|
- if (!client->pipelined) { |
|
+ INSIST(client->tcpconn != NULL); |
|
+ if (!client->tcpconn->pipelined) { |
|
client_read(client); |
|
client->newstate = NS_CLIENTSTATE_MAX; |
|
return (ISC_TRUE); /* We're done. */ |
|
@@ -402,10 +516,13 @@ exit_check(ns_client_t *client) { |
|
*/ |
|
INSIST(client->recursionquota == NULL); |
|
INSIST(client->newstate <= NS_CLIENTSTATE_READY); |
|
- if (client->nreads > 0) |
|
+ |
|
+ if (client->nreads > 0) { |
|
dns_tcpmsg_cancelread(&client->tcpmsg); |
|
- if (client->nreads != 0) { |
|
- /* Still waiting for read cancel completion. */ |
|
+ } |
|
+ |
|
+ /* Still waiting for read cancel completion. */ |
|
+ if (client->nreads > 0) { |
|
return (ISC_TRUE); |
|
} |
|
|
|
@@ -413,14 +530,49 @@ exit_check(ns_client_t *client) { |
|
dns_tcpmsg_invalidate(&client->tcpmsg); |
|
client->tcpmsg_valid = ISC_FALSE; |
|
} |
|
+ |
|
+ /* |
|
+ * Soon the client will be ready to accept a new TCP |
|
+ * connection or UDP request, but we may have enough |
|
+ * clients doing that already. Check whether this client |
|
+ * needs to remain active and allow it go inactive if |
|
+ * not. |
|
+ * |
|
+ * UDP clients always go inactive at this point, but a TCP |
|
+ * client may need to stay active and return to READY |
|
+ * state if no other clients are available to listen |
|
+ * for TCP requests on this interface. |
|
+ * |
|
+ * Regardless, if we're going to FREED state, that means |
|
+ * the system is shutting down and we don't need to |
|
+ * retain clients. |
|
+ */ |
|
+ if (client->mortal && TCP_CLIENT(client) && |
|
+ client->newstate != NS_CLIENTSTATE_FREED && |
|
+ !ns_g_clienttest && |
|
+ isc_atomic_xadd(&client->interface->ntcpaccepting, 0) == 0) |
|
+ { |
|
+ /* Nobody else is accepting */ |
|
+ client->mortal = ISC_FALSE; |
|
+ client->newstate = NS_CLIENTSTATE_READY; |
|
+ } |
|
+ |
|
+ /* |
|
+ * Detach from TCP connection and TCP client quota, |
|
+ * if appropriate. If this is the last reference to |
|
+ * the TCP connection in our pipeline group, the |
|
+ * TCP quota slot will be released. |
|
+ */ |
|
+ if (client->tcpconn) { |
|
+ tcpconn_detach(client); |
|
+ } |
|
+ |
|
if (client->tcpsocket != NULL) { |
|
CTRACE("closetcp"); |
|
isc_socket_detach(&client->tcpsocket); |
|
+ mark_tcp_active(client, ISC_FALSE); |
|
} |
|
|
|
- if (client->tcpquota != NULL) |
|
- isc_quota_detach(&client->tcpquota); |
|
- |
|
if (client->timerset) { |
|
(void)isc_timer_reset(client->timer, |
|
isc_timertype_inactive, |
|
@@ -428,45 +580,26 @@ exit_check(ns_client_t *client) { |
|
client->timerset = ISC_FALSE; |
|
} |
|
|
|
- client->pipelined = ISC_FALSE; |
|
- |
|
client->peeraddr_valid = ISC_FALSE; |
|
|
|
client->state = NS_CLIENTSTATE_READY; |
|
- INSIST(client->recursionquota == NULL); |
|
- |
|
- /* |
|
- * Now the client is ready to accept a new TCP connection |
|
- * or UDP request, but we may have enough clients doing |
|
- * that already. Check whether this client needs to remain |
|
- * active and force it to go inactive if not. |
|
- * |
|
- * UDP clients go inactive at this point, but TCP clients |
|
- * may remain active if we have fewer active TCP client |
|
- * objects than desired due to an earlier quota exhaustion. |
|
- */ |
|
- if (client->mortal && TCP_CLIENT(client) && !ns_g_clienttest) { |
|
- LOCK(&client->interface->lock); |
|
- if (client->interface->ntcpcurrent < |
|
- client->interface->ntcptarget) |
|
- client->mortal = ISC_FALSE; |
|
- UNLOCK(&client->interface->lock); |
|
- } |
|
|
|
/* |
|
* We don't need the client; send it to the inactive |
|
* queue for recycling. |
|
*/ |
|
if (client->mortal) { |
|
- if (client->newstate > NS_CLIENTSTATE_INACTIVE) |
|
+ if (client->newstate > NS_CLIENTSTATE_INACTIVE) { |
|
client->newstate = NS_CLIENTSTATE_INACTIVE; |
|
+ } |
|
} |
|
|
|
if (NS_CLIENTSTATE_READY == client->newstate) { |
|
if (TCP_CLIENT(client)) { |
|
client_accept(client); |
|
- } else |
|
+ } else { |
|
client_udprecv(client); |
|
+ } |
|
client->newstate = NS_CLIENTSTATE_MAX; |
|
return (ISC_TRUE); |
|
} |
|
@@ -478,41 +611,51 @@ exit_check(ns_client_t *client) { |
|
/* |
|
* We are trying to enter the inactive state. |
|
*/ |
|
- if (client->naccepts > 0) |
|
+ if (client->naccepts > 0) { |
|
isc_socket_cancel(client->tcplistener, client->task, |
|
ISC_SOCKCANCEL_ACCEPT); |
|
+ } |
|
|
|
/* Still waiting for accept cancel completion. */ |
|
- if (! (client->naccepts == 0)) |
|
+ if (client->naccepts > 0) { |
|
return (ISC_TRUE); |
|
+ } |
|
|
|
/* Accept cancel is complete. */ |
|
- if (client->nrecvs > 0) |
|
+ if (client->nrecvs > 0) { |
|
isc_socket_cancel(client->udpsocket, client->task, |
|
ISC_SOCKCANCEL_RECV); |
|
+ } |
|
|
|
/* Still waiting for recv cancel completion. */ |
|
- if (! (client->nrecvs == 0)) |
|
+ if (client->nrecvs > 0) { |
|
return (ISC_TRUE); |
|
+ } |
|
|
|
/* Still waiting for control event to be delivered */ |
|
- if (client->nctls > 0) |
|
+ if (client->nctls > 0) { |
|
return (ISC_TRUE); |
|
- |
|
- /* Deactivate the client. */ |
|
- if (client->interface) |
|
- ns_interface_detach(&client->interface); |
|
+ } |
|
|
|
INSIST(client->naccepts == 0); |
|
INSIST(client->recursionquota == NULL); |
|
- if (client->tcplistener != NULL) |
|
+ if (client->tcplistener != NULL) { |
|
isc_socket_detach(&client->tcplistener); |
|
+ mark_tcp_active(client, ISC_FALSE); |
|
+ } |
|
|
|
- if (client->udpsocket != NULL) |
|
+ if (client->udpsocket != NULL) { |
|
isc_socket_detach(&client->udpsocket); |
|
+ } |
|
|
|
- if (client->dispatch != NULL) |
|
+ /* Deactivate the client. */ |
|
+ if (client->interface != NULL) { |
|
+ ns_interface_detach(&client->interface); |
|
+ } |
|
+ |
|
+ if (client->dispatch != NULL) { |
|
dns_dispatch_detach(&client->dispatch); |
|
+ } |
|
|
|
client->attributes = 0; |
|
client->mortal = ISC_FALSE; |
|
@@ -537,10 +680,13 @@ exit_check(ns_client_t *client) { |
|
client->newstate = NS_CLIENTSTATE_MAX; |
|
if (!ns_g_clienttest && manager != NULL && |
|
!manager->exiting) |
|
+ { |
|
ISC_QUEUE_PUSH(manager->inactive, client, |
|
ilink); |
|
- if (client->needshutdown) |
|
+ } |
|
+ if (client->needshutdown) { |
|
isc_task_shutdown(client->task); |
|
+ } |
|
return (ISC_TRUE); |
|
} |
|
} |
|
@@ -650,7 +796,7 @@ client_start(isc_task_t *task, isc_event_t *event) { |
|
return; |
|
|
|
if (TCP_CLIENT(client)) { |
|
- if (client->pipelined) { |
|
+ if (client->tcpconn != NULL) { |
|
client_read(client); |
|
} else { |
|
client_accept(client); |
|
@@ -660,7 +806,6 @@ client_start(isc_task_t *task, isc_event_t *event) { |
|
} |
|
} |
|
|
|
- |
|
/*% |
|
* The client's task has received a shutdown event. |
|
*/ |
|
@@ -2301,6 +2446,7 @@ client_request(isc_task_t *task, isc_event_t *event) { |
|
client->nrecvs--; |
|
} else { |
|
INSIST(TCP_CLIENT(client)); |
|
+ INSIST(client->tcpconn != NULL); |
|
REQUIRE(event->ev_type == DNS_EVENT_TCPMSG); |
|
REQUIRE(event->ev_sender == &client->tcpmsg); |
|
buffer = &client->tcpmsg.buffer; |
|
@@ -2484,18 +2630,27 @@ client_request(isc_task_t *task, isc_event_t *event) { |
|
/* |
|
* Pipeline TCP query processing. |
|
*/ |
|
- if (client->message->opcode != dns_opcode_query) |
|
- client->pipelined = ISC_FALSE; |
|
- if (TCP_CLIENT(client) && client->pipelined) { |
|
- result = isc_quota_reserve(&ns_g_server->tcpquota); |
|
- if (result == ISC_R_SUCCESS) |
|
- result = ns_client_replace(client); |
|
+ if (TCP_CLIENT(client) && |
|
+ client->message->opcode != dns_opcode_query) |
|
+ { |
|
+ client->tcpconn->pipelined = ISC_FALSE; |
|
+ } |
|
+ if (TCP_CLIENT(client) && client->tcpconn->pipelined) { |
|
+ /* |
|
+ * We're pipelining. Replace the client; the |
|
+ * replacement can read the TCP socket looking |
|
+ * for new messages and this one can process the |
|
+ * current message asynchronously. |
|
+ * |
|
+ * There will now be at least three clients using this |
|
+ * TCP socket - one accepting new connections, |
|
+ * one reading an existing connection to get new |
|
+ * messages, and one answering the message already |
|
+ * received. |
|
+ */ |
|
+ result = ns_client_replace(client); |
|
if (result != ISC_R_SUCCESS) { |
|
- ns_client_log(client, NS_LOGCATEGORY_CLIENT, |
|
- NS_LOGMODULE_CLIENT, ISC_LOG_WARNING, |
|
- "no more TCP clients(read): %s", |
|
- isc_result_totext(result)); |
|
- client->pipelined = ISC_FALSE; |
|
+ client->tcpconn->pipelined = ISC_FALSE; |
|
} |
|
} |
|
|
|
@@ -3051,8 +3206,7 @@ client_create(ns_clientmgr_t *manager, ns_client_t **clientp) { |
|
client->signer = NULL; |
|
dns_name_init(&client->signername, NULL); |
|
client->mortal = ISC_FALSE; |
|
- client->pipelined = ISC_FALSE; |
|
- client->tcpquota = NULL; |
|
+ client->tcpconn = NULL; |
|
client->recursionquota = NULL; |
|
client->interface = NULL; |
|
client->peeraddr_valid = ISC_FALSE; |
|
@@ -3062,6 +3216,7 @@ client_create(ns_clientmgr_t *manager, ns_client_t **clientp) { |
|
client->filter_aaaa = dns_aaaa_ok; |
|
#endif |
|
client->needshutdown = ns_g_clienttest; |
|
+ client->tcpactive = ISC_FALSE; |
|
|
|
ISC_EVENT_INIT(&client->ctlevent, sizeof(client->ctlevent), 0, NULL, |
|
NS_EVENT_CLIENTCONTROL, client_start, client, client, |
|
@@ -3156,9 +3311,10 @@ client_read(ns_client_t *client) { |
|
|
|
static void |
|
client_newconn(isc_task_t *task, isc_event_t *event) { |
|
+ isc_result_t result; |
|
ns_client_t *client = event->ev_arg; |
|
isc_socket_newconnev_t *nevent = (isc_socket_newconnev_t *)event; |
|
- isc_result_t result; |
|
+ uint32_t old; |
|
|
|
REQUIRE(event->ev_type == ISC_SOCKEVENT_NEWCONN); |
|
REQUIRE(NS_CLIENT_VALID(client)); |
|
@@ -3168,13 +3324,18 @@ client_newconn(isc_task_t *task, isc_event_t *event) { |
|
|
|
INSIST(client->state == NS_CLIENTSTATE_READY); |
|
|
|
+ /* |
|
+ * The accept() was successful and we're now establishing a new |
|
+ * connection. We need to make note of it in the client and |
|
+ * interface objects so client objects can do the right thing |
|
+ * when going inactive in exit_check() (see comments in |
|
+ * client_accept() for details). |
|
+ */ |
|
INSIST(client->naccepts == 1); |
|
client->naccepts--; |
|
|
|
- LOCK(&client->interface->lock); |
|
- INSIST(client->interface->ntcpcurrent > 0); |
|
- client->interface->ntcpcurrent--; |
|
- UNLOCK(&client->interface->lock); |
|
+ old = isc_atomic_xadd(&client->interface->ntcpaccepting, -1); |
|
+ INSIST(old > 0); |
|
|
|
/* |
|
* We must take ownership of the new socket before the exit |
|
@@ -3207,6 +3368,7 @@ client_newconn(isc_task_t *task, isc_event_t *event) { |
|
NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3), |
|
"accept failed: %s", |
|
isc_result_totext(nevent->result)); |
|
+ tcpconn_detach(client); |
|
} |
|
|
|
if (exit_check(client)) |
|
@@ -3244,20 +3406,13 @@ client_newconn(isc_task_t *task, isc_event_t *event) { |
|
* telnetting to port 53 (once per CPU) will |
|
* deny service to legitimate TCP clients. |
|
*/ |
|
- client->pipelined = ISC_FALSE; |
|
- result = isc_quota_attach(&ns_g_server->tcpquota, |
|
- &client->tcpquota); |
|
- if (result == ISC_R_SUCCESS) |
|
- result = ns_client_replace(client); |
|
- if (result != ISC_R_SUCCESS) { |
|
- ns_client_log(client, NS_LOGCATEGORY_CLIENT, |
|
- NS_LOGMODULE_CLIENT, ISC_LOG_WARNING, |
|
- "no more TCP clients(accept): %s", |
|
- isc_result_totext(result)); |
|
- } else if (ns_g_server->keepresporder == NULL || |
|
- !allowed(&netaddr, NULL, NULL, 0, NULL, |
|
- ns_g_server->keepresporder)) { |
|
- client->pipelined = ISC_TRUE; |
|
+ result = ns_client_replace(client); |
|
+ if (result == ISC_R_SUCCESS && |
|
+ (ns_g_server->keepresporder == NULL || |
|
+ !allowed(&netaddr, NULL, NULL, 0, NULL, |
|
+ ns_g_server->keepresporder))) |
|
+ { |
|
+ client->tcpconn->pipelined = ISC_TRUE; |
|
} |
|
|
|
client_read(client); |
|
@@ -3273,12 +3428,66 @@ client_accept(ns_client_t *client) { |
|
|
|
CTRACE("accept"); |
|
|
|
+ /* |
|
+ * Set up a new TCP connection. This means try to attach to the |
|
+ * TCP client quota (tcp-clients), but fail if we're over quota. |
|
+ */ |
|
+ result = tcpconn_init(client, ISC_FALSE); |
|
+ if (result != ISC_R_SUCCESS) { |
|
+ isc_boolean_t exit; |
|
+ |
|
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT, |
|
+ NS_LOGMODULE_CLIENT, ISC_LOG_WARNING, |
|
+ "TCP client quota reached: %s", |
|
+ isc_result_totext(result)); |
|
+ |
|
+ /* |
|
+ * We have exceeded the system-wide TCP client quota. But, |
|
+ * we can't just block this accept in all cases, because if |
|
+ * we did, a heavy TCP load on other interfaces might cause |
|
+ * this interface to be starved, with no clients able to |
|
+ * accept new connections. |
|
+ * |
|
+ * So, we check here to see if any other clients are |
|
+ * already servicing TCP queries on this interface (whether |
|
+ * accepting, reading, or processing). If we find that at |
|
+ * least one client other than this one is active, then |
|
+ * it's okay *not* to call accept - we can let this |
|
+ * client go inactive and another will take over when it's |
|
+ * done. |
|
+ * |
|
+ * If there aren't enough active clients on the interface, |
|
+ * then we can be a little bit flexible about the quota. |
|
+ * We'll allow *one* extra client through to ensure we're |
|
+ * listening on every interface; we do this by setting the |
|
+ * 'force' option to tcpconn_init(). |
|
+ * |
|
+ * (Note: In practice this means that the real TCP client |
|
+ * quota is tcp-clients plus the number of listening |
|
+ * interfaces plus 1.) |
|
+ */ |
|
+ exit = (isc_atomic_xadd(&client->interface->ntcpactive, 0) > |
|
+ (client->tcpactive ? 1 : 0)); |
|
+ if (exit) { |
|
+ client->newstate = NS_CLIENTSTATE_INACTIVE; |
|
+ (void)exit_check(client); |
|
+ return; |
|
+ } |
|
+ |
|
+ result = tcpconn_init(client, ISC_TRUE); |
|
+ RUNTIME_CHECK(result == ISC_R_SUCCESS); |
|
+ } |
|
+ |
|
+ /* |
|
+ * If this client was set up using get_client() or get_worker(), |
|
+ * then TCP is already marked active. However, if it was restarted |
|
+ * from exit_check(), it might not be, so we take care of it now. |
|
+ */ |
|
+ mark_tcp_active(client, ISC_TRUE); |
|
+ |
|
result = isc_socket_accept(client->tcplistener, client->task, |
|
client_newconn, client); |
|
if (result != ISC_R_SUCCESS) { |
|
- UNEXPECTED_ERROR(__FILE__, __LINE__, |
|
- "isc_socket_accept() failed: %s", |
|
- isc_result_totext(result)); |
|
/* |
|
* XXXRTH What should we do? We're trying to accept but |
|
* it didn't work. If we just give up, then TCP |
|
@@ -3286,13 +3495,37 @@ client_accept(ns_client_t *client) { |
|
* |
|
* For now, we just go idle. |
|
*/ |
|
+ UNEXPECTED_ERROR(__FILE__, __LINE__, |
|
+ "isc_socket_accept() failed: %s", |
|
+ isc_result_totext(result)); |
|
+ |
|
+ tcpconn_detach(client); |
|
+ mark_tcp_active(client, ISC_FALSE); |
|
return; |
|
} |
|
+ |
|
+ /* |
|
+ * The client's 'naccepts' counter indicates that this client has |
|
+ * called accept() and is waiting for a new connection. It should |
|
+ * never exceed 1. |
|
+ */ |
|
INSIST(client->naccepts == 0); |
|
client->naccepts++; |
|
- LOCK(&client->interface->lock); |
|
- client->interface->ntcpcurrent++; |
|
- UNLOCK(&client->interface->lock); |
|
+ |
|
+ /* |
|
+ * The interface's 'ntcpaccepting' counter is incremented when |
|
+ * any client calls accept(), and decremented in client_newconn() |
|
+ * once the connection is established. |
|
+ * |
|
+ * When the client object is shutting down after handling a TCP |
|
+ * request (see exit_check()), if this value is at least one, that |
|
+ * means another client has called accept() and is waiting to |
|
+ * establish the next connection. That means the client may be |
|
+ * be free to become inactive; otherwise it may need to start |
|
+ * listening for connections itself to prevent the interface |
|
+ * going dead. |
|
+ */ |
|
+ isc_atomic_xadd(&client->interface->ntcpaccepting, 1); |
|
} |
|
|
|
static void |
|
@@ -3363,15 +3596,17 @@ ns_client_replace(ns_client_t *client) { |
|
REQUIRE(client->manager != NULL); |
|
|
|
tcp = TCP_CLIENT(client); |
|
- if (tcp && client->pipelined) { |
|
+ if (tcp && client->tcpconn != NULL && client->tcpconn->pipelined) { |
|
result = get_worker(client->manager, client->interface, |
|
- client->tcpsocket); |
|
+ client->tcpsocket, client); |
|
} else { |
|
result = get_client(client->manager, client->interface, |
|
client->dispatch, tcp); |
|
+ |
|
} |
|
- if (result != ISC_R_SUCCESS) |
|
+ if (result != ISC_R_SUCCESS) { |
|
return (result); |
|
+ } |
|
|
|
/* |
|
* The responsibility for listening for new requests is hereby |
|
@@ -3557,9 +3792,12 @@ get_client(ns_clientmgr_t *manager, ns_interface_t *ifp, |
|
client->dscp = ifp->dscp; |
|
|
|
if (tcp) { |
|
+ mark_tcp_active(client, ISC_TRUE); |
|
+ |
|
client->attributes |= NS_CLIENTATTR_TCP; |
|
isc_socket_attach(ifp->tcpsocket, |
|
&client->tcplistener); |
|
+ |
|
} else { |
|
isc_socket_t *sock; |
|
|
|
@@ -3577,7 +3815,8 @@ get_client(ns_clientmgr_t *manager, ns_interface_t *ifp, |
|
} |
|
|
|
static isc_result_t |
|
-get_worker(ns_clientmgr_t *manager, ns_interface_t *ifp, isc_socket_t *sock) |
|
+get_worker(ns_clientmgr_t *manager, ns_interface_t *ifp, isc_socket_t *sock, |
|
+ ns_client_t *oldclient) |
|
{ |
|
isc_result_t result = ISC_R_SUCCESS; |
|
isc_event_t *ev; |
|
@@ -3585,6 +3824,7 @@ get_worker(ns_clientmgr_t *manager, ns_interface_t *ifp, isc_socket_t *sock) |
|
MTRACE("get worker"); |
|
|
|
REQUIRE(manager != NULL); |
|
+ REQUIRE(oldclient != NULL); |
|
|
|
if (manager->exiting) |
|
return (ISC_R_SHUTTINGDOWN); |
|
@@ -3617,14 +3857,15 @@ get_worker(ns_clientmgr_t *manager, ns_interface_t *ifp, isc_socket_t *sock) |
|
ns_interface_attach(ifp, &client->interface); |
|
client->newstate = client->state = NS_CLIENTSTATE_WORKING; |
|
INSIST(client->recursionquota == NULL); |
|
- client->tcpquota = &ns_g_server->tcpquota; |
|
|
|
client->dscp = ifp->dscp; |
|
|
|
client->attributes |= NS_CLIENTATTR_TCP; |
|
- client->pipelined = ISC_TRUE; |
|
client->mortal = ISC_TRUE; |
|
|
|
+ tcpconn_attach(oldclient, client); |
|
+ mark_tcp_active(client, ISC_TRUE); |
|
+ |
|
isc_socket_attach(ifp->tcpsocket, &client->tcplistener); |
|
isc_socket_attach(sock, &client->tcpsocket); |
|
isc_socket_setname(client->tcpsocket, "worker-tcp", NULL); |
|
diff --git a/bin/named/include/named/client.h b/bin/named/include/named/client.h |
|
index 262b906..0f54d22 100644 |
|
--- a/bin/named/include/named/client.h |
|
+++ b/bin/named/include/named/client.h |
|
@@ -9,8 +9,6 @@ |
|
* information regarding copyright ownership. |
|
*/ |
|
|
|
-/* $Id: client.h,v 1.96 2012/01/31 23:47:31 tbox Exp $ */ |
|
- |
|
#ifndef NAMED_CLIENT_H |
|
#define NAMED_CLIENT_H 1 |
|
|
|
@@ -77,6 +75,13 @@ |
|
*** Types |
|
***/ |
|
|
|
+/*% reference-counted TCP connection object */ |
|
+typedef struct ns_tcpconn { |
|
+ isc_refcount_t refs; |
|
+ isc_quota_t *tcpquota; |
|
+ isc_boolean_t pipelined; |
|
+} ns_tcpconn_t; |
|
+ |
|
/*% nameserver client structure */ |
|
struct ns_client { |
|
unsigned int magic; |
|
@@ -91,6 +96,7 @@ struct ns_client { |
|
int nupdates; |
|
int nctls; |
|
int references; |
|
+ isc_boolean_t tcpactive; |
|
isc_boolean_t needshutdown; /* |
|
* Used by clienttest to get |
|
* the client to go from |
|
@@ -129,8 +135,7 @@ struct ns_client { |
|
dns_name_t signername; /*%< [T]SIG key name */ |
|
dns_name_t * signer; /*%< NULL if not valid sig */ |
|
isc_boolean_t mortal; /*%< Die after handling request */ |
|
- isc_boolean_t pipelined; /*%< TCP queries not in sequence */ |
|
- isc_quota_t *tcpquota; |
|
+ ns_tcpconn_t *tcpconn; |
|
isc_quota_t *recursionquota; |
|
ns_interface_t *interface; |
|
|
|
diff --git a/bin/named/include/named/interfacemgr.h b/bin/named/include/named/interfacemgr.h |
|
index 36870f3..d9ac90f 100644 |
|
--- a/bin/named/include/named/interfacemgr.h |
|
+++ b/bin/named/include/named/interfacemgr.h |
|
@@ -9,8 +9,6 @@ |
|
* information regarding copyright ownership. |
|
*/ |
|
|
|
-/* $Id: interfacemgr.h,v 1.35 2011/07/28 23:47:58 tbox Exp $ */ |
|
- |
|
#ifndef NAMED_INTERFACEMGR_H |
|
#define NAMED_INTERFACEMGR_H 1 |
|
|
|
@@ -75,9 +73,14 @@ struct ns_interface { |
|
/*%< UDP dispatchers. */ |
|
isc_socket_t * tcpsocket; /*%< TCP socket. */ |
|
isc_dscp_t dscp; /*%< "listen-on" DSCP value */ |
|
- int ntcptarget; /*%< Desired number of concurrent |
|
- TCP accepts */ |
|
- int ntcpcurrent; /*%< Current ditto, locked */ |
|
+ int32_t ntcpaccepting; /*%< Number of clients |
|
+ ready to accept new |
|
+ TCP connections on this |
|
+ interface */ |
|
+ int32_t ntcpactive; /*%< Number of clients |
|
+ servicing TCP queries |
|
+ (whether accepting or |
|
+ connected) */ |
|
int nudpdispatch; /*%< Number of UDP dispatches */ |
|
ns_clientmgr_t * clientmgr; /*%< Client manager. */ |
|
ISC_LINK(ns_interface_t) link; |
|
diff --git a/bin/named/interfacemgr.c b/bin/named/interfacemgr.c |
|
index d8c7188..96c080b 100644 |
|
--- a/bin/named/interfacemgr.c |
|
+++ b/bin/named/interfacemgr.c |
|
@@ -384,8 +384,9 @@ ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, |
|
* connections will be handled in parallel even though there is |
|
* only one client initially. |
|
*/ |
|
- ifp->ntcptarget = 1; |
|
- ifp->ntcpcurrent = 0; |
|
+ ifp->ntcpaccepting = 0; |
|
+ ifp->ntcpactive = 0; |
|
+ |
|
ifp->nudpdispatch = 0; |
|
|
|
ifp->dscp = -1; |
|
@@ -520,9 +521,7 @@ ns_interface_accepttcp(ns_interface_t *ifp) { |
|
*/ |
|
(void)isc_socket_filter(ifp->tcpsocket, "dataready"); |
|
|
|
- result = ns_clientmgr_createclients(ifp->clientmgr, |
|
- ifp->ntcptarget, ifp, |
|
- ISC_TRUE); |
|
+ result = ns_clientmgr_createclients(ifp->clientmgr, 1, ifp, ISC_TRUE); |
|
if (result != ISC_R_SUCCESS) { |
|
UNEXPECTED_ERROR(__FILE__, __LINE__, |
|
"TCP ns_clientmgr_createclients(): %s", |
|
diff --git a/lib/isc/include/isc/quota.h b/lib/isc/include/isc/quota.h |
|
index b9bf598..36c5830 100644 |
|
--- a/lib/isc/include/isc/quota.h |
|
+++ b/lib/isc/include/isc/quota.h |
|
@@ -100,6 +100,13 @@ isc_quota_attach(isc_quota_t *quota, isc_quota_t **p); |
|
* quota if successful (ISC_R_SUCCESS or ISC_R_SOFTQUOTA). |
|
*/ |
|
|
|
+isc_result_t |
|
+isc_quota_force(isc_quota_t *quota, isc_quota_t **p); |
|
+/*%< |
|
+ * Like isc_quota_attach, but will attach '*p' to the quota |
|
+ * even if the hard quota has been exceeded. |
|
+ */ |
|
+ |
|
void |
|
isc_quota_detach(isc_quota_t **p); |
|
/*%< |
|
diff --git a/lib/isc/quota.c b/lib/isc/quota.c |
|
index 3ddff0d..20976a4 100644 |
|
--- a/lib/isc/quota.c |
|
+++ b/lib/isc/quota.c |
|
@@ -74,20 +74,39 @@ isc_quota_release(isc_quota_t *quota) { |
|
UNLOCK("a->lock); |
|
} |
|
|
|
-isc_result_t |
|
-isc_quota_attach(isc_quota_t *quota, isc_quota_t **p) |
|
-{ |
|
+static isc_result_t |
|
+doattach(isc_quota_t *quota, isc_quota_t **p, isc_boolean_t force) { |
|
isc_result_t result; |
|
- INSIST(p != NULL && *p == NULL); |
|
+ REQUIRE(p != NULL && *p == NULL); |
|
+ |
|
result = isc_quota_reserve(quota); |
|
- if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) |
|
+ if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) { |
|
+ *p = quota; |
|
+ } else if (result == ISC_R_QUOTA && force) { |
|
+ /* attach anyway */ |
|
+ LOCK("a->lock); |
|
+ quota->used++; |
|
+ UNLOCK("a->lock); |
|
+ |
|
*p = quota; |
|
+ result = ISC_R_SUCCESS; |
|
+ } |
|
+ |
|
return (result); |
|
} |
|
|
|
+isc_result_t |
|
+isc_quota_attach(isc_quota_t *quota, isc_quota_t **p) { |
|
+ return (doattach(quota, p, ISC_FALSE)); |
|
+} |
|
+ |
|
+isc_result_t |
|
+isc_quota_force(isc_quota_t *quota, isc_quota_t **p) { |
|
+ return (doattach(quota, p, ISC_TRUE)); |
|
+} |
|
+ |
|
void |
|
-isc_quota_detach(isc_quota_t **p) |
|
-{ |
|
+isc_quota_detach(isc_quota_t **p) { |
|
INSIST(p != NULL && *p != NULL); |
|
isc_quota_release(*p); |
|
*p = NULL; |
|
-- |
|
2.20.1 |
|
|
|
|