From b2c0c7a522eab5e5b6ec8a9b212fe4c861601c6e Mon Sep 17 00:00:00 2001 From: Pavel Punsky Date: Mon, 20 Oct 2025 14:02:16 -0700 Subject: [PATCH] ### Why are the changes being made? Add CLI flag to enable early packet validation Drop packets that do not pass basic STUN command/channel or DTLS parsing Before this change, no validation on packets were done and they were passed (through libevent queue) to relay thread pool. Relay thread would, for a new source, allocate a new SS (18KB) which will only be released after 1s of no traffic, and then do the validation. So with old code, invalid packet would have extra: - Queuing - Processing on a different thread - Memory allocation of 18KB Assuming DDoS attack is spoofing IPs it reduces processing capacity dramatically. Testing possible by: ``` hping3 -2 -p 3478 -d 2 -rand-source --flood turn_ip ``` which floods `turn_ip:3478` with packets of size 2 from random sources. Size 2 is especially bad case - the packet is obviously invalid (too short) but still goes through a long process of queuing, thread switching, memory allocation and only then validation (and then memory cleanup etc). In worst cases, memory is never cleaned up because sources repeat. --- src/apps/relay/dtls_listener.c | 76 +++++++++++++++++++++++++++++----- src/apps/relay/mainrelay.c | 4 +- src/apps/relay/mainrelay.h | 1 + src/apps/relay/prom_server.c | 24 +++++++++++ src/apps/relay/prom_server.h | 5 +++ 5 files changed, 98 insertions(+), 12 deletions(-) diff --git a/src/apps/relay/dtls_listener.c b/src/apps/relay/dtls_listener.c index 5d7da07d..1944e252 100644 --- a/src/apps/relay/dtls_listener.c +++ b/src/apps/relay/dtls_listener.c @@ -39,6 +39,7 @@ #include "ns_ioalib_impl.h" #include "ns_turn_openssl.h" +#include "prom_server.h" #include @@ -62,6 +63,8 @@ typedef uint16_t in_port_t; #define MAX_SINGLE_UDP_BATCH (16) +static int packetcounter = 1; + struct dtls_listener_relay_server_info { char ifname[1025]; ioa_addr addr; @@ -130,6 +133,33 @@ int get_dtls_version(const unsigned char *buf, int len) { return 0; } +static size_t print_packet_txt2pcap(uint64_t now, uint8_t *payload, size_t payload_length, uint8_t *txt2pcap, + size_t txt2pcap_length) { + + size_t index = 0; + int64_t remaining = now % (24 * 60 * 60 * 1000); + int hours = remaining / (60 * 60 * 1000); + remaining = remaining % (60 * 60 * 1000); + int minutes = remaining / (60 * 1000); + remaining = remaining % (60 * 1000); + int seconds = remaining / 1000; + int ms = remaining % 1000; + + snprintf((char *)(txt2pcap + index), txt2pcap_length - index, "%02d:%02d:%02d.%03d", hours, minutes, seconds, ms); + + index += sizeof("00:00:00.000") - 1; + snprintf((char *)(txt2pcap + index), txt2pcap_length - index, " 0000"); + index += sizeof(" 0000") - 1; + + for (size_t i = 0; i < payload_length; i++) { + snprintf((char *)(txt2pcap + index), txt2pcap_length - index, " %02x", payload[i]); + index += 3; + } + snprintf((char *)(txt2pcap + index), txt2pcap_length - index, " # STUN_PACKET "); + index += sizeof(" # STUN_PACKET "); + return index; +} + ///////////// utils ///////////////////// #if DTLS_SUPPORTED @@ -695,6 +725,7 @@ start_udp_cycle: } ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh); server->sm.m.sm.nd.nbh = NULL; + prom_inc_packet_dropped(); FUNCEND; return; } @@ -704,21 +735,44 @@ start_udp_cycle: int rc = 0; ioa_network_buffer_set_size(elem, (size_t)bsize); - if (server->connect_cb) { + // Do minimal validation on the received UDP packet + // stun_is_channel_message_str and stun_is_command_message_str + size_t blen = bsize; + uint16_t chnum = 0; + uint8_t *data = ioa_network_buffer_data(elem); - rc = create_new_connected_udp_socket(server, s); - if (rc < 0) { - TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot handle UDP packet, size %d\n", (int)bsize); + if ((turn_params.drop_invalid_packets && !stun_is_channel_message_str(data, &blen, &chnum, false) && + !stun_is_command_message_str(data, blen)) +#if DTLS_SUPPORTED + || (!turn_params.no_dtls && !is_dtls_message(data, blen)) +#endif + ) { + packetcounter++; + if (packetcounter % 1000 == 0) { + uint8_t txt2pcap[1000]; // 1000 is enough to print ~300B packet (3 chars per byte) with extras + print_packet_txt2pcap(packetcounter, data, blen, txt2pcap, sizeof(txt2pcap)); + TURN_LOG_FUNC(TURN_LOG_LEVEL_DEBUG, "TXT2PCAP: %s\n", txt2pcap); } - + prom_inc_packet_dropped(); } else { - server->sm.m.sm.s = s; - rc = handle_udp_packet(server, &(server->sm), server->e, server->ts); - } + prom_inc_packet_processed(); - if (rc < 0) { - if (eve(server->e->verbose)) { - TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot handle UDP event\n"); + if (server->connect_cb) { + + rc = create_new_connected_udp_socket(server, s); + if (rc < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot handle UDP packet, size %d\n", (int)bsize); + } + + } else { + server->sm.m.sm.s = s; + rc = handle_udp_packet(server, &(server->sm), server->e, server->ts); + } + + if (rc < 0) { + if (eve(server->e->verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot handle UDP event\n"); + } } } } diff --git a/src/apps/relay/mainrelay.c b/src/apps/relay/mainrelay.c index bfc39e61..15259f7e 100644 --- a/src/apps/relay/mainrelay.c +++ b/src/apps/relay/mainrelay.c @@ -238,7 +238,8 @@ turn_params_t turn_params = { false, /* log_binding */ false, /* stun_backward_compatibility */ - false /* respond_http_unsupported */ + false, /* respond_http_unsupported */ + false /* drop_invalid_packets */ }; //////////////// OpenSSL Init ////////////////////// @@ -1355,6 +1356,7 @@ static char Usage[] = "connections made to ports not\n" " supporting HTTP. The default behaviour is to immediately " "close the connection.\n" + " --drop-invalid-packets Drop invalid packets early. The default behaviour is to accept all packets.\n" " --version Print version (and exit).\n" " -h Help\n" "\n"; diff --git a/src/apps/relay/mainrelay.h b/src/apps/relay/mainrelay.h index 96cf4bea..25732c97 100644 --- a/src/apps/relay/mainrelay.h +++ b/src/apps/relay/mainrelay.h @@ -347,6 +347,7 @@ typedef struct _turn_params_ { bool log_binding; bool stun_backward_compatibility; bool respond_http_unsupported; + bool drop_invalid_packets; } turn_params_t; extern turn_params_t turn_params; diff --git a/src/apps/relay/prom_server.c b/src/apps/relay/prom_server.c index ab21bd11..bacbcc1c 100644 --- a/src/apps/relay/prom_server.c +++ b/src/apps/relay/prom_server.c @@ -43,6 +43,9 @@ #if !defined(TURN_NO_PROMETHEUS) +prom_counter_t *packet_processed; +prom_counter_t *packet_dropped; + prom_counter_t *stun_binding_request; prom_counter_t *stun_binding_response; prom_counter_t *stun_binding_error; @@ -178,6 +181,11 @@ void start_prometheus_server(void) { const char *typeLabel[] = {"type"}; turn_total_allocations = prom_collector_registry_must_register_metric( prom_gauge_new("turn_total_allocations", "Represents current allocations number", 1, typeLabel)); + // Packet counters + packet_processed = prom_collector_registry_must_register_metric( + prom_counter_new("turn_packet_processed", "Incoming packet processed", 0, NULL)); + packet_dropped = prom_collector_registry_must_register_metric( + prom_counter_new("turn_packet_dropped", "Incoming packet dropped", 0, NULL)); // some flags appeared first in microhttpd v0.9.53 unsigned int flags = 0; @@ -283,6 +291,18 @@ void prom_dec_allocation(SOCKET_TYPE type) { } } +void prom_inc_packet_processed(void) { + if (turn_params.prometheus == 1) { + prom_counter_add(packet_processed, 1, NULL); + } +} + +void prom_inc_packet_dropped(void) { + if (turn_params.prometheus == 1) { + prom_counter_add(packet_dropped, 1, NULL); + } +} + void prom_inc_stun_binding_request(void) { if (turn_params.prometheus == 1) { prom_counter_add(stun_binding_request, 1, NULL); @@ -339,4 +359,8 @@ void prom_inc_allocation(SOCKET_TYPE type) { UNUSED_ARG(type); } void prom_dec_allocation(SOCKET_TYPE type) { UNUSED_ARG(type); } +void prom_inc_packet_processed(void) {} + +void prom_inc_packet_dropped(void) {} + #endif /* TURN_NO_PROMETHEUS */ diff --git a/src/apps/relay/prom_server.h b/src/apps/relay/prom_server.h index 5fab19de..a636b7b0 100644 --- a/src/apps/relay/prom_server.h +++ b/src/apps/relay/prom_server.h @@ -59,6 +59,9 @@ extern "C" { } #endif /* __clplusplus */ +extern prom_counter_t *packet_processed; +extern prom_counter_t *packet_dropped; + extern prom_counter_t *stun_binding_request; extern prom_counter_t *stun_binding_response; extern prom_counter_t *stun_binding_error; @@ -104,5 +107,7 @@ void prom_set_finished_traffic(const char *realm, const char *user, unsigned lon void prom_inc_allocation(SOCKET_TYPE type); void prom_dec_allocation(SOCKET_TYPE type); +void prom_inc_packet_processed(void); +void prom_inc_packet_dropped(void); #endif /* __PROM_SERVER_H__ */