diff --git a/common.c b/common.c index db97490..2be45d4 100644 --- a/common.c +++ b/common.c @@ -595,6 +595,37 @@ void dump_connection(struct connection *cnx) } +/* *cnx must have its proto field probed already. + * If required, increment the connection count for this protocol. + * Returns 1 if connection count is exceeded, 0 otherwise */ +int inc_connections(struct connection* cnx) +{ + if (cnx->proto->max_connections_is_present) { + cnx->proto->num_connections++; + print_message(msg_connections, "Proto %s +1: %d/%d cnx\n", + cnx->proto->name, + cnx->proto->num_connections, + cnx->proto->max_connections); + if (cnx->proto->num_connections > cnx->proto->max_connections) { + print_message(msg_connections_error, "%s: too many connections, dropping", cnx->proto->name); + return 1; + } + } + return 0; +} + +void dec_connections(struct connection* cnx) +{ + if (cnx->proto) { + cnx->proto->num_connections--; + print_message(msg_connections, "Proto %s -1: %d/%d cnx\n", + cnx->proto->name, + cnx->proto->num_connections, + cnx->proto->max_connections); + } +} + + /* * moves data from one fd to other * diff --git a/common.h b/common.h index 7243104..16505f9 100644 --- a/common.h +++ b/common.h @@ -174,6 +174,8 @@ void drop_privileges(const char* user_name, const char* chroot_path); void set_capabilities(int cap_net_admin); void write_pid_file(const char* pidfile); void dump_connection(struct connection *cnx); +int inc_connections(struct connection* cnx); +void dec_connections(struct connection* cnx); int resolve_split_name(struct addrinfo **out, char* hostname, char* port); int start_listen_sockets(struct listen_endpoint *sockfd[]); diff --git a/echosrv-conf.c b/echosrv-conf.c index a46ee1e..1e02f54 100644 --- a/echosrv-conf.c +++ b/echosrv-conf.c @@ -1,5 +1,5 @@ /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) - * on Sun Apr 6 11:44:59 2025. + * on Wed Jul 16 17:59:23 2025. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle diff --git a/echosrv-conf.h b/echosrv-conf.h index de4b439..0c44b41 100644 --- a/echosrv-conf.h +++ b/echosrv-conf.h @@ -1,5 +1,5 @@ /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) - * on Sun Apr 6 11:44:59 2025. + * on Wed Jul 16 17:59:23 2025. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle diff --git a/example.cfg b/example.cfg index 74a8e10..9f58f9c 100644 --- a/example.cfg +++ b/example.cfg @@ -70,7 +70,11 @@ listen: # keepalive: Should TCP keepalive be on or off for that # connection (default is off) # fork: Should a new process be forked for this protocol? -# (only useful for sslh-select) +# (only useful for sslh-select and sslh-ev) +# max_connections: further connections for this protocol +# will be rejected once the limit is reached. Does not +# work when fork is used (either in sslh-fork or if the +# previous option is set). # tfo_ok: Set to true if the server supports TCP FAST OPEN # resolve_on_forward: Set to true if server address should be resolved on # (every) newly incoming connection (again) diff --git a/processes.c b/processes.c index b7a748d..878889f 100644 --- a/processes.c +++ b/processes.c @@ -49,6 +49,8 @@ int tidy_connection(struct connection *cnx, struct loop_info* fd_info) if (gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing) != -1) fd_info->num_probing--; + dec_connections(cnx); + collection_remove_cnx(fd_info->collection, cnx); return 0; } diff --git a/sslh-conf.c b/sslh-conf.c index 325e300..e7d8ab4 100644 --- a/sslh-conf.c +++ b/sslh-conf.c @@ -1,5 +1,5 @@ /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) - * on Sun Apr 6 11:44:58 2025. + * on Wed Jul 16 17:59:23 2025. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle @@ -500,7 +500,7 @@ struct arg_file* sslhcfg_conffile; struct arg_str* sslhcfg_anyprot; struct arg_end* sslhcfg_end; - + static struct config_desc table_sslhcfg_protocols[] = { @@ -712,6 +712,22 @@ static struct config_desc table_sslhcfg_protocols[] = { /* default_val*/ .default_val.def_bool = 0 }, + { + /* name */ "max_connections", + /* type */ CFG_INT, + /* sub_group*/ NULL, + /* arg_cl */ NULL, + /* base_addr */ NULL, + /* offset */ offsetof(struct sslhcfg_protocols_item, max_connections), + /* offset_len */ 0, + /* offset_present */ offsetof(struct sslhcfg_protocols_item, max_connections_is_present), + /* size */ sizeof(int), + /* array_type */ -1, + /* mandatory */ 0, + /* optional */ 1, + /* default_val*/ .default_val.def_int = 0 + }, + { /* name */ "sni_hostnames", /* type */ CFG_ARRAY, @@ -2421,6 +2437,11 @@ static void sslhcfg_protocols_fprint( fprintf(out, "keepalive: %d", sslhcfg_protocols->keepalive); fprintf(out, "\n"); indent(out, depth); + fprintf(out, "max_connections: %d", sslhcfg_protocols->max_connections); + if (! sslhcfg_protocols->max_connections_is_present) + fprintf(out, " "); + fprintf(out, "\n"); + indent(out, depth); fprintf(out, "sni_hostnames [%zu]:\n", sslhcfg_protocols->sni_hostnames_len); for (i = 0; i < sslhcfg_protocols->sni_hostnames_len; i++) { indent(out, depth+1); diff --git a/sslh-conf.h b/sslh-conf.h index 86058f4..3eff28b 100644 --- a/sslh-conf.h +++ b/sslh-conf.h @@ -1,5 +1,5 @@ /* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) - * on Sun Apr 6 11:44:58 2025. + * on Wed Jul 16 17:59:23 2025. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2024 Yves Rutschle @@ -63,6 +63,8 @@ struct sslhcfg_protocols_item { int resolve_on_forward; int log_level; int keepalive; + int max_connections_is_present; + int max_connections; size_t sni_hostnames_len; char** sni_hostnames; size_t alpn_protocols_len; @@ -76,6 +78,7 @@ struct sslhcfg_protocols_item { T_PROBE* probe; struct addrinfo* saddr; void* data; + unsigned int num_connections; dl_list timeouts; }; diff --git a/sslhconf.cfg b/sslhconf.cfg index 6b8444f..e5fa5de 100644 --- a/sslhconf.cfg +++ b/sslhconf.cfg @@ -123,6 +123,7 @@ config: { description: "Set to true if server address should be resolved on (every) newly incoming connection (again)" }, { name: "log_level"; type: "int"; default: 1 }, { name: "keepalive"; type: "bool"; default: false }, + { name: "max_connections"; type: "int"; optional: true }, { name: "sni_hostnames", type: "array", element_type: "string" @@ -142,6 +143,7 @@ config: { { name: "probe"; type: "runtime"; c_type: "T_PROBE*" }, { name: "saddr"; type: "runtime"; c_type: "struct addrinfo*" }, { name: "data"; type: "runtime"; c_type: "void*" }, + { name: "num_connections"; type: "runtime"; c_type: "unsigned int" }, { name: "timeouts"; type: "runtime"; c_type: "dl_list" } ) } diff --git a/tcp-listener.c b/tcp-listener.c index 7110dbe..49f6ec8 100644 --- a/tcp-listener.c +++ b/tcp-listener.c @@ -276,6 +276,11 @@ void probing_read_process(struct connection* cnx, remove_probing_cnx(fd_info, cnx); cnx->state = ST_SHOVELING; + if (inc_connections(cnx)) { + tidy_connection(cnx, fd_info); + return; + } + /* libwrap check if required for this protocol */ if (cnx->proto->service && check_access_rights(cnx->q[0].fd, cnx->proto->service)) { diff --git a/test.cfg b/test.cfg index 9aa7877..41cc499 100644 --- a/test.cfg +++ b/test.cfg @@ -43,7 +43,8 @@ listen: protocols: ( - { name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; }, + { name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; resolve_on_forward: true; +max_connections: 1; }, { name: "socks5"; host: "localhost"; port: "9001"; }, { name: "http"; host: "localhost"; port: "80"; proxyprotocol: 2; }, { name: "tinc"; host: "localhost"; port: "9003"; }, @@ -52,6 +53,7 @@ protocols: { name: "adb"; host: "localhost"; port: "9010"; }, { name: "syslog"; host: "localhost"; port: "9013"; }, { name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020"; + max_connections: 1; udp_timeout: 30; regex_patterns: [ "^foo" ]; resolve_on_forward: true; @@ -72,6 +74,7 @@ protocols: { name: "tls"; host: "localhost"; port: "9023"; alpn_protocols: [ "alpn3" ]; }, { name: "tls"; host: "localhost"; port: "9024"; sni_hostnames: [ "sni3" ]; }, { name: "tls"; host: "localhost"; port: "9025"; }, + { name: "anyprot"; host: "ip4-localhost"; is_udp: true; port: "9999"; }, { name: "anyprot"; host: "localhost"; port: "9099"; } ); diff --git a/udp-listener.c b/udp-listener.c index 720cc94..4f967a2 100644 --- a/udp-listener.c +++ b/udp-listener.c @@ -302,6 +302,11 @@ struct connection* udp_c2s_forward(int sockfd, struct loop_info* fd_info) cnx->addrlen = addrlen; cnx->local_endpoint = sockfd; + if (inc_connections(cnx)) { + tidy_connection(cnx, fd_info); + return NULL; + } + res = new_source(fd_info->hash_sources, cnx); if (res == -1) { print_message(msg_connections_error, "Out of hash space for new incoming UDP connection -- increase udp_max_connections");