生asterisk x PJSIP で Brastel社 my050を悪戦苦闘しつつ収容した話

:::note warn
もし上手くいかなくても、Brastelへの問い合わせは絶対しないように。
サポート外や！
:::
## 沼へのエントリー
 アスタリスクという存在は、いつの間にか知っていた。なんか、実家に内線欲しいなと思った時に調べていたからだ。freePBXはGUIで楽と聞くが、込んだことすると結局.confファイルをいじることになると散見したので、生asteriskをUbuntu Server(22.04LTS)へ入れることにした。

### まず、お手元に○○をご用意いたします。
+ PC
1\. CPU(中古のRyzenは裏の配線を確かめよう！（1敗）)
2\. 新品の電源（中古は燃えると怖いから、やめようね！）
3\. マザボ
4\. SSD
5\. CPUクーラー
6\. 適当なグラボ
流用できれば実質無料。

+ 仮想マシン
1\. proxmoxとか無料でGUIもHAも使えるし、OSSベースで情報も比較的多くて、生KVMと違って同じ構成だからコミュニティのサポートも受けやすい。おすすめです
2\. 無償版ESXi? あいつはいい奴だったよ…（提供終了見込み）

+ 適当なクラウド
1\. AWSとかいいんじゃないですかね。24時間動かすとアレだけど。

いずれかをご用意ください。また、安定稼働には固定グローバルIPかDDNSが必要です。（あとやる気も）

## さぁ、PJSIPだ。
### はじめに
FreePBXはchan_sipとPJSIPが入り混じった素敵な構成のようですが、Asteriskにおいてchan_sipはObsoletedどころか削除対象でお先が長くない。今のLTSではコンパイルされないし、次のLTSじゃ存在抹消の憂き目に。
そこで、今回のPJSIPの出番になる。作例も情報も少ないわ分散してるわで、大変だったのでここに情報を残そうとしたわけですね…
インストール？makeの仕方は https://voip-info.jp https://docs.asterisk.org/ をご覧ください。
### 基本
PJSIPを利用する際にいじるファイルは、原則以下の3つになる。
+ /etc/asterisk/pjsip.conf
+ /etc/asterisk/rtp.conf
+ /etc/asterisk/extensions.conf

でも、pjsip.confに全部書くと”可読性・保守性×”なため、ファイルを分けることが出来る。
分けたファイルは```#include ファイル名```で読み込める。
私は
+ pjsip_phone.conf
+ pjsip_wizard.conf

の2つを作った。wizardは内線で、今回は関係ないので割愛させていただく…

### extensions.conf
モジュール名のDialplanの方が分かりやすいかも？要は、どこに電話を掛けるかの設定ファイルになる。
セクション分けして書いていける上、そのセクション丸ごと他のセクションから見られるので、```include => セクション名```を使って可読性を上げよう。

設定例
```ruby:extensions.conf
[general]
writeprotect=no
priorityjumping=no

[globals]
GROUP1=PJSIP/201&PJSIP/202&PJSIP/211

[default]
;内線
exten => _2XX,1,NoOp(internal call)
same => n,Dial(${PJSIP_DIAL_CONTACTS(${EXTEN},${EXTEN})},30,r)
same => n,Answer()
same => n,Ringing
same => n,Wait(1)
same => n,VoiceMail(${EXTEN})
same => n,Hangup()

;内線 留守電チェック
exten => 500,1,NoOp(CheckVoiceMailFrom${CALLERID(all)})
same => n,Ringing
same => n,Wait(1)
same => n,VoiceMailMain(${CALLERID(num)},s)
same => n,Hangup

include => outgoing

[outgoing]
;外線
exten => _0Z0XXXXXXXX,1,NoOp(11 call)
same => n,GotoIf($[${EXTEN:0:2} = 00]?international)
same => n,Dial(PJSIP/${EXTEN}@my050,60,D(w**w**))
same => n,Hangup()
same => n(international),SayDigits(${EXTEN})
same => n,Hangup()

exten => _0XXXXXXXXX,1,NoOp(10 call)
same => n,GotoIf($[${EXTEN:0:2} = 00]?international)
same => n,Dial(PJSIP/${EXTEN}@my050,60,D(w**w**))
same => n,Hangup()
same => n(international),SayDigits(${EXTEN})
same => n,Hangup()

[incoming]
exten => _s,1,NoOp(incoming call)
exten => 契約電話番号,1,Ringing
same => n,Dial(${GROUP1},60,r)
same => n,Hangup
```

