SIP のレスポンスを Lua Script を利用して変換してみた


# はじめに
こんにちは :smiley: 
およそ1年振りになってしまいましたが、元音声・ビデオ系の SIP エンジニアが、今回は SIPの レスポンス・コードを Lua Script を利用して変換してみたお話しです :sunny: 

# 今回の背景
現在、日本のキャリアさんでは多くのIP公衆交換電話網サービス（IP Public Switched Telephone Network : 以下、IP-PSTN) を提供しています。
また、多くの企業は自社のIP電話網を公衆電話網に接続する際に、これらのIP-PSTN を利用する機会が増えています。
この IP-PSTN と 企業内の IP電話網を接続して利用する際には、ユーザ利便性に影響がないように相互接続性を気をつける必要があります。
（私が在籍しているシスコを含め多くの IP電話システムを提供しているメーカーやシステムインテグレーターさんが接続ガイドを提供しているかと思います。）

今回は、この相互接続でユーザ利便性にちょっとした不便があったところを Lua Script を使って、SIPレスポンスコードを変換することによって、
簡易的に運用上でその不便をカバー出来るようにした例になっています。 

# 今回の問題点
キャリアさんが提供するIP-PSTNサービスでは、コールが確立される前にユーザ側からメディア（音声等）を流すことは出来ません。　
これは、コールが確立して課金が始まるまではユーザ側からメディアを出せないようにしているという事になります。
通常は、このようなパターンにはあまりならないのですが、企業内のIP-PBXを使ったIP電話網で付加機能を使うと、このようなコールフローになる場合もあります。
具体的には、IP-PSTN から入ってきたコールが "全転送" を設定している電話機へ着信した場合に、その転送先の携帯電話が圏外や電源オフの時に、
圏外アナウンス(トーキー)が流れる場合があるのですが、このアナウンスが発信者に送信される時がコールが確立される前になってしまいます。
このパターンの場合は、発信者には、なにも音が聞こえない状態になってしまいます。（ちゃんと発信しているのかさえ分からない。）
( 転送先の携帯電話が留守番電話サービス等の付加サービスを使っている場合はコールが確立される場合もあるので、その場合は問題にならない場合もある )

このアナウンスのメディア情報は、 183 Session Progress に付いてくる SDP に記載されています。
キャリア側では、このユーザ側からくる 183 w/SDP を禁止にしている場合が多くなっています。（ユーザ側からのアーリーメディアを禁止。）
SDP = Session Descrption Protocol (セッション記述プロトコル/RFC4566)

コールを図にすると以下のようになります。
![Call-flow1.png](https://qiita-image-store.s3.amazonaws.com/0/199936/280ee268-57b0-93c0-9e87-914f523c0ff7.png)
＜図1＞


# 今回の解決の例
今回の構成としては、 IP-PBX として Cisco Unified Communications Manager (Cisco UCM), キャリアの接続ゲートウェイとしては、
Cisco Unified Border Element (CUBE)を利用した企業内 IP電話網の構成になります。
Cisco Unified Communications Manager には SIP Normalization (SIP正規化)機能というものがあり、この機能は Lua Script を
読み込んで送受信する SIP信号の編集・変換が行えます。
今回はせめて無音（なにも聞こえない）は防いで、リングバックトーン（RBT: プルルルル音) を発信者に聞かせる方法の Script の例になります。

