NTT光ネクストのひかり電話へAsteriskを直接接続する

概要

この投稿では、フレッツ光ネクスト(NGN)で提供されているひかり電話へ、Asteriskを直接接続(直収)する方法を紹介する。
Asteriskをひかり電話へ直接接続するための情報は、従来から存在はしたものの非常に少なく断片的で、また古くて参考にしにくい状況がある。
更には最近のAsteriskの方向として、sipモジュールからpjsipモジュールを使うことになっており、この点でも従来からの情報は参考に出来ない。
以上のことから、ONU直下のAsteriskをpjsipを使って接続する方法を通しで紹介する。

ポイント/狙い

  • 2021年時点で使える具体的な手順や設定内容を通しで紹介する。
  • それに至る試行錯誤も合わせて紹介して、今後の仕様変更に対応できる手がかりを残したい。
  • お世話になった5ch.netや公式コミュニティに感謝する。

作戦

  • まずフレッツ網から、ひかり電話に必要な各種情報をDHCPで取得する。
  • 次にAsteriskをソースからインストールする。
  • 最後にDHCPで取得した情報を元に、pjsip.confを設定してレジスト(&発着信)する。

前提条件

構成

            NTT/NGN
               |
             +---+
             |ONU|
             +---+
               |
             +---+
       +-----|HUB|--------+
       |     +---+        |
       |                  |
  (Public IP)        (Public IP)
   +------+        +-------------+
   |      |        |    (eth0)   |
   |Router|        | Raspberry Pi|
   |      |        |    (eth1)   |
   +------+        +-------------+
 (192.168.0.x)      (192.168.0.x)
       |                  |
       |     +---+        |
       +-----|HUB|--------+
             +---+
              | |
       +------+ +---------+
       |                  |
     +---+           +---------+
     |PC |           |SIP Phone|
     +---+           +---------+

  • ルータとRaspberry Piそれぞれで、IPv4 PPPoE接続している。特にRaspberry Piは、オンボードのNIC(eth0)がONUへ直接接続されている。
    なお投稿の趣旨と直接関係ないが、Raspberry Piをルータ直下に接続しなかったのは、面倒なNAT越えを避けるためである。
  • Raspberry PiのLAN側接続は、USBのNIC(eth1)を追加して実現している。
    なおRaspberry Piのeth0~eth1間は、ルーティング/フォワーディングしていない。
  • Raspberry Piでは、Raspbian 10(buster)が動いている。

おことわり

  • 上記の構成や以後の設定がベスト/正しいとは思っていません。
    材料を提供することで、某wikiや5chで議論が出来ればと思っています。
  • 法律や約款は考慮に入れていません。(この場では勘弁してください....)

手順

フレッツ網から各種情報をDHCPで取得する

まずひかり電話に関する諸情報、例えばSIPサーバのIPアドレスや電話番号などは、フレッツ網からDHCPオプション(RFC3361)で取得する。昔はPPPoE接続&POSTして情報を得ていたようだが、今は不要/不可?である。おそらくBフレッツとか光プレミアム時代には必要だったのかもしれない。

DHCPオプションについては、このような試行と判断により、dhclient(ISC DHCP)を使った。
そして設定はvoip-info.jpにある設定を使わせてもらった。

cat <<'EOF' >> /etc/dhcp/dhclient.conf

option ip-sip-servers code 120 = { boolean, array of ip-address };
option vendor-class.ntt code 210 = string;
option space ntt code width 1 length width 1 hash size 7;
option ntt.mac code 201 = string;
option ntt.number code 202 = text;
option ntt.domain code 204 = domain-list;
option ntt.firmware code 210 = domain-list;
option vendor.ntt code 210 = encapsulate ntt;

interface "eth0" {
  send dhcp-client-identifier = hardware;
  request subnet-mask, routers, ip-sip-servers, rfc3442-classless-static-routes, vivso;
  send vendor-class.ntt = concat(06, suffix(hardware, 6));
}
EOF

