From 17c42774dbf6b1b74429c60a2377c13f52835a37 Mon Sep 17 00:00:00 2001 From: Jonathan Hulme Date: Wed, 29 Apr 2026 06:47:50 +0000 Subject: [PATCH] dialog: add options_ping_max_retries parameter for OPTIONS ping retries --- modules/dialog/README.md | 26 +++++++++++++++++++++++++ modules/dialog/dialog.c | 7 +++++++ modules/dialog/dlg_hash.h | 1 + modules/dialog/dlg_timer.c | 39 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/modules/dialog/README.md b/modules/dialog/README.md index e5a6f91663e..25fbb1c24ea 100644 --- a/modules/dialog/README.md +++ b/modules/dialog/README.md @@ -446,6 +446,32 @@ modparam("dialog", "options_ping_interval", 20) ``` +#### options_ping_max_retries (integer) + + +The number of additional in-dialog OPTIONS pings to send when the +previous OPTIONS transaction times out, before declaring the dialog leg +unreachable. Retries are sent immediately after each transaction timeout, +without waiting for the next `options_ping_interval` tick. The retry counter +is maintained independently for each dialog leg and is reset by any final +response other than 408 or 481. A 481 response is considered definitive and +is not retried. + +The maximum time needed to detect an unreachable peer is approximately +`(options_ping_max_retries + 1) * fr_timeout`, where `fr_timeout` is configured +by the "tm" module. + + +*Default value is "0" (no additional retries).* + + +```opensips title="Set options_ping_max_retries parameter" +... +modparam("dialog", "options_ping_max_retries", 2) +... +``` + + #### reinvite_ping_interval (integer) diff --git a/modules/dialog/dialog.c b/modules/dialog/dialog.c index 2f550e7c5eb..90f0a343e50 100644 --- a/modules/dialog/dialog.c +++ b/modules/dialog/dialog.c @@ -72,6 +72,7 @@ static char* profiles_nv_s = NULL; int dlg_bulk_del_no = 1; /* delete one by one */ int seq_match_mode = SEQ_MATCH_FALLBACK; int options_ping_interval = 30; /* seconds */ +int options_ping_max_retries = 0; /* extra OPTIONS pings to send on timeout before declaring leg dead */ int reinvite_ping_interval = 300; /* seconds */ int dlg_del_delay = 0; /* in seconds, default off */ str dlg_extra_hdrs = {NULL,0}; @@ -310,6 +311,7 @@ static const param_export_t mod_params[]={ { "rr_param", STR_PARAM, &rr_param.s }, { "default_timeout", INT_PARAM, &default_timeout }, { "options_ping_interval", INT_PARAM, &options_ping_interval }, + { "options_ping_max_retries", INT_PARAM, &options_ping_max_retries }, { "reinvite_ping_interval",INT_PARAM, &reinvite_ping_interval }, { "delete_delay", INT_PARAM, &dlg_del_delay }, { "dlg_extra_hdrs", STR_PARAM, &dlg_extra_hdrs.s }, @@ -805,6 +807,11 @@ static int mod_init(void) return -1; } + if (options_ping_max_retries < 0) { + LM_ERR("Negative options_ping_max_retries not accepted!!\n"); + return -1; + } + /* update the len of the extra headers */ if (dlg_extra_hdrs.s) dlg_extra_hdrs.len = strlen(dlg_extra_hdrs.s); diff --git a/modules/dialog/dlg_hash.h b/modules/dialog/dlg_hash.h index 9d16aa0843f..53051864dec 100644 --- a/modules/dialog/dlg_hash.h +++ b/modules/dialog/dlg_hash.h @@ -113,6 +113,7 @@ struct dlg_leg { struct dlg_leg_cseq_map *cseq_maps; /* used when translating ACKs */ char reply_received; char reinvite_confirmed; + unsigned char ping_retries; /* failed OPTIONS pings since last success */ const struct socket_info *bind_addr; }; diff --git a/modules/dialog/dlg_timer.c b/modules/dialog/dlg_timer.c index b90e04ea22e..2167bf47544 100644 --- a/modules/dialog/dlg_timer.c +++ b/modules/dialog/dlg_timer.c @@ -38,6 +38,13 @@ struct dlg_reinvite_ping_timer *reinvite_ping_timer=0; str options_str=str_init("OPTIONS"); str invite_str=str_init("INVITE"); +extern int options_ping_max_retries; + +/* forward declarations for OPTIONS ping retry from inside the reply callback */ +void reply_from_caller(struct cell* t, int type, struct tmcb_params* ps); +void reply_from_callee(struct cell* t, int type, struct tmcb_params* ps); +void unref_dlg_cb(void *dlg); + /* for the dlg timer, there are 3 possible states : * prev=next=0 -> dialog not in timer list * prev=0 -> dialog expired @@ -765,6 +772,36 @@ int dlg_handle_seq_reply(struct dlg_cell *dlg, struct sip_msg* rpl, LM_DBG("Status Code received = [%d]\n", statuscode); if (rpl == FAKED_REPLY || statuscode == 408) { + /* OPTIONS ping timeout: optionally retry immediately before giving up. + * Re-INVITE ping path is unchanged. */ + if (!is_reinvite_rpl && + dlg->legs[leg].ping_retries < (unsigned char)options_ping_max_retries) { + dlg_request_callback *cb; + + dlg->legs[leg].ping_retries++; + LM_INFO("OPTIONS ping timeout on %s leg, retry %u/%d, " + "ci: [%.*s]\n", + leg == DLG_CALLER_LEG ? "caller" : "callee", + dlg->legs[leg].ping_retries, options_ping_max_retries, + dlg->callid.len, dlg->callid.s); + + /* clear the gate so a new in-flight ping can be tracked */ + *ping_status = DLG_PING_SUCCESS; + + cb = (leg == DLG_CALLER_LEG) ? reply_from_caller : reply_from_callee; + ref_dlg(dlg, 1); + if (send_leg_msg(dlg, &options_str, other_leg(dlg, leg), leg, + 0, 0, cb, dlg, unref_dlg_cb, + &dlg->legs[leg].reply_received) < 0) { + LM_ERR("failed to send OPTIONS retry on %s leg\n", + leg == DLG_CALLER_LEG ? "caller" : "callee"); + unref_dlg(dlg, 1); + /* leave ping_retries as-is; the next interval-driven ping + * will continue from the current retry count */ + } + return 0; + } + /* timeout occurred, nothing else to do now * next time timer fires, it will detect ping reply was not received */ @@ -788,6 +825,8 @@ int dlg_handle_seq_reply(struct dlg_cell *dlg, struct sip_msg* rpl, } *ping_status = DLG_PING_SUCCESS; + if (!is_reinvite_rpl) + dlg->legs[leg].ping_retries = 0; if (is_reinvite_rpl && statuscode < 300 && send_leg_msg(dlg, &ack, other_leg(dlg, leg), leg, NULL, NULL, NULL, NULL, NULL, NULL) < 0) LM_ERR("cannot send ACK message!\n");