電話回線をJitsi Meet(個人用Web会議サーバー)につなぐ


ここ最近いじり倒しているJitsi Meet(個人で作るWeb会議サーバー)。Twilio経由で電話回線からも会議に参加できるようになったのでその検証記録。

構成としては、「PC(Webブラウザ)」>「Jitsi Meet」>「Jigasi」>「Aterisk」>「Twilio」>「電話」となる。

つまり、Webブラウザを使って電話回線上の携帯電話と通話できるシステムであると。Twilioのアカウントをトライアルから通常版にアップグレードすれば固定電話もいけるはず。

意外だったのが、前回SIPクライアントでJitsi Meetにつないだら音声にノイズ乗りまくりだったのが、Twilio→携帯電話でつなぐと大変クリアで音質満足だった点。Twilio(KDDI?)かNTT Docomoの交換機にノイズキャンセラーがあるんだろうか。

PCにSIPクライアントを導入する必要もないし、そもそもPCがNATネットワーク内にいても通話可能であるので、Webブラウザ電話としてそこそこ有用ではないだろうか。
地方で競争相手がいなければこれを構築してメンテするのでも飯食っていけるかも。

Jitsi Meet~Asteriskの構築について

前回記事参照。
https://qiita.com/rk05231977/items/d7360724806f10346089

Jitsi Meetはサーバーが直接パブリックIPを持ってなければいけないんじゃないかと思うので、IBM Cloudに構築する必要がある。今回AWS側(JigasiがNATの中に入る)は試していないが。

Twilioで電話番号を購入し、SIPトランクを構成する

以下の記事を参考に構築。
https://qiita.com/xecus/items/a5e677bb85372e502b37
公式マニュアルは以下。
https://jp.twilio.com/docs/sip-trunking

1.Twilioで電話番号を申し込む。
運転免許証の写真とかを送って先方が審査する必要があるのだが、申し込んでから1日ぐらいで審査完了する。

2.TwilioでElastic SIP Trunkingを構成する。
注意する点として、Jitsiから外部に向けた発信はしないからTerminationはいらないや、とするとOrigination側も動作しないので、しかもちょっと頑張れば何とかなりそうなという時間が掛かる動作しない加減に陥るため、Terminationも構成が必要。

(Authentication > IP Access Control Lists)
「IP ADDRESS RANGE」は、AsteriskサーバーのIPアドレスを設定する。
「ASSOCIATED SIP TRUNKS」はSip Trunkを作るときに設定する。

(Authentication > Credential Lists)
「CREDENTIAL USERNAME」とパスワードは、ここで設定したものを後にAsteriskに仕込む必要がある。

(Elastic SIP Trunking > General)
電話転送するだけならFeatureは全部無効で良い。


(Elastic SIP Trunking > Termination)
「TERMINATION SIP URI」は、ここで設定したものを後にAsteriskに仕込む必要がある。

(Elastic SIP Trunking > Origination)
「ORIGINATION URI」に、自前で構築したAsteriskサーバーのホスト名を入力する。今回だと「sip:pbx.example.com」。

(Elastic SIP Trunking > Numbers)
購入した電話番号を紐づける。

Asteriskを構成する

1.Twilioに接続するための設定を、Asteriskサーバーのpjsip.conf、extensions.confに追加する。なお、TwilioからやってくるIPアドレスは以下である。
https://www.twilio.com/docs/sip-trunking/ip-addresses#asia-pacific-tokyo-gateways

pjsip.confについては、前回記事からの差分として「[twilio]」以降を追加する。

/etc/asterisk/pjsip.conf
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0

[6001]
type=endpoint
disallow=all
allow=ulaw
auth=6001
aors=6001

[6001]
type=auth
auth_type=userpass
password=<パスワード>
username=6001

[6001]
type=aor
max_contacts=1

[6002]
type=endpoint
disallow=all
allow=ulaw
auth=6002
aors=6002

[6002]
type=auth
auth_type=userpass
password=<パスワード>
username=6002

[6002]
type=aor
max_contacts=4

[twilio]
type=endpoint
transport=transport-udp
disallow=all
allow=ulaw
outbound_auth=twilio
aors=twilio

[twilio]
type=auth
auth_type=userpass
password=<Twilio Credentialのパスワード>
username=<Twilio Credentialのユーザー名(+8150~)>

