- Patch to implement tcp-connection-limit from Jim Hague (Sinodun).

This limits the number of simultaneous TCP client connections
  from a nominated netblock.
And a simple test for TCP connection limit.


git-svn-id: file:///svn/unbound/trunk@4835 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards 2018-08-07 11:57:42 +00:00
parent 070019c9be
commit 586b811b87
24 changed files with 1324 additions and 692 deletions

File diff suppressed because it is too large Load Diff

View File

@ -76,6 +76,7 @@
#include "util/shm_side/shm_main.h"
#include "util/storage/lookup3.h"
#include "util/storage/slabhash.h"
#include "util/tcp_conn_limit.h"
#include "services/listen_dnsport.h"
#include "services/cache/rrset.h"
#include "services/cache/infra.h"
@ -270,11 +271,20 @@ daemon_init(void)
free(daemon);
return NULL;
}
daemon->tcl = tcl_list_create();
if(!daemon->tcl) {
acl_list_delete(daemon->acl);
edns_known_options_delete(daemon->env);
free(daemon->env);
free(daemon);
return NULL;
}
if(gettimeofday(&daemon->time_boot, NULL) < 0)
log_err("gettimeofday: %s", strerror(errno));
daemon->time_last_stat = daemon->time_boot;
if((daemon->env->auth_zones = auth_zones_create()) == 0) {
acl_list_delete(daemon->acl);
tcl_list_delete(daemon->tcl);
edns_known_options_delete(daemon->env);
free(daemon->env);
free(daemon);
@ -575,6 +585,8 @@ daemon_fork(struct daemon* daemon)
if(!acl_list_apply_cfg(daemon->acl, daemon->cfg, daemon->views))
fatal_exit("Could not setup access control list");
if(!tcl_list_apply_cfg(daemon->tcl, daemon->cfg))
fatal_exit("Could not setup TCP connection limits");
if(daemon->cfg->dnscrypt) {
#ifdef USE_DNSCRYPT
daemon->dnscenv = dnsc_create();
@ -735,6 +747,7 @@ daemon_delete(struct daemon* daemon)
ub_randfree(daemon->rand);
alloc_clear(&daemon->superalloc);
acl_list_delete(daemon->acl);
tcl_list_delete(daemon->tcl);
free(daemon->chroot);
free(daemon->pidfile);
free(daemon->env);

View File

@ -113,6 +113,8 @@ struct daemon {
struct module_stack mods;
/** access control, which client IPs are allowed to connect */
struct acl_list* acl;
/** TCP connection limit, limit connections from client IPs */
struct tcl_list* tcl;
/** local authority zones */
struct local_zones* local_zones;
/** last time of statistics printout */

View File

@ -1744,6 +1744,7 @@ worker_init(struct worker* worker, struct config_file *cfg,
cfg->do_tcp_keepalive
? cfg->tcp_keepalive_timeout
: cfg->tcp_idle_timeout,
worker->daemon->tcl,
worker->daemon->listen_sslctx,
dtenv, worker_handle_request, worker);
if(!worker->front) {

View File

@ -5,6 +5,9 @@
about missing privileges during startup). Add 'AF_INET6' to
'RestrictAddressFamilies' (without it IPV6 can't work). From
Guido Shanahan.
- Patch to implement tcp-connection-limit from Jim Hague (Sinodun).
This limits the number of simultaneous TCP client connections
from a nominated netblock.
6 August 2018: Wouter
- Fix for #4136: Fix to unconditionally call destroy in daemon.c.

View File

@ -497,6 +497,11 @@ Enable or disable whether the unbound server forks into the background as
a daemon. Set the value to \fIno\fR when unbound runs as systemd service.
Default is yes.
.TP
.B tcp\-connection\-limit: \fI<IP netblock> <limit>
Allow up to \fIlimit\R simultaneous TCP connections from the given netblock.
When at the limit, further connections are accepted but closed immediately.
This option is experimental at this time.
.TP
.B access\-control: \fI<IP netblock> <action>
The netblock is given as an IP4 or IP6 address with /size appended for a
classless network block. The action can be \fIdeny\fR, \fIrefuse\fR,

View File

@ -1219,8 +1219,8 @@ listen_cp_insert(struct comm_point* c, struct listen_dnsport* front)
struct listen_dnsport*
listen_create(struct comm_base* base, struct listen_port* ports,
size_t bufsize, int tcp_accept_count, int tcp_idle_timeout,
void* sslctx, struct dt_env* dtenv,
comm_point_callback_type* cb, void *cb_arg)
struct tcl_list* tcp_conn_limit, void* sslctx,
struct dt_env* dtenv, comm_point_callback_type* cb, void *cb_arg)
{
struct listen_dnsport* front = (struct listen_dnsport*)
malloc(sizeof(struct listen_dnsport));
@ -1247,11 +1247,11 @@ listen_create(struct comm_base* base, struct listen_port* ports,
ports->ftype == listen_type_tcp_dnscrypt)
cp = comm_point_create_tcp(base, ports->fd,
tcp_accept_count, tcp_idle_timeout,
bufsize, cb, cb_arg);
tcp_conn_limit, bufsize, cb, cb_arg);
else if(ports->ftype == listen_type_ssl) {
cp = comm_point_create_tcp(base, ports->fd,
tcp_accept_count, tcp_idle_timeout,
bufsize, cb, cb_arg);
tcp_conn_limit, bufsize, cb, cb_arg);
cp->ssl = sslctx;
} else if(ports->ftype == listen_type_udpancil ||
ports->ftype == listen_type_udpancil_dnscrypt)

View File

@ -47,6 +47,7 @@ struct listen_list;
struct config_file;
struct addrinfo;
struct sldns_buffer;
struct tcl_list;
/**
* Listening for queries structure.
@ -138,6 +139,7 @@ void listening_ports_free(struct listen_port* list);
* @param tcp_accept_count: max number of simultaneous TCP connections
* from clients.
* @param tcp_idle_timeout: idle timeout for TCP connections in msec.
* @param tcp_conn_limit: TCP connection limit info.
* @param sslctx: nonNULL if ssl context.
* @param dtenv: nonNULL if dnstap enabled.
* @param cb: callback function when a request arrives. It is passed
@ -148,8 +150,8 @@ void listening_ports_free(struct listen_port* list);
struct listen_dnsport* listen_create(struct comm_base* base,
struct listen_port* ports, size_t bufsize,
int tcp_accept_count, int tcp_idle_timeout,
void* sslctx, struct dt_env *dtenv, comm_point_callback_type* cb,
void* cb_arg);
struct tcl_list* tcp_conn_limit, void* sslctx,
struct dt_env *dtenv, comm_point_callback_type* cb, void* cb_arg);
/**
* delete the listening structure

View File

@ -252,6 +252,23 @@ aclchecks(struct config_file* cfg)
}
}
/** check tcp connection limit ips */
static void
tcpconnlimitchecks(struct config_file* cfg)
{
int d;
struct sockaddr_storage a;
socklen_t alen;
struct config_str2list* tcl;
for(tcl=cfg->tcp_connection_limits; tcl; tcl = tcl->next) {
if(!netblockstrtoaddr(tcl->str, UNBOUND_DNS_PORT, &a, &alen,
&d)) {
fatal_exit("cannot parse tcp connection limit address %s %s",
tcl->str, tcl->str2);
}
}
}
/** true if fname is a file */
static int
is_file(const char* fname)
@ -381,6 +398,7 @@ morechecks(struct config_file* cfg, const char* fname)
warn_hosts("forward-host", cfg->forwards);
interfacechecks(cfg);
aclchecks(cfg);
tcpconnlimitchecks(cfg);
if(cfg->verbosity < 0)
fatal_exit("verbosity value < 0");

View File

@ -868,6 +868,7 @@ struct listen_dnsport*
listen_create(struct comm_base* base, struct listen_port* ATTR_UNUSED(ports),
size_t bufsize, int ATTR_UNUSED(tcp_accept_count),
int ATTR_UNUSED(tcp_idle_timeout),
struct tcl_list* ATTR_UNUSED(tcp_conn_limit),
void* ATTR_UNUSED(sslctx), struct dt_env* ATTR_UNUSED(dtenv),
comm_point_callback_type* cb, void* cb_arg)
{

View File

@ -0,0 +1,16 @@
server:
verbosity: 2
# num-threads: 1
interface: 127.0.0.1
port: @PORT@
use-syslog: no
directory: .
pidfile: "unbound.pid"
chroot: ""
username: ""
do-not-query-localhost: no
tcp-connection-limit: 0.0.0.0/0 0
forward-zone:
name: "."
forward-addr: "127.0.0.1@@TOPORT@"

View File

@ -0,0 +1,16 @@
BaseName: tcp_conn_limit
Version: 1.0
Description: Test tcp-connection-limit setting.
CreationDate: Fri Aug 3 17:18:00 BST 2018
Maintainer: dr. J. Hague
Category:
Component:
CmdDepends:
Depends:
Help:
Pre: tcp_conn_limit.pre
Post: tcp_conn_limit.post
Test: tcp_conn_limit.test
AuxFiles:
Passed:
Failure:

View File

@ -0,0 +1,10 @@
# #-- tcp_conn_limit.post --#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# source the test var file when it's there
[ -f .tpkg.var.test ] && source .tpkg.var.test
#
# do your teardown here
. ../common.sh
kill_pid $FWD_PID
kill_pid $UNBOUND_PID

View File

@ -0,0 +1,31 @@
# #-- tcp_conn_limit.pre--#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# use .tpkg.var.test for in test variable passing
[ -f .tpkg.var.test ] && source .tpkg.var.test
. ../common.sh
get_random_port 2
UNBOUND_PORT=$RND_PORT
FWD_PORT=$(($RND_PORT + 1))
echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
# start forwarder
get_ldns_testns
$LDNS_TESTNS -p $FWD_PORT tcp_conn_limit.testns >fwd.log 2>&1 &
FWD_PID=$!
echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
# make config file
sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' < tcp_conn_limit.conf > ub.conf
# start unbound in the background
PRE="../.."
$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
UNBOUND_PID=$!
echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
cat .tpkg.var.test
wait_ldns_testns_up fwd.log
wait_unbound_up unbound.log

View File

@ -0,0 +1,42 @@
# #-- tcp_conn_limit.test --#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# use .tpkg.var.test for in test variable passing
[ -f .tpkg.var.test ] && source .tpkg.var.test
# Set unbound to limit all IPv4 addresses to 0 connections.
# Make sure TCP connection fails.
if uname | grep MINGW >/dev/null; then
echo "no job control in shell on windows. end test"
exit 0
fi
PRE="../.."
. ../common.sh
get_make
(cd $PRE; $MAKE streamtcp)
# test query should fail.
echo "> query www.example.com."
$PRE/streamtcp -f 127.0.0.1@$UNBOUND_PORT www.example.com. A IN >outfile 2>&1
if test "$?" -eq 0; then
echo "exit status OK"
echo "> cat logfiles"
cat outfile
cat fwd.log
cat unbound.log
echo "Not OK"
exit 1
else
echo "exit status not OK"
fi
echo "> cat logfiles"
cat outfile
cat fwd.log
cat unbound.log
echo "OK"
exit 0

View File

@ -0,0 +1,42 @@
; nameserver test file
$ORIGIN example.com.
$TTL 3600
ENTRY_BEGIN
MATCH UDP opcode qtype qname
REPLY QR AA NOERROR TC
ADJUST copy_id
SECTION QUESTION
www IN A
ENTRY_END
ENTRY_BEGIN
MATCH TCP opcode qtype qname
REPLY QR AA NOERROR
ADJUST copy_id sleep=2
SECTION QUESTION
www IN A
SECTION ANSWER
www IN A 10.20.30.40
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
REPLY QR AA NOERROR
ADJUST copy_id
SECTION QUESTION
www2 IN A
SECTION ANSWER
www2 IN A 10.20.30.42
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
REPLY QR AA NOERROR
ADJUST copy_id
SECTION QUESTION
www3 IN A
SECTION ANSWER
www3 IN A 10.20.30.43
ENTRY_END

View File

@ -195,6 +195,7 @@ config_create(void)
#endif
cfg->views = NULL;
cfg->acls = NULL;
cfg->tcp_connection_limits = NULL;
cfg->harden_short_bufsize = 0;
cfg->harden_large_queries = 0;
cfg->harden_glue = 1;
@ -945,6 +946,7 @@ config_get_option(struct config_file* cfg, const char* opt,
else O_STR(opt, "control-cert-file", control_cert_file)
else O_LST(opt, "root-hints", root_hints)
else O_LS2(opt, "access-control", acls)
else O_LS2(opt, "tcp-connection-limit", tcp_connection_limits)
else O_LST(opt, "do-not-query-address", donotqueryaddrs)
else O_LST(opt, "private-address", private_address)
else O_LST(opt, "private-domain", private_domain)
@ -1347,6 +1349,7 @@ config_delete(struct config_file* cfg)
free(cfg->dlv_anchor_file);
config_delstrlist(cfg->dlv_anchor_list);
config_deldblstrlist(cfg->acls);
config_deldblstrlist(cfg->tcp_connection_limits);
free(cfg->val_nsec3_key_iterations);
config_deldblstrlist(cfg->local_zones);
config_delstrlist(cfg->local_zones_nodefault);

View File

@ -220,6 +220,9 @@ struct config_file {
/** use default localhost donotqueryaddr entries */
int donotquery_localhost;
/** list of tcp connection limitss, linked list */
struct config_str2list* tcp_connection_limits;
/** harden against very small edns buffer sizes */
int harden_short_bufsize;
/** harden against very large query sizes */

View File

@ -466,6 +466,7 @@ redis-server-host{COLON} { YDVAR(1, VAR_CACHEDB_REDISHOST) }
redis-server-port{COLON} { YDVAR(1, VAR_CACHEDB_REDISPORT) }
redis-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) }
udp-upstream-without-downstream{COLON} { YDVAR(1, VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM) }
tcp-connection-limit{COLON} { YDVAR(2, VAR_TCP_CONNECTION_LIMIT) }
<INITIAL,val>{NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++; }
/* Quoted strings. Strip leading and ending quotes */

View File

@ -158,7 +158,7 @@ extern struct config_parser_state* cfg_parser;
%token VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM VAR_FOR_UPSTREAM
%token VAR_AUTH_ZONE VAR_ZONEFILE VAR_MASTER VAR_URL VAR_FOR_DOWNSTREAM
%token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL
%token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT
%token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT VAR_TCP_CONNECTION_LIMIT
%%
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
@ -251,7 +251,8 @@ content_server: server_num_threads | server_verbosity | server_port |
server_ipsecmod_whitelist | server_ipsecmod_strict |
server_udp_upstream_without_downstream | server_aggressive_nsec |
server_tls_cert_bundle | server_tls_additional_port | server_low_rtt |
server_low_rtt_permil | server_tls_win_cert
server_low_rtt_permil | server_tls_win_cert |
server_tcp_connection_limit
;
stubstart: VAR_STUB_ZONE
{
@ -2726,6 +2727,17 @@ redis_timeout: VAR_CACHEDB_REDISTIMEOUT STRING_ARG
free($2);
}
;
server_tcp_connection_limit: VAR_TCP_CONNECTION_LIMIT STRING_ARG STRING_ARG
{
OUTYY(("P(server_tcp_connection_limit:%s %s)\n", $2, $3));
if (atoi($3) < 0)
yyerror("positive number expected");
else {
if(!cfg_str2list_insert(&cfg_parser->cfg->tcp_connection_limits, $2, $3))
fatal_exit("out of memory adding tcp connection limit");
}
}
;
%%
/* parse helper routines could be here */

View File

@ -43,6 +43,7 @@
#include "util/ub_event.h"
#include "util/log.h"
#include "util/net_help.h"
#include "util/tcp_conn_limit.h"
#include "util/fptr_wlist.h"
#include "sldns/pkthdr.h"
#include "sldns/sbuffer.h"
@ -850,6 +851,15 @@ int comm_point_perform_accept(struct comm_point* c,
#endif
return -1;
}
if(c->tcp_conn_limit && c->type == comm_tcp_accept) {
c->tcl_addr = tcl_addr_lookup(c->tcp_conn_limit, addr, *addrlen);
if(!tcl_new_connection(c->tcl_addr)) {
log_err_addr("accept rejected",
"connection limit exceeded", addr, *addrlen);
close(new_fd);
return -1;
}
}
#ifndef HAVE_ACCEPT4
fd_set_nonblock(new_fd);
#endif
@ -2544,6 +2554,8 @@ comm_point_create_tcp_handler(struct comm_base *base,
c->tcp_byte_count = 0;
c->tcp_parent = parent;
c->tcp_timeout_msec = parent->tcp_timeout_msec;
c->tcp_conn_limit = parent->tcp_conn_limit;
c->tcl_addr = NULL;
c->tcp_keepalive = 0;
c->max_tcp_count = 0;
c->cur_tcp_count = 0;
@ -2586,7 +2598,7 @@ comm_point_create_tcp_handler(struct comm_base *base,
struct comm_point*
comm_point_create_tcp(struct comm_base *base, int fd, int num,
int idle_timeout, size_t bufsize,
int idle_timeout, struct tcl_list* tcp_conn_limit, size_t bufsize,
comm_point_callback_type* callback, void* callback_arg)
{
struct comm_point* c = (struct comm_point*)calloc(1,
@ -2609,6 +2621,8 @@ comm_point_create_tcp(struct comm_base *base, int fd, int num,
c->tcp_is_reading = 0;
c->tcp_byte_count = 0;
c->tcp_timeout_msec = idle_timeout;
c->tcp_conn_limit = tcp_conn_limit;
c->tcl_addr = NULL;
c->tcp_keepalive = 0;
c->tcp_parent = NULL;
c->max_tcp_count = num;
@ -2689,6 +2703,8 @@ comm_point_create_tcp_out(struct comm_base *base, size_t bufsize,
c->tcp_is_reading = 0;
c->tcp_byte_count = 0;
c->tcp_timeout_msec = TCP_QUERY_TIMEOUT;
c->tcp_conn_limit = NULL;
c->tcl_addr = NULL;
c->tcp_keepalive = 0;
c->tcp_parent = NULL;
c->max_tcp_count = 0;
@ -2931,6 +2947,7 @@ comm_point_close(struct comm_point* c)
log_err("could not event_del on close");
}
}
tcl_close_connection(c->tcl_addr);
/* close fd after removing from event lists, or epoll.. is messed up */
if(c->fd != -1 && !c->do_not_close) {
if(c->type == comm_tcp || c->type == comm_http) {

View File

@ -65,6 +65,7 @@
struct sldns_buffer;
struct comm_point;
struct comm_reply;
struct tcl_list;
struct ub_event_base;
/* internal event notification data storage structure. */
@ -262,6 +263,11 @@ struct comm_point {
/** if set, checks for pending error from nonblocking connect() call.*/
int tcp_check_nb_connect;
/** if set, check for connection limit on tcp accept. */
struct tcl_list* tcp_conn_limit;
/** the entry for the connection. */
struct tcl_addr* tcl_addr;
#ifdef USE_MSG_FASTOPEN
/** used to track if the sendto() call should be done when using TFO. */
int tcp_do_fastopen;
@ -447,6 +453,7 @@ struct comm_point* comm_point_create_udp_ancil(struct comm_base* base,
* @param num: becomes max_tcp_count, the routine allocates that
* many tcp handler commpoints.
* @param idle_timeout: TCP idle timeout in ms.
* @param tcp_conn_limit: TCP connection limit info.
* @param bufsize: size of buffer to create for handlers.
* @param callback: callback function pointer for TCP handlers.
* @param callback_arg: will be passed to your callback function.
@ -456,8 +463,8 @@ struct comm_point* comm_point_create_udp_ancil(struct comm_base* base,
* Inits timeout to NULL. All handlers are on the free list.
*/
struct comm_point* comm_point_create_tcp(struct comm_base* base,
int fd, int num, int idle_timeout, size_t bufsize,
comm_point_callback_type* callback, void* callback_arg);
int fd, int num, int idle_timeout, struct tcl_list* tcp_conn_limit,
size_t bufsize, comm_point_callback_type* callback, void* callback_arg);
/**
* Create an outgoing TCP commpoint. No file descriptor is opened, left at -1.

191
util/tcp_conn_limit.c Normal file
View File

@ -0,0 +1,191 @@
/*
* daemon/tcp_conn_limit.c - client TCP connection limit storage for the server.
*
* Copyright (c) 2018, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
*
* This file helps the server discard excess TCP connections.
*/
#include "config.h"
#include "util/regional.h"
#include "util/log.h"
#include "util/config_file.h"
#include "util/net_help.h"
#include "util/tcp_conn_limit.h"
#include "services/localzone.h"
#include "sldns/str2wire.h"
struct tcl_list*
tcl_list_create(void)
{
struct tcl_list* tcl = (struct tcl_list*)calloc(1,
sizeof(struct tcl_list));
if(!tcl)
return NULL;
tcl->region = regional_create();
if(!tcl->region) {
tcl_list_delete(tcl);
return NULL;
}
return tcl;
}
static void
tcl_list_free_node(rbnode_type* node, void* ATTR_UNUSED(arg))
{
struct tcl_addr* n = (struct tcl_addr*) node;
lock_quick_destroy(&n->lock);
}
void
tcl_list_delete(struct tcl_list* tcl)
{
if(!tcl)
return;
traverse_postorder(&tcl->tree, tcl_list_free_node, NULL);
regional_destroy(tcl->region);
free(tcl);
}
/** insert new address into tcl_list structure */
static struct tcl_addr*
tcl_list_insert(struct tcl_list* tcl, struct sockaddr_storage* addr,
socklen_t addrlen, int net, uint32_t limit,
int complain_duplicates)
{
struct tcl_addr* node = regional_alloc_zero(tcl->region,
sizeof(struct tcl_addr));
if(!node)
return NULL;
lock_quick_init(&node->lock);
node->limit = limit;
if(!addr_tree_insert(&tcl->tree, &node->node, addr, addrlen, net)) {
if(complain_duplicates)
verbose(VERB_QUERY, "duplicate tcl address ignored.");
}
return node;
}
/** apply tcl_list string */
static int
tcl_list_str_cfg(struct tcl_list* tcl, const char* str, const char* s2,
int complain_duplicates)
{
struct sockaddr_storage addr;
int net;
socklen_t addrlen;
uint32_t limit;
if(atoi(s2) < 0) {
log_err("bad connection limit %s", s2);
return 0;
}
limit = (uint32_t)atoi(s2);
if(!netblockstrtoaddr(str, UNBOUND_DNS_PORT, &addr, &addrlen, &net)) {
log_err("cannot parse connection limit netblock: %s", str);
return 0;
}
if(!tcl_list_insert(tcl, &addr, addrlen, net, limit,
complain_duplicates)) {
log_err("out of memory");
return 0;
}
return 1;
}
/** read tcl_list config */
static int
read_tcl_list(struct tcl_list* tcl, struct config_file* cfg)
{
struct config_str2list* p;
for(p = cfg->tcp_connection_limits; p; p = p->next) {
log_assert(p->str && p->str2);
if(!tcl_list_str_cfg(tcl, p->str, p->str2, 1))
return 0;
}
return 1;
}
int
tcl_list_apply_cfg(struct tcl_list* tcl, struct config_file* cfg)
{
regional_free_all(tcl->region);
addr_tree_init(&tcl->tree);
if(!read_tcl_list(tcl, cfg))
return 0;
addr_tree_init_parents(&tcl->tree);
return 1;
}
int
tcl_new_connection(struct tcl_addr* tcl)
{
if(tcl) {
int res = 1;
lock_quick_lock(&tcl->lock);
if(tcl->count >= tcl->limit)
res = 0;
else
tcl->count++;
lock_quick_unlock(&tcl->lock);
return res;
}
return 1;
}
void
tcl_close_connection(struct tcl_addr* tcl)
{
if(tcl) {
lock_quick_lock(&tcl->lock);
log_assert(tcl->count > 0);
tcl->count--;
lock_quick_unlock(&tcl->lock);
}
}
struct tcl_addr*
tcl_addr_lookup(struct tcl_list* tcl, struct sockaddr_storage* addr,
socklen_t addrlen)
{
return (struct tcl_addr*)addr_tree_lookup(&tcl->tree,
addr, addrlen);
}
size_t
tcl_list_get_mem(struct tcl_list* tcl)
{
if(!tcl) return 0;
return sizeof(*tcl) + regional_get_mem(tcl->region);
}

130
util/tcp_conn_limit.h Normal file
View File

@ -0,0 +1,130 @@
/*
* daemon/tcp_conn_limit.h - client TCP connection limit storage for the server.
*
* Copyright (c) 2018, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
*
* This file keeps track of the limit on the number of TCP connections
* each client makes the server.
*/
#ifndef DAEMON_TCP_CONN_LIMIT_H
#define DAEMON_TCP_CONN_LIMIT_H
#include "util/storage/dnstree.h"
#include "util/locks.h"
struct config_file;
struct regional;
/**
* TCP connection limit storage structure
*/
struct tcl_list {
/** regional for allocation */
struct regional* region;
/**
* Tree of the addresses that are TCP connection limited.
* contents of type tcl_addr.
*/
rbtree_type tree;
};
/**
*
* An address span with connection limit information
*/
struct tcl_addr {
/** node in address tree */
struct addr_tree_node node;
/** lock on structure data */
lock_quick_type lock;
/** connection limit on this netblock */
uint32_t limit;
/** current connection count on this netblock */
uint32_t count;
};
/**
* Create TCP connection limit structure
* @return new structure or NULL on error.
*/
struct tcl_list* tcl_list_create(void);
/**
* Delete TCP connection limit structure.
* @param tcl: to delete.
*/
void tcl_list_delete(struct tcl_list* tcl);
/**
* Process TCP connection limit config.
* @param tcl: where to store.
* @param cfg: config options.
* @return 0 on error.
*/
int tcl_list_apply_cfg(struct tcl_list* tcl, struct config_file* cfg);
/**
* Increment TCP connection count if found, provided the
* count was below the limit.
* @param tcl: structure for tcl storage, or NULL.
* @return: 0 if limit reached, 1 if tcl was NULL or limit not reached.
*/
int tcl_new_connection(struct tcl_addr* tcl);
/**
* Decrement TCP connection count if found.
* @param tcl: structure for tcl storage, or NULL.
*/
void tcl_close_connection(struct tcl_addr* tcl);
/**
* Lookup address to see its TCP connection limit structure
* @param tcl: structure for address storage.
* @param addr: address to check
* @param addrlen: length of addr.
* @return: tcl structure from this address.
*/
struct tcl_addr*
tcl_addr_lookup(struct tcl_list* acl, struct sockaddr_storage* addr,
socklen_t addrlen);
/**
* Get memory used by TCP connection limit structure.
* @param tcl: structure for address storage.
* @return bytes in use.
*/
size_t tcl_list_get_mem(struct tcl_list* tcl);
#endif /* DAEMON_TCP_CONN_LIMIT_H */