2014/8/25

opensips 簡介 1/2

opensips

opensips 在SIP 環境的定位是提供 registrar, proxy, redirect, location services。最重要的是,可高速處理 sip headers 並提供封包的 routing 功能。其他的部份,例如 NAT traversal, IMS, load balancing 等等功能則是由 3rd party modules 提供。

registrar 可管理某個 domain 下的 SIP UA,讓這些 UA 註冊並受該 server 管理。

proxy server的用途是,接收 UA 的 request,當受話端不在同一個 domain 時,就把 request 轉送到另一個 SIP proxy server。

redirect server 接收 request 之後,直接以 302 Moved Temporarily 回應給 UA。

opensips history

在 2004 年,德國 FhG Fokus research institute 以 GPL 發布了 SER,其後 OpenSERs fork 該專案,開發 SER 的單位輾轉成立了 Voice System 公司,繼續維護 OpenSER。

OpenSERs 在 2008 年又經歷了一次分裂,成了兩個專案:Kamailio 與 OpenSIPs。opensips 核心非常小,但以上百個 modules 來延伸其功能,目前可以用來提供 SIP firewals, session border controllers(SBCs), load balancers 等等功能。

以下列出重要的 module 與功能

  1. dispatcher, path: 這兩個模組用來提供 load balancing 的功能
  2. mediaproxy, rtpproxy, nathelper: 提供 NAT traversal 的功能
  3. presence: 處理 presense server
  4. IMC, XMPP: instant messaging

安裝 opensip 的程序,可參閱這篇OpenSIPS and Control Panel Install Guide,雖然版本有點差異,安裝過程可套用在目前的版本 opensips 1.11.1, opensips-cp 5.0 上。

opensips.cfg

該設定檔可分成三大區塊: Global Configuration, Modules, Routing Logic。

# Global Configurations
debug=3
log_stderror=no
log_facility=LOG_LOCAL0

fork=yes
children=4

# Modules Section
mpath="/usr/lib64/opensips/modules/"

### Module Loading: 用 loadmodule 載入 modules
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "tm.so"

### Module Specific Parameters
modparam("tm", "fr_timer", 5)
modparam("tm", "fr_inv_timer", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)

# Routing Logic
### Main routing block: SIP request 處理的起點,每個 request 都會經過這裡

### Secondary routing blocks: 可定義 route(),用起來類似 subroutines

### Reply routing blocks: 處理 reply messages, 包含 provisional, successfule final replies, negative final replies,通常是 200 OK

### Failure routes blocks: 處理錯誤狀況, 例如 busy or timeout

### Branch route blocks: 在把 request forward 出去之前,每一條 branch 都可執行一段 logic 

### Local routing blocks: 當 opensips 內部產生 request (通常只作為 UAS),就使用 Transaction Module(TM) 

### Error routing block: 當 sip request parsing error 發生時,就執行這個

Sessions, dialogs, and transactions

在處理 opensips processing 之前,我們要先知道這三個名詞的意思

  1. SIP transaction: A SIP request,包含了 resends 與 direct responses,也就是 REGISTER 與 200 OK

  2. SIP dialog: 兩個 SIP entities 在某個時間點,存在的相互關係,換句話說,就是在兩個 UACs 之間,從 INVITE 到 BYE 之間,建立的一個對話

  3. SIP Session: 在兩個 SIP entities 之間傳送的 media flow,可能是 audio/video/text

SIP proxy: stateless or stateful

stateless 的作法,proxy server 會在 forward 訊息之後,把所有 message 的 internal information 丟棄。單純地只會把資料轉送到 request 裡面提供的下一個節點。

如果需要提供 billing, call forwarding, voicemail 功能,就需要使用 stateful mode,每個 transaction 都需要存在記憶體中,並處理 failure, response, retransmissions 等等狀況。這是 Transaction Module(TM) module 提供的功能。

要注意,stateful 的狀態維護是針對 transaction 處理而不是 dialog,因此從 INVITE 到 200 OK response 的處理是 stateful,而不是從 INVITE 到 BYE request,這個是 dialog。

stateful operation 的處理過程



Script and Routing Basics

一般安裝好 opensips 之後,沒有調整什麼特別的設定的時候,設定檔會放在 /usr/etc/opensips 這個目錄中,我們可以自己做個 link,改用 /etc/opensips 這個目錄。

根據上面 opensips.cfg 那個段落的內容,這個設定檔裡面可分為 (1) Global Parameters (2) Load Modules (3) Module Parameters (4) Routing Script 這四個部份。

