Project Clearwaterを触ってみる

# Project Clearwater
OPNFVなどNFVのコンテキストの中で仮想化ネットワークファンクション(VNF)として度々登場する **Project Clearwater**。

オープンソースのvIMS（visualized IP Multimedia Subsystem）としてよく登場していますが日本語ドキュメントも全くなく（少なくともわたしは見つけられない・・）、一体何者だろうという興味がここ半年ばかし。

そんななかで暇ができたので触ってみました。

## IMSとは
IP Multimedia Subsystemの略で、3GPPなどで標準化された、端的に言えば「IP電話など」を扱うための標準化アーキテクチャです。
きっと、この記事を読んでいらっしゃる方（Clearwaterに興味を持たれる方）などには説明は不要かと思いますが、一応触れておきます。

https://ja.wikipedia.org/wiki/IP%E3%83%9E%E3%83%AB%E3%83%81%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2%E3%82%B5%E3%83%96%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0

IMSの特徴はSIP（Session Initiation Protocol）を主に使用し、その他様々なプロトコルを補完的に扱い、システムとしての概念を築きあげているという点。

SIPの標準ではセッションの確立などのいわゆる**呼処理**の手順を決めているのに対して、システム全体でどうやって電話網等を構成すべきか、ということを定義しているという感じでしょうか。

IMSは携帯電話用のネットワーク内でも、VoLTEなどで使用されていますね。

## Open Source IMS
そんなIMSといえば通常であれば国内外の電気・通信メーカ、ソフトウェアベンダがプロプライエタリなソフトウェアとして通信キャリア向けに作っているもの。それがOSSで、しかもクラウドで動くというのがこの**Project Clearwater**のすごいところ。一体誰得なのか？

世の中にはオープンソースのVoIPアプリケーションも多々ありますが、このIMSのアーキテクチャに従っている、というのがオープンソースのIP-PBXである*Asterisk*などとは大きく違うところでないでしょうか。

## 触ってみる
やっぱりこの手のものは実際に触ってみるのに限る。ということで公式ドキュメントを見てワンコール（SIPp）が通るまでとりあえずやってみました。

http://clearwater.readthedocs.io/en/stable/All_in_one_OVF_Installation.html

### 環境
- Host
 - Cento OS 7
 - Qemu-kvm
 - CPU 2 core 4 th(Core i5)
 - mem 4GB

こちらの環境でKVM上にProject Clearwaterを入れたVMを立てて、前後をシミュレータ(SIPp)で挟む構成でテストします。

### 構築（All In Oneイメージの取得）
ovaファイルが提供されているのでそちらをまずダウンロード。

http://vm-images.cw-ngv.com/cw-aio.ova
※2017-10-29現在

ovaファイルはVirtualBoxやVM wareには良いですが、
KVM環境の場合はコンバートがいるので変換します。
KVMじゃない人はよみとばし

tarで展開
ovaの実態はtarで固めただけなので解凍。

```
$ tar xvf cw-aio.ova
-rw-r--r-- root/root     11771 2017-09-11 20:58 cw-aio.ovf
-rw------- root/root 1100163072 2017-09-11 20:58 cw-aio-disk1.vmdk
-rw-r--r-- root/root        127 2017-09-11 20:58 cw-aio.mf
```

vmdkからqcow2へ``qemu-img``を使って変換します
```
$ qemu-img convert -f vmdk -O qcow2 cw-aio-disk1.vmdk ./cw-aio-disk1.qcow2 
```

ディスクを移動

```bash
$ sudo mv cw-aio-disk1.qcow2 /var/lib/libvirt/images/.
```

kvm用のVM定義を作成

Guestの必要リソースはovaの場合下記の通りなので揃えておきます
- Guest
 - vCPU　1
 - メモリ1.6GB

以下にkvm用のXMLを貼っておきますが、あくまでも一例ということで。

