ICOM VE-TA10 を使うためにパケットを書き換えたりする
はじめに
前々回の記事 “いまさら VoIP 網” から続く、電話についてのお話です。 前回の記事 “VoIP ルータを使って黒電話を IP 電話機にする” では YAMAHA 製の VoIP ルータを用いて黒電話を IP 電話機にする方法について紹介しました。 この記事では、YAMAHA 製のルータほど大規模でない、ICOM VE-TA10 を用いて黒電話を IP 電話機にする方法について紹介します。
実行環境
これらの環境の構成については “いまさら VoIP 網” を参照してください。
ホスト環境(Proxmox)
# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
# uname -a
Linux dekopon.local.kusaremkn.com 6.8.12-5-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-5 (2024-12-03T10:26Z) x86_64 GNU/Linux
LXC コンテナ(Ubuntu)
# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.2 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
# uname -a
Linux mikopie 6.8.12-5-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-5 (2024-12-03T10:26Z) x86_64 x86_64 x86_64 GNU/Linux
Docker コンテナ(MikoPBX)
*** This is MikoPBX v.2024.1.114
built on Tue May 14 10:08:57 UTC 2024 for Generic (x64)
MikoPBX is Copyright © 2017-2024. All rights reserved.
# uname -a
Linux mikopie.local.kusaremkn.com 6.8.12-5-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-5 (2024-12-03T10:26Z) x86_64 GNU/Linux
VE-TA10 の初期設定
まずは初期設定のうち、進められるところまでを進めていきます。
マニュアルをインターネット上で確認できます: https://www.icom.co.jp/support/manual/2386/
初期化
VE-TA10 を初期化し、工場出荷時の状態に戻すには以下のようにします。
- 本体背面に 初期化 ボタンを見付けます。
- VE-TA10 の電源を入れます。
- 初期化 ボタンを 30 秒以上押し続けます。
- 途中、ランプが橙色に点滅し、そして赤色に点滅します。
- 初期化 ボタンを離します。
- おわりです。
WAN 側設定
VE-TA10 をネットワークに接続するための設定を行います。
-
本体背面に 設定/PC のポートと ネットワーク のポートを見付けます。
-
設定/PC のポートとコンピュータとを接続し、ネットワーク のポートとネットワーク(スイッチングハブなど)とを接続します。
-
VE-TA10 の電源を入れます。
-
コンピュータで Web ブラウザを起動し、http://setup.html(原文ママ)または http://ta10.html(原文ママ)、あるいは http://192.168.111.1/ にアクセスします。
-
左側のペインにある WAN側設定 をクリックします。
WAN側設定 -
動作モード の選択肢から アダプタモード か 固定 IP モード を選択します。 DHCP を利用できる場合は アダプタモード を選択できます。 IP アドレスを固定して運用したい場合は 固定 IP モード を選択します。
-
アダプタモード を選択する場合、表示される 回線設定 の DNS サーバの欄に適当な DNS サーバの IP アドレスを指定します。 何を指定してよいのかわからない場合、8.8.8.8 や 8.8.4.4 あるいは 1.1.1.1 などを指定しておきます。
アダプタモード の場合 -
固定 IP モード を選択する場合、表示される 回線設定 の各項目に適当な値を指定します。
固定 IP モード の場合 -
設定が済んだら 登録して再起動 ボタンをクリックします。
登録して再起動 -
正常に通信できている場合、切り替わった画面の 接続状況 に 接続中 と表示されます。
正常に通信できている
電話設定(失敗)
VE-TA10 と MikoPBX を接続するための設定をします(が、失敗します)。
-
コンピュータで Web ブラウザを起動し、http://setup.html(原文ママ)または http://ta10.html(原文ママ)、あるいは http://192.168.111.1/ にアクセスします。
-
左側のペインにある 電話設定 をクリックします。
電話設定 -
各種項目の設定を入力します。 市外局番 は空欄にしてください。 VoIP 電話番号 には内線番号を指定します。 VoIP サーバ 及び サービスドメイン には MikoPBX の IP アドレス、またはホスト名を指定します。 VoIP ユーザ ID には内線番号を指定します。 VoIP ユーザパスワード には内線番号のパスワードを指定します。 設定が済んだら 登録 ボタンをクリックします。
各種項目の設定 -
しばらくすると 接続状況 の欄に 接続失敗 と表示されます。
接続失敗
失敗する理由
接続に失敗してしまったので、その原因を突き止めてみましょう。
MikoPBX 側のログ
まずは MikoPBX 側のログを確認してみましょう。 MikoPBX の WEB UI にアクセスし、Maintenance にある System log entries を確認します。
System log entries
ここで気になるものは繰り返し 11 回表示されている次のメッセージです。 どうやら SIP パケットの Authorization ヘッダに構文エラーがあるようです。
[2025-02-18 21:39:17] WARNING[8559] pjproject: sip_transport.c Dropping 720 bytes packet from UDP 172.20.250.7:5060 : PJSIP syntax error exception when parsing 'Authorization' header on line 12 col 2
パケットキャプチャリング
パケット中に問題があると言われているのですから、そのパケットを覗いてみないことには何も始まりません。 パケットを覗くために MikoPBX に添付されている tcpdump を用います。 これはパケットを掴まえる度に掴まえたパケットを表示してくれます。 MikoPBX に対して SSH 接続するとシェルを起動してコマンドを実行できるので、ここからパケットを覗いてみます。
MikoPBX に SSH 接続したところ
SIP はデフォルトで UDP の 5060 ポートを用いますから、これをキャプチャリングします。 次のように実行し、パケットを待ち受けている間に VE-TA10 の 電話設定 から 再接続 しようとしてみます。 ここで、172.20.250.6 の部分には VE-TA10 の IP アドレスを指定します。
~# tcpdump -vvv -XX udp port 5060 and host 172.20.250.6
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
22:08:01.787610 IP (tos 0xa0, ttl 255, id 351, offset 0, flags [none], proto UDP (17), length 393)
172.20.250.6.sip > 172.20.80.106.sip: [udp sum ok] SIP, length: 365
REGISTER sip:172.20.80.106 SIP/2.0
Via: SIP/2.0/UDP 172.20.250.6:5060;branch=z9hG4bK2f5d00021964984e8af3
... (SNIP) ...
問題のあるパケット(VE-TA10)
問題になっている Authorization ヘッダを含むパケットを眺めてみましょう。 どこが問題かわかりますか?
22:11:00.751749 IP (tos 0xa0, ttl 255, id 722, offset 0, flags [none], proto UDP (17), length 667)
172.20.250.6.sip > 172.20.80.106.sip: [udp sum ok] SIP, length: 639
REGISTER sip:172.20.80.106 SIP/2.0
Via: SIP/2.0/UDP 172.20.250.6:5060;branch=z9hG4bKd6800003f6fb986ac588
Call-ID: CFAB0688@172.20.250.6
CSeq: 2 REGISTER
From: <sip:6502@172.20.80.106;user=phone>;tag=be2d6571985c195e
To: <sip:6502@172.20.80.106;user=phone>
Max-Forwards: 70
User-Agent: ICOM_SIP/VE-TA10_Ver. 1.00
Expires: 0
Contact: *
Authorization: Digest username="6502", realm="asterisk", nonce="1739884245/eadd3ca818719a95c7d7d3577fa6ad26", uri="sip:172.20.80.106", qop=auth, cnonce="eb1304786ac689ee7ca52ae73ed1525c", nc=00000001, opaque="3ca09bc95397dc8d"
response="b6d8be68fe99d0d40378060479813fd5"
Content-Length: 0
問題のないパケット(RT-58i)
問題の部分を探すためには問題のないパケットも眺めなければなりませんね。
22:18:22.958456 IP (tos 0x0, ttl 255, id 43013, offset 0, flags [none], proto UDP (17), length 604)
172.20.250.3.sip > 172.20.80.106.sip: [udp sum ok] SIP, length: 576
REGISTER sip:172.20.80.106 SIP/2.0
Authorization: Digest username="6502", realm="asterisk", nonce="1739884702/1b6f9b613091182255ab91992c74f045", opaque="57e7d735155a5f32", qop=auth, nc=00000001, cnonce="a1ad7127", uri="sip:172.20.80.106", response="4aca25319af0078a39c6023552302795"
Call-ID: b7507563934d1c1d4566@172.20.250.3
Contact: <sip:6502@172.20.250.3:5060>
Content-Length: 0
CSeq: 10 REGISTER
Expires: 3600
From: <sip:6502@172.20.80.106>;tag=881359778
Max-Forwards: 70
To: <sip:6502@172.20.80.106>
Via: SIP/2.0/UDP 172.20.250.3:5060;branch=z9hG4bK38552743
え、どこが問題なの?
パッと見たところ、VE-TA10 から送出されたパケットでは Authorization ヘッダが 11 行目と 12 行目に渡っているため、それが問題であるようにも見えます。 しかし、SIP を規定している RFC 3261 では複数行に渡るヘッダフィールドについてきちんと述べられていますし、そもそも MikoPBX のエラーログ上でも 12 行目が Authorization ヘッダであることを理解しているため、これが直接の問題であるとは考えにくそうです。
ここで、VE-TA10 から送出されたパケットの 11 行目の末尾をじっくり見てみると、各キーと値との対を区切っているカンマが無いことに気付きます。 エラーログの内容が syntax error であることも加味すると、カンマが無いことによるエラーであることは十分に考えられそうです。
インチキプログラミング
上のエラーを解決するための手段として、次の二点が考えられます。
- MikoPBX よりも「寛大な」SIP プロキシを経由して、MikoPBX にも理解できる正常なリクエストに書き換えてもらう
- 何らかの方法を駆使し、MikoPBX にパケットが渡される前に問題の部分をちょろまかす
順当な手立てを用いるのであれば、一つ目の SIP プロキシを用いる方法が賢明でしょう。 しかし、SIP のプロキシまわりはなんだか面倒そうな印象があります。 ここでは何とかしてパケットを書き換えることでこの問題を解決してみましょう。
パケットを捕まえる方法
パケットを書き換えるためには、書き換えたいパケットを捕まえる必要があります。 パケットを捕まえるための仕組みとして、Berkelay Packet Filter(BPF)があります。 これはパケットを通過させるのか、あるいは破棄するのかを決定するプログラム上の小さな仮想マシンです。 先ほどパケットをキャプチャするために用いた tcpdump は BPF を利用しているプログラムのよい例です。 条件にマッチする(例えば、UDP のパケットで、ポート 5060 で、など)パケットのみをキャプチャプログラムに渡すために BPF が用いられています。
本来の BPF はパケットの通過と破棄とを制御するだけの仕事をしていましたが、“拡張された” BPF(extended BPF: eBPF)が Linux に導入されました。 これはパケットのフィルタフィングや書き換えに留まらず、ネットワーク以外の部分のカーネルの振る舞いをも拡張できます。 今回はこの ePBF を活用してパケットを書き換えます。
パケットを書き換える方法
eBPF の機能のうち Traffic Control(TC)や eXpress Data Path(XDP)を使うことでパケットの内容を書き換えることができます。 この記事では TC と XDP との違いについて深く触れません。 今回は受信パケットのみを扱えれば良いので XDP を利用することにします。 XDP では、プロトコルスタックに渡される前の “生” のパケットデータを相手に操作を行います。
開発環境の準備
eBPF のプログラミングには clang やその他の開発ツールが必要です。 Ubuntu では、次のように実行してインストールできます。
$ sudo apt update && sudo apt upgrade
$ sudo reboot # 必要に応じて
$ sudo apt install clang llvm libelf-dev libbpf-dev
パケットを書き換えるプログラム
eBPF なんて仰々しい名前をしていますが、所詮はただのプログラムですから、肩の力を抜いていつものように取り組むことにします。 今回の目的を果たすためには、次のような処理をすれば良いです。
- 対象とするパケットか調べる。
そうでなければ何もしない。 -
Authorization ヘッダを探す。
見付からなければ何もしない。 -
Authorization ヘッダが二行に渡っているか調べる。
そうでなければ何もしない。 - 行を連結しながら間にカンマを付ける。
具体的には、復帰改行をカンマスペースに置き換える。 - おしまい。
ソースコードから eBPF プログラムを生成するには、次のように実行します。 ここで、main.c はソースコードのファイル名です。
$ clang -O2 -g -target bpf -c -I/usr/include/x86_64-linux-gnu/ main.c
そして、そのプログラムを読み込むには次のように実行します。 ここで、main.o は生成された eBPF プログラムファイル、eth0 は対象とするネットワークインタフェイス名です。
$ sudo ip link set eth0 xdp obj main.o
$ sudo ip link set eth0 xdp off # プログラムをアンロードする
しかし、いつものプログラムを書くノリで書かれた eBPF を読み込もうとすると、例えば次のようなエラーが発生して読み込まれません。 プログラムにおける条件分岐が複雑すぎるため、eBPF プログラムの検証器が検証を諦めてしまいます。
The sequence of 8193 jumps is too complex.
processed 49144 insns (limit 1000000) max_states_per_insn 4 total_states 512 peak_states 512 mark_read 10
-- END PROG LOAD LOG --
libbpf: prog 'xdp_rewrite': failed to load: -14
libbpf: failed to load object 'main.o'
破壊的仕様変更
文字列検索が二段階に続くと eBPF 検証器が諦めてしまうので、プログラムの仕様を次のように変更します。 本来、こんな乱暴な仕様変更があってはなりませんが、今回相手にするパケットのうち、ヘッダ部分が二行に渡るものはこの他に無いようですから甘んじて受け入れることにしましょう。
- 対象とするパケットか調べる。
そうでなければ何もしない。 - 二行に渡っているヘッダがあるか調べる。
あればカンマを挿入して連結する。 - おしまい。
これを記述したプログラムを GitHub に公開しています。 この程度のプログラムであれば、eBPF プログラムの検証器が諦めることなくプログラムを検証してくれます。 このプログラムの処理内容はプログラムの本来の処理内容と大きく異なりますが、とにかく動いているので良いのです(ダメです)。
電話設定(再開)
さて、電話設定に戻ってきましょう。 まずは、MikoPBX を動かしている Ubuntu において、上で作成した eBPF プログラムをロードします。 この eBPF プログラムによって、受信パケットのうち二行に渡っているヘッダは連結されて MikoPBX に渡されます。 MikoPBX からパケットを見てみると、改行されていた部分には確かにカンマが挿入され、行は連結されています。 これなら問題なく接続できるはずです。
00:23:29.622568 IP (tos 0xa0, ttl 255, id 42506, offset 0, flags [none], proto UDP (17), length 667)
172.20.250.6.sip > 172.20.80.106.sip: [no cksum] SIP, length: 639
REGISTER sip:172.20.80.106 SIP/2.0
Via: SIP/2.0/UDP 172.20.250.6:5060;branch=z9hG4bKae1b00e6db82a61d5183
Call-ID: 77B46187@172.20.250.6
CSeq: 2 REGISTER
From: <sip:6502@172.20.80.106;user=phone>;tag=dde49bc1a60f50fd
To: <sip:6502@172.20.80.106;user=phone>
Max-Forwards: 70
User-Agent: ICOM_SIP/VE-TA10_Ver. 1.00
Expires: 0
Contact: *
Authorization: Digest username="6502", realm="asterisk", nonce="1740065009/8158ceaab3185a6fb02e9f44a463e5dd", uri="sip:172.20.80.106", qop=auth, cnonce="2b715c94137a89e5f864222eef889176", nc=00000001, opaque="3d462de017051470", response="533f74036e609a5491c60e9ae53ef3f2"
Content-Length: 0
お、接続できましたね。 おめでとうございます。
接続成功
おわりに
おわりです。
参考
- J. Rosenberg; et al. SIP: Session Initiation Protocol. RFC 3261. June 2002. https://datatracker.ietf.org/doc/html/rfc3261, (accessed 2025-02-19).
- Liz Rice. 武内覚; 近藤宇智朗 訳. 入門 eBPF. 株式会社オライリー・ジャパン, 2023-12-15, 228p., ISBN-978-4-8144-0056-0.
謝辞
この VoIP ルータをハードオフのジャンク箱から掘り出してきた Jiminy 氏に感謝します。
Discussion