詳細の設定方法は以下のドキュメントを参照して下さい。（ 今回の記事の内容は参考例で " やってみた系 "です。)
- [Developer Guide for SIP Transparency and Normalization](https://www.cisco.com/c/en/us/td/docs/voice_ip_comm/cucm/sip_tn/9_1_1/sip_t_n.html)
- [開発者用 SIP 透過性および正規化ガイド] (https://www.cisco.com/c/ja_jp/td/docs/vauc/iptelep/ucmgr-callmgr-/prg/003/sip-t-n.html)



## スクリプトのサンプル
   * 単純に 183/SDP を 180 Ringing へ変換。

```
M={}

function M.outbound_183_INVITE(msg)
  msg:setResponseCode (180, "Ringing")
  msg:removeUnreliableSdp() 
  end

return M
```
上記のスクリプトを Cisco UCM上の SIP Normalization Script で設定。
Cisco UCM 設定: Device - Device Settings – SIP Normalization script
<img width="762" alt="Script-config.png" src="https://qiita-image-store.s3.amazonaws.com/0/199936/971880d8-81ba-0511-ee03-95bcdb82c9a7.png">
＜図2＞

上記スクリプト設定を Cisco UCM 上の SIPトランク（全転送で利用するトランク）へ適用。
Cisco UCM 設定: Device - Trunk
<img width="737" alt="Script-config2.png" src="https://qiita-image-store.s3.amazonaws.com/0/199936/22807f22-9fdf-ee44-d27b-86116e524bed.png">
＜図3＞

上記設定により、Cisco UCMから発信者側のトランクへ送信される 183 Session Progress/SDP を 180 Ringing (SDPなし)に変換することが出来ました。

![Call-flow2.png](https://qiita-image-store.s3.amazonaws.com/0/199936/0c805f7a-8601-63a4-101b-1038af291798.png)
＜図4＞

実際に内部のSIPメッセージのトレースを見ると以下のように変換されています。
＜変換を適用前する前のSIPメッセージ＞

```
00377371.001 |14:16:50.217 |AppInfo  |//SIP/SIPNormalization/trace_sip_message:
Before outbound SIP Normalization msg is:[19800,INT]
SIP/2.0 183 Session Progress
Via: SIP/2.0/UDP 10.137.222.211:5060;branch=z9hG4bK4A82637
From: <sip:0368645011@192.168.10.1>;tag=23C76E78-B4E
To: <sip:050XXXXYYYY@10.137.222.1>;tag=4658~745fabfd-f80a-4680-9144-d2c5d323f270-21761635
Date: Thu, 22 Aug 2013 05:16:49 GMT
Call-ID: E18C4B0B-A2011E3-82F0BF2E-68AAF41D@abcdef.jp
CSeq: 101 INVITE
Allow: INVITE, OPTIONS, INFO, BYE, CANCEL, ACK, PRACK, UPDATE, REFER, SUBSCRIBE, NOTIFY
Allow-Events: presence
Supported: X-cisco-srtp-fallback
Supported: Geolocation
P-Asserted-Identity: <sip:090XXXXYYYY@10.137.222.1>
Remote-Party-ID: <sip:090XXXXYYYY@10.137.222.1>;party=called;screen=yes;privacy=off
Contact: <sip:050XXXXYYYY@10.137.222.1:5060>
Content-Type: application/sdp
Content-Length: 237

v=0
o=CiscoSystemsCCM-SIP 4658 1 IN IP4 10.137.222.1
s=SIP Call
c=IN IP4 10.137.222.211
b=TIAS:64000
b=AS:64
t=0 0
m=audio 16998 RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=ptime:20
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
```
＜変換を適用した後のSIPメッセージ>

```
00377372.001 |14:16:50.217 |AppInfo  |//SIP/SIPUdp/wait_SdlSPISignal: 
Outgoing SIP UDP message to 10.137.222.211:[5060]:[19800,NET]
SIP/2.0 180 Ringing
Via: SIP/2.0/UDP 10.137.222.211:5060;branch=z9hG4bK4A82637
From: <sip:0368645011@192.168.10.1>;tag=23C76E78-B4E
To: <sip:050XXXXYYYY@10.137.222.1>;tag=4658~745fabfd-f80a-4680-9144-d2c5d323f270-21761635
Date: Thu, 22 Aug 2013 05:16:49 GMT
Call-ID: E18C4B0B-A2011E3-82F0BF2E-68AAF41D@abcdef.jp
CSeq: 101 INVITE
Allow: INVITE, OPTIONS, INFO, BYE, CANCEL, ACK, PRACK, UPDATE, REFER, SUBSCRIBE, NOTIFY
Allow-Events: presence
Supported: X-cisco-srtp-fallback
Supported: Geolocation
P-Asserted-Identity: <sip:090XXXXYYYY@10.137.222.1>
Remote-Party-ID: <sip:090XXXXYYYY@10.137.222.1>;party=called;screen=yes;privacy=off
Contact: <sip:050XXXXYYYY@10.137.222.1:5060>
Content-Length: 0
```

これにより、発信者は相手側の携帯が圏外・電源断の場合も RBTまでは聴取が可能になります。
しかし、以前として問題として残る点は、発信側には相手側が圏外・電源断の状態にも関わらず、プルルルル音しか聞こえないので、実際に自分が発信をしているのは分かるのですが、相手が圏外・電源断と判断が出来ないという問題点があります。　
（ 電話鳴ってるけど相手が出ないので諦めて切るような形になります。）
これを更に改善するためには、一旦企業内でコールを終端させるなどの対応が必要になります。　

# 応用編
今回の例では　単純に 183/SDP を180 Ringing に変換するスクリプトの参考例を提示しましたが、実際 には、転送先の携帯キャリアさんによって呼び出しのシーケンスが違ったりしている場合があったりします。　これらのいろんな呼び出しのフローにも対応するように、以下のようなスクリプトの例もあります。

基本的には 18X/SDP(183,180含み) のメッセージは、180 Ringing に変換して SDPがあれば削除する。
この削除したSDPの内容を一度ストアして、次に続くキャリア側への200 OKメッセージにSDPがなければ、このSDP内容を付加するをするというスクリプトの例です。

```
M={}

function readSdp(msg,key)
    local sdp = msg:getSdp()
    if sdp
    then
        local context = msg:getContext()
        context[key] = sdp
        msg:setResponseCode (180, "Ringing")
        msg:removeContentBody("application/sdp")
    end
end

function writeSdp(msg,key)
    local context = msg:getContext()
    local sdp = msg:getSdp()
    if not sdp
        then
        local sdp = context[key]
        if sdp
            then
            msg:setSdp(sdp)
        end
    end
end

function readSDPOutbound(msg)
    readSdp(msg,"OUTBOUND_SDP")
end

function writeSDPOutbound(msg)
    writeSdp(msg,"OUTBOUND_SDP")
end

M.outbound_18X_INVITE = readSDPOutbound
M.outbound_200_INVITE = writeSDPOutbound

return M
```

# さいごに
最後まで読んで頂いて有難うございました :smile: 　
今回は、キャリアとの相互接続でのSIP変換の例の話でしたが、キャリアサービス以外の他のシステムとの相互接続にも使えるとは機能と思います。
スクリプトを書くのに抵抗がないとすんなり設定も出来るのではないかと思います。

注意点としては、これらのスクリプトを利用する際は、システム間の実装の相互接続をよく理解して検討・設定するのも必要ですが、それは標準規格(RFC等)とも合致しているかも、きちんと確認したうえで設定・実装するのが重要になります。

次は、ルータの SIP ケードウェイ（最近流行りのキーワード(笑) 〇〇ゲートウェイ）と ルータのプログラマビリティ機能の連携ネタで出来るところを書いてみようかなと思ってますが、まだ未定です。 m_O_m