systemdに全部賭けろ(第一部)

Linuxでルータを作る(フレッツ光 + v6プラス、下流にホームゲートウェイあり)

ルータの稼働状況
ルータの稼働状況

まえが

systemd森羅万象を統べるようになって千年ほどが経った。

ダイソン球の姿勢制御から、マスドライバーの運行管理まで、あらゆるプロセスがかれの傘下に入った。政治systemd-politicsd)、経済systemd-economyd)、文化systemd-cultured)など、名だたるサブシステムがデーモン置き換えられ、社会構造は大きく改変されていった。ときには抵抗もあったが、systemdがPID 1」と叫べば、ひれ伏さぬものはなかった。

そのように強力であるため、systemdはご家庭のルータになることもできる(ここからは本当)。そこで、この記事ではsystemd-networkd使い、Linuxルータを作る。最終的には、

環境を実現する。


記事の構成について

記事は三部に分かれている。

第一部では、ネットワーク構成とルータの環境構築について扱う。


背景

とはいえ、なぜそもそもこんな手間が必要なのだろうか? 背景を補足しておこう。

なぜ自前ルータ?

すでにあちこちで書かれているように、フレッツ光の回線にひかり電話オプション付くと、/56IPv6プレフィックスが払い出される。このとき、同時に貸し出されるHGWは以下のような挙動をする。

つまり残念なことに、与えられたアドレス空間の1/2561/256ないし1/161/16しか有効に使われない1

そのほかにも、HGWを上流に置くことの弊害はいろいろある(性能の限界、自由度の低さなど)。そこで代わりに自前のルータを上流に置きたい、という欲が出てくる。

なぜLinux?

ところがひかり電話を使う場合、何も考えずHGWを取り外すことはできない。いうのも、HGWはひかり電話ルータでもあり、まともに機能するには以下の二つが必要だからだ。

この制約に対し、二つほど解決策がある。

法1: 選手交代

一つ目は、単純に自前の市販ルータでHGWを置き換えてしまえ、というものだ。これはNTTお墨付き法だが、いくつかデメリットもある。

  1. ひかり電話対応ルータしか使えない
    • NVR500、NVR510、NVR700Wなど
  2. HGW特有の機能(例:SIPサーバ機能)を捨てることになる
  3. そこまで自由度は増えない
  4. 優等生すぎて面白くない

法2: IPv4ブリッジ

もうひとつは、IPv4を選択的にブリッジするいう法だ。これについては下の記事が詳しい。

IX2215直下にHGWを置いてひかり電話をする【2024年夏版】 - Qiita
概要だよ UNIVERGE IX2215直下に繋いだHGWと同一リンクにDHCPv6サーバを置くことでNTTからのDHCPv6パケットを偽装するといいよ 従来の方法は最新HGWのファームウェアに対応できていなかったよ NVR500・510を使うほうが楽だよ。ひかり電話で...
qiita.com

ざっくりいえば、フレッツ網-ルータ-HGWのように接続し、ルータで

いう構成である。これはかなりうまく動く一方で、まだ以下の難点を残している。

  1. IPv4ブリッジに対応したルータしか使えない2
    • e.g. UNIVERGE IXシリーズ
  2. 別途DHCPv6サーバが必要
  3. 別途外部からパケットキャプチャが必要

特に 2. と 3. は重要で、結局のところ市販ルータが扱えるDHCPv6オプションは少ないため、別途パケットをキャプチャしたり、DHCPv6サーバを立てたりする必要がある3

しかしLinuxでルータを作れば、これらすべて──IPv4のブリッジ、DHCPv6サーバ、DHCPv6オプションの受信──を一台で担える。そのうえ適当な端末(たとえばミニPC)さえあれば済む。ちょっとしたDIYで、シンプルかつ多機能な構成を実現できるのだ。

環境

参考のため、使用した回線・機器などを記しておく。

回線契約

事情あってVDSLだが、それ以外はごく一般的な契約内容になっている。

以下では例示のため、プロバイダから割り振られたIPv6プレフィックスを3fff:0:0:ab00::/56する。IPv4 over IPv6の接続に必要な情報は、あらかじめプロバイダから得るか、さもなくば計算機などで算出しておく。ここでは下表のように仮定する。

項目
CE3fff:0:0:ab00::ce
IPv4アドレス192.0.2.1
PS(= ポートセット)ID171(10進)/ ab(16進)
option peeraddr(= BR)2001:db8::1/64

機器

項目機種用途・備考
ミニPC(某社のミニPC)ルータ用。LANポートが二つ以上あればよい5
L2スイッチTP-Link TL-SG105PEポート数を増やすために必要。タグVLAN(802.1Q)が切れればよい
HGW沖電気 RX-600KIひかり電話ルータ
VDSLモデム住友電工 VH100-4ESFTTH回線であれば、単独型ONU、しくは「UNI出し」したHGWの下半分(ONU部分)がこれにあたる

ルータ用の機材には、適当なミニPCを用意した。ミニPCだけではポート数が足りないため、タグVLANに対応したL2スイッチで補った。

ネットワーク設計

今回自作ルータが担う役割を整理しておこう。

  1. 基本的なルータの機能
    • DHCPv6クライアントを動かしフレッツ網からIPv6プレフィックスを受け取る
      • 同時に、ひかり電話関連情報(DHCPv6オプション)も受け取る
    • IPv4 over IPv6(MAP-E)のトンネルを張る
    • 宅内機器側のセグメントを作り、IPv6・IPv4を配る
    • その他、ファイアウォールなど
  2. ひかり電話ルータ(HGW)の世話
    • HGW側のセグメントを分離する
    • フレッツ網-HGW間のIPv4パケットをブリッジする
    • DHCPv6-PDサーバを動かし、HGW向けにフレッツ網に似たIPv6環境を整える
      • IPv6プレフィックスの一部をHGWに再委譲する
      • ひかり電話関連情報をHGWに配布する

このうち太字の項目に関連して、いくつか考慮すべき点がある。

IPv4パケットのブリッジについて

ブリッジデバイスを使ってもよいが、nftablesのfwd文便利だ。入ってくる生のパケットをそのまま別のインターフェースに転送できるので、実質L2スイッチのように振る舞ってくれる。

MAP-Eのポート振り分けについて

MAP-Eに関しては、以下の記事が注目に値する。

LinuxルーターのMAP-Eで良い感じにNATポートを使い回す方法 - turgenev’s blog
OpenWRTなどLinuxを使用してMAP-E(v6プラス、OCNバーチャルコネクトなど)接続を行う方法は様々なサイトで紹介されていますが、iptablesやnftables(バックで動いているのはnetfilter)のNATでは使用ポート範囲を複数(1000-2000, 3000-4000みたいな感じで)指定できないため、MAP-Eの利用可能ポートをうまく使い回すのが難しいという問題があります。具体的には、多くの接続を同時に行ってポートが不足気味のときに、実際には利用可能なポートが余っているにもかかわらずそれが割り当てられる対象になっていないため新規接続が失敗するという現象が発生する可能性…
turgenev.hatenablog.com

執筆時点では、この記事に示されているようなtc使ったポート振り分けの実用例はインターネット上に見つからなかった。今回はこの法を実環境で試してみる。

ひかり電話関連情報の取得・配布について

ひかり電話関連情報の扱いは意外に難しい。

理想としては、フレッツ網から受け取ったDHCPv6オプションをそのままHGWに「中継」できればよいが、これができるソフトウェアはいまのところない。そこで代わりに、DHCPv6のリースを受けるたびに、リース内容をもとにDHCPv6サーバの設定を更新する仕組みを作る。

鍵となるのは、systemd-networkdのD-Bus APIと、Kea(DHCPv6サーバ)のManagement APIだ。systemd-networkdは、リースを受けるたびにD-Busシグナルを飛ばしてくれるうえ、リースの内容自体もD-Busで取得できる。一方、KeaのManagement APIを使うと、HTTPで動的に設定を投入できる。両者を合わせると6以下のような処理が可能になる。

  1. DHCPv6リースを受けた際のD-Busシグナルを検知
  2. D-Busを叩き、リース情報を取得
  3. リース情報を元に、KeaのAPIを叩いて設定を更新

この処理を適当な言語7実装8すれば、仮に将来フレッツ網から降ってくる情報が変わっても対応できる。

ネットワーク構成

以上を実際の接続に落とし込み、以下の図のようになった。

物理接続

物理接続

(青色は光ファイバー・LANケーブル、灰色は電話線)

論理接続

論理接続

(黒色はIPv6 IPoE接続、赤色はIPv4 over IPv6接続、水色はHGW専用のIPv4接続)

補足

ルータの物理インターフェースは以下の2つ。

これに加えて、仮想インターフェースとして以下の3つを作成する。

L2スイッチ側でも、これに対応したタグVLAN(ID = 10, 100)を設定する9

手順1:環境構築

免責

手元では、以下の構成は安定して動作しており、実用上も問題は出ていない。しかしあくまでも事業者の想定していない構成であることは留意ねがいたい。動作は保証できないし、この記事の内容を実践した結果としていかなる損害が生じても、私は一切責任を負わない。

#ArchLinuxInstallBattle

まず、ミニPCにArch Linuxをインストールする(I use Arch, BTW)

使用するLinuxディストリビューションは自由だが、systemd-networkdを使う都合上、systemdのバージョンが新しいほうがおそらく良い。

解説記事が無数にあるため、詳細なインストールプロセスは省略する。お、意味があるかはわからないが、カーネルにはlinux-rt使った。

この段階で、後で必要になるソフトウェアもインストールしておく。

Terminal window
pacman -S ethtool nftables iptables-nft ndisc6 kea jq
paru -S python-sdbus

NICのチューニング

続いてNICのチューニングをする(このあたりは使う機材による)。

今回使った機材には、RTL8125BというRealtek 🦀 の2.5GbE NICが載っていた。しかし初期状態では汎用のドライバr8169)で動いており、本来の機能を発揮できない。そこで製造元から配られている専用ドライバをインストールする。Arch Linuxの場合、AURにあるパッケージ使うと楽だ。

デフォルトのビルド設定はかなり保守的になっていて、とえばマルチキューなどの機能が有効化されていない。そのため、適宜書き換えてビルドする10

r8125-rss-dkms/PKGBUILD
# (前略)
_pkgname=r8125
pkgname=${_pkgname}-dkms
pkgname=${_pkgname}-rss-dkms
pkgver=9.016.01
# (中略)
package() {
# (中略)
sed -e "s/@_PKGNAME@/${_pkgname}/g" \
-e "s/@PKGVER@/${pkgver}/g" \
-i "${pkgdir}/usr/src/${dir_name}/dkms.conf"
sed -e 's/ENABLE_RSS_SUPPORT = n/ENABLE_RSS_SUPPORT = y/' \
-e 's/ENABLE_MULTIPLE_TX_QUEUE = n/ENABLE_MULTIPLE_TX_QUEUE = y/' \
-e 's/CONFIG_ASPM = y/CONFIG_ASPM = n/' \
-e 's/ENABLE_PAGE_REUSE = n/ENABLE_PAGE_REUSE = y/' \
-i "${pkgdir}/usr/src/${dir_name}/Makefile"
}

そして汎用ドライバを読み込まないようにする。

/etc/modprobe.d/blacklist-r8169.conf
blacklist r8169

次回の起動時にはr8125ドライバがロードされ、マルチキューが有効化されているはずだ。

Terminal window
> ethtool -i wan0
driver: r8125
version: 9.016.01-NAPI-RSS
firmware-version:
expansion-rom-version:
bus-info: 0000:03:00.0
supports-statistics: yes
supports-test: no
supports-eeprom-access: no
supports-register-dump: yes
supports-priv-flags: no
Terminal window
> ethtool -l wan0
Channel parameters for wan0:
Pre-set maximums:
RX: 4
TX: 2
Other: n/a
Combined: n/a
Current hardware settings:
RX: 3
TX: 2
Other: n/a
Combined: n/a

せっかく複数の送受信キューが使えるようになったので、irqbalanceでキューを各CPUコアに振り分けるようにしておく(おまじない)。

Terminal window
pacman -S irqbalance
systemctl enable --now irqbalance

ついでにNICの細かい設定もしておく。

/etc/systemd/network/01-wan0.link
[Match]
PermanentMACAddress=de:ad:be:ef:00:01
[Link]
Name=wan0
# MACアドレスのランダム化
MACAddressPolicy=random
/etc/systemd/network/01-lan0.link
[Match]
PermanentMACAddress=de:ad:be:ef:00:02
[Link]
Name=lan0
# Wake-On-LAN
WakeOnLan=magic

カーネルパラメータのチューニング

ArchWiki教えに従い、TCP関連のさまざまなパラメータを調整する。

/etc/sysctl.d/99-tuning.conf
net.ipv4.tcp_slow_start_after_idle = 0
net.core.default_qdisc = cake
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_ecn = 1

もちろんルータとして使う以上、IPv4 / IPv6の転送設定は必須だ。

/etc/sysctl.d/99-ip-forwarding.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv6.conf.all.forwarding = 1

あとでIPv4 over IPv6トンネルを張る際に、不要なトンネルデバイスip6tnl0)が作成されないような設定もしておく。

/etc/sysctl.d/99-no-fb-tunnel.conf
net.core.fb_tunnels_only_for_init_net = 2

ここまでで一通りの環境構築が済んだ。第二部では端末をフレッツ網へ接続し、基本的なルータとしての機能を実現する。


  1. もちろん、これでもアドレス数は十分なのだが、せっかく与えられたものが無駄になるのは気に入らない

  2. とはいえIPv4のブリッジ程度であれば、他社のルータでも実現できるかもしれない

  3. 詳しくは第三部で述べるが、DHCPv6リクエストに適切なオプション(Option 16, Vendor Class)を含めれば、ルータで直接ひかり電話関連情報を受信できる。しかしこれに対応した市販ルータは(おそらく)少ないし、よしんば受信できても、その情報を下流に送信できない

  4. この記事では、VDSLモデム(ないしONU)とHGWが独立していることを暗黙の前提としている。両者が一体化しているHGWの場合、いわゆる「UNI出し」をするとこの状態にできる。小型ONUの場合は、Linuxルータに別途SFP+ポートが必要になる

  5. VLANを駆使すれば、ポートが一つでもなんとかなるかもしれない

  6. systemd-networkdにはDHCPv6サーバ機能がないため、併用が必要

  7. お、実装はPythonで行った。こんな処理をわざわざコンパイルするのも気が引けるし、かといってシェルスクリプトには荷が重いからだ

  8. D-Busとのやり取りには、python-sdbusいうライブラリを使った

  9. お、もう一つVLANを切ってHGWのLAN側接続しておくと、ルータ経由でHGWのWeb設定画面にアクセスできて便利だが、今回は省略した

  10. OpenWrtではデフォルトでマルチキューが有効化されているため、それに倣った