📞

【AI Shift Advent Calendar 2023】Kamailioでカスタムヘッダを制御する

2023/12/18に公開

こんにちは!
開発チームの鈴木(@sZma5a)です!
この記事はAI Shift Advent Calendar 2023の8日目になります。
今回はTwilio SIP Domainの前段にSIPプロキシサーバを配置し、カスタムヘッダを制御する方法について紹介します。

背景

国内で電話事業に参入することは難しく、CPaaSを利用するのが一般的です。有名なものとしてはTwilioがあり、弊社でも公衆回線とVoicebotを接続する際に利用しています。
TwilioではProgrammable Voiceという機能を提供しており、Twimlと呼ばれるXML形式の独自マークアップ言語を用いて、自動応対や音声ストリーミングなど様々な機能を実現しています。また、SIP Domainというサービスを通じてSIPを受け入れることができ、こちらも同様にProgrammable Voiceを利用することができます。これにより、様々な方法でVoicebotなどの独自システムへの統合が可能となります。
しかし、このProgrammable Voiceでは、特定のユースケース次第では対応しきれない部分があります。もちろん一般的なコールフローであれば十分な機能がありますが、例えばSIPによる他のシステムと連携の場合、カスタムヘッダによるデータのやりとりが求められるケースもあると思います。そこで、今回はTwilio SIP Domainの前段にプロキシを噛ませ、カスタムヘッダを柔軟に設定できるか検証を行いたいと思います。
(一部のシーケンスではカスタムヘッダを送ることは可能ですが、INVITEなどTwilio起点でないとカスタムヘッダを触ることができません。)

構成

今回はSIP Domainで受けた通話に対し、カスタムヘッダを用いて情報を送信する方法について検証します。
具体的には下記の図の2箇所にカスタムヘッダを付与し、それぞれのSIPサーバに送られたパケットからKamailioが意図した挙動をしているかを確認します。

Kamailioとは

今回プロキシとしてKamailioを使用しました。KamailioとはOSSのSIPサーバであり、同様にOSSのSIPサーバであるAsteriskと比較し、SIPでの通信に特化しているという特徴があります。そのため、キャリアレベルのスケーラビリティや、LB・プロキシとして利用実績もあることからこちらを選択しています。
公式サイトによると4GBのメモリを積んだマシンで300,000ユーザにサービス提供できるみたいです。

on systems with 4GB memory, Kamailio can serve a population over 300 000 online subscribers

また、既存のモジュールを追加することで機能拡張を行うことができ、PythonやJavascriptで独自のモジュールを作成し組み込むこともできます。

Kamailio上でのカスタムヘッダ追加

今回の検証では外部SIPサーバからTwilio SIP Domainに対する通信の間にKamailioでプロキシするため、それぞれ通信の方向は以下の通りとなります。

  • request: 外部SIPサーバ→Kamailio→Twilio SIP Domain
  • reply: Twilio SIP Domain→Kamailio→外部SIPサーバ

これをもとに、下記の設定では「X-Custom-Header」にrequest時には「request」、reply時には「reply」が送られるよう設定します。ちなみに、転送先のSIP URIについては$ruに指定することで設定できます。(sip:xxxx@yyyy.sip.twilio.com)

# モジュール
loadmodule "textops.so"

...

request_route {
        append_hf("X-Custom-Header: request\r\n");
	# 転送先のSIP URI(Twilio SIP Domain)
        $ru = "sip:xxxx@yyyy.sip.twilio.com";
        if (!t_relay()) {
                sl_reply_error();
                exit;
        }
}
onreply_route {
    append_hf("X-Custom-Header: reply\r\n");
}

上記の設定のもと、Wiresharkを用いて外部SIPサーバ・Twilio SIP Domainでどのようなパケットを受け取ったか見ていきたいと思います。

外部SIPサーバ→Twilio SIP Domain

先ほどの設定で追加した通りX-Custom-Headerでrequestが送られていることが確認できます。余談ですが、Twilio上で受けたSIP通信についてはpcapファイルが提供されており、デバッグ時に非常に重宝しています。

Twilioに送られたパケット

Twilio SIP Domain→外部SIPサーバ

こちらも同様にX-Custom-Headerでreplyが送られていることが確認できます。また、別途Twilioから通話を識別するために「X-Twilio-CallSID」が付与されていることも確認できます。

外部SIPサーバに送られたパケット

さらなるヘッダ加工

現状Programmable Voiceでは、受け取ったSIPリクエストに対しカスタムヘッダを付与できません。そのため、通話を一意に識別するには先程送られてきていた「X-Twilio-CallSID」を用いる必要があります。しかし、もう少し抽象化してIDを利用したいケースもあると思います。
そこで、この項では「X-Twilio-CallSID」を取得し、ハッシュ化して新しいカスタムヘッダとしてSIPメッセージに付与します。その後、不要となった元の「X-Twilio-CallSID」ヘッダの削除を行います。

# モジュール
loadmodule "textops.so"
# ハッシュ化するためのモジュール追加
loadmodule "crypto.so"

...

request_route {
        append_hf("X-Custom-Header: request\r\n");
	# 転送先のSIP URI(Twilio SIP Domain)
        $ru = "sip:xxxx@yyyy.sip.twilio.com";
        if (!t_relay()) {
                sl_reply_error();
                exit;
        }
}
onreply_route {
    append_hf("X-Custom-Header: reply\r\n");
    
    # ===========追記===========
    if(search("X-Twilio-CallSid:")) {
        $var(header_value) = $(hdr(X-Twilio-CallSid){s.unbracket});
    }
    # ハッシュ化
    $var(hash_value) = $(var(header_value){s.sha256});
    # ハッシュ化された値を新しいカスタムヘッダに設定
    append_hf("X-Hashed-Header: $var(hash_value)\r\n");
    # カスタムヘッダの削除
    remove_hf("X-Twilio-CallSid");
}

先程の設定に、新たにハッシュ化モジュールを加え、ハッシュ値の算出とヘッダの詰め替えを行なっています。この結果、下記で示す通り新たに「X-Hashed-Header」が付与されCallSidをハッシュ化した値が入っています。また、「X-Twilio-CallSid」をremove_hfで指定したことにより、実際に削除されている様子が見て取れると思います。

外部SIPサーバに送られたパケット

最後に

今回はKamailioを用いて、Twilio上では対応しきれないカスタムヘッダの変更について検証しました。これにより、RTPなどのコア部分はTwilioのマネージドサービスを使用しつつ、データの受け渡しをより柔軟に行うことができました。個人的な感想として、Kamailioの設定ファイルはAsteriskと比較し、コールフローを直感的に定義することができると感じました。今回紹介したもの以外に、外部APIを用いるためのモジュール等様々な拡張機能があるため、また機会があれば他の機能も検証していきたいと思います。

AI Shiftではエンジニアの採用に力を入れています!この分野に少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?(オンライン・19時以降の面談も可能です!)
面談フォームはこちら

明日のAdvent Calendarは、内定者バイトでAIチームに来ている長澤くんによるLLM関連の記事を予定しています。
こちらも是非よろしくお願いいたします!

AI Shift Tech Blog

Discussion