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