他にもやろうと思えばDHCPv6で諸情報が取れたりするが、今回のひかり電話直収において必須ではないので取らなかった=voip-info.jpで紹介されているdhclient.confでほぼ必要十分である。

このdhclientの設定で取れる西日本での情報は以下の通り。おそらく東日本でもIPアドレスとドメイン名が違う以外、同じ項目が取得できると思われる。

/var/lib/dhcp/dhclient.leases
lease {
  interface "eth0";
  fixed-address 124.xxx.xxx.xxx;
  option subnet-mask 255.255.255.252;
  option dhcp-lease-time 14400;
  option routers 124.xxx.xxx.xxx;
  option dhcp-message-type 5;
  option dhcp-server-identifier 124.xxx.xxx.xxx;
  option dhcp-renewal-time 7200;
  option ip-sip-servers true 124.xxx.xxx.1;
  option dhcp-rebinding-time 10800;
  option rfc3442-classless-static-routes xxx,xxx,xxx,....;
  option vivso xx:xx:xx:....;
  option vendor.ntt xx:xx:xx:....;
  option ntt.domain "ntt-west.ne.jp.";
  option ntt.firmware "www.verinfo.hgw.flets-west.jp.";
  option ntt.mac xx:xx:xx:xx:xx:xx;
  option ntt.number "0xxxxxxxxx";
  renew 3 2021/02/xx xx:xx:xx;
  rebind 3 2021/02/xx xx:xx:xx;
  expire 3 2021/02/xx xx:xx:xx;
}

先ほど「ほぼ必要十分な情報が取れる」と言ったが、実は上記の通りDHCPv4ではDNSのアドレスが取得できない。DNSはDHCPv6では取れるのだが、ひかり電話に関してDNSが必要な場面は【ntt-west.ne.jp <--> 124.xxx.xxx.1】だけなので、雑ではあるが/etc/hostsで対応することにした。(IPv6はRaspberry Piの隣のルータがv4 over v6で使っており、ルータのconfigを変えるのが面倒くさかった)

cat <<'EOF' >> /etc/hosts
124.xxx.xxx.xxx ntt-west.ne.jp
EOF

その他これは言うまでもないが、経路情報は自動的に設定されるので、手作業で追加する必要はない。

ここまでの作業により、ifconfigrouteの結果は以下の通りとなる。

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 124.xxx.xxx.xxx  netmask 255.255.255.252  broadcast 124.xxx.xxx.xxx
        ether xx:xx:xx:xx:xx:xx  txqueuelen 1000  (イーサネット)
        (略)

eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.xx  netmask 255.255.255.0  broadcast 192.168.0.255
        ether xx:xx:xx:xx:xx:xx  txqueuelen 1000  (イーサネット)
        (略)

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (ローカルループバック)
        (略)

ppp0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1454
        inet 153.xxx.xxx.xxx  netmask 255.255.255.255  destination 153.xxx.xxx.xxx
        ether xx:xx:xx:xx:xx:xx  txqueuelen 1000  (イーサネット)
        (略)
カーネルIP経路テーブル
受信先サイト    ゲートウェイ    ネットマスク   フラグ Metric Ref 使用数 インタフェース
default         0.0.0.0         0.0.0.0         U     0      0        0 ppp0
124.xxx.0.0     124.xxx.xxx.xxx 255.255.xxx.0   UG    0      0        0 eth0
124.xxx.xxx.xxx 0.0.0.0         255.255.255.252 U     0      0        0 eth0
124.xxx.xxx.0   124.xxx.xxx.xxx 255.255.xxx.0   UG    0      0        0 eth0
153.xxx.xxx.xxx 0.0.0.0         255.255.255.255 UH    0      0        0 ppp0
192.168.0.0     0.0.0.0         255.255.255.0   U     0      0        0 eth1
xxx.xxx.xxx.0   124.xxx.xxx.xxx 255.255.224.0   UG    0      0        0 eth0

