Skip to content

Script Helper usage

View module: Script Helper

The purpose of this module is to help you start in an easy way with the OpenSIPS script. The learning curve gets milder as the beginners can start with a simplified format of the script - the idea is to shift the main focus on routing the SIP initial requests (which define the service logic), while transparently handling a lot of “standard” SIP scripting logic, in order to both save script coding time and mitigate potential SIP scripting errors. This tutorial offers both a brief overview on the features of the new module and an example script.

As stated in the documentation, the module currently features:

  • transparent handling of SIP sequential requests

    • sequential requests are properly and silently handled - they will not hit the script at all anymore
    • for initial SIP requests, the module performs record-routing (i.e. OpenSIPS adds itself in the signaling path using a Record-Route header), after which the main route is triggered, just as before
    • for sequential SIP requests, the module is automatically doing loose_route() for proper routing.
    • optionally a special route may be run just before all sequential requests are routed out
  • automatic dialog support

    • automatically create dialog support for the INVITE-based sessions
    • for sequential requests within a dialog, a complete dialog matching (Call-ID, From-tag, To-tag) will be attempted if the quick ‘did’-based Record-Route parameter matching fails

Future directions for the module include adding authentication and NAT support.

SIP logic provided:

  • simple standard SIP-2-SIP calling (with registering)
  • initial and sequential SIP request routing (using the script helper)
  • dialog support (using the script helper)

This is the OpenSIPS script with the script_helper support.

#
# OpenSIPS residential configuration script
# by OpenSIPS Solutions <team@opensips-solutions.com>
#
# This is a basic residential configuration.
#
# Please refer to the OpenSIPS Manuals at:
# https://opensips.org/Documentation/Manuals
# for an explanation of available statements, functions and parameters.
#
####### Global Parameters #########
/* uncomment the following lines to enable debugging */
#debug_mode=yes
log_level=3
xlog_level=3
stderror_enabled=no
syslog_enabled=yes
syslog_facility=LOG_LOCAL0
udp_workers=4
/* uncomment the next line to enable the auto temporary blacklisting of
not available destinations (default disabled) */
#disable_dns_blacklist=no
/* uncomment the next line to enable IPv6 lookup after IPv4 dns
lookup failures (default disabled) */
#dns_try_ipv6=yes
socket=udp:127.0.0.1:5060 # CUSTOMIZE ME
####### Modules Section ########
#set module path
mpath="/usr/local/lib/opensips/modules/"
#### SIGNALING module
loadmodule "signaling.so"
#### StateLess module
loadmodule "sl.so"
#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timeout", 5)
modparam("tm", "fr_inv_timeout", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)
#### Record Route Module
loadmodule "rr.so"
/* do not append from tag to the RR (no need for this script) */
modparam("rr", "append_fromtag", 0)
#### MAX ForWarD module
loadmodule "maxfwd.so"
#### SIP MSG OPerationS module
loadmodule "sipmsgops.so"
#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/run/opensips/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)
#### USeR LOCation module
loadmodule "usrloc.so"
modparam("usrloc", "nat_bflag", "NAT")
modparam("usrloc", "working_mode_preset", "single-instance-no-db")
#### REGISTRAR module
loadmodule "registrar.so"
modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT")
/* uncomment the next line not to allow more than 10 contacts per AOR */
#modparam("registrar", "max_contacts", 10)
#### ACCounting module
loadmodule "acc.so"
/* what special events should be accounted ? */
modparam("acc", "early_media", 0)
modparam("acc", "report_cancels", 0)
/* by default we do not adjust the direct of the sequential requests.
if you enable this parameter, be sure to enable "append_fromtag"
in "rr" module */
modparam("acc", "detect_direction", 0)
#### DIALOG module
loadmodule "dialog.so"
modparam("dialog", "db_mode", 0) # no DB
#### SCRIPT HELPER module
loadmodule "script_helper.so"
# this route will be run right before sequential requests are routed out
modparam("script_helper", "sequential_route", "my_seq_route")
modparam("script_helper", "use_dialog", 1)
modparam("script_helper", "create_dialog_flags", "bye-on-timeout,options-ping-caller,options-ping-callee")
loadmodule "proto_udp.so"
####### Routing Logic ########
# main request routing logic
route{
if (!mf_process_maxfwd_header(10)) {
send_reply(483,"Too Many Hops");
exit;
}
if ( !(is_method("REGISTER") ) ) {
if (is_myself("$fd")) {
} else {
# if caller is not local, then called number must be local
if (!is_myself("$rd")) {
send_reply(403,"Relay Forbidden");
exit;
}
}
}
# account only INVITEs
if (is_method("INVITE")) {
do_accounting("log","cdr");
}
if (!is_myself("$rd")) {
append_hf("P-hint: outbound\r\n");
route(relay);
}
# requests for my domain
if (is_method("PUBLISH|SUBSCRIBE")) {
send_reply(503, "Service Unavailable");
exit;
}
if (is_method("REGISTER")) {
# store the registration and generate a SIP reply
if (!save("location"))
xlog("failed to register AoR $tu\n");
exit;
}
if ($rU==NULL) {
# request with no Username in RURI
send_reply(484,"Address Incomplete");
exit;
}
# do lookup with method filtering
if (!lookup("location","method-filtering")) {
t_reply(404, "Not Found");
exit;
}
# when routing via usrloc, log the missed calls also
do_accounting("log","missed");
route(relay);
}
route [my_seq_route]
{
xlog("---- Sequential request handler ---- \n");
xlog("$rm | Call-ID: $ci | From-tag: $ft | To-tag: $tt\n");
}
route[relay] {
# for INVITEs enable some additional helper routes
if (is_method("INVITE")) {
t_on_branch("per_branch_ops");
t_on_reply("handle_nat");
t_on_failure("missed_call");
}
if (!t_relay()) {
send_reply(500,"Internal Error");
}
exit;
}
branch_route[per_branch_ops] {
xlog("new branch at $ru\n");
}
onreply_route[handle_nat] {
xlog("incoming reply\n");
}
failure_route[missed_call] {
if (t_was_cancelled()) {
exit;
}
# uncomment the following lines if you want to block client
# redirect based on 3xx replies.
##if (t_check_status("3[0-9][0-9]")) {
##t_reply(404,"Not found");
## exit;
##}
}