[twilio]
type=aor
contact=sip:<TwilioのSIP URI(8150~)>.pstn.twilio.com

[twilio]
type=identify
endpoint=twilio
match=54.65.63.192/30

extensions.confについては、前回記事からの差分として「exten => _0.」以降を追加する。
「Dial(PJSIP/6001,30)」の6001がJitsi Meet(Jigasi)を呼び出す部分。

/etc/asterisk/extensions.conf
[general]
static=yes
writeprotect=no
clearglobalvars=no

[globals]

[default]
exten => _600X,1,Dial(PJSIP/${EXTEN},30)
same => n,Hangup()

exten => _0.,1,Set(CALLERID(all)="Jitsi Meet" <+815035030326>)
same => n,Dial(PJSIP/+81${EXTEN:1}@twilio,30)
same => n,Hangup()

exten => _+8150<Twilioの電話番号>,1,Dial(PJSIP/6001,30)
same => n,Hangup()

2.Ateriskで設定をリロードする。

# asterisk -rvv
pbx*CLI> core reload

接続を試す

1.Jitsi Meetで「siptest」という会議室を開いて、携帯電話からTwilioで購入した電話番号に電話する。
Twilioトライアル版の場合、登録した番号の携帯電話からのみ可。
ちなみに「siptest」はJigasiの設定値で、/etc/jitsi/jigasi/sip-communicator.propertiesの以下で変更可。
org.jitsi.jigasi.DEFAULT_JVB_ROOM_NAME=siptest


掛かる。画面は小さいが、右下の「+8190~ 接続されました」に注目。090から始まる番号なのでこれは携帯電話。

2.いったん携帯電話を切り、今度はJitsi Meetの会議室から携帯につないでみる。



掛かる。
なお、会議室から外に掛ける場合は、会議室名はsiptestでなくても良い。
通話品質も良好。

(追記)そういえば、Asteriskを経由せずにJigasi->Twilioで繋げばいいんじゃね?と思い立ってやってみたが、簡単には動作せず。断念。

(追記2)サーバーがパブリックIPを直接持っているため、外からやってくるsshのログイン失敗やSIPのRegister失敗がうざい。
ufwで制限をかけようとするも、ちょっと動作が分からずSIPの接続を制限できなかったため、直接iptablesで制限することにした。

Jitsi Meetサーバー、Asteriskサーバーの両方で以下を実施。

(ufwを止めて、iptables-permanentをインストール)
# systemctl stop ufw
# systemctl disable ufw
# apt-get install iptables-persistent

iptablesのルールファイルは両サーバー以下で共通でいいだろう。
Ateriskサーバーは、Jitsi Meet、TwilioのSIPクライアントのみ受付けるようになる。

/etc/iptables/rules.v4
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -s localhost -j ACCEPT
-A INPUT -s <踏み台サーバーのIPアドレス> -j ACCEPT
-A INPUT -s <Jitsi MeetサーバーのIPアドレス> -j ACCEPT
-A INPUT -s <AsterikサーバーのIPアドレス> -j ACCEPT
-A INPUT -s 0.0.0.0/0 -d <Jitsi MeetサーバーのIPアドレス> -p tcp --dport 80 -j ACCEPT
-A INPUT -s 0.0.0.0/0 -d <Jitsi MeetサーバーのIPアドレス> -p tcp --dport 443 -j ACCEPT
-A INPUT -s 0.0.0.0/0 -d <Jitsi MeetサーバーのIPアドレス> -p tcp --dport 5349 -j ACCEPT
-A INPUT -s 0.0.0.0/0 -d <Jitsi MeetサーバーのIPアドレス> -p udp --dport 3478 -j ACCEPT
-A INPUT -s 0.0.0.0/0 -d <Jitsi MeetサーバーのIPアドレス> -p udp --dport 10000 -j ACCEPT
-A INPUT -s 54.65.63.192/30 -d <AsterikサーバーのIPアドレス> -j ACCEPT
COMMIT
# ip a
# iptables-restore < /etc/iptables/rules.v4

ip aコマンドを実行しているのと、ip6tableをいじらないのは念のため。つながらなくなったらIPv6でssh接続して直せばいい。IPv6側に攻撃というか、うざいアクセスが入ることはめったにない。