当たり前だが環境が人それぞれで違うので、この通りになるという意味ではない。
ポイントは、ifconfigにおいてはeth0にフレッツ網からDHCPで取得した情報(この例だと124.xxx.xxx.xxx)がついていること、そしてrouteにおいてはその情報に応じた経路(この例だと124.xxx.xxx.xxxがeth0を向く)設定がされていること、である。

Asteriskをソースからインストールする

後ほど説明するが、ひかり電話直収に必要なpjsip.confのパラメータ(disable_rport)は、Asterisk 16のChangeLogAsterisk 18のChangeLogによると、16.12.0以降もしくは18.0.0以降で使えるようだ。
しかし2021年2月現在、Raspberry Pi(Raspbian)のaptで入るAsteriskは16.2.1であり、当該パラメータが使えない。従って現時点では公式サイトからソースを落としてきて、入れなければならない。

まずtar.gzを落としてきて、公式ドキュメントでも言われているとおり、何も考えずに./configureすれば必要なパッケージをあぶり出せる。

cd /usr/src
wget https://downloads.asterisk.org/pub/telephony/asterisk/asterisk-18.2.0.tar.gz
tar -zxvf asterisk-18.2.0.tar.gz
cd asterisk-18.2.0
./configure

configureの途中で「xxxxが足りない」などと言われたら、都度それをaptで入れていった。
なぜこうしたかと言うと、現時点でひかり電話直収に必要/必須なパッケージを明らかにしたかったからだ。voip-info.jpや他のqiitaの記事で必要なパッケージがまとめられているが、それはその時入れるAsteriskのバージョンやOS(Distribution)によって異なるので、必ずしも正しいとは限らない。
今回Raspbian 10(Buster)でAsterisk 18.2.0を入れるに際しては、以下のものの不足が指摘されたので入れた。

apt install libedit-dev uuid uuid-runtime uuid-dev libjansson-dev libxml2-dev sqlite3 libsqlite3-dev

そしてAsteriskの動作としては必須ではないが、公式コミュニティ公式ドキュメントによると、Asteriskが/etc/hostsを読むためには以下のものが必要だそうなので、これも入れた。

apt install libunbound-dev

なおconfigureの引数について、またpjsip対応についても公式ドキュメントで書かれているとおり、Asterisk 15.0.0以降であればデフォルトで組み込まれるようだ。なので何も設定しなかった。
configureが問題なく終わると,以下のようになる。

./configure
(略)
               .$$$$$$$$$$$$$$$=..     
            .$7$7..          .7$$7:.   
          .$$:.                 ,$7.7  
        .$7.     7$$$$           .$$77 
     ..$$.       $$$$$            .$$$7
    ..7$   .?.   $$$$$   .?.       7$$$.
   $.$.   .$$$7. $$$$7 .7$$$.      .$$$.
 .777.   .$$$$$$77$$$77$$$$$7.      $$$,
 $$$~      .7$$$$$$$$$$$$$7.       .$$$.
.$$7          .7$$$$$$$7:          ?$$$.
$$$          ?7$$$$$$$$$$I        .$$$7
$$$       .7$$$$$$$$$$$$$$$$      :$$$.
$$$       $$$$$$7$$$$$$$$$$$$    .$$$. 
$$$        $$$   7$$$7  .$$$    .$$$.  
$$$$             $$$$7         .$$$.   
7$$$7            7$$$$        7$$$     
 $$$$$                        $$$      
  $$$$7.                       $$  (TM)    
   $$$$$$$.           .7$$$$$$  $$     
     $$$$$$$$$$$$7$$$$$$$$$.$$$$$$     
       $$$$$$$$$$$$$$$$.               

configure: Package configured for:
configure: OS type  : linux-gnueabihf
configure: Host CPU : armv7l
configure: build-cpu:vendor:os: armv7l : unknown : linux-gnueabihf :
configure: host-cpu:vendor:os: armv7l : unknown : linux-gnueabihf :

続いて公式ドキュメントの通り、makemake installする。

make
(略)
 +--------- Asterisk Build Complete ---------+
 + Asterisk has successfully been built, and +
 + can be installed by running:              +
 +                                           +
 +                make install               +
 +-------------------------------------------+