+ Dial()のD(w\*\*w\*\*)って何よ?
→ ブラステルの残念アナウンス(あと、〇分　ご利用いただけます｜本日もブラステルをご利用いただきありがとうございます。そのままお待ちください。)をスキップするためのおまじない。
2セクションに分かれているのでw**を2回。（なんでこんなおかしなイントネーションなんだか…）参考：https://kshigeyama.dreampresenter.info/2021/04/04/5500/
ちなみに"r"オプションは音が聞こえない不具合のもとになるので入れないように。

+ 10桁番号・11桁番号と国際番号の排除は、参考にしたページのリンクを失念…
+ 外線の留守電は？ →WIP!
+ ナンバーディスプレイは？ →同上！

### pjsip.conf
PJSIPモジュールの設定ファイルになる。
設定例
```ruby:pjsip.conf
[global]
type = global
max_forwards = 50
user_agent =Asterisk

[transport-udp]
type=transport
protocol=udp

;default port:5060
bind=0.0.0.0:5060

;set PublicIP
external_media_address = DDNSのドメインorグローバルIP
external_signaling_address = DDNSのドメインorグローバルIP

;set PrivateIP(LAN)
local_net = ローカルのaddr/subnet ;1
local_net = ローカルのaddr/subnet ;2

[transport-my050]
type=transport
allow_reload=yes
protocol=udp
;symmetric_transport = yes ;大抵はいらない、というかおかしくなる
bind=0.0.0.0:5061

;set PublicIP
external_media_address = DDNSのドメインorグローバルIP
external_signaling_address = DDNSのドメインorグローバルIP

;set PrivateIP(LAN)
local_net = ローカルのaddr/subnet ;1
local_net = ローカルのaddr/subnet ;2

;他ファイルの読み込み
;#include pjsip_wizard.conf
#include pjsip_phones.conf

[acl]
type = acl
deny = 0.0.0.0/0.0.0.0
permit = ローカルのCIDR1
permit = ローカルのCIDR2
permit = softphone.spc.brastel.ne.jpのIPを逆引きすること
```

```ruby:pjsip_phones.conf
[my050]
type = registration
transport = transport-my050
outbound_auth = my050
server_uri = sip:softphone.spc.brastel.ne.jp
client_uri = sip:ユーザ名@softphone.spc.brastel.ne.jp
retry_interval = 60
contact_user = 契約電話番号
expiration = 1000

[my050]
type = auth
auth_type = userpass
username = ユーザ名
password = パスワード

[ユーザ名]
type = aor
contact = sip:softphone.spc.brastel.ne.jp
qualify_frequency = 30
max_contacts = 2
authenticate_qualify = no
;remove_existing = true
maximum_expiration = 300

[my050]
type = endpoint
context = incoming
transport = transport-my050
;100rel = no
disallow = all
allow = ulaw
allow = gsm

rtp_symmetric = yes
rewrite_contact = yes
direct_media = no
;disable_direct_media_on_nat = true
dtmf_mode = inband
send_pai = yes
inband_progress = yes
outbound_auth = my050
aors = ユーザ名

contact_user = ユーザ名
from_user = ユーザ名
from_domain = softphone.spc.brastel.ne.jp
language = ja

[my050]
type = identify
endpoint = my050
match = softphone.spc.brastel.ne.jp
```

詰まったところ
+ from_domain 自分のIPじゃなくて、Brastelのものを入れる必要がある
+ rewrite_contact = yes この構成では必須
+ aors なんかおかしい ⇒ ガチャガチャしてたら直った
+ send_pai = yes　肝かもしれない
+ dtmf_mode = inband いらないかも
+ 100rel もしかするといるかも。たぶんいらない

困ったときはmy050アプリとasteriskの通信をパケットキャプチャして比較しようね！
SIPの中身を見て、異なる部分を修正していけば繋がるようになる。

ちなみに外部ポート開放はしてないです。

たぶん後日追記されます。わからない点あったり、個人情報お漏らししてたら、コメント欄からそっと教えてほしい。