```xml
<domain type='kvm'>
  <name>cw-aio</name>
  <uuid>12e91f11-05f7-4899-9600-1a925841e5d6</uuid>
  <memory unit='KiB'>1691648</memory>
  <currentMemory unit='KiB'>1691648</currentMemory>
  <vcpu placement='static'>1</vcpu>
  <os>
    <type arch='x86_64' machine='pc-i440fx-vivid'>hvm</type>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <cpu mode='custom' match='exact'>
    <model fallback='allow'>SandyBridge</model>
  </cpu>
  <clock offset='utc'>
    <timer name='rtc' tickpolicy='catchup'/>
    <timer name='pit' tickpolicy='delay'/>
    <timer name='hpet' present='no'/>
  </clock>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>restart</on_crash>
  <pm>
    <suspend-to-mem enabled='no'/>
    <suspend-to-disk enabled='no'/>
  </pm>
  <devices>
    <emulator>/usr/bin/kvm-spice</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/var/lib/libvirt/images/cw-aio-disk1.qcow2'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
    </disk>
    <controller type='usb' index='0' model='ich9-ehci1'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x7'/>
    </controller>
    <controller type='usb' index='0' model='ich9-uhci1'>
      <master startport='0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0' multifunction='on'/>
    </controller>
    <controller type='usb' index='0' model='ich9-uhci2'>
      <master startport='2'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x1'/>
    </controller>
    <controller type='usb' index='0' model='ich9-uhci3'>
      <master startport='4'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x2'/>
    </controller>
    <controller type='pci' index='0' model='pci-root'/>
    <controller type='virtio-serial' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
    </controller>
    <interface type='network'>
      <mac address='08:00:27:8f:e4:17'/>
      <source network='default'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
    <serial type='pty'>
      <target port='0'/>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <channel type='spicevmc'>
      <target type='virtio' name='com.redhat.spice.0'/>
      <address type='virtio-serial' controller='0' bus='0' port='1'/>
    </channel>
    <input type='mouse' bus='ps2'/>
    <input type='keyboard' bus='ps2'/>
    <graphics type='spice' autoport='yes'/>
    <sound model='ich6'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </sound>
    <video>
      <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </video>
    <redirdev bus='usb' type='spicevmc'>
    </redirdev>
    <redirdev bus='usb' type='spicevmc'>
    </redirdev>
    <redirdev bus='usb' type='spicevmc'>
    </redirdev>
    <redirdev bus='usb' type='spicevmc'>
    </redirdev>
    <memballoon model='virtio'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
    </memballoon>
  </devices>
</domain>
```

### VMの起動

```bash
virsh start cw-aio
```

GUIがあれば``virt-manager``でも。コンソールが見れますし。

・・・・なぜか起動しない？

```bash
sudo virsh net-dhcp-leases default
```

んーなぜかDHCP割り当てられてない。
とりあえず　reset。
```
sudo virsh reset cw-aio
```

```
 Expiry Time          MAC アドレス   Protocol  IP address                Hostname        Client ID or DUID
-------------------------------------------------------------------------------------------------------------------
 2017-10-24 22:49:34  08:00:27:8f:e4:17  ipv4      192.168.122.120/24         cw-aio          -
```

なんか起動しました。詳細不明。まあ起動したからいいや。

### VMにアクセス

ssh にて、 ``ubuntu``/``cw-aio``にてアクセス可能です。
普通に``sudo``もいけます。

## とりあえずワンコールを。
せっかく作ったのでワンコール入れたいと思います。

ワンコールを通すためにも、まず加入者のデータを入れなければ。

プロビジョニングがよくわからなかったけど、
ドキュメントを見ればちゃんと書いてました。

``ellis``というコンポーネントがHTTPでのプロビジョニング支援ツールを提供しており、
そちらを使えば**とりあえず**加入者を作ることができます。

ellisにアクセスします。

``http://[VMのIPアドレス]/``でアクセスすると

Please log in
Enter your email address and password