make install
(略)
 +---- Asterisk Installation Complete -------+
 +                                           +
 +    YOU MUST READ THE SECURITY DOCUMENT    +
 +                                           +
 + Asterisk has successfully been installed. +
 + If you would like to install the sample   +
 + configuration files (overwriting any      +
 + existing config files), run:              +
 +                                           +
 + For generic reference documentation:      +
 +    make samples                           +
 +                                           +
 + For a sample basic PBX:                   +
 +    make basic-pbx                         +
 +                                           +
 +                                           +
 +-----------------  or ---------------------+
 +                                           +
 + You can go ahead and install the asterisk +
 + program documentation now or later run:   +
 +                                           +
 +               make progdocs               +
 +                                           +
 + **Note** This requires that you have      +
 + doxygen installed on your local system    +
 +-------------------------------------------+

最後の仕上げとして、公式ドキュメント(Installing Initialization ScriptInstalling Sample Files)で紹介されているスクリプトやファイルを入れたりする。

make config
make samples
make install-logrotate
systemctl daemon-reload
systemctl enable asterisk

これでAsteriskのsystemdユニット定義ファイルが作成されたり、/etc/asteriskの中にサンプルのconfファイルが置かれたり、/etc/logrotate.d/asteriskが仕込まれたりする。

Asterisk(pjsip.conf, extensions.conf)を設定する

sip.confを使ったひかり電話直収については各所で時々見かけるが、pjsip.confを使ったひかり電話直収を私は見つけられなかった。なので今回頑張ってpjsip.confを作った。
当初はsip.confからpjsip.confへ変換するスクリプト(contrib/scripts/sip_to_pjsip/sip_to_pjsip.py)を使えばすぐ出来ると思っていたのだが、公式ドキュメントでも言われているとおり、常に互換性のあるconfを吐けるものではないようだ。

それを念頭において、スクリプトで一旦pjsip.confを作成し、それを元にレジスト&発着信できるパラメータを探った。
その苦労話は後で書くとして、結果としては以下のpjsip.confで動いてくれた。(注:ひかり電話直収部分のみ詳解)

/etc/asterisk/pjsip.conf
[global]
type = global
debug = no

[system]
type = system
disable_rport = yes ;via headerにrportが入らないよう必須

[transport-udp]
type = transport
protocol = udp
bind = 0.0.0.0

[reg_HIKARI-DENWA]
type = registration
contact_user = 0xxxxxxxxx ;電話番号(=DHCPで取得したntt.number)
transport = transport-udp
client_uri = sip:0xxxxxxxxx@ntt-west.ne.jp ;"電話番号@DHCPで取得したntt.domain"である必要がある
server_uri = sip:ntt-west.ne.jp ;DHCPで取得したntt.domain"である必要がある

[HIKARI-DENWA]
type = aor
contact = sip:124.xxx.xxx.1 ;DHCPで取得したip-sip-servers

[HIKARI-DENWA]
type = identify
endpoint = HIKARI-DENWA
match = 124.xxx.xxx.1 ;DHCPで取得したip-sip-servers

[HIKARI-DENWA]
type = endpoint
context = incomming ;extension.conf
disallow = all
allow = ulaw
rtp_symmetric = no ;via headerにrportが入らないよう必須
force_rport = no ;via headerにrportが入らないよう必須
rewrite_contact = no ;via headerにrportが入らないよう必須
direct_media = no ;RTPをAsteriskで中継するため必要
from_domain = ntt-west.ne.jp ;DHCPで取得したntt.domain"である必要がある
aors = HIKARI-DENWA

[100]
type = aor
(略)

[100]
type = auth
(略)

