From b8a915246fe59988a49132ae09ea4ab564a1a487 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Tue, 29 Oct 2024 10:08:17 -0400 Subject: [PATCH] MT#61352 support signalling templates Change-Id: If5624e3294bff7f677bdb08f98ea920c3b5f929a --- daemon/call_interfaces.c | 34 ++++++++++++++++++- daemon/main.c | 23 +++++++++---- docs/ng_control_protocol.md | 6 ++++ docs/rtpengine.md | 54 ++++++++++++++++++++++++++++++ etc/rtpengine.conf | 6 ++++ include/call_interfaces.h | 3 +- lib/auxlib.c | 25 ++++++++++++-- lib/auxlib.h | 11 ++++-- t/auto-daemon-tests-config-file.pl | 38 ++++++++++++++++++++- t/test-stats.c | 2 +- t/{test.conf => test1.conf} | 4 +++ 11 files changed, 191 insertions(+), 15 deletions(-) rename t/{test.conf => test1.conf} (55%) diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 6f9861b1a..8bee42fb5 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -36,6 +36,7 @@ static pcre2_code *streams_re; bool trust_address_def; bool dtls_passive_def; +str_case_value_ht rtpe_signalling_templates; enum basic_errors { NG_ERROR_NO_SDP_BODY = 1, @@ -1453,6 +1454,7 @@ static void call_ng_received_from_iter(str *key, unsigned int i, helper_arg arg) break; } } + void call_ng_main_flags(const ng_parser_t *parser, str *key, parser_arg value, helper_arg arg) { str s = STR_NULL; sdp_ng_flags *out = arg.flags; @@ -1977,6 +1979,17 @@ void call_ng_main_flags(const ng_parser_t *parser, str *key, parser_arg value, h call_ng_flags_str_list(parser, value, ng_t38_option, out); break; #endif + case CSH_LOOKUP("template"):; + str *tplate = t_hash_table_lookup(rtpe_signalling_templates, &s); + if (!tplate) { + ilog(LOG_WARN, "Templates for signalling flags '" STR_FORMAT "' not found", + STR_FMT(&s)); + break; + } + // naive approach: just parse them out every time + // TODO: improve this by pre-parsing the flags at startup + parse_rtpp_flags(tplate, out); + break; case CSH_LOOKUP("to-interface"): out->direction[1] = s; break; @@ -3999,9 +4012,25 @@ void call_interfaces_free(void) { pcre2_code_free(streams_re); streams_re= NULL; } + + t_hash_table_destroy(rtpe_signalling_templates); +} + +static void parse_templates(GHashTable *templates) { + if (!templates) + return; + + GHashTableIter iter; + g_hash_table_iter_init(&iter, templates); + void *keyp, *valuep; + while (g_hash_table_iter_next(&iter, &keyp, &valuep)) { + char *key = keyp; + char *value = valuep; + t_hash_table_insert(rtpe_signalling_templates, str_dup(STR_PTR(key)), str_dup(STR_PTR(value))); + } } -int call_interfaces_init(void) { +int call_interfaces_init(GHashTable *templates) { int errcode; PCRE2_SIZE erroff; @@ -4015,5 +4044,8 @@ int call_interfaces_init(void) { if (!streams_re) return -1; + rtpe_signalling_templates = str_case_value_ht_new(); + parse_templates(templates); + return 0; } diff --git a/daemon/main.c b/daemon/main.c index 44fa2d493..5c107d1ae 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -485,7 +485,7 @@ static void release_listeners(GQueue *q) { } -static void options(int *argc, char ***argv) { +static void options(int *argc, char ***argv, GHashTable *templates) { g_autoptr(char_p) if_a = NULL; g_autoptr(char_p) ks_a = NULL; unsigned long uint_keyspace_db; @@ -533,6 +533,7 @@ static void options(int *argc, char ***argv) { g_autoptr(char) nftables_family = NULL; #endif g_autoptr(char) redis_format = NULL; + g_autoptr(char) templates_section = NULL; GOptionEntry e[] = { { "table", 't', 0, G_OPTION_ARG_INT, &rtpe_config.kernel_table, "Kernel table to use", "INT" }, @@ -547,6 +548,7 @@ static void options(int *argc, char ***argv) { { "nftables-status",0, 0, G_OPTION_ARG_NONE, &nftables_status, "Check nftables rules, print result and exit", NULL }, #endif { "interface", 'i', 0, G_OPTION_ARG_STRING_ARRAY,&if_a, "Local interface for RTP", "[NAME/]IP[!IP]"}, + { "templates", 0, 0, G_OPTION_ARG_STRING, &templates_section, "Config section to read signalling templates from ", "STR"}, { "save-interface-ports",'S', 0, G_OPTION_ARG_NONE, &rtpe_config.save_interface_ports, "Bind ports only on first available interface of desired family", NULL }, { "subscribe-keyspace", 'k', 0, G_OPTION_ARG_STRING_ARRAY,&ks_a, "Subscription keyspace list", "INT INT ..."}, { "listen-ng", 'n', 0, G_OPTION_ARG_STRING_ARRAY, &listenngs, "UDP ports to listen on, NG protocol","[IP46|HOSTNAME:]PORT ..." }, @@ -703,8 +705,9 @@ static void options(int *argc, char ***argv) { { NULL, } }; - config_load(argc, argv, e, " - next-generation media proxy", - "/etc/rtpengine/rtpengine.conf", "rtpengine", &rtpe_config.common); + config_load_ext(argc, argv, e, " - next-generation media proxy", + "/etc/rtpengine/rtpengine.conf", "rtpengine", &rtpe_config.common, + &templates_section, templates); // default values, if not configured if (rtpe_config.rec_method == NULL) @@ -1120,6 +1123,9 @@ static void options(int *argc, char ***argv) { if (rtpe_config.cpu_affinity <= 0) die("Number of CPU cores is unknown, cannot auto-set socket CPU affinity"); } + + // everything OK, do post-processing + } static void fill_initial_rtpe_cfg(struct rtpengine_config* ini_rtpe_cfg) { @@ -1276,7 +1282,7 @@ fallback: } -static void init_everything(void) { +static void init_everything(GHashTable *templates) { bufferpool_init(); gettimeofday(&rtpe_now, NULL); log_init(rtpe_common_config_ptr->log_name); @@ -1299,7 +1305,7 @@ static void init_everything(void) { interfaces_init(&rtpe_config.interfaces); iptables_init(); control_ng_init(); - if (call_interfaces_init()) + if (call_interfaces_init(templates)) abort(); statistics_init(); #ifdef WITH_TRANSCODING @@ -1506,8 +1512,11 @@ static void uring_poller_loop(void *ptr) { int main(int argc, char **argv) { early_init(); - options(&argc, &argv); - init_everything(); + { + g_autoptr(GHashTable) templates = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + options(&argc, &argv, templates); + init_everything(templates); + } create_everything(); fill_initial_rtpe_cfg(&initial_rtpe_config); diff --git a/docs/ng_control_protocol.md b/docs/ng_control_protocol.md index 758be1599..e4304cfe1 100644 --- a/docs/ng_control_protocol.md +++ b/docs/ng_control_protocol.md @@ -582,6 +582,12 @@ Optionally included keys are: Contains an integer corresponding to the SIP response code (e.g. 180 or 200) if this signalling message was triggered by a SIP response. +* `template` + + Contains the name of a signalling template to be used for this particular + control message. See documentation for *SIGNALLING TEMPLATES* in the man + page. + * `via-branch` The SIP `Via` branch as string. Used to additionally refine the matching logic between media streams diff --git a/docs/rtpengine.md b/docs/rtpengine.md index e8ccca210..f9d3759a5 100644 --- a/docs/rtpengine.md +++ b/docs/rtpengine.md @@ -151,6 +151,18 @@ at the command line. See the __\-\-config-file__ option below for details. In this case, startup of the daemon will fail with an error if this option is given. +- __\-\-templates=__*STR* + + Name of the config file section to contain signalling templates. Requires a + configuration file to be in use (i.e. not __\-\-config-file=none__). + Default value is unset (i.e. no templates supported). + + If set, then each entry within the given config section corresponds to a + named signalling template, which can then be used by referencing it via the + __template=...__ key in a signalling message to *rtpengine*. + + See section *SIGNALLING TEMPLATES* below. + - __-S__, __\-\-save-interface-ports__ Will bind ports only on the first available local interface, of desired @@ -1522,6 +1534,48 @@ used by the __rtpproxy__ module, the interfaces must be named __internal__ and __external__ corresponding to the __i__ and __e__ flags if you wish to use network bridging in this mode. +## SIGNALLING TEMPLATES + +Since much of the behaviour of *rtpengine* is controlled by flags and +keys/values given to it during runtime as part of the signalling control +protocol that is used for communication between the controlling agent (e.g. a +SIP proxy) and the *rtpengine* process, there often is a need to repeatedly +give the same set of default flags and values to *rtpengine* for each message +sent to it. This can lead to controlling scripts that are hard to maintain or +hard to read. To alleviate this problem, *rtpengine* supports signalling +templates that can be configured in its main configuration file and can then be +referred to by short names. + +To use this feature, a configuration file must be in use (by default +`/etc/rtpengine/rtpengine.conf`) and the configuration key __templates=...__ +must be set to a non-empty string. The value gives the name of the section in +the configuration file to contain signalling templates. For example, if the +value is set to __templates=templates__, then the section __[templates]__ will +be used to read signalling templates. + +Each key/value in this file section then corresponds to one signalling +template, and can be referred to via __template=...__ in any control message. + +For example, in order to make an offer to a WebRTC-compliant client, a Kamailio +or OpenSIPS script may have used: + + rtpengine_offer("transport-protocol=UDP/TLS/RTP/SAVPF ICE=force trickle-ICE rtcp-mux=[offer require] no-rtcp-attribute SDES=off generate-mid"); + +This entire string of flags can now be converted into a signalling template in +the config file as such: + + [rtpengine] + ... + templates = templates + ... + + [templates] + WebRTC = transport-protocol=UDP/TLS/RTP/SAVPF ICE=force trickle-ICE rtcp-mux=[offer require] no-rtcp-attribute SDES=off generate-mid + +The __offer__ command in Kamailio or OpenSIPS can then simply be turned into: + + rtpengine_offer("template=WebRTC"); + ## EXIT STATUS - __0__ diff --git a/etc/rtpengine.conf b/etc/rtpengine.conf index ec6fc20b9..4ebd35dcb 100644 --- a/etc/rtpengine.conf +++ b/etc/rtpengine.conf @@ -14,6 +14,8 @@ table = 0 interface = any +# name of config section in this file to contain signalling templates +templates = templates listen-ng = localhost:2223 @@ -149,6 +151,10 @@ recording-method = proc # socket-cpu-affinity = -1 # rtcp-interval = 5000 +# signalling templates (see key `templates` above) +[templates] +WebRTC = transport-protocol=UDP/TLS/RTP/SAVPF ICE=force trickle-ICE rtcp-mux=[offer require] no-rtcp-attribute SDES=off generate-mid + [rtpengine-testing] table = -1 interface = 10.15.20.121 diff --git a/include/call_interfaces.h b/include/call_interfaces.h index 0aff982f5..480d0db5a 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -263,6 +263,7 @@ RTPE_NG_FLAGS_STR_CASE_HT_PARAMS extern bool trust_address_def; extern bool dtls_passive_def; +extern str_case_value_ht rtpe_signalling_templates; str *call_request_tcp(char **); str *call_lookup_tcp(char **); @@ -312,7 +313,7 @@ void call_unlock_release(call_t *c); G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(sdp_ng_flags, call_ng_free_flags) G_DEFINE_AUTOPTR_CLEANUP_FUNC(call_t, call_unlock_release) -int call_interfaces_init(void); +int call_interfaces_init(GHashTable *); void call_interfaces_free(void); void call_interfaces_timer(void); diff --git a/lib/auxlib.c b/lib/auxlib.c index 9aa324a08..a67b031a6 100644 --- a/lib/auxlib.c +++ b/lib/auxlib.c @@ -155,13 +155,31 @@ void config_load_free(struct rtpengine_common_config *cconfig) { g_free(cconfig->pidfile); } +static void load_templates(GKeyFile *kf, const char *template_section, GHashTable *templates) { + size_t length; + g_autoptr(GError) err = NULL; + g_autoptr(char_p) keys = g_key_file_get_keys(kf, template_section, &length, &err); + if (err) + die("Failed to load templates from given config file section '%s': %s", template_section, err->message); + if (!keys) + return; // empty config section + + for (char **key = keys; *key; key++) { + char *val = g_key_file_get_string(kf, template_section, *key, &err); + if (err) + die("Failed to read template value '%s' from config file: %s", *key, err->message); + g_hash_table_insert(templates, g_strdup(*key), val); // hash table takes ownership of both + } +} + G_DEFINE_AUTOPTR_CLEANUP_FUNC(GOptionEntry, free) typedef char *char_p_shallow; G_DEFINE_AUTOPTR_CLEANUP_FUNC(char_p_shallow, g_free) -void config_load(int *argc, char ***argv, GOptionEntry *app_entries, const char *description, +void config_load_ext(int *argc, char ***argv, GOptionEntry *app_entries, const char *description, char *default_config, char *default_section, - struct rtpengine_common_config *cconfig) + struct rtpengine_common_config *cconfig, + char * const *template_section, GHashTable *templates) { g_autoptr(GOptionContext) c = NULL; g_autoptr(GError) er = NULL; @@ -360,6 +378,9 @@ void config_load(int *argc, char ***argv, GOptionEntry *app_entries, const char } } + if (template_section && *template_section && templates) + load_templates(kf, *template_section, templates); + out: // default common values, if not configured if (rtpe_common_config_ptr->log_name == NULL) diff --git a/lib/auxlib.h b/lib/auxlib.h index 98555dab8..088082e83 100644 --- a/lib/auxlib.h +++ b/lib/auxlib.h @@ -66,9 +66,16 @@ void resources(void); void wpidfile(void); void service_notify(const char *message); void config_load_free(struct rtpengine_common_config *); -void config_load(int *argc, char ***argv, GOptionEntry *entries, const char *description, +void config_load_ext(int *argc, char ***argv, GOptionEntry *entries, const char *description, char *default_config, char *default_section, - struct rtpengine_common_config *); + struct rtpengine_common_config *, + char * const *template_section, GHashTable *templates); +INLINE void config_load(int *argc, char ***argv, GOptionEntry *entries, const char *description, + char *default_config, char *default_section, + struct rtpengine_common_config *cc) +{ + config_load_ext(argc, argv, entries, description, default_config, default_section, cc, NULL, NULL); +} char *get_thread_buf(void); int thread_create(void *(*func)(void *), void *arg, bool joinable, pthread_t *handle, const char *name); diff --git a/t/auto-daemon-tests-config-file.pl b/t/auto-daemon-tests-config-file.pl index e4ffec257..640482749 100755 --- a/t/auto-daemon-tests-config-file.pl +++ b/t/auto-daemon-tests-config-file.pl @@ -11,7 +11,7 @@ use NGCP::Rtpclient::ICE; use POSIX; -autotest_start(qw(--config-file=test.conf)) or die; +autotest_start(qw(--config-file=test1.conf)) or die; @@ -69,6 +69,42 @@ a=rtcp:PORT SDP +new_call; + +offer('template', { template => 'WebRTC' }, <