From 76d5e6d439e25120bc05c23bc5011588cb207f47 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 27 Feb 2026 10:49:40 -0400 Subject: [PATCH] MT#55283 support from-tag aliases Change-Id: Iffd87cc821e35d3d775a5bde1986c2d7dd0192ee --- daemon/call.c | 41 +++- daemon/call_interfaces.c | 37 ++- docs/ng_control_protocol.md | 22 ++ include/arena.h | 8 + include/call.h | 6 +- include/call_interfaces.h | 5 + t/auto-daemon-tests.pl | 452 ++++++++++++++++++++++++++++++++++++ utils/rtpengine-ng-client | 1 + 8 files changed, 557 insertions(+), 15 deletions(-) diff --git a/daemon/call.c b/daemon/call.c index 62250fa90..ab5dedcc6 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -5082,6 +5082,8 @@ static void __call_free(call_t *c) { t_hash_table_destroy(c->tags); t_hash_table_destroy(c->viabranches); t_hash_table_destroy(c->labels); + t_hash_table_destroy(c->sdps); + t_hash_table_destroy(c->endpoints); t_queue_clear(&c->callid_aliases); while (c->streams.head) { @@ -5111,6 +5113,8 @@ static call_t *call_create(const str *callid) { c->tags = str_ml_ht_new(); c->viabranches = str_ml_ht_new(); c->labels = str_ml_ht_new(); + c->sdps = str_ml_ht_new(); + c->endpoints = endpoint_ml_ht_new(); call_memory_arena_set(c); c->callid = call_str_cpy(callid); c->created = rtpe_now; @@ -5654,6 +5658,25 @@ struct call_monologue *call_get_monologue(call_t *call, const str *fromtag) { return t_hash_table_lookup(call->tags, fromtag); } +static struct call_monologue *call_get_monologue_alias(call_t *call, const str *fromtag, + sdp_ng_flags *flags, const str *sdp, const endpoint_t *ep) +{ + __auto_type ret = t_hash_table_lookup(call->tags, fromtag); + if (ret) + return ret; + + if (flags->alias_key == AK_SDP && sdp->len) + ret = t_hash_table_lookup(call->sdps, sdp); + else if (flags->alias_key == AK_ADDRESS && ep && ep->port) + ret = t_hash_table_lookup(call->endpoints, ep); + + if (!ret) + return NULL; + + __monologue_tag(ret, fromtag); + return ret; +} + /** * Based on the monologue tag, try to lookup the monologue in the 'tags' GHashTable. * If not found create a new one (call_monologue) and associate with a given tag. @@ -5715,14 +5738,14 @@ static int call_get_monologue_new(struct call_monologue *monologues[2], call_t * const str *fromtag, const str *totag, const str *viabranch, - sdp_ng_flags *flags) + sdp_ng_flags *flags, const endpoint_t *ep) { struct call_monologue *ret, *os = NULL; /* ret - initial offer, os - other side */ __C_DBG("getting monologue for tag '"STR_FORMAT"' in call '"STR_FORMAT"'", STR_FMT(fromtag), STR_FMT(&call->callid)); - ret = call_get_monologue(call, fromtag); + ret = call_get_monologue_alias(call, fromtag, flags, &flags->sdp, ep); if (!ret) { /* this is a brand new offer */ ret = __monologue_create(call); @@ -5738,7 +5761,7 @@ static int call_get_monologue_new(struct call_monologue *monologues[2], call_t * * Create a new monologue for the other side, if the monologue with such to-tag not found. */ if (totag && totag->s) { - struct call_monologue * monologue = call_get_monologue(call, totag); + struct call_monologue *monologue = call_get_monologue(call, totag); if (!monologue) goto new_branch; } @@ -5807,7 +5830,7 @@ static int call_get_dialogue(struct call_monologue *monologues[2], call_t *call, const str *fromtag, const str *totag, const str *viabranch, - sdp_ng_flags *flags) + sdp_ng_flags *flags, const endpoint_t *ep) { struct call_monologue *ft, *tt; @@ -5821,10 +5844,10 @@ static int call_get_dialogue(struct call_monologue *monologues[2], call_t *call, /* we start with the to-tag. if it's not known, we treat it as a branched offer */ tt = call_get_monologue(call, totag); if (!tt) - return call_get_monologue_new(monologues, call, fromtag, totag, viabranch, flags); + return call_get_monologue_new(monologues, call, fromtag, totag, viabranch, flags, ep); /* if the from-tag is known already, return that */ - ft = call_get_monologue(call, fromtag); + ft = call_get_monologue_alias(call, fromtag, flags, &flags->sdp, ep); if (ft) { __C_DBG("found existing dialogue"); @@ -5915,13 +5938,13 @@ int call_get_mono_dialogue(struct call_monologue *monologues[2], call_t *call, const str *fromtag, const str *totag, const str *viabranch, - sdp_ng_flags *flags) + sdp_ng_flags *flags, const endpoint_t *ep) { /* initial offer */ if (!totag || !totag->s) - return call_get_monologue_new(monologues, call, fromtag, NULL, viabranch, flags); + return call_get_monologue_new(monologues, call, fromtag, NULL, viabranch, flags, ep); - return call_get_dialogue(monologues, call, fromtag, totag, viabranch, flags); + return call_get_dialogue(monologues, call, fromtag, totag, viabranch, flags, ep); } static void media_stop(struct call_media *m) { diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index afe4d3a9f..d45841a38 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -192,7 +192,7 @@ static str call_update_lookup_udp(char **out, enum ng_opmode opmode, const char* updated_created_from(c, addr); - if (call_get_mono_dialogue(monologues, c, &fromtag, &totag, NULL, NULL)) + if (call_get_mono_dialogue(monologues, c, &fromtag, &totag, NULL, NULL, NULL)) goto ml_fail; struct call_monologue *from_ml = monologues[0]; @@ -355,7 +355,7 @@ static str call_request_lookup_tcp(char **out, enum ng_opmode opmode) { str_swap(&fromtag, &totag); } - if (call_get_mono_dialogue(monologues, c, &fromtag, &totag, NULL, NULL)) { + if (call_get_mono_dialogue(monologues, c, &fromtag, &totag, NULL, NULL, NULL)) { ilog(LOG_WARNING, "Invalid dialogue association"); goto out2; } @@ -1799,6 +1799,26 @@ void call_ng_main_flags(const ng_parser_t *parser, str *key, parser_arg value, h STR_FMT(&s)); } break; + case CSH_LOOKUP("alias-key"): + switch (__csh_lookup_n(1, &s)) { + case CSH_LOOKUP_N(1, "none"): + case CSH_LOOKUP_N(1, "off"): + case CSH_LOOKUP_N(1, "no"): + out->alias_key = AK_NONE; + break; + case CSH_LOOKUP_N(1, "sdp"): + case CSH_LOOKUP_N(1, "SDP"): + out->alias_key = AK_SDP; + break; + case CSH_LOOKUP_N(1, "address"): + case CSH_LOOKUP_N(1, "endpoint"): + out->alias_key = AK_ADDRESS; + break; + default: + ilog(LOG_WARN, "Unknown 'alias-key' flag encountered: '" STR_FORMAT "'", + STR_FMT(&s)); + } + break; case CSH_LOOKUP("audio-player"): case CSH_LOOKUP("player"): switch (__csh_lookup_n(1, &s)) { @@ -2588,7 +2608,7 @@ static const char *call_offer_answer_ng(ng_command_ctx_t *ctx, const char* addr) g_auto(sdp_sessions_q) parsed = TYPED_GQUEUE_INIT; g_auto(sdp_streams_q) streams = TYPED_GQUEUE_INIT; g_autoptr(call_t) call = NULL; - struct call_monologue * monologues[2]; + struct call_monologue *monologues[2]; int ret; g_auto(sdp_ng_flags) flags; parser_arg output = ctx->resp; @@ -2659,7 +2679,8 @@ static const char *call_offer_answer_ng(ng_command_ctx_t *ctx, const char* addr) errstr = "Invalid dialogue association"; if (call_get_mono_dialogue(monologues, call, &flags.from_tag, &flags.to_tag, - flags.via_branch.s ? &flags.via_branch : NULL, &flags)) { + flags.via_branch.s ? &flags.via_branch : NULL, &flags, + streams.length ? &streams.head->data->rtp_endpoint : NULL)) { goto out; } @@ -2680,7 +2701,13 @@ static const char *call_offer_answer_ng(ng_command_ctx_t *ctx, const char* addr) } if (flags.block_dtmf) - call_set_dtmf_block(call, monologues[0], &flags); + call_set_dtmf_block(call, from_ml, &flags); + + if (flags.alias_key == AK_SDP) + t_hash_table_insert(call->sdps, call_str_dup(&sdp), from_ml); + else if (flags.alias_key == AK_ADDRESS && streams.length && streams.head->data->rtp_endpoint.port) + t_hash_table_insert(call->endpoints, memory_arena_objdup(streams.head->data->rtp_endpoint), + from_ml); struct recording *recording = NULL; diff --git a/docs/ng_control_protocol.md b/docs/ng_control_protocol.md index 5ea15c9ce..2792f3ea0 100644 --- a/docs/ng_control_protocol.md +++ b/docs/ng_control_protocol.md @@ -168,6 +168,28 @@ Optionally included keys are: body. The default is to auto-detect the address family if possible (if the receiving end is known already) or otherwise to leave it unchanged. +* `alias key` + + Contains a string value to control the creation of from-tag aliases. The + default is to disable from-tag aliases, which corresponds to the string + value `off`. Any other value enables the creation of from-tag aliases. + + A from-tag alias is created when an offer from an unknown from-tag is + received, and the criteria selected through the `alias key` value is + matched against an existing, known from-tag. If a match is found, the newly + appeared from-tag will be remembered as an alias for the existing from-tag + going forward. Note that the same `alias key` value must have been used + when the offer for the existing from-tag was received for this to work. + + With the value `SDP`, matching is performed on the entire SDP body as + received in the offer. In other words, if an offer from an unknown from-tag + is received and the identical SDP was previously used in another offer with + a different from-tag, then these two from-tags will be considered aliases. + + With the value `address` or `endpoint`, matching is performed on the + address/port pair used as connection address in the SDP, specifically the + address and port used by the first media section. + * `audio player` Contains a string value of either `default`, `transcoding`, `off`, or `always`. diff --git a/include/arena.h b/include/arena.h index a7cd946d4..a1368029b 100644 --- a/include/arena.h +++ b/include/arena.h @@ -23,6 +23,14 @@ INLINE void *__memory_arena_alloc0(size_t len) { return ret; } #define memory_arena_alloc0(type) ((type *) __memory_arena_alloc0(sizeof(type))) + +INLINE char *__memory_arena_memdup(const void *b, size_t len) { + char *ret = __memory_arena_alloc(len); + memcpy(ret, b, len); + return ret; +} +#define memory_arena_objdup(o) ((__typeof(&(o))) __memory_arena_memdup(&(o), sizeof(o))) + INLINE char *memory_arena_dup(const char *b, size_t len) { char *ret = __memory_arena_alloc(len + 1); memcpy(ret, b, len); diff --git a/include/call.h b/include/call.h index d64155aff..46303034c 100644 --- a/include/call.h +++ b/include/call.h @@ -676,6 +676,8 @@ struct sdp_fragment; TYPED_GQUEUE(fragment, struct sdp_fragment) TYPED_GHASHTABLE(fragments_ht, str, fragment_q, str_hash, str_equal, NULL, NULL) +TYPED_GHASHTABLE(endpoint_ml_ht, endpoint_t, struct call_monologue, endpoint_hash, endpoint_eq, NULL, NULL) + struct call_iterator_list { call_list *first; @@ -779,6 +781,8 @@ struct call { str_ml_ht tags; str_ml_ht viabranches; str_ml_ht labels; + str_ml_ht sdps; + endpoint_ml_ht endpoints; fragments_ht sdp_fragments; packet_stream_q streams; stream_fd_q stream_fds; /* stream_fd */ @@ -869,7 +873,7 @@ int call_get_mono_dialogue(struct call_monologue *monologues[2], call_t *call, const str *fromtag, const str *totag, const str *viabranch, - sdp_ng_flags *); + sdp_ng_flags *, const endpoint_t *); struct call_monologue *call_get_monologue(call_t *call, const str *fromtag); struct call_monologue *call_get_or_create_monologue(call_t *call, const str *fromtag); __attribute__((nonnull(1, 2, 4, 5, 6))) diff --git a/include/call_interfaces.h b/include/call_interfaces.h index ce661d1ff..087942fae 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -142,6 +142,11 @@ struct sdp_ng_flags { AP_TRANSCODING, AP_FORCE, } audio_player:2; + enum { + AK_NONE = 0, + AK_SDP, + AK_ADDRESS, + } alias_key:2; enum endpoint_learning el_option; enum block_dtmf_mode block_dtmf_mode; int delay_buffer; diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index c7c57325a..ebe746cf3 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -89,6 +89,458 @@ sub stun_succ { +new_call; + +($port_a) = offer('from-tag alias control', { }, < 'SDP' }, < 'SDP' }, < 'SDP' }, < 'SDP' }, < 'SDP' }, < 'SDP' }, < 'address' }, < 'address' }, < 'address' }, < 'SDP' }, < 'SDP' }, < 'SDP' }, < 'SDP' }, < 'SDP' }, < 'SDP' }, < 'SDP', 'to-tag' => tt() }, < 'SDP' }, <