[100]
type = endpoint
(略)
苦労した/大事なポイント
  • disable_rportは必須。
    pjsipではこれを設定しないと、Viaヘッダにrportという文字列が入る。rportが入るとレジストと着信は出来るが、発信が出来ない。(発信しようとinviteを投げると400 Bad Requestが返ってくる)
  • client_uriserver_uriは、DHCPで取得したntt.domain(この場合はntt-west.ne.jp)である必要がある。
    sip_to_pjsip.pyで変換するとここがIPアドレスになったりするが、それだと何を投げても400 Bad Requestが返ってくる。
    • 一方でntt.domainの値を設定しただけではそのドメインの名前解決が出来ず、No Response receivedになる。
      なので/etc/hostsで名前解決できるようにしてある。
  • contactmatchはDHCPで取得したSIPサーバのIPアドレスで良い。
  • allow公式ドキュメントでデフォルト値が空欄だったので、NTTの技参資(,西)を見てG.711 ulawにした。
  • rtp_symmetric, force_rport, rewrite_contactは必須。
    公式ドキュメントに書かれているが、sip.confにおけるnat=neverにあたるもので、これと先のdisable_rportを指定することで、Viaヘッダにrportという文字列が入らない。
  • direct_mediaはエンドエンドで通信するか否かの設定だと理解していて、インターネットからひかり電話(124.xxx.xxx.1)へ直接繋がるわけがないと考えて、noにした。(今改めて考えてみると必須なのだろうか....)

その他、入れなかったパラメータで気になっているものを以下のメモに残す。

  • dtmf_mode公式ドキュメントでデフォルトがRFC4733であると確認し、NTTの技参資(,西)でもRFC4733に対応していると書かれていたので、設定しなかった。
    なおsip.confではよくdtmfmode=rfc2833としていたが、RFC2833を改廃したものがRFC4733である。
  • timers, timers_min_se, timers_sess_expiresはセッションタイマに関する設定で、今のところ必要なかったので入れなかった。もし今後、意図せず通話が切れたり切れなかったりしたら、この辺を調整してみる。
  • tosは設定しなくても動いたので、設定しなかった。QoS担保の観点では設定すべきなのかもしれないが。

次、ひかり電話直収とは直接関係ないが、extension.confも参考に紹介しておく。

/etc/asterisk/extensions.conf
[general]
writeprotect=no
priorityjumping=no

[globals]
USEVOICEMAIL=NO
MYNUMBER=0xxxxxxxxx
PHONEALL=PJSIP/100(略)

[default]
exten => 100,1,Dial(PJSIP/100)
exten => 100,n,Hangup
(略)
exten => _X.,1,Dial(PJSIP/${EXTEN}@HIKARI-DENWA)
exten => _X.,n,Hangup

[incomming]
exten => _X.,1,Set(free_dial="0120")
exten => _X.,2,Set(free_call="0800")
exten => _X.,3,GotoIf($["${CALLERID(num)}"="anonymous"]?99:4)
exten => _X.,4,GotoIf($[${CALLERID(num)}:${free_dial}]?99:5)
exten => _X.,5,GotoIf($[${CALLERID(num)}:${free_call}]?99:6)
exten => _X.,6,Dial(${PHONEALL})
exten => _X.,99,Answer()
exten => _X.,n,Hangup

これに間違いがないのか自信はないが、私の理解ではこれでいいと思っていて、意図通り動いているので良しとしている。
なお少し頑張ったところとしては、incommingでフリーダイヤルを拒否している。これはひかり電話直収と直接関係ないが、以前5ch.netでヒントをもらったので、御礼として紹介しておく。

上記の通り、DHCPで取得した電話番号やIPアドレスはpjsip.confextensions.confで直接設定されている。この場合もしDHCPで与えられる情報が変更されると、pjsip.confextensions.confはその情報に追随できない。
この解決策として、voip-info.jpではdhclientのhookを使ってsip.confを修正するようにしている。
ただ/var/lib/dhcp/dhclient.leasesを観察している限り、これらDHCPの情報は今まで変わったことがなく、これからも変わらないような気がする。
なので面倒くさいというのもあり、今のところdhclient.leasespjsip.confへ反映するスクリプトは作らず放置している。

あとがき

疲れました。
誤字脱字、内容が飛んでいる、単語の揺らぎなどあればご指摘ください。

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
ユーザーは見つかりませんでした