はじめに
Linux マシンを IPv6 ルーターにするプログラム、sixup を作りました。ISP からのプレフィックス取得、LAN への RA と DHCPv6、近隣探索の代理応答、MAP-E や DS-Lite のトンネルまで、普通は複数のデーモンとスクリプトを組み合わせて行う仕事を、1 つのプログラムで行います。
sudo sixup -wan eth0 -lan eth1
インターフェイスを指定するだけで、回線の方式を自分で判断して動きます。Go で書いた単一の静的バイナリで、MIT ライセンスです。
筆者は数年前に中国から日本に来ました。中国では Linux マシンを家庭のルーターにするのが珍しくなく、日本でも同じことをしようとして作ったのが sixup です。この記事では、まず中国のソフトルーター事情を紹介し、そのあとで使い方と仕組みを説明します。
中国のソフトルーター事情
日本では Linux マシンを家庭のルーターにする人はあまり多くないと思いますが、中国では技術好きの人の間でかなり一般的で、「軟路由(ソフトルーター)」と呼ばれています。x86 の小型 PC や ARM ボードに、OpenWrt 系のファームウェア(ImmortalWrt、iStoreOS など)や爱快(iKuai)を入れるのが定番で、Ubuntu や Debian などの普通の Linux ディストリビューションをそのまま使う人もいます。機械は安いもので十分で、マイニングブームが終わったあとに大量に出回った中古のマイニング用 ARM ボックスなら、数十元(1000 円前後)で手に入ります。プラグインを追加すれば、広告ブロックや DDNS なども 1 台にまとめられます。
ここまで広まった理由の一つは、中国のネット事情です。日本ではあまり知られていないかもしれませんが、中国からは Google、X、Facebook などに直接つながらず、GitHub も回線によってはまったく開けないことがあります。そのため、海外のサービスへの接続経路を細かく制御するプラグインが欠かせません。
凝る人は、1 台の小型 PC に Proxmox VE や ESXi を入れ、ソフトルーターと NAS を仮想マシンとして同居させます。この構成は「All in One」と呼ばれますが、その 1 台が止まるとネットワークもファイルもまとめて使えなくなるので、「All in Boom」とも言われます。ネットには「中年男性の三大趣味は充電器、NAS、ソフトルーター」という冗談まであります。充電器は GaN の普及で小型のものや画面付きのものが次々に出るので、用途ごとに買い足すうちに引き出しがいっぱいになる、というわけです。
ONU の扱いにも違いがあります。中国では、事業者が設置する ONU はルーター機能を内蔵した一体型が普通で、PPPoE の接続もその中で行われます。中国語ではモデムを音の近い「猫(マオ)」と呼ぶので、ONU は「光猫」と呼ばれています。自分のルーターで接続を管理したい人は、ONU の管理者パスワードを調べてブリッジモードに切り替えたり、認証情報(LOID や SN)を書き写して自分で買った ONU に交換したりします。SFP 型の ONU(スティック状なので「猫棒」と呼ばれます)をソフトルーターに直接差して、事業者の ONU を外してしまう人もいて、こうした手順はコミュニティで広く共有されています。
一方、日本のフレッツの ONU は NTT からの貸与品で、利用者が自分で用意した機器に交換する正規の方法はありません。NTT が提供する SFP+ 型の小型 ONU を選べる場合もありますが、差せるのは NTT が認めた対応機器だけです。もっとも、ONU の後ろに自分のルーターをつなぐ構成が最初から可能なので、ONU に手を入れる必要もあまりありません。ルーターにこだわる人も、ヤマハの RTX シリーズのような完成度の高い製品を選ぶことが多いようです。
ソフトルーターで IPv6 を動かす難しさ
自由度の高いソフトルーターですが、IPv6 を動かそうとすると、いくつものプログラムを組み合わせることになります。
-
odhcp6cやdhcpcdで ISP からプレフィックスを受け取る -
radvdで LAN に RA(ルーター広告)を送る -
odhcpdやdnsmasqで DHCPv6 を配る - /64 しかもらえない回線では
ndppdで近隣探索を代理応答する - MAP-E や DS-Lite の回線では、トンネルと NAT のルールをスクリプトで組む
さらに、これらの間でプレフィックスを受け渡し、変わったときに設定を書き換えて再起動するスクリプトも必要です。普段は動いていても、ISP がプレフィックスを変えたときやリースが切れたときに、どこかの段階が追従できずに壊れることがあります。そうなると家のネットワークが止まり、家族から苦情が来ます。
sixup は、この部分を 1 つのプログラムにまとめるために作りました。プレフィックスが変わっても、関係する設定がすべて再起動なしで追従します。
使い方
Releases ページから .deb、.rpm、.apk、Arch Linux 用パッケージ、または静的バイナリを入手できます。バイナリは x86 と ARM のほか、MIPS や RISC-V 向けも用意しています。Docker イメージ(amd64、arm64、armv7)もあります。
まず、回線が何を提供しているかをドライランで確認します。システムには何も変更を加えず、LAN に RA も送らないので、今のルーターを動かしたまま試せます。
sudo sixup -wan eth0 -dry-run
取得したプレフィックス、DNS サーバー、トンネルの種類などが表示されます。macOS 版と FreeBSD 版のバイナリもドライラン専用として配布しているので、ノート PC を ONU に直接つないで回線を調べることもできます。
問題がなければ、WAN と LAN のインターフェイスを指定して起動します。プレフィックスを DHCPv6-PD で受け取るか RA で受け取るかは、回線を見て自動で判断します。
sudo sixup -wan eth0 -lan eth1
LAN セグメントが複数あるときは、インターフェイスごとにサブネット番号を付けます。
sudo sixup -wan eth0 -lan eth1:0 -lan eth2:1
sixup がすること
起動すると、次の仕事をすべて内部で行います。
- DHCPv6-PD クライアント、上流 RA の受信と SLAAC
- LAN への RA 送信と DHCPv6 サーバー(stateless / stateful)
- 下流ルーターへのプレフィックス委任。sixup の下にさらに sixup を置けます
- 上流が /64 しかくれない場合の LAN との共有(RFC 7278)と、WAN 側での NDP 代理応答
- RFC 6092 に従った LAN 向けファイアウォールと、PCP によるポート開放
- DS-Lite、MAP-E、IPIP6 トンネルの構築と、MAP-E のポートセットに合わせた NAT ルール
- Jool を使った NAT64 と、464XLAT のための PREF64 の広告(後述)
外部コマンドは呼ばず、netlink と nftables を直接操作します。ip コマンドも nft コマンドも不要です。
設計:状態を 1 か所に集める
複数のデーモンを組み合わせる構成が壊れやすいのは、プレフィックスという 1 つの事実を、それぞれのプログラムが別々に持っているからです。sixup ではこの状態を Store という 1 つの goroutine だけが持ちます。
// Store is the only goroutine that owns prefix state; all changes serialize through it.
// Each consumer gets a channel of capacity 1 that only ever holds the latest snapshot.
type Store struct {
in chan storeMsg
subReq chan chan Snapshot
// ...
}
DHCPv6 クライアントや RA クライアントは、受け取った内容を SourceUpdate として Store に送ります。Store はそれを統合して Snapshot を作り、購読しているすべての部品に配ります。
DHCPv6 クライアント ─┐
├─> Store ─> Snapshot ─┬─> WAN/LAN のアドレス管理
RA クライアント ─────┘ ├─> RA サーバー
├─> DHCPv6 サーバー
├─> NDP 代理応答
├─> ファイアウォール
└─> トンネル管理
購読用のチャネルは容量 1 で、常に最新のスナップショットだけを保持します。処理の遅い部品があっても、途中の状態を順に処理する必要はなく、最新の状態に合わせればよいだけです。
この構造のおかげで、ISP がプレフィックスを変えたときも、アドレス、RA、DHCPv6 リース、代理応答のエントリ、トンネルのエンドポイントが、同じ 1 つのイベントから更新されます。手作業も再起動も要りません。
取り上げられたプレフィックスは、LAN に対してライフタイムを 0 にして一定時間広告し続けるので、ホストは古いアドレスをすぐに捨てます。LAN に広告したプレフィックスは状態ディレクトリに記録しているので、再起動をまたいでも同じように扱えます。
日本の回線への対応
日本の IPoE 回線でよく使われる方式にも対応しています。
MAP-E(v6プラス、OCN、BIGLOBE、NURO など)
DHCPv6 に MAP-E のオプションがあればそれを使います。オプションがない回線でも、取得したプレフィックスが各事業者のルール表に合えば、そこから MAP-E のパラメータを計算します。
MAP-E では、送信元ポートを回線に割り当てられたポートセットの内側に収めないと、戻りの通信が届きません。sixup は nftables に専用のテーブル inet sixup を作り、そこでポートの割り当てと TCP MSS の調整を行い、終了時にはこのテーブルを削除します。既存の masquerade ルールが残っているとポートセット外のポートを使ってしまうので、見つけたときは警告を出します。
SoftBank 光
SoftBank 光の IPv4 は、DHCPv6 では通知されない 4in6 トンネルを通ります。sixup はパラメータをどこにも問い合わせず、WAN に届くトンネルのパケットを観察して、両端のアドレスと IPv4 アドレスを読み取ります。ネットで紹介されている方法には、光BBユニットの認証手順(RADIUS)を再現して設定を受け取るものもありますが、sixup はそうした認証情報を一切使いません。光BBユニットが WAN で使っているインターフェイス ID を指定すれば、トンネルがこちらに届くようになり、sixup がその代わりを務めます。
sudo sixup -wan eth0 -wan-iid ::1111:1111:1111:1111 -lan eth1
DS-Lite
DHCPv6 で AFTR の名前が通知されれば、それを名前解決してトンネルを作ります。
IPv4 をやめる:NAT64 と 464XLAT
IPv6 が使えるようになっても、多くの家庭では LAN に IPv4 も残し、2 つのプロトコルを並行して運用しています。思い切った家庭なら、LAN の IPv4 をやめてしまうこともできます。DHCPv4 も IPv4 アドレスの管理も要らなくなります。
sudo sixup -wan eth0 -lan eth1 -nat64 jool
-nat64 jool を付けると、sixup は Jool を専用のネットワーク名前空間で動かし、64:ff9b::/96 あての IPv6 パケットを IPv4 に変換します。同時に、そのプレフィックスを RA の PREF64 オプション(RFC 8781)で LAN に知らせます。変換後の IPv4 はルーターの IPv4 の経路で出ていくので、MAP-E の回線ならトンネルを通り、ポートセットもほかの通信と共有します。
クライアント側では、PREF64 を受け取った OS が CLAT を有効にし、アプリケーションの IPv4 通信を自分で IPv6 に変換します。これが 464XLAT(RFC 6877)です。IPv4 アドレスを直接指定するアプリケーションも動くので、DNS64 だけに頼るより互換性が高くなります。執筆時点では、次の OS が CLAT を組み込みで持っています。
| OS | CLAT |
|---|---|
| iOS、iPadOS 16 以降、macOS 13 以降 | 組み込み。ネットワークに IPv4 がないときに有効になる |
| Android 11 以降 | 組み込み |
| Windows 11 | Insider ビルドでパブリックプレビュー中。既定では無効 |
| Linux | 組み込みの対応はなく、clatd などが必要 |
いきなり IPv4 をなくすのが不安なら、IPv6-mostly という段階もあります。DHCPv4 サーバーがオプション 108(IPv6-Only Preferred、RFC 8925)を送ると、対応するクライアントは IPv4 アドレスを手放して NAT64 を使い、対応していない機器だけが IPv4 を使い続けます。sixup は DHCPv4 を扱わないので、このオプションは今使っている DHCPv4 サーバーで設定します。詳しくは docs/recipes.ja.md の該当の節にまとめました。
RFC への準拠
家庭用ルーターの要件を定めた RFC 7084 と、ファイアウォールの RFC 6092 を基準にしています。各 RFC の要件を 1 項目ずつ表にし、sixup の対応状況と、それを確かめるテストの名前を docs/standards.md にまとめました。未対応の項目や、意図して従っていない項目もそのまま載せています。
たとえばファイアウォールの既定の動作は RFC 6092 に従い、インターネット側から始まる通信を LAN に入れません。ただし相手のアドレスによらないフィルタリング(REC-11 など)を採用しているので、最近外へ送信したポートには届き、Tailscale などの P2P プログラムはそのまま動きます。サーバーを公開したいときは -unsolicited allow で開放できます。
テスト
ネットワークの設定を変えるプログラムには、単体テストだけでは確かめられない部分が多くあります。そこで sixup では、Docker の中で実際の Linux カーネルを相手にする統合テストを用意しました。ネットワーク名前空間と veth で回線側と LAN 側を作り、RA、DHCPv6、NDP 代理応答、nftables、トンネルがカーネル上で期待どおりに動くかを確認します。
さらに相互運用テストとして、上流や下流に dnsmasq、radvd、Kea、dhcpcd を置き、既存の実装と正しくやり取りできるかを確かめています。コアのカバレッジは 98% を超えていて、その過程で見つかったバグもいくつか修正しました。
移行
既存のルーターから移行する手順は docs/migrating.ja.md にまとめました。ドライランで動作を確認し、今の構成のうち sixup が引き継ぐ仕事を書き出してから、古いサービスを止めて sixup を起動する、という順番です。DHCPv4、ネイティブ IPv4 の NAT、DNS リゾルバは、引き続き今のプログラムが担当します。
用途ごとの設定例は docs/recipes.ja.md にあります。
おわりに
先日 v1.0.0 をリリースしました。自分の回線では問題なく動いていますが、回線の種類は多く、まだ試せていない構成もたくさんあります。ドライランなら今の設定を止めずに試せるので、気軽に動かしてみてください。出力に気になる点があれば、issue で教えてもらえるとうれしいです。