Сегодня мы поговорим о том, зачем использовать макропроцессор m4 для конфигурирования opensips и рассмотрим startup-скрипты, которые мы используем в production-окружении.
Для тех, кто не знает, что такое m4, приведу выдержку из википедии:
Макропроцессор m4, разработанный в 1977 году программистами Брайаном Керниганом и Денисом Ритчи, предназначен для макрогенерации на предварительном проходе в различных языках. Макрогенерация означает копирование входного символьного потока в выходной с подстановкой макросов по мере их появления. Макросы могут быть встроенными или определенными пользователями, и принимать произвольное число аргументов. Имеется множество встроенных функций для включения файлов, запуска внешних команд, выполнения целочисленной арифметики, манипуляции строками. Название «m4» раскрывается как «macro», то есть «m» + ещё 4 буквы.
Макропроцессор m4 применяется во многих различных областях.
В нашем случае он используется для гибкого генерирования, повышения удобства чтения и унификации конфигурационных файлов opensips.
Рассмотрим сборку конфигурационного файла ноды sip-кластера из предыдущей статьи.
Конфигурация состоит из 5 файлов:
— Константы конкретного узла:
divert(-1)
define(`INT_IP', `192.168.0.101')
define(`EXT_IP', `8.8.4.4')
define(`RTPPROXY_SOCK', `udp:192.168.0.200:22222')
define(`SERVER_UA', `Gateway 1')
define(`NAT_PING', `30')
divert(0)dnl
include(`opensips_defs.m4')
— Общие константы для всех узлов:
divert(-1)
define(`BALANCER_EXT_IP', `8.8.8.8')
define(`BALANCER_INT_IP', `192.168.0.1')
define(`SOFTSWITCHES', ``192.168.0.3',`192.168.0.4',`192.168.0.5' ')
define(`FOREACH',`ifelse(eval($#>2),1,
`pushdef(`last_$1',eval($#==3))dnl
`'pushdef(`$1',`$3')$2`'popdef(`$1')dnl
`'popdef(`last_$1')
`'ifelse(eval($#>3),1,`$0(`$1',`$2',shift(shift(shift($@))))')')')dnl
define(`UDP_CHILDREN', `3')
define(`TCP_CHILDREN', `10')
define(`RTPPROXY_NOTIFY_SOCK', `tcp:INT_IP:22222')
define(`PG_USER', `opensips')
define(`PG_PASSWD', `opensips')
define(`PG_HOST', `192.168.0.101')
define(`PG_DB', `opensips')
define(`PG_URL', `postgres://PG_USER:PG_PASSWD@PG_HOST/PG_DB')
define(`DOMAIN', `cool.sip')
define(`LOGHDR1', `M=$rm IP=$si:$sp origIP=$hdr(x-src-uri)')
define(`LOGHDR2', `F=$fu T=$tu oP=$oP pr=$pr dP=$dP rP=$rP ID=$ci origID=$hdr(x-orig-ci)')
define(`LOGCSEQ', `cseq=$cs')
define(`LOGUA', `UA=$ua')
define(`LOGHDR', `(LOGHDR1 RURI=$ru DURI=$du LOGHDR2 LOGCSEQ LOGUA)n')
define(`RPLHDR', `(STATUS="$rs $rr" LOGHDR1 LOGHDR2 LOGCSEQ)n')
define(`FAILHDR', `(LOGHDR1 LOGHDR2 LOGCSEQ)n')
define(`FLB_NAT', `6')
define(`FL_SIPTRACE', `19')
define(`FLB_RTPPROXY', `22')
divert(0)dnl
include(`opensips_global.m4')
include(`opensips_modules.m4')
include(`opensips_routes.m4')
— Глобальные параметры opensips:
####### Global Parameters ##############################################
debug=3
memlog=0
log_stderror=no
log_facility=LOG_LOCAL0
fork=yes
children=UDP_CHILDREN
disable_tcp=yes
#
mhomed=1
port=5060
listen = udp:INT_IP:5060
listen = udp:EXT_IP:5060
server_header="Server: SERVER_UA"
user_agent_header="User-Agent: SERVER_UA"
#
disable_core_dump=no
— Инициализация и параметры модулей:
####### Modules Section ################################################
mpath="/usr/lib64/opensips/modules"
########################################################################
loadmodule "maxfwd.so"
########################################################################
modparam("maxfwd", "max_limit", 256)
########################################################################
loadmodule "sipmsgops.so"
########################################################################
########################################################################
loadmodule "textops.so"
########################################################################
########################################################################
loadmodule "mi_fifo.so"
########################################################################
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)
modparam("mi_fifo", "fifo_group", "opensips")
modparam("mi_fifo", "fifo_user", "opensips")
modparam("mi_fifo", "reply_dir", "/tmp/")
modparam("mi_fifo", "reply_indent", "t")
########################################################################
loadmodule "db_postgres.so"
########################################################################
########################################################################
loadmodule "drouting.so"
#########################################################################
modparam("drouting", "db_url", "PG_URL")
modparam("drouting", "drd_table", "dr_gateways")
modparam("drouting", "drr_table", "dr_rules")
modparam("drouting", "drg_table", "dr_groups")
modparam("drouting", "drc_table", "dr_carriers")
modparam("drouting", "use_domain", 1)
modparam("drouting", "drg_user_col", "username")
modparam("drouting", "drg_domain_col", "domain")
modparam("drouting", "drg_grpid_col", "groupid")
modparam("drouting", "force_dns", 1)
modparam("drouting", "probing_interval", 30)
modparam("drouting", "probing_method", "OPTIONS")
modparam("drouting", "probing_from", "sip:pinger@DOMAIN")
modparam("drouting", "probing_reply_codes", "501, 403")
########################################################################
loadmodule "avpops.so"
########################################################################
modparam("avpops", "db_url", "PG_URL")
modparam("avpops", "avp_table", "usr_preferences")
modparam("avpops", "use_domain", 1)
modparam("avpops", "username_column", "username")
modparam("avpops", "domain_column", "domain")
modparam("avpops", "attribute_column", "attribute")
modparam("avpops", "value_column", "value")
modparam("avpops", "type_column", "type")
modparam("avpops", "buf_size", 1024)
########################################################################
loadmodule "domain.so"
########################################################################
modparam("domain", "db_url", "PG_URL")
modparam("domain", "db_mode", 1)
modparam("domain", "domain_table", "domain")
modparam("domain", "domain_col", "domain")
########################################################################
loadmodule "usrloc.so"
########################################################################
modparam("usrloc", "nat_bflag", FLB_NAT)
modparam("usrloc", "user_column", "username")
modparam("usrloc", "domain_column", "domain")
modparam("usrloc", "contact_column", "contact")
modparam("usrloc", "expires_column", "expires")
modparam("usrloc", "q_column", "q")
modparam("usrloc", "callid_column", "callid")
modparam("usrloc", "cseq_column", "cseq")
modparam("usrloc", "methods_column", "methods")
modparam("usrloc", "flags_column", "flags")
modparam("usrloc", "cflags_column", "cflags")
modparam("usrloc", "user_agent_column", "user_agent")
modparam("usrloc", "received_column", "received")
modparam("usrloc", "socket_column", "socket")
modparam("usrloc", "path_column", "path")
modparam("usrloc", "use_domain", 1)
modparam("usrloc", "desc_time_order", 0)
modparam("usrloc", "timer_interval", 60)
modparam("usrloc", "db_url", "PG_URL")
modparam("usrloc", "db_mode", 3)
modparam("usrloc", "matching_mode", 1)
modparam("usrloc", "cseq_delay", 20)
modparam("usrloc", "hash_size", 9)
########################################################################
loadmodule "alias_db.so"
########################################################################
modparam("alias_db", "db_url", "PG_URL")
modparam("alias_db", "append_branches", 1)
########################################################################
loadmodule "rr.so"
########################################################################
modparam("rr", "append_fromtag", 1)
modparam("rr", "enable_double_rr", 1)
modparam("rr", "add_username", 1)
########################################################################
loadmodule "sl.so"
########################################################################
modparam("sl", "enable_stats", 1)
########################################################################
loadmodule "tm.so"
########################################################################
modparam("tm", "fr_timer", 30)
modparam("tm", "fr_inv_timer", 120)
modparam("tm", "wt_timer", 5)
modparam("tm", "delete_timer", 2)
modparam("tm", "T1_timer", 500)
modparam("tm", "T2_timer", 4000)
modparam("tm", "ruri_matching", 1)
modparam("tm", "via1_matching", 1)
modparam("tm", "unix_tx_timeout", 2)
modparam("tm", "restart_fr_on_each_reply", 1)
modparam("tm", "pass_provisional_replies", 0)
modparam("tm", "syn_branch", 1)
modparam("tm", "onreply_avp_mode", 0)
modparam("tm", "disable_6xx_block", 0)
modparam("tm", "enable_stats", 1)
########################################################################
loadmodule "signaling.so"
########################################################################
########################################################################
loadmodule "dialog.so"
########################################################################
modparam("dialog", "enable_stats", 1)
modparam("dialog", "hash_size", 4096)
modparam("dialog", "rr_param", "did")
modparam("dialog", "default_timeout", 43200)
modparam("dialog", "dlg_match_mode", 1)
modparam("dialog", "db_url", "PG_URL")
modparam("dialog", "db_mode", 1)
modparam("dialog", "db_update_period", 60)
modparam("dialog", "table_name", "dialog")
modparam("dialog", "call_id_column", "callid")
modparam("dialog", "from_uri_column", "from_uri")
modparam("dialog", "from_tag_column", "from_tag")
modparam("dialog", "to_uri_column", "to_uri")
modparam("dialog", "to_tag_column", "to_tag")
modparam("dialog", "from_cseq_column", "caller_cseq")
modparam("dialog", "to_cseq_column", "callee_cseq")
modparam("dialog", "from_route_column", "caller_route_set")
modparam("dialog", "to_route_column", "callee_route_set")
modparam("dialog", "from_contact_column", "caller_contact")
modparam("dialog", "to_contact_column", "callee_contact")
modparam("dialog", "from_sock_column", "caller_sock")
modparam("dialog", "to_sock_column", "callee_sock")
modparam("dialog", "h_id_column", "hash_id")
modparam("dialog", "h_entry_column", "hash_entry")
modparam("dialog", "state_column", "state")
modparam("dialog", "start_time_column", "start_time")
modparam("dialog", "timeout_column", "timeout")
########################################################################
loadmodule "registrar.so"
########################################################################
modparam("registrar", "default_expires", 3600)
modparam("registrar", "min_expires", 0)
modparam("registrar", "max_expires", 7200)
modparam("registrar", "default_q", 0)
modparam("registrar", "tcp_persistent_flag", -1)
modparam("registrar", "realm_prefix", "sip.")
modparam("registrar", "case_sensitive", 0)
modparam("registrar", "received_avp", "$avp(rcv)")
modparam("registrar", "received_param", "received")
modparam("registrar", "max_contacts", 10)
modparam("registrar", "retry_after", 0)
########################################################################
loadmodule "nathelper.so"
########################################################################
ifdef(`NAT_PING', `modparam("nathelper", "natping_interval", NAT_PING)
modparam("nathelper", "ping_nated_only", 1)
modparam("nathelper", "natping_processes", 1)')
modparam("nathelper", "received_avp", "$avp(rcv)")
ifdef(`NAT_PING', `modparam("nathelper", "sipping_bflag", FLB_NAT)
modparam("nathelper", "sipping_from", "sip:pinger@DOMAIN")
modparam("nathelper", "sipping_method", "OPTIONS")')
modparam("nathelper", "nortpproxy_str", "a=sdpmangled:yesrn")
########################################################################
loadmodule "rtpproxy.so"
########################################################################
modparam("rtpproxy", "rtpproxy_sock", "RTPPROXY_SOCK")
modparam("rtpproxy", "rtpproxy_disable_tout", 20)
modparam("rtpproxy", "rtpproxy_timeout", "10")
modparam("rtpproxy", "rtpproxy_autobridge", 1)
modparam("rtpproxy", "rtpproxy_retr", 5)
modparam("rtpproxy", "nortpproxy_str", "a=sdpmangled:yesrn")
modparam("rtpproxy", "rtpp_notify_socket", "RTPPROXY_NOTIFY_SOCK")
########################################################################
loadmodule "auth.so"
########################################################################
modparam("auth", "nonce_expire", 20)
modparam("auth", "rpid_suffix", ";party=calling;id-type=subscriber;screen=yes")
modparam("auth", "realm_prefix", "sip.")
modparam("auth", "rpid_avp", "$avp(rpid)")
modparam("auth", "calculate_ha1", 0)
modparam("auth", "disable_nonce_check", 1)
########################################################################
loadmodule "auth_db.so"
########################################################################
modparam("auth_db", "db_url", "PG_URL")
modparam("auth_db", "user_column", "username")
modparam("auth_db", "domain_column", "domain")
modparam("auth_db", "password_column", "ha1")
modparam("auth_db", "password_column_2", "ha1b")
modparam("auth_db", "calculate_ha1", 0)
modparam("auth_db", "use_domain", 1)
########################################################################
loadmodule "uri.so"
########################################################################
modparam("uri", "db_url", "PG_URL")
modparam("uri", "db_table", "subscriber")
modparam("uri", "user_column", "username")
modparam("uri", "domain_column", "domain")
modparam("uri", "uriuser_column", "username")
modparam("uri", "use_uri_table", 0)
modparam("uri", "use_domain", 1)
########################################################################
loadmodule "snmpstats.so"
########################################################################
modparam("snmpstats", "sipEntityType", "registrarServer")
modparam("snmpstats", "sipEntityType", "proxyServer")
modparam("snmpstats", "MsgQueueMinorThreshold", 2000)
modparam("snmpstats", "MsgQueueMajorThreshold", 5000)
modparam("snmpstats", "dlg_minor_threshold", 500)
modparam("snmpstats", "dlg_major_threshold", 750)
modparam("snmpstats", "snmpgetPath", "/usr/bin/")
modparam("snmpstats", "snmpCommunity", "public")
########################################################################
loadmodule "mi_xmlrpc.so"
########################################################################
modparam("mi_xmlrpc", "port", 8080)
########################################################################
loadmodule "cachedb_local.so"
########################################################################
modparam("cachedb_local", "cache_table_size", 9)
modparam("cachedb_local", "cache_clean_period", 600)
— Секция маршрутизации:
route {
xlog("L_INFO", "[MAIN] Incoming request LOGHDR$mb");
if (!mf_process_maxfwd_header("10")) {
xlog("L_ERR", "[MAIN] Too many hops LOGHDR");
send_reply("483", "Too Many Hops");
exit;
}
force_rport();
route(PING);
if (!is_method("REGISTER")) {
if (nat_uac_test("19")) {
record_route(";nat=yes");
} else {
record_route();
}
}
if (is_method("CANCEL|BYE")) unforce_rtp_proxy();
$var(preload) = 0;
if (loose_route()) {
if (nat_uac_test("19") || search("^Route:.*;nat=yes")) {
fix_nated_contact();
setbflag(FLB_NAT);
}
if (is_method("INVITE|UPDATE")) resetbflag(FLB_RTPPROXY); # reINVITE, UPDATE
route(RELAY);
} else {
if (is_method("ACK")) route(CHECK_TRANS);
send_reply("403", "Not Relaying");
}
}
if (is_method("CANCEL")) route(CHECK_TRANS);
t_check_trans();
if (loose_route()) { # Preloaded route
xlog("L_ERR", "Denied attempt to route with preloaded route");
if (!is_method("ACK")) sl_send_reply("403", "Preloaded Route Denied");
exit;
}
if (is_method("REGISTER")) route(REGISTER);
if (is_method("INVITE")) {
send_reply("100", "Trying");
route(INVITE);
}
if (is_method("MESSAGE")) route(INVITE);
xlog("L_ERR", "[MAIN] Call leg/Transaction does not exist LOGHDR");
send_reply("481", "Call leg/Transaction does not exist");
exit;
}
route[REGISTER] {
route(AUTH);
if (!db_check_to()) {
xlog("L_ERR", "[REGISTER] Spoofed to URI detected LOGHDR");
send_reply("403", "Spoofed to URI Detected");
exit;
}
consume_credentials();
if ( (is_present_hf("Contact")) && (nat_uac_test("19")) ) {
xlog("L_INFO", "[REGISTER] NAT detected LOGHDR");
fix_nated_register();
setbflag(FLB_NAT);
}
if($ua =~ "(dlink)|(MP204)") {
xlog("L_INFO", "[REGISTER] Fixed Expires HDR on bad UAC (ct expire=$ct.fields(expires), hdr expire=$hdr(Expires)) LOGHDR");
if (!save("location","rp0")) {
sl_reply_error();
exit;
}
append_to_reply("Contact: $ct;expires=$hdr(Expires)rn");
send_reply("200", "OK");
} else {
if(!save("location", "p0"))
{
xlog("L_ERR", "[REGISTER] Saving contact failed LOGHDR");
sl_reply_error();
exit;
}
}
xlog("L_INFO", "[REGISTER] Registration successful LOGHDR");
exit;
}
route[INVITE] {
if (nat_uac_test("19")) {
xlog("L_INFO", "[INVITE] NAT detected (oldct=$ct) LOGHDR");
fix_nated_contact();
setbflag(FLB_NAT);
}
if (route(FROM_SOFTSWITCH)) { # Softswitch -> SIP
xlog("L_INFO", "[INVITE] Call from softswitch LOGHDR");
if ((!is_domain_local("$rd")) || ($rd == "qip.ru")) {
xlog("L_INFO", "[INVITE] Call to foreign domain LOGHDR");
$ru = "sip:" + $rU + "@" + $rd;
route(RELAY);
}
if (db_does_uri_exist()) {
xlog("L_INFO", "[INVITE] Callee is local user LOGHDR");
route(LOOKUP);
route(RELAY);
} else {
xlog("L_ERR", "[INVITE] User not found LOGHDR");
send_reply("404", "Not Found");
exit;
}
} else { # SIP -> SIP
xlog("L_INFO", "[INVITE] Call from SIP LOGHDR");
if ($fd =~ ".*DOMAIN") {
xlog("L_INFO", "[INVITE] Call from local domain LOGHDR");
route(AUTH);
if (!db_check_from()) {
xlog("L_ERR", "[INVITE] Spoofed from URI detected LOGHDR");
send_reply("403", "Spoofed from URI Detected");
exit;
}
$var(routing_retcode) = do_routing("", "W");
} else {
xlog("L_INFO", "[INVITE] Call from foreign domain LOGHDR");
$var(routing_retcode) = do_routing("1", "W");
}
if($var(routing_retcode) != 1) {
xlog("L_ERR", "[INVITE] Error loading gateways LOGHDR");
send_reply("503", "Termination Currently Unavailable");
exit;
}
}
exit;
}
route[FROM_SOFTSWITCH] {
if ($hdr(x-src-uri)) $var(realip) = $(hdr(x-src-uri){uri.host});
FOREACH(`SOFTSWITCH',`if ($var(realip) == "SOFTSWITCH") return(1);',SOFTSWITCHES)
return(-1);
}
route[TO_SOFTSWITCH] {
FOREACH(`SOFTSWITCH',`if ($dd == "SOFTSWITCH") return(1);',SOFTSWITCHES)
return(-1);
}
route[PING] {
if (is_method("PING")) {
xlog("L_INFO", "[PING] PING - Sending 200 OK LOGHDR");
send_reply("200", "OK");
exit;
}
if (is_method("NOTIFY")) {
if ($hdr(Event) == "keep-alive") {
xlog("L_INFO", "[PING] NOTIFY - Sending 200 OK LOGHDR");
send_reply("200", "OK");
exit;
}
}
if (is_method("OPTIONS")) {
xlog("L_INFO", "[PING] OPTIONS - Sending 200 OK LOGHDR");
send_reply("200", "OK");
exit;
}
return;
}
route[AUTH] {
if(!www_authorize("", "subscriber")) {
$var(auth_retcode) = $retcode;
switch ($var(auth_retcode)) {
case -5:
xlog("L_ERR", "[AUTH] Generic error LOGHDR");
break;
case -4:
xlog("L_ERR", "[AUTH] No credentials LOGHDR");
www_challenge("", "1");
break;
case -3:
xlog("L_INFO", "[AUTH] Stale nonce LOGHDR");
www_challenge("", "1");
break;
case -2:
xlog("L_ERR", "[AUTH] Invalid password LOGHDR");
break;
case -1:
xlog("L_ERR", "[AUTH] Invalid user LOGHDR");
break;
default:
xlog("L_ERR", "[AUTH] Unknown error ($var(auth_retcode)) LOGHDR");
}
send_reply("403", "Forbidden");
exit;
}
consume_credentials();
return;
}
route[LOOKUP] {
alias_db_lookup("dbaliases" , "");
lookup("location");
$var(lookup_retcode) = $retcode;
switch ($var(lookup_retcode)) {
case 1:
xlog("L_INFO", "[LOOKUP] Contact found LOGHDR");
break;
case -1:
xlog("L_ERR", "[LOOKUP] No contact found LOGHDR");
send_reply("404", "Not Found");
exit;
case -2:
xlog("L_ERR", "[LOOKUP] Contact found, but method not supported LOGHDR");
send_reply("405", "Method Not Allowed");
exit;
case -3:
xlog("L_ERR", "[LOOKUP] Internal error during processing LOGHDR");
send_reply("404", "Not Found");
exit;
default:
xlog("L_ERR", "[LOOKUP] Unknown error ($var(lookup_retcode)) LOGHDR");
exit;
}
return;
}
route[RELAY] {
if ( (!isbflagset(FLB_RTPPROXY)) && (has_body("application/sdp")) ) {
setbflag(FLB_RTPPROXY);
xlog("L_INFO", "[RELAY] RTPproxy offer LOGHDR");
rtpproxy_offer("cofr");
}
if (route(TO_SOFTSWITCH)) $ru = "sip:" + $rU + "@" + $rd + ":" + $rp + ";transport=udp"; # Ensure UDP
if ($du != null) {
$var(dest_ip) = $dd;
} else {
$var(dest_ip) = $rd;
}
# Choose interface
if (avp_check("$var(dest_ip)", "re/192.168./g")) {
$var(balancer_ip) = "BALANCER_INT_IP";
force_send_socket("INT_IP");
} else {
$var(balancer_ip) = "BALANCER_EXT_IP";
force_send_socket("EXT_IP");
}
if ($var(preload) == 1) { # Need to add preload route
xlog("L_INFO", "[RELAY] Add preloaded route to request LOGHDRn");
$var(dest_uri) = "sip:"+$dd+":"+$dp+";transport="+$dP;
append_hf("Route: <sip:$var(balancer_ip);lr;received=$var(dest_uri)>rn", "Via");
$var(preload) = 0;
}
$du = "sip:"+$var(balancer_ip)+":5060;transport=udp";
remove_hf("x-src-uri");
remove_hf("x-orig-to");
remove_hf("x-orig-ci");
xlog("L_INFO", "[RELAY] Request leaving server LOGHDR");
t_on_reply("ON_REPLY");
t_on_failure("ON_FAIL");
if (!t_relay("0x01")) {
sl_reply_error();
if ( (isbflagset(FLB_RTPPROXY)) && (is_method("INVITE")) ) unforce_rtp_proxy();
}
exit;
}
route[CHECK_TRANS] {
t_on_reply("ON_REPLY");
if (t_check_trans()) {
route(RELAY);
} else {
xlog("L_ERR", "[CHECK_TRANS] Dropping misrouted request LOGHDR");
}
exit;
}
onreply_route[ON_REPLY] {
xlog("L_INFO", "[ON_REPLY] Incoming reply RPLHDR$mb");
if (status != "100") route(11);
if ( (status=~"(180)|(183)|2[0-9][0-9]") && (has_body("application/sdp")) ) {
rtpproxy_answer("cofr");
}
remove_hf("x-src-uri");
remove_hf("x-orig-to");
remove_hf("x-orig-ci");
}
failure_route[ON_FAIL] {
if (!t_check_status("408|500|503")) {
xlog("L_INFO", "[ON_FAIL] No failover routing needed for this response code LOGHDR");
if(isbflagset(FLB_RTPPROXY)) unforce_rtp_proxy();
exit;
}
if (route(TO_SOFTSWITCH)) {
if (use_next_gw()) route(RELAY);
xlog("L_ERR", "[ON_FAIL] Failed to select next PSTN gateway LOGHDR");
}
if(isbflagset(FLB_RTPPROXY)) unforce_rtp_proxy();
}
local_route {
if (is_method("OPTIONS")) return;
xlog("L_INFO", "[LOCAL] Generated request LOGHDR$mbn");
xlog("L_INFO", "[LOCAL] Request leaving server LOGHDR");
}
opensips.m4 уникален для каждого хоста, остальные файлы хранятся в git и общие для всех хостов.
Используемые директивы m4:
divert(-1) — выключает вывод
divert(0) — включает вывод
define(`INT_IP', `192.168.0.101') — заменяет во всех местах, где встречается INT_IP на 192.168.0.101
include(`opensips_defs.m4') — включает содержимое файла opensips_defs.m4
define(`FOREACH',`ifelse(eval($#>2),1,
`pushdef(`last_$1',eval($#==3))dnl
`'pushdef(`$1',`$3')$2`'popdef(`$1')dnl
`'popdef(`last_$1')
`'ifelse(eval($#>3),1,`$0(`$1',`$2',shift(shift(shift($@))))')')')dn — эмулирует foreach
Собирается конфигурационный файл командой m4 opensips.m4 >opensips.cfg
Ну и напоследок предлагаю наши startup-скрипты для RHEL и debian.
/usr/sbin/safe_opensips — используется для цикличного перезапуска opensips в случае падения (на всякий случай).
/etc/init.d/opensips — init-скрипт. Обратите внимание на функцию check_config(). Она нужна для того, чтобы «сконвертировать» вывод opensips в случае ошибок и указать в каких файлах и строках m4-файлов произошла ошибка.
RHEL:
#!/bin/bash
[ -f /etc/sysconfig/opensips ] && . /etc/sysconfig/opensips
OPENSIPS="/usr/sbin/opensips"
START_TIME=0
FIRST_START=1
PATH=/sbin:/bin:/usr/sbin:/usr/bin
while true; do
if (!(pidof opensips >/dev/null 2>&1)); then
FINISH_TIME=$(date +%s)
if [ $((FINISH_TIME - START_TIME)) -le 3 ]; then
echo -e "`date` @ `hostname`nnCannot start service - errors in configurationnSAFE script was killed" | mail -r opensips@`hostname` -s "OpenSIPS PROBLEM" $EMAIL
exit
fi
ulimit -c unlimited
ulimit -n $MAX_FILES
START_TIME=$(date +%s)
opensips -P ${PIDFILE} -f ${CONFIGFILE} $OPTIONS >/dev/null 2>&1
if [ $FIRST_START = 1 ]; then
echo -e "`date` @ `hostname`nnService started" | mail -r opensips@`hostname` -s "OpenSIPS INFO" $EMAIL
else
echo -e "`date` @ `hostname`nnService restarted by safe script" | mail -r opensips@`hostname` -s "OpenSIPS PROBLEM" $EMAIL
fi
fi
FIRST_START=0
sleep 2
done
#!/bin/bash
#
# Startup script for OpenSIPS
#
# chkconfig: - 85 15
# description: OpenSIPS is a fast SIP Server.
#
# processname: opensips
# pidfile: /var/run/opensips.pid
# config: /etc/opensips/opensips.cfg
#
### BEGIN INIT INFO
# Provides: opensips
# Required-Start: $local_fs $network $named
# Should-Start: mysqld postgresql
# Short-Description: start, stop OpenSIPS
# Description: OpenSIPS is a very fast and flexible SIP (RFC3261) server.
### END INIT INFO
# Source function library.
. /etc/rc.d/init.d/functions
PROG=opensips
[ -f /etc/sysconfig/${PROG} ] && . /etc/sysconfig/${PROG}
OPENSIPS="/usr/sbin/${PROG}"
SAFE_OPENSIPS="/usr/sbin/safe_${PROG}"
LOCKFILE="/var/lock/subsys/${PROG}"
RETVAL=0
check_config() {
LINECNT=1
LINENUM=1
FILENAME=
OSOUTPUT=`opensips -c 2>&1`
if [ $? != 0 ]; then
if [ -f "${M4_CONFIG}" ]; then
echo "${OSOUTPUT}" | while read -r LINE; do
echo "${LINE}"
if [[ "${LINE}" =~ "line" ]]; then
LINEERR=`echo ${LINE} | sed -r 's/.*lines+([0-9]+).*/1/'`
cd "${CONFIGDIR}"
m4 -Qs ${M4_CONFIG} | while read -r LINE; do
if [ "${LINE:0:5}" = "#line" ]; then
FILENAME=`echo ${LINE} | awk '{print $3}'`
LINENUM=0
else
LINENUM=$[${LINENUM}+1]
if [ ${LINECNT} -eq ${LINEERR} ]; then
echo -e ">n> Line ${LINENUM} in file ${FILENAME}:n> ${LINE}n>n"
break
fi
LINECNT=$[${LINECNT}+1]
fi
done
fi
done
else
echo -e "${OSOUTPUT}n"
fi
echo -n "errors in configuration" && failure && echo
exit 1
fi
return
}
generate_config() {
if [ -f "${M4_CONFIG}" ]; then
cd "${CONFIGDIR}"
M4SIZE=0
for i in ${CONFIGDIR}/*m4; do
M4SIZE=$((${M4SIZE} + `stat -c %s ${i}`))
done
M4SIZE=`expr ${M4SIZE} \* 95`
m4 -Q ${M4_CONFIG} >${CONFIGFILE}.tmp
RETVAL=$?
CFGSIZE=`stat -c %s ${CONFIGFILE}.tmp`
CFGSIZE=`expr ${CFGSIZE} \* 100`
if [ ${RETVAL} != 0 ]; then
echo -n "cannot process macro" && failure && echo
rm "${CONFIGFILE}.tmp"
exit 1
fi
if [ ${CFGSIZE} -le ${M4SIZE} ]; then
echo
echo -n "macro processor returns suspicious small cfg-file" && failure && echo
rm "${CONFIGFILE}.tmp"
exit 1
fi
# compare configs
if [ `md5sum ${CONFIGFILE}|awk '{print $1}'` != `md5sum ${CONFIGFILE}.tmp|awk '{print $1}'` ]; then
mkdir -p "${ARCHIVEDIR}"
mv "${CONFIGFILE}" "${ARCHIVEDIR}/${PROG}.cfg-`date +%Y%m%d_%H%M%S`"
fi
mv "${CONFIGFILE}.tmp" "${CONFIGFILE}"
chown ${PROG}:${PROG} ${CONFIGFILE}
chmod 600 ${CONFIGFILE}
fi
return
}
is_running() {
pidof ${PROG} >/dev/null 2>&1
STATUS=$?
pidof -x ${SAFE_OPENSIPS} >/dev/null 2>&1
STATUS=$(($STATUS + $?))
if [ $STATUS -le 1 ]; then
return 0
else
return 1
fi
}
start() {
echo -n $"Starting ${PROG}: "
ulimit -c unlimited
if is_running; then
echo -n "already running" && failure && echo
return 0
fi
generate_config
check_config
# kill all opensipsctl processes (they block fifo)
ps -C bash -o pid,command | grep opensipsctl | awk '{print $1}' | xargs -r kill -9
${SAFE_OPENSIPS} -P ${PIDFILE} -f ${CONFIGFILE} > /dev/null 2>&1 &
echo -n "completed" && success && echo
touch ${LOCKFILE}
RETVAL=0
return ${RETVAL}
}
stop() {
echo -n $"Stopping ${PROG}: "
if ! is_running; then
echo -n "not running" && failure && echo
return 0
fi
# check whether OpenSIPs is running
killproc ${SAFE_OPENSIPS} >/dev/null 2>&1
killproc ${OPENSIPS} >/dev/null 2>&1
# Wait not more than 20 seconds to stop process
# (needed sometimes when it saves coredump or debug logs)
STOP_CNT=0
while is_running; do
STOP_CNT=$[${STOP_CNT}+1]
if [ ${STOP_CNT} -ge 20 ]; then
killall -9 ${PROG}
fi
sleep 1
done
echo -n "completed" && success && echo
rm -f ${LOCKFILE} ${PIDFILE}
RETVAL=0
return 0
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
BASE=${PROG}
if is_running; then
echo $"${BASE} (pid `cat ${PIDFILE}`) is running"
RETVAL=0
else
echo $"${BASE} is stopped"
RETVAL=3
fi
;;
restart|reload)
generate_config
check_config
stop
start
;;
condrestart|try-restart)
if is_running; then
check_config
stop
start
fi
;;
*)
echo $"Usage: ${PROG} {start|stop|reload|restart|condrestart|status|help}"
RETVAL=2
esac
exit ${RETVAL}
#
# OpenSIPS variables
#
CONFIGDIR="/etc/opensips"
CONFIGFILE="${CONFIGDIR}/opensips.cfg"
M4_CONFIG="${CONFIGDIR}/opensips.m4"
ARCHIVEDIR="${CONFIGDIR}/archive"
PIDFILE="/var/run/opensips.pid"
OPTIONS="-M 64 -m 128"
EMAIL="root"
MAX_FILES=262144
Debian:
#!/bin/bash
INSTANCE=$1
. /etc/default/${INSTANCE}
shift
NAME=opensips
CONFIGDIR="/etc/${INSTANCE}"
CONFIGFILE="${CONFIGDIR}/opensips.cfg"
HOMEDIR=/var/run/${NAME}
PIDFILE=${HOMEDIR}/${INSTANCE}.pid
OPENSIPS="/usr/sbin/opensips"
START_TIME=0
FIRST_START=1
PATH=/sbin:/bin:/usr/sbin:/usr/bin
while true; do
MYPIDS=
for pid in $(pidof ${NAME}); do
ps -p $pid -o command=|grep -q etc/${INSTANCE}/ && MYPIDS="${MYPIDS} $pid"
done
if [ -z "${MYPIDS}" ]; then
FINISH_TIME=$(date +%s)
if [ $((FINISH_TIME - START_TIME)) -le 3 ]; then
echo -e "`date` @ `hostname`nnCannot start service [${INSTANCE}] - errors in configurationnSAFE script was killed" | mail -s "OpenSIPS PROBLEM" $EMAIL
exit
fi
if [ "$DUMP_CORE" = "yes" ]; then
# set proper ulimit
ulimit -c unlimited
# directory for the core dump files
COREDIR=/tmp
echo "$COREDIR/core.%e.sig%s.%p" > /proc/sys/kernel/core_pattern
fi
ulimit -c unlimited
ulimit -n $MAX_FILES >/dev/null
START_TIME=$(date +%s)
opensips -f ${CONFIGFILE} -M ${P_MEMORY} -m ${S_MEMORY} ${OPTIONS} >/dev/null 2>&1
if [ ${FIRST_START} = 0 ]; then
echo -e "`date` @ `hostname`nnService [${INSTANCE}] restarted by safe script" | mail -s "OpenSIPS [${INSTANCE}] PROBLEM" $EMAIL
fi
fi
FIRST_START=0
sleep 2
done
#!/bin/bash
### BEGIN INIT INFO
# Provides: opensips
# Required-Start: $syslog $network $local_fs $time
# Required-Stop: $syslog $network $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start the OpenSIPS SIP server
# Description: Start the OpenSIPS SIP server
# Should-Start: postgresql mysql radius
# Should-Stop: postgresql mysql radius
### END INIT INFO
PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/opensips
NAME=opensips
SAFE_DAEMON="/usr/sbin/safe_${NAME}"
HOMEDIR=/var/run/${NAME}
INSTANCE=`basename $0`
CONFIGDIR="/etc/${INSTANCE}"
CONFIGFILE="${CONFIGDIR}/opensips.cfg"
M4_CONFIG="${CONFIGDIR}/opensips.m4"
ARCHIVEDIR="${CONFIGDIR}/archive"
PIDFILE=${HOMEDIR}/safe_${INSTANCE}.pid
RUN_OPENSIPS=no
# Load startup options if available
[ -f /etc/default/${INSTANCE} ] && . /etc/default/${INSTANCE}
# Generates configuration file from M4 template
generate_m4_config() {
if [ ! -d ${CONFIGDIR} ]; then
echo "failed, config directory ${CONFIGDIR} doesn't exists"
exit 1
fi
if [ -f "${M4_CONFIG}" ]; then
cd "${CONFIGDIR}"
M4SIZE=0
for i in ${CONFIGDIR}/*m4; do
M4SIZE=$((${M4SIZE} + `stat -c %s ${i}`))
done
M4SIZE=`expr ${M4SIZE} \* 95`
m4 -Q ${M4_CONFIG} >${CONFIGFILE}.tmp
RETVAL=$?
CFGSIZE=`stat -c %s ${CONFIGFILE}.tmp`
CFGSIZE=`expr ${CFGSIZE} \* 100`
if [ ${RETVAL} != 0 ]; then
echo "failed, cannot process macro"
rm "${CONFIGFILE}.tmp"
exit 1
fi
if [ ${CFGSIZE} -le ${M4SIZE} ]; then
echo "failed, macro processor returns suspicious small cfg-file"
rm "${CONFIGFILE}.tmp"
exit 1
fi
touch ${CONFIGFILE}
# compare configs
if [ `md5sum ${CONFIGFILE}|awk '{print $1}'` != `md5sum ${CONFIGFILE}.tmp|awk '{print $1}'` ]; then
mkdir -p "${ARCHIVEDIR}"
mv "${CONFIGFILE}" "${ARCHIVEDIR}/${NAME}.cfg-`date +%Y%m%d_%H%M%S`"
fi
mv "${CONFIGFILE}.tmp" "${CONFIGFILE}"
fi
if [ ! -f ${CONFIGFILE} ]; then
echo "failed, config file ${CONFIGFILE} doesn't exists"
exit 1
fi
chown ${USER}:${GROUP} ${CONFIGFILE}
chmod 600 ${CONFIGFILE}
return
}
# Check if opensips configuration is valid before starting the server
check_opensips_config() {
LINECNT=1
LINENUM=1
FILENAME=
set +e
OSOUTPUT=`${DAEMON} -c 2>&1`
if [ $? != 0 ]; then
if [ -f "${M4_CONFIG}" ]; then
echo "${OSOUTPUT}" | while read -r LINE; do
echo "${LINE}"
if [[ "${LINE}" =~ "line" ]]; then
LINEERR=`echo ${LINE} | sed -r 's/.*lines+([0-9]+).*/1/'`
cd "${CONFIGDIR}"
m4 -Qs ${M4_CONFIG} | while read -r LINE; do
if [ "${LINE:0:5}" = "#line" ]; then
FILENAME=`echo ${LINE} | awk '{print $3}'`
LINENUM=0
else
LINENUM=$[${LINENUM}+1]
if [ ${LINECNT} -eq ${LINEERR} ]; then
echo -e ">n> Line ${LINENUM} in file ${FILENAME}:n> ${LINE}n>n"
break
fi
LINECNT=$[${LINECNT}+1]
fi
done
fi
done
else
echo -e "${OSOUTPUT}n"
fi
exit 1
fi
return
}
# Check daemon (not safe_script) is running
is_running() {
MYPIDS=
for pid in $(pidof ${NAME}); do
ps -p $pid -o command=|grep -q etc/${INSTANCE}/ && MYPIDS="${MYPIDS} $pid"
done
[ -n "${MYPIDS}" ] && return 0 # opensips processes found
if [ -f ${PIDFILE} ]; then # check safe_opensips if pid file exists
pid=$(cat ${PIDFILE})
ps -p $pid >/dev/null 2>&1 && return 0 # safe_opensips process found
fi
# nothing found
return 1
}
# Do not start opensips if fork=no is set in the config file
# otherwise the boot process will just stop
check_fork ()
{
if grep -q "^[[:space:]]*fork[[:space:]]*=[[:space:]]*no.*" ${CONFIGFILE}; then
echo "failed; fork=no specified in config file; run $0 debug instead"
exit 1
fi
}
create_radius_seqfile ()
{
# Create a radius sequence file to be used by the radius client if
# radius accounting is enabled. This is needed to avoid any issue
# with the file not being writable if opensips first starts as user
# root because DUMP_CORE is enabled and creates this file as user
# root and then later it switches back to user opensips and cannot
# write to the file. If the file exists before opensips starts, it
# won't change it's ownership and will be writable for both root
# and opensips, no matter what options are chosen at install time
RADIUS_SEQ_FILE=/var/run/opensips/opensips_radius.seq
if [ -d /var/run/opensips ]; then
chown ${USER}:${GROUP} /var/run/opensips
if [ ! -f $RADIUS_SEQ_FILE ]; then
touch $RADIUS_SEQ_FILE
fi
chown ${USER}:${GROUP} $RADIUS_SEQ_FILE
chmod 660 $RADIUS_SEQ_FILE
fi
}
test -f $DAEMON || exit 0
if [ "$RUN_OPENSIPS" != "yes" ]; then
echo "OpenSIPS is not configured. Edit /etc/default/${INSTANCE} first."
exit 0
fi
set -e
[ -z "$USER" ] && USER=${NAME}
[ -z "$GROUP" ] && GROUP=${NAME}
[ $S_MEMORY -le 0 ] && S_MEMORY=32
[ $P_MEMORY -le 0 ] && P_MEMORY=32
OPTIONS="-P $PIDFILE -m $S_MEMORY -M $P_MEMORY -u $USER -g $GROUP"
case "$1" in
start|debug)
echo -n "Starting $NAME: "
if is_running; then
echo "failed, already running"
exit 0
fi
generate_m4_config
check_opensips_config
create_radius_seqfile
[ "$1" != "debug" ] && check_fork
# dirs under /var/run can go away on reboots.
mkdir -p "$HOMEDIR"
chmod 775 "$HOMEDIR"
chown "$USER:$GROUP" "$HOMEDIR" >/dev/null 2>&1 || true
# kill all opensipsctl processes (they block fifo)
ps -C bash -o pid,command | grep opensipsctl | awk '{print $1}' | xargs -r kill -9 >/dev/null 2>&1
start-stop-daemon --start --quiet
--background --make-pidfile --pidfile "$PIDFILE"
--exec ${SAFE_DAEMON} -- ${INSTANCE} $OPTIONS
echo "ok"
;;
stop)
echo -n "Stopping $NAME: "
if ! is_running; then
echo "failed, not running"
exit 0
fi
start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
rm -f $PIDFILE
for pid in $(pidof ${NAME}); do
ps -p $pid -o command=|grep -q etc/${INSTANCE}/ && kill $pid >/dev/null 2>&1
done
# check whether OpenSIPs is running
# Wait not more than 20 seconds to stop process
# (needed sometimes when it saves coredump or debug logs)
STOP_CNT=0
while is_running; do
STOP_CNT=$[${STOP_CNT}+1]
if [ ${STOP_CNT} -ge 20 ]; then
for pid in $(pidof ${NAME}); do
ps -p $pid -o command=|grep -q etc/${INSTANCE}/ && kill -9 $pid >/dev/null 2>&1
done
fi
sleep 1
done
echo "ok"
;;
restart|force-reload)
generate_m4_config
check_opensips_config
$0 stop
$0 start
;;
status)
echo -n "Status of $NAME: "
if is_running; then
echo "is running"
exit 0
else
echo "is not running"
exit 3
fi
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload|debug|status}" >&2
exit 1
;;
esac
exit 0
#
# OpenSIPS startup options
#
# Set to yes to enable opensips, once configured properly.
RUN_OPENSIPS=yes
# User to run as
USER=opensips
# Group to run as
GROUP=opensips
# Amount of shared memory to allocate for the running OpenSIPS server (in Mb)
S_MEMORY=128
# Amount of pkg memory to allocate for the running OpenSIPS server (in Mb)
P_MEMORY=64
# Enable the server to leave a core file when it crashes.
DUMP_CORE=yes
# Daemon options
OPTIONS=""
# E-Mail for notifications
EMAIL="root"
# Maximum files
MAX_FILES=262144
To be continued…
Если сообществу интерсна тема sip-кластера в частности и opensips в целом, собираю предложения по написанию более подробных статей на эти темы.
P.S. Если Вы работаете с opensips, обратитесь ко мне в личную почту или на email [мой_логин]@гуглопочта.com, есть вакансия.
Автор: nikbyte