Commit 47949eff1b for asterisk.org
commit 47949eff1b314a924834b25faba25a6aa53c691c
Author: Cetin Cem <cetincem@gaga.dev>
Date: Sat Oct 3 23:49:14 2026 +0300
res_rtp_asterisk.c: Fix cleanup after RTP allocation failure.
Initialize DTLS timer IDs before RTP allocation can fail, preventing
cleanup from cancelling uninitialized timers. Add a regression test
for port exhaustion and reuse without a scheduler.
AI assistance: Codex generated the code, test, validation scripts and text.
Fixes: #1757
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index f5b4619e3a..894ce243ad 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -4147,11 +4147,17 @@ static int ice_create(struct ast_rtp_instance *instance, struct ast_sockaddr *ad
}
#endif
+/* Allocate an RTP transport with timer state safe for cleanup on failure. */
static int rtp_allocate_transport(struct ast_rtp_instance *instance, struct ast_rtp *rtp)
{
int x, startplace, i, maxloops;
unsigned int port_start, port_end;
+#if defined(HAVE_OPENSSL) && (OPENSSL_VERSION_NUMBER >= 0x10001000L) && !defined(OPENSSL_NO_SRTP)
+ rtp->rekeyid = -1;
+ rtp->dtls.timeout_timer = -1;
+#endif
+
rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_CLOSED : STRICT_RTP_OPEN);
/* Determine the port range to use: per-instance override or global */
@@ -4224,11 +4230,6 @@ static int rtp_allocate_transport(struct ast_rtp_instance *instance, struct ast_
}
#endif
-#if defined(HAVE_OPENSSL) && (OPENSSL_VERSION_NUMBER >= 0x10001000L) && !defined(OPENSSL_NO_SRTP)
- rtp->rekeyid = -1;
- rtp->dtls.timeout_timer = -1;
-#endif
-
return 0;
}
diff --git a/tests/test_res_rtp.c b/tests/test_res_rtp.c
index 46dad0932e..211bc610d9 100644
--- a/tests/test_res_rtp.c
+++ b/tests/test_res_rtp.c
@@ -259,6 +259,63 @@ static void test_write_and_read_interleaved_frames(struct ast_rtp_instance *inst
}
}
+/* Verify failed RTP allocation without a scheduler is safe and releases its resources. */
+AST_TEST_DEFINE(rtp_port_exhaustion)
+{
+ RAII_VAR(struct ast_rtp_instance *, occupied, NULL, ast_rtp_instance_destroy);
+ RAII_VAR(struct ast_rtp_instance *, instance, NULL, ast_rtp_instance_destroy);
+ struct ast_sockaddr address;
+ struct ast_rtp_instance_options options;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "rtp_port_exhaustion";
+ info->category = "/res/res_rtp/";
+ info->summary = "RTP port exhaustion without a scheduler";
+ info->description =
+ "Exhaust a per-instance RTP port range with no scheduler, as used by "
+ "UnicastRTP. Verify allocation fails safely and succeeds after the "
+ "occupied port is released.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ ast_sockaddr_parse(&address, "127.0.0.1", 0);
+ occupied = ast_rtp_instance_new("asterisk", NULL, &address, NULL);
+ if (!occupied) {
+ ast_test_status_update(test, "Unable to allocate the initial RTP instance\n");
+ return AST_TEST_FAIL;
+ }
+
+ ast_rtp_instance_get_local_address(occupied, &address);
+ /* The occupied port is the only even port in this range. */
+ options.port_start = ast_sockaddr_port(&address);
+ options.port_end = options.port_start + 1;
+ instance = ast_rtp_instance_new_with_options("asterisk", NULL, &address,
+ NULL, &options);
+ if (instance) {
+ ast_test_status_update(test, "Allocation succeeded with an exhausted port range\n");
+ return AST_TEST_FAIL;
+ }
+
+ ast_rtp_instance_destroy(occupied);
+ occupied = NULL;
+ instance = ast_rtp_instance_new_with_options("asterisk", NULL, &address,
+ NULL, &options);
+ if (!instance) {
+ ast_test_status_update(test, "Unable to reuse the released RTP port\n");
+ return AST_TEST_FAIL;
+ }
+ ast_rtp_instance_get_local_address(instance, &address);
+ if (ast_sockaddr_port(&address) != options.port_start) {
+ ast_test_status_update(test, "RTP instance did not reuse the released port\n");
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
AST_TEST_DEFINE(nack_no_packet_loss)
{
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
@@ -839,8 +896,10 @@ cleanup:
return result;
}
+/* Unregister RTP regression tests when the module unloads. */
static int unload_module(void)
{
+ AST_TEST_UNREGISTER(rtp_port_exhaustion);
AST_TEST_UNREGISTER(payload_merge_preserves_preferences);
AST_TEST_UNREGISTER(payload_merge_abandoned_offer);
AST_TEST_UNREGISTER(mes);
@@ -854,8 +913,10 @@ static int unload_module(void)
return 0;
}
+/* Register RTP regression tests with the unit test framework. */
static int load_module(void)
{
+ AST_TEST_REGISTER(rtp_port_exhaustion);
AST_TEST_REGISTER(nack_no_packet_loss);
AST_TEST_REGISTER(nack_nominal);
AST_TEST_REGISTER(nack_overflow);