と書かれたウェブページにアクセスできます。
![Screenshot from 2017-10-29 20-02-45.png](https://qiita-image-store.s3.amazonaws.com/0/207353/ece5b9e8-b59d-dc9e-0bd8-974ff28a8166.png)

まずアカウントがないので *signup* を押して作ります。
適当なメールアドレス風のもので大丈夫でした。

![Screenshot from 2017-10-29 20-06-24.png](https://qiita-image-store.s3.amazonaws.com/0/207353/f7939de3-c6c8-d975-acab-4f7d3b8354c5.png)

Signup codeは下記ドキュメント中に書いています
http://clearwater.readthedocs.io/en/stable/All_in_one_OVF_Installation.html#running-and-using-the-image

## 加入者の追加
![Screenshot from 2017-10-29 20-09-12.png](https://qiita-image-store.s3.amazonaws.com/0/207353/a73f2f20-e4e5-2bd1-83f8-4757d09363ec.png)
signup/signin するとこのような加入者情報が表示されます。

ここに表示された

- Private Identity:
- Password:
- Associated Public Identities:

これらがSIPの認証に必要なので書き留めておきます。
パスワードは一度しか表示されませんのでご注意を・・・。
忘れたらリセットする他ないです。

*Add Private Identity*の*Create*をクリックすると新しい加入者を追加できますのでもう１加入者追加しておきましょう。

## ワンコール作成
実機でできればいいですが、VMで立ててしまったのでSIPpでワンコールを通したいと思います。

発着信双方でREGISTERをあげ、下記シナリオを流します。

クセモノなのが``example.com``というドメインです。
デフォルトでこれが固定（設定ファイル中に注記されているようです）なのですが、``example.com``がdnsで解決出来てしまうため、下手をするとインターネットに飛んでいってしまうので``/etc/hosts``に書くなり、適宜うまいこと対処しましょう・・・

### REGISTER
シナリオはググッて下記のシナリオを拝借

https://github.com/saghul/sipp-scenarios/blob/master/sipp_uac_register.xml

今回の端末に合わせて下記の要領でREGISTER用のデータを作ります

```text:reg.csv
SEQUENTIAL
# user, domain, authentication
6505550751;example.com;[authentication username=6505550751@example.com password=ErpbrSqnE]
```

### UAS
着信のために当然REGISTERが必要になります。
REGISTERがExpireしないように注意しながらUACの発呼を待つシナリオを起動します。

シナリオは先程と同じところを参考にさせていただきました。
https://github.com/saghul/sipp-scenarios/blob/master/sipp_uas_basic.xml

シナリオの例
CSCFからRecord-Routeが渡されてくるため、Record-Routeを付与してメッセージを返送するように変更してます。

```xml
--- uas-basic.orig.xml	2017-10-29 21:08:07.222390573 +0900
+++ uas-basic.xml	2017-10-29 20:38:32.083080746 +0900
@@ -23,7 +23,7 @@
   <!-- By adding rrs="true" (Record Route Sets), the route sets         -->
   <!-- are saved and used for following messages sent. Useful to test   -->
   <!-- against stateful SIP proxies/B2BUAs.                             -->
-  <recv request="INVITE" crlf="true">
+  <recv request="INVITE" crlf="true" rrs="true">
   </recv>
 
   <!-- The '[last_*]' keyword is replaced automatically by the          -->
@@ -45,6 +45,7 @@
       [last_To:];tag=[pid]SIPpTag01[call_number]
       [last_Call-ID:]
       [last_CSeq:]
+      [last_Record-Route:]
       Contact: <sip:[local_ip]:[local_port];transport=[transport]>
       Content-Length: 0
     ]]>
@@ -58,9 +59,11 @@
       [last_To:];tag=[pid]SIPpTag01[call_number]
       [last_Call-ID:]
       [last_CSeq:]
+      [last_Record-Route:]
       Contact: <sip:[local_ip]:[local_port];transport=[transport]>
       Content-Type: application/sdp
       Content-Length: [len]
+      
       v=0
       o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
       s=-
@@ -88,6 +91,7 @@
       [last_To:]
       [last_Call-ID:]
       [last_CSeq:]
+      [last_Record-Route:]
       Contact: <sip:[local_ip]:[local_port];transport=[transport]>
       Content-Length: 0
     ]]>
```


### UAC
REGISTERをあげない状態でINVITEを送っても403が帰ってきます。
着信の有無にかかわらずREGISTERを上げる必要があります。
またINVITEに対してもREGISTERが上がっている状態であれば特に認証不要でした。

シナリオは先程と同じくこちらを参考にさせていただきました。
https://github.com/saghul/sipp-scenarios/blob/master/sipp_uac_basic.xml

ドメイン名を固定したのとRouteSetが返されるのでRouteSetを付与するように変更してます。

シナリオの例

```xml
--- uac-basic.orig.xml	2017-10-29 20:51:27.965000000 +0900
+++ uac-basic.s.xml	2017-10-29 21:03:10.998000000 +0900
@@ -27,7 +27,7 @@
   -->
   <send retrans="500">
     <![CDATA[
-      INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
+      INVITE sip:[service]@example.com:[remote_port] SIP/2.0
       Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
       From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
       To: sut <sip:[service]@[remote_ip]:[remote_port]>
@@ -38,6 +38,7 @@
       Subject: Performance Test
       Content-Type: application/sdp
       Content-Length: [len]
+
       v=0
       o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
       s=-
@@ -47,7 +48,7 @@
       a=rtpmap:0 PCMU/8000
     ]]>
   </send>
-
+ 
   <recv response="100"
         optional="true">
   </recv>
@@ -61,19 +62,20 @@
   <!-- By adding rrs="true" (Record Route Sets), the route sets         -->
   <!-- are saved and used for following messages sent. Useful to test   -->
   <!-- against stateful SIP proxies/B2BUAs.                             -->
-  <recv response="200" rtd="true">
+  <recv response="200" rtd="true" rrs="true">
   </recv>
 
   <!-- Packet lost can be simulated in any send/recv message by         -->
   <!-- by adding the 'lost = "10"'. Value can be [1-100] percent.       -->
   <send>
     <![CDATA[
-      ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
+      ACK [next_url] SIP/2.0
       Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
       From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
       To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
       Call-ID: [call_id]
       CSeq: 1 ACK
+      [routes]
       Contact: sip:sipp@[local_ip]:[local_port]
       Max-Forwards: 70
       Subject: Performance Test
@@ -83,17 +85,18 @@
 
   <!-- This delay can be customized by the -d command-line option       -->
   <!-- or by adding a 'milliseconds = "value"' option here.             -->
-  <pause/>
+  <pause milliseconds = "5000"/>
 
   <!-- The 'crlf' option inserts a blank line in the statistics report. -->
   <send retrans="500">
     <![CDATA[
-      BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
+      BYE [next_url] SIP/2.0
       Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
       From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
       To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
       Call-ID: [call_id]
       CSeq: 2 BYE
+      [routes]
       Contact: sip:sipp@[local_ip]:[local_port]
       Max-Forwards: 70
       Subject: Performance Test
@@ -111,3 +114,5 @@
   <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
 
 </scenario>
+
+
```

## ワンコールの実施

やっとこさ準備がととのってワンコールが流せる状態になりましたので、
ここでワンコール実施。

アドレスや加入者情報が先ほどと違うのはご愛顧で。
わかりやすいように、sippのuas/uacは別のホストを設定しています。


![one-call.png](https://qiita-image-store.s3.amazonaws.com/0/207353/7e5762b4-1a94-31d3-90b2-6d5dfa1da398.png)


### 送出INVITE
```sip
INVITE sip:6505550252@example.com:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.122.252:5060;branch=z9hG4bK-2172-1-0
From: sipp <sip:sipp@192.168.122.252:5060>;tag=2172SIPpTag001
To: sut <sip:6505550252@192.168.122.59:5060>
Call-ID: 1-2172@192.168.122.252
CSeq: 1 INVITE
Contact: sip:sipp@192.168.122.252:5060
Max-Forwards: 70
Subject: Performance Test
Content-Type: application/sdp
Content-Length:   141

v=0
o=user1 53655765 2353687637 IN IP4 192.168.122.252
s=-
c=IN IP4 192.168.122.252
t=0 0
m=audio 6000 RTP/AVP 0
a=rtpmap:0 PCMU/8000
```

### 受信INVITE
```sip
INVITE sip:sipp@192.168.122.1:5060 SIP/2.0
Via: SIP/2.0/UDP cw-aio:5060;rport;branch=z9hG4bKPjtRGVhve-YKG1vwgk.lnqbxzCSzlkrIr4
Record-Route: <sip:TuLT7/IzJo@cw-aio:5060;transport=UDP;lr>
Record-Route: <sip:192.168.122.59:5058;transport=TCP;lr>
Via: SIP/2.0/TCP 192.168.122.59:5052;rport=36093;received=192.168.122.59;branch=z9hG4bKPj4X3QJJRCKXTydXpHEaXCgN5MQgoQ35i7
Record-Route: <sip:scscf.cw-aio:5054;transport=TCP;lr;billing-role=charge-term>
Record-Route: <sip:scscf.cw-aio:5054;transport=TCP;lr;billing-role=charge-orig>
Via: SIP/2.0/TCP 192.168.122.59:5058;rport=58783;received=192.168.122.59;branch=z9hG4bKPj0CakBe.7HH1NdY2fCg0ZV5JVhmnXV.NA
Record-Route: <sip:192.168.122.59:5058;transport=TCP;lr>
Record-Route: <sip:uhPcmg5Jru@cw-aio:5060;transport=UDP;lr>
Via: SIP/2.0/UDP 192.168.122.252:5060;received=192.168.122.252;branch=z9hG4bK-2172-1-0
From: "sipp" <sip:sipp@192.168.122.252>;tag=2172SIPpTag001
To: "sut" <sip:6505550252@192.168.122.59>
Call-ID: 1-2172@192.168.122.252
CSeq: 1 INVITE
Contact: <sip:sipp@192.168.122.252:5060>
Max-Forwards: 63
Subject: Performance Test
P-Asserted-Identity: <sip:6505550392@example.com>
Session-Expires: 600
P-Called-Party-ID: <sip:6505550252@example.com>
Content-Type: application/sdp
Content-Length:   141

v=0
o=user1 53655765 2353687637 IN IP4 192.168.122.252
s=-
c=IN IP4 192.168.122.252
t=0 0
m=audio 6000 RTP/AVP 0
a=rtpmap:0 PCMU/8000
```

# まとめ
all-in-one imageのおかげで非常に簡単にProject Clearwaterの構築ができました。
とっても簡単。設定いらずで試せるというのは素晴らしいですね。

各コンポーネントの中身はまだ追いかけきれていませんが、設定の変更や、各コンポーネントの処理の流れをもう少し追ってみたいと思います。

内部の処理をちょっとパケットキャプチャしてみましたが、Registerの処理などはSIPで受けて、裏ではHTTP/RESTライクなAPIで連携しているようです。

しかし、ここまできちんとしたものがオープンソースというのがすさまじいです。
でも一体誰が使うのか・・・？MVNOとか？商用導入の実績など気になります。