From 6b6ebb3e7f30e0c11101b1a0db84cdd0a717ff12 Mon Sep 17 00:00:00 2001 From: Maximilian Fridrich Date: Mon, 16 Oct 2023 10:14:01 +0200 Subject: [PATCH] qualify: add qualify module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This module implements a pinging mechanism using SIP OPTIONS to qualify the peer while a call is in the INCOMING state to ensure that the peer is reachable and we will be able to successfully answer the call. Configuration: qual_freq [seconds] qualify frequency qual_to [ms] qualify timeout The OPTIONS are only sent if both options are present, both are not zero, qualify_freq is greater than qual_to, and the call is incoming. As soon as the call is established or closed, we stop sending OPTIONS. If we run don’t receive a response to an OPTIONS request within the specified qual_to, the call is terminated and UA_EVENT_CALL_CLOSED is triggered. Extra account address parameters: The module can be activated by adding both the `qual_freq` and the `qual_to` parameters to the accounts parameter `extra`. Example: ;extra=qual_freq=5,qual_to=2000 --- cmake/modules.cmake | 1 + modules/qualify/CMakeLists.txt | 10 ++ modules/qualify/qualify.c | 300 +++++++++++++++++++++++++++++++++ 3 files changed, 311 insertions(+) create mode 100644 modules/qualify/CMakeLists.txt create mode 100644 modules/qualify/qualify.c diff --git a/cmake/modules.cmake b/cmake/modules.cmake index c9fff13..610a098 100644 --- a/cmake/modules.cmake +++ b/cmake/modules.cmake @@ -11,6 +11,7 @@ set(MODULES kaoptions vidloop parcall + qualify ) if(DEFINED EXTRA_MODULES) diff --git a/modules/qualify/CMakeLists.txt b/modules/qualify/CMakeLists.txt new file mode 100644 index 0000000..fa6cda1 --- /dev/null +++ b/modules/qualify/CMakeLists.txt @@ -0,0 +1,10 @@ +project(qualify) + +set(SRCS qualify.c) + +if(STATIC) + add_library(${PROJECT_NAME} OBJECT ${SRCS}) +else() + add_library(${PROJECT_NAME} MODULE ${SRCS}) +endif() + diff --git a/modules/qualify/qualify.c b/modules/qualify/qualify.c new file mode 100644 index 0000000..63468d2 --- /dev/null +++ b/modules/qualify/qualify.c @@ -0,0 +1,300 @@ +/** + * @file qualify.c Pinging of peer in CALL_STATE_INCOMING via SIP OPTIONS + * + * Copyright (C) 2023 Commend.com - m.fridrich@commend.com + */ + +#include +#include +#include +#include + + +/** + * @defgroup qualify module + * + * This module implements a pinging mechanism using SIP OPTIONS to qualify the + * peer while a call is in the INCOMING state to ensure that the peer is + * reachable and we will be able to successfully answer the call. + * + * Configuration: + * qual_freq [seconds] qualify frequency + * qual_to [ms] qualify timeout + * + * The OPTIONS are only sent if both options are present, both are not zero, + * qualify_freq is greater than qual_to, and the call is incoming. As soon as + * the call is established or closed, we stop sending OPTIONS. + * If we run don’t receive a response to an OPTIONS request within the + * specified qual_to, the call is terminated and UA_EVENT_CALL_CLOSED is + * triggered. + * + * Extra account address parameters: + * The module can be activated by adding both the `qual_freq` and the `qual_to` + * parameters to the accounts parameter `extra`. + * + * Example: + * ;extra=qual_freq=5,qual_to=2000 + * + */ + + +#define DEBUG_MODULE "qualify" +#define DEBUG_LEVEL 5 +#include + + +struct qualle { + struct le he; + struct call *call; + struct tmr freq_tmr; + struct tmr to_tmr; +}; + + +struct qualify { + struct hash *qual_map; +}; + + +static struct qualify q = { NULL }; + + +/* Forward declaration */ +static int call_start_qualify(struct call *call, + const struct account *acc, + struct qualle *qualle); + + +static void qualle_destructor(void *arg) +{ + struct qualle *qualle = arg; + + hash_unlink(&qualle->he); + tmr_cancel(&qualle->to_tmr); + tmr_cancel(&qualle->freq_tmr); +} + + +/** + * Get specified field in the accounts extra parameter list + * + * @param acc Accounts object + * @param n Specified field + * @param v uint32_t ptr for the specified value + * + * @return 0 if success, otherwise errorcode + */ +static int account_extra_uint(const struct account *acc, const char *n, + uint32_t *v) +{ + struct pl pl; + struct pl val; + const char *extra = NULL; + + if (!acc || !n || !v) + return EINVAL; + + extra = account_extra(acc); + if (!str_isset(extra)) + return EINVAL; + + pl_set_str(&pl, extra); + if (!fmt_param_sep_get(&pl, n, ',', &val)) + return EINVAL; + + *v = pl_u32(&val); + + return 0; +} + + +static void options_resp_handler(int err, const struct sip_msg *msg, void *arg) +{ + (void)msg; + struct qualle *qualle = arg; + + if (err) { + warning("OPTIONS reply error: %m\n", err); + return; + } + + tmr_cancel(&qualle->to_tmr); +} + + +static void to_handler(void *arg) +{ + struct qualle *qualle = arg; + struct call *call = qualle->call; + uint32_t qual_to = 0; + conf_get_u32(conf_cur(), "qual_to", &qual_to); + + tmr_cancel(&qualle->freq_tmr); + + call_hangup(call, 408, "No response to OPTIONS received"); + + ua_event(call_get_ua(call), UA_EVENT_CALL_CLOSED, call, + "No response recevied to OPTIONS in %u ms.", qual_to); +} + + +static void freq_handler(void *arg) +{ + struct qualle *qualle = arg; + (void)call_start_qualify(qualle->call, call_account(qualle->call), + qualle); +} + + +/** + * Returns -1 if qual_freq or qual_to are zero + * -2 if qual_to is greater than or equal to qual_freq + * 0 on success + * else error code + */ +static int call_start_qualify(struct call *call, + const struct account *acc, + struct qualle *qualle) +{ + int err; + struct sa peer_addr; + char peer_uri[128]; + uint32_t qual_to = 0; + uint32_t qual_freq = 0; + int newle = qualle == NULL; + + account_extra_uint(acc, "qual_freq", &qual_freq); + account_extra_uint(acc, "qual_to", &qual_to); + + if (!call || !qual_freq || !qual_to) { + return -1; + } + + if ((qual_to / 1000) >= qual_freq) { + warning("Will not send OPTIONS because qualify timeout is " + "greater than or equal to qualify frequency.\n" + "qual_to: %u, qual_freq: %u\n", qual_to, qual_freq); + return -2; + } + + if (newle) { + qualle = mem_zalloc(sizeof(*qualle), qualle_destructor); + if (!qualle) + return ENOMEM; + + qualle->call = call; + tmr_init(&qualle->to_tmr); + tmr_init(&qualle->freq_tmr); + hash_append(q.qual_map, hash_fast_str(account_aor(acc)), + &qualle->he, qualle); + } + + (void)call_msg_src(call, &peer_addr); + err = re_snprintf(peer_uri, sizeof(peer_uri), "sip:%H:%d", + sa_print_addr, &peer_addr, sa_port(&peer_addr)); + + if (err == -1 || err == 0) { + warning("Failed to get peer URI for sending OPTIONS ping. " + "Trying again in %u seconds.\n", err, qual_freq); + tmr_start(&qualle->freq_tmr, qual_freq * 1000, freq_handler, + qualle); + return err; + } + + err = ua_options_send(call_get_ua(call), peer_uri, + options_resp_handler, qualle); + if (err) { + warning("Sending OPTIONS failed with err %d. " + "Trying again in %u seconds.\n", err, qual_freq); + tmr_start(&qualle->freq_tmr, qual_freq * 1000, freq_handler, + qualle); + return err; + } + + tmr_start(&qualle->to_tmr, qual_to, to_handler, qualle); + tmr_start(&qualle->freq_tmr, qual_freq * 1000, freq_handler, qualle); + + return 0; +} + + +static bool qualle_get_applyh(struct le *le, void *arg) +{ + (void)le; + (void)arg; + + return true; +} + + +static void call_stop_qualify(struct account *acc) +{ + struct qualle *qualle; + struct le *le = hash_lookup(q.qual_map, + hash_fast_str(account_aor(acc)), + qualle_get_applyh, NULL); + + if (!le || !le->data) + return; + + qualle = le->data; + mem_deref(qualle); +} + + +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + struct account *acc = ua_account(ua); + (void) call; + (void) prm; + (void) arg; + + switch (ev) { + case UA_EVENT_CALL_INCOMING: + (void)call_start_qualify(call, acc, NULL); + break; + case UA_EVENT_CALL_ANSWERED: + if (call_is_outgoing(call)) + break; + + call_stop_qualify(acc); + break; + case UA_EVENT_CALL_CLOSED: + call_stop_qualify(acc); + break; + default: + break; + } +} + + +static int module_init(void) +{ + int err; + + info("qualify: init\n"); + + err = uag_event_register(ua_event_handler, NULL); + err |= hash_alloc(&q.qual_map, 32); + + return err; +} + + +static int module_close(void) +{ + uag_event_unregister(ua_event_handler); + hash_flush(q.qual_map); + mem_deref(q.qual_map); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(qualify) = { + "qualify", + "application", + module_init, + module_close +};