You can check it against the regular OpenSIPS script without the script_helper support.

#
# OpenSIPS residential configuration script
# by OpenSIPS Solutions <team@opensips-solutions.com>
#
# This is a basic residential configuration.
#
# Please refer to the OpenSIPS Manuals at:
# https://opensips.org/Documentation/Manuals
# for an explanation of available statements, functions and parameters.
#
####### Global Parameters #########
/* uncomment the following lines to enable debugging */
#debug_mode=yes
log_level=3
xlog_level=3
stderror_enabled=no
syslog_enabled=yes
syslog_facility=LOG_LOCAL0
udp_workers=4
/* uncomment the next line to enable the auto temporary blacklisting of
not available destinations (default disabled) */
#disable_dns_blacklist=no
/* uncomment the next line to enable IPv6 lookup after IPv4 dns
lookup failures (default disabled) */
#dns_try_ipv6=yes
socket=udp:127.0.0.1:5060 # CUSTOMIZE ME
####### Modules Section ########
#set module path
mpath="/usr/local/lib/opensips/modules/"
#### SIGNALING module
loadmodule "signaling.so"
#### StateLess module
loadmodule "sl.so"
#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timeout", 5)
modparam("tm", "fr_inv_timeout", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)
#### Record Route Module
loadmodule "rr.so"
/* do not append from tag to the RR (no need for this script) */
modparam("rr", "append_fromtag", 0)
#### MAX ForWarD module
loadmodule "maxfwd.so"
#### SIP MSG OPerationS module
loadmodule "sipmsgops.so"
#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/run/opensips/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)
#### USeR LOCation module
loadmodule "usrloc.so"
modparam("usrloc", "nat_bflag", "NAT")
modparam("usrloc", "working_mode_preset", "single-instance-no-db")
#### REGISTRAR module
loadmodule "registrar.so"
modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT")
/* uncomment the next line not to allow more than 10 contacts per AOR */
#modparam("registrar", "max_contacts", 10)
#### ACCounting module
loadmodule "acc.so"
/* what special events should be accounted ? */
modparam("acc", "early_media", 0)
modparam("acc", "report_cancels", 0)
/* by default we do not adjust the direct of the sequential requests.
if you enable this parameter, be sure to enable "append_fromtag"
in "rr" module */
modparam("acc", "detect_direction", 0)
#### DIALOG module
loadmodule "dialog.so"
modparam("dialog", "db_mode", 0) # no DB
loadmodule "proto_udp.so"
####### Routing Logic ########
# main request routing logic
route{
if (!mf_process_maxfwd_header(10)) {
send_reply(483,"Too Many Hops");
exit;
}
if (has_totag()) {
# handle hop-by-hop ACK (no routing required)
if ( is_method("ACK") && t_check_trans() ) {
t_relay();
exit;
}
# sequential request within a dialog should
# take the path determined by record-routing
if ( !loose_route() ) {
# we do record-routing for all our traffic, so we should not
# receive any sequential requests without Route hdr.
send_reply(404,"Not here");
exit;
}
if (is_method("BYE")) {
# do accounting even if the transaction fails
do_accounting("log","failed");
}
# route it out to whatever destination was set by loose_route()
# in $du (destination URI).
route(relay);
exit;
}
# CANCEL processing
if (is_method("CANCEL")) {
if (t_check_trans())
t_relay();
exit;
}
# absorb retransmissions, but do not create transaction
t_check_trans();
if ( !(is_method("REGISTER") ) ) {
if (is_myself("$fd")) {
} else {
# if caller is not local, then called number must be local
if (!is_myself("$rd")) {
send_reply(403,"Relay Forbidden");
exit;
}
}
}
# preloaded route checking
if (loose_route()) {
xlog("L_ERR",
"Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
if (!is_method("ACK"))
send_reply(403,"Preload Route denied");
exit;
}
# record routing
if (!is_method("REGISTER|MESSAGE"))
record_route();
# account only INVITEs
if (is_method("INVITE")) {
do_accounting("log","cdr");
}
if (!is_myself("$rd")) {
append_hf("P-hint: outbound\r\n");
route(relay);
}
# requests for my domain
if (is_method("PUBLISH|SUBSCRIBE")) {
send_reply(503, "Service Unavailable");
exit;
}
if (is_method("REGISTER")) {
# store the registration and generate a SIP reply
if (!save("location"))
xlog("failed to register AoR $tu\n");
exit;
}
if ($rU==NULL) {
# request with no Username in RURI
send_reply(484,"Address Incomplete");
exit;
}
# do lookup with method filtering
if (!lookup("location","method-filtering")) {
t_reply(404, "Not Found");
exit;
}
# when routing via usrloc, log the missed calls also
do_accounting("log","missed");
route(relay);
}
route[relay] {
# for INVITEs enable some additional helper routes
if (is_method("INVITE")) {
t_on_branch("per_branch_ops");
t_on_reply("handle_nat");
t_on_failure("missed_call");
}
if (!t_relay()) {
send_reply(500,"Internal Error");
}
exit;
}
branch_route[per_branch_ops] {
xlog("new branch at $ru\n");
}
onreply_route[handle_nat] {
xlog("incoming reply\n");
}
failure_route[missed_call] {
if (t_was_cancelled()) {
exit;
}
# uncomment the following lines if you want to block client
# redirect based on 3xx replies.
##if (t_check_status("3[0-9][0-9]")) {
##t_reply(404,"Not found");
## exit;
##}
}
--- modules/script_helper/samples/opensips-regular.cfg 2026-07-21 13:11:53.483884563 +0300
+++ modules/script_helper/samples/opensips-script-helper.cfg 2026-07-21 13:11:47.175918222 +0300
@@ -95,6 +95,13 @@
loadmodule "dialog.so"
modparam("dialog", "db_mode", 0) # no DB
+#### SCRIPT HELPER module
+loadmodule "script_helper.so"
+# this route will be run right before sequential requests are routed out
+modparam("script_helper", "sequential_route", "my_seq_route")
+modparam("script_helper", "use_dialog", 1)
+modparam("script_helper", "create_dialog_flags", "bye-on-timeout,options-ping-caller,options-ping-callee")
+
loadmodule "proto_udp.so"
@@ -109,44 +116,6 @@
exit;
}
- if (has_totag()) {
-
- # handle hop-by-hop ACK (no routing required)
- if ( is_method("ACK") && t_check_trans() ) {
- t_relay();
- exit;
- }
-
- # sequential request within a dialog should
- # take the path determined by record-routing
- if ( !loose_route() ) {
- # we do record-routing for all our traffic, so we should not
- # receive any sequential requests without Route hdr.
- send_reply(404,"Not here");
- exit;
- }
-
- if (is_method("BYE")) {
- # do accounting even if the transaction fails
- do_accounting("log","failed");
- }
-
- # route it out to whatever destination was set by loose_route()
- # in $du (destination URI).
- route(relay);
- exit;
- }
-
- # CANCEL processing
- if (is_method("CANCEL")) {
- if (t_check_trans())
- t_relay();
- exit;
- }
-
- # absorb retransmissions, but do not create transaction
- t_check_trans();
-
if ( !(is_method("REGISTER") ) ) {
if (is_myself("$fd")) {
@@ -159,19 +128,6 @@
}
}
- # preloaded route checking
- if (loose_route()) {
- xlog("L_ERR",
- "Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
- if (!is_method("ACK"))
- send_reply(403,"Preload Route denied");
- exit;
- }
-
- # record routing
- if (!is_method("REGISTER|MESSAGE"))
- record_route();
-
# account only INVITEs
if (is_method("INVITE")) {
do_accounting("log","cdr");
@@ -214,6 +170,13 @@
}
+route [my_seq_route]
+{
+ xlog("---- Sequential request handler ---- \n");
+ xlog("$rm | Call-ID: $ci | From-tag: $ft | To-tag: $tt\n");
+}
+
+
route[relay] {
# for INVITEs enable some additional helper routes
if (is_method("INVITE")) {