Использование m4 для конфигурирования opensips

в 12:32, , рубрики: ip-телефония, opensips, системное администрирование, метки:

Сегодня мы поговорим о том, зачем использовать макропроцессор m4 для конфигурирования opensips и рассмотрим startup-скрипты, которые мы используем в production-окружении.

Для тех, кто не знает, что такое m4, приведу выдержку из википедии:

Макропроцессор m4, разработанный в 1977 году программистами Брайаном Керниганом и Денисом Ритчи, предназначен для макрогенерации на предварительном проходе в различных языках. Макрогенерация означает копирование входного символьного потока в выходной с подстановкой макросов по мере их появления. Макросы могут быть встроенными или определенными пользователями, и принимать произвольное число аргументов. Имеется множество встроенных функций для включения файлов, запуска внешних команд, выполнения целочисленной арифметики, манипуляции строками. Название «m4» раскрывается как «macro», то есть «m» + ещё 4 буквы.

Макропроцессор m4 применяется во многих различных областях.
В нашем случае он используется для гибкого генерирования, повышения удобства чтения и унификации конфигурационных файлов opensips.

Рассмотрим сборку конфигурационного файла ноды sip-кластера из предыдущей статьи.
Конфигурация состоит из 5 файлов:
— Константы конкретного узла:

opensips.m4

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')

— Общие константы для всех узлов:

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:

opensips_global.m4

####### 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

— Инициализация и параметры модулей:

opensips_modules.m4

####### 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)

— Секция маршрутизации:

opensips_routes.m4

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:

/usr/sbin/safe_opensips

#!/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
/etc/init.d/opensips

#!/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}
/etc/sysconfig/opensips

#
# 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:

/usr/sbin/safe_opensips

#!/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
/etc/init.d/opensips

#!/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
/etc/default/opensips

#
# 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js