將 opensips 設定完成後,最基本可以達成以下任務。

  1. 建立一個 SIP Server
  2. 可讓內部網路的 UACs 連接
  3. 在不同 UACs 之間互相撥打電話
  4. 話機不需要驗證,不使用資料庫
  5. 不支援 PSTN,只能在話機之間通話

Global Parameters

Listen Interfaces

不設定 listen 時,系統將會自動綁定所有的網路界面。

port 參數只是針對 SIP Server 設定 UDP service port

listen=udp:192.168.152.148:5060
listen=tcp:192.168.152.148:5061
listen=tls:192.168.152.148:5062
port=5060
Logging

在正式環境,就設定 debug=3,在 debug 環境,就設定 debug=9。

可以用這個 MI command 調整 log level

opensipsctl fifo debug 1

在 opensips.cfg 裡面的 log 相關設定範例如下

debug=3
log_stderror=no
log_facility=LOG_LOCAL0

Number of processes

opensips process 可運作在 foreground 或background,可設定 fork=yes,指定運作在 background。如果設定運作在 foreground,opensips就無法 listen 多個網路界面,tcp與tls 也會自動 disabled。

children 是設定每一個網路界面有幾個 processes 提供服務。

fork=yes
children=4
tcp_children=6
disable_tcp=no
disable_tls=no
Daemon options
gid/group=sip # unix group
uid/user=sip # unix user
wdir="/" # working directory
chroot="/usr/local/opensips-1.6"
SIP identity

設定 SIP request/response 裡面的標準參數。

server_header="Server: My openSIpS 
#default is "openSIpS (<version> (<arch>/<os>))"
server_signature = yes
user_agent_header="User-Agent: My openSIpS
其他

alias 很重要,這是設定 SIP server 支援的 domain name。

alias="mydomain.sip" # to set alias hostnames for the server
auto_aliases=no # discover aliases via reversed DNS
disable_dns_failover = yes
sip_warning=yes #add a debugging header in replies
global parameters 區塊
####### Global parameters #########
debug=3      # set the debug leve to 3
log_stderror=no    # log to syslog
log_facility=LoG_LoCAL0  # Log to facility LoG_LoCAL0
fork=yes      # Run as a daemon
children=4     # open 4 child process for 
each UDpaddress

/* uncomment the next line to disable TCp(default on) */
#disable_tcp=yes

/* 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

/* uncomment the next line to disable the auto discovery of local aliases based on revers DNS on Ips (default on) */
#auto_aliases=no

/* uncomment the following lines to enable TLS support (default off) */
#disable_tls = no
#listen = tls:your_Ip:5061
#tls_verify_server = 1
#tls_verify_client = 1
#tls_require_client_certificate = 0
#tls_method = TLSv1
#tls_certificate = "//etc/opensips/tls/user/user-cert.pem"
#tls_private_key = "//etc/opensips/tls/user/user-privkey.pem"
#tls_ca_list = "//etc/opensips/tls/user/user-calist.pem"
port=5060 # Run on port 5060

/* uncomment and configure the following line if you want opensips to bind on a specific interface/port/proto (default bind on all  available) */
#listen=udp:192.168.1.2:5060

Modules

指定 modules 的路徑

#set module path
mpath="/usr/lib64/opensips/modules/"

設定 FIFO file 的路徑,用來處理與暫存外部指令。

#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)

Scripting Basics

opensips 使用類似 C 語言的 scripting language,這可以用來 routing SIP requests 與處理 SIP replies。

除了 core functions/values 之外,其他 functions 都是由 modules 提供。

Core functions
  1. forward();
    Route the request "stateless" (based on R-URI)
  2. drop();
    停止執行 script
  3. exit();
    立即終止 script processing

其他 core functions:
seturi()
setflag()
isflagset()
strip()
prefix()
rewritehostport()

Core values

script 裡面預先定義了一些 values 可以使用

  1. INET/INET6
    IPv4 or IPv6
  2. TCP/TLS/UDP
    protocol
  3. myself
    a reference to the list of local IP addresses, hostnames, and aliases
Core keywords

可用來識別 SIP message裡面的特殊欄位的值

  1. af
    Address family(INET/INET6)
  2. proto
    Protocol(TCP/TLS/UDP)
  3. dst_ip
    在 SIP message 中收到的 local interface 的 IP
  4. method
    SIP method of this message
  5. Status
    用在 onreply_route 時,這個變數會參考到 reply 的 status code
  6. retcode
    上一個 function 處理的結果
  7. uri
    request URI
  8. from_uri
    FROM header 裡面的 URI
  9. to_uri
    TO header 裡面的 URI

使用範例

if(af==INET6) {
log("Message received over Ipv6 link\n");
};
if(is_method("INVITE") && from_uri=~".*@opensips.org")
{
log("the caller is from opensips.org\n");
};
Pseudo-variables

就是 system variables,例如 SIP Messages 可取得 headers, R-URI, source IP。從 opensips 可取得 time, process ID。

Pseudo-variables 是以 $ 開頭,如果 script 裡面要使用該符號,就要加上 esacape $$,完整的 Pseudo-variables 可在這個網頁取得。

Script variables

可在 config script 裡面使用的變數,這些變數只存在於 script 的執行期,執行結束後,就會被刪除。該變數可儲存數字或字串。

$var(name)

範例

$var(b)=1;
$var(b)="1";
$var(b)="$fu"+"$tu";
$var(b)=1+2;

可使用以下的 operations

+
-
/
*
%: modulo division
|: Bitwise OR
&: Bitwise AND
^: Bitwise XOR
~: Bitwise NOT

也可使用下列的字串處理方法

{s.len}
{s.int}
{s.substr,offset,length}
{s.select,index,separator}
{uri.user}
{uri.host}
{uri.params}
{param.value,name} - returns the value of parameter "name"

範例

"a=1;b=2;c=3"{param.value,c} = "3"
Attribute-Value Pair (AVP)

這個變數是在 statefule mode,附屬在 SIP message 的變數,所以 AVP 是 transaction-persistent variables。AVP 會在 transaction 開始時被配置,而在 transaction 結束時被釋放。

AVP 的格式:
$avp(id[N])

id
(1) si:name
    AVP identifier name, s 與 i 是字串或數字
(2) name
    AVP alias name, 字串或數字

範例:
$avp(i:700)
$avp(s:blacklist)

AVPs 相關的函數

  1. avp_db_load: 由 DB 將 AVPs 載入至記憶體
  2. avp_db_store: 將 AVPs 存到 DB
  3. avp_db_delete
  4. avp_db_query: 進行 DB 查詢,並將結果存至 AVP
  5. avp_delete: 刪除記憶體中的 AVP
  6. avp_pushto: 將 AVP 放入 SIP message 中
  7. avp_check: 用 operator(equal, greater than, and a value) 取得值,例如 avp_check("i:500", "lt/i:501");
  8. avp_copy: 複製 avp
  9. avp_printf: 格式化 AVP
  10. avp_subst: 在 AVP 內尋找並取代某個值
  11. avp_op: 對 AVPs 做 math operations
  12. is_avp_set: check if this AVP's name is set
  13. avp_print: 列印記憶體中的 AVP

從 DB 中取得 user_preference table 變成 AVPs。

範例:在 call forward 時,可取得該 user 的 user_preference 資料。

user_preference table structure:

  1. id: auto-increment field
  2. uuid: unique user id
  3. username: username
  4. domain: domain
  5. attribute: AVP name
  6. type: 0–Avp str|Val Str,1–Avp str|Val Int,2–Avp int|Val Str,3-Avp int|Val int
  7. value: AVP value
  8. last modified: 上次修改的時間

Flags

用來 trigger some processes 例如 accouting, dialog control, NAT handling。有三種 flags: message, script, branch flags。

Type Persistence Function Purpose
Message flag transaction setflag(flag_idx) 在 transaction level 啟動 some functions
Branch flag Branch setbflag(flag_idx) 在 branch level 啟動 some functions
Script flag Top-level Routing setsflag(flag_idx) 儲存其他 flags

pseudo-variables: $mf (message flags), $bf (branch flags), $sf (script flags)

module GFLAGS

只能用在 external flags,這是用在 MI commands,可在 MI interface 的 external program 中,使用 set_gflag(), is_gflag(), reset_gflag() 這些 functions。

Statements

if-else
if ( t_check_trans() ) {
    t_relay();
    exit;
} else {
    exit;
}
Switch
switch($retcode)
{
    case -1:
        log("process INVITE requests here\n");
        break;
    case 1:
        log("process REGISTER requests here\n");
        break;
    case 2:
    case 3:
        log("process SUBSCRIBE and NoTIFY requests here\n");
        break;
    default:
        log("process other requests here\n");
}
Subroutes
route[1]{
    if(is_method("INVITE")) 
    {
        return(-1);
    };
    if(is_method("REGISTER"))
        return(1);
    }
    if(is_method("SUBSCRIBE"))
        return(2);
    }
    if(is_method("NoTIFY"))
        return(3);
    }
    return(-2);
}