⚙️

OpenWrt 22.03/23.05 で MAP-E のポートセット全部使う

2023/02/23に公開

MAP-E(OCN バーチャルコネクト, v6 プラス) のポートセットを活用するためのルール設定を OpenWrt 22.03 以降の新ファイアウォールで使えるようにする。

また後述する方法は OpenWrt 23.05-rc2 でも使えることを確認した。

事の発端

怪レい箱

前から気になっていた SFP+ が付いている怪レい箱 を遂に購入した。

https://twitter.com/wistnki/status/1625146786221527041

スペックやレビューは既に先駆者がおられるので割愛するが、ネットワークインタフェースは Intel I225(2.5Gbps)3 口と Mellanox ConnectX-3(SFP+, 10Gbps)2 口という、ルーターを作れと言わんばかりの布陣になっている。公式サイトによると 1Gbps x 1 と 2.5Gbps x 2 ということになっているし、Q&A にもなんで全部 2.5Gbps じゃないん?という項があったが、実際は 2.5Gbps x 3 になっているようだ。

新しい箱でルーターを組むにあたり、せっかくなら OpenWrt の新しいバージョン 22.03 で組むことにした。

新しいファイアウォール

OpenWrt 22.03 の目玉の一つに nftables をベースとした新しいファイアウォール(fw4)がある。nftables は iptables よりも性能が向上しているらしい[1] ので、活用できるならしたいところ。

しかし一方で、従来の iptables をベースとしたファイアウォール(fw3)で使用されていた、MAP-E(OCN バーチャルコネクト, v6 プラス)のポートセットを活用するためのルール設定がそのまま通用しない。実際、LuCI から「手動設定ルール」タブが消滅しており、/etc/firewall.user も削除されている。そのため、fw4 に合わせて何かしらの修正が要る。

OpenWrt のインストールと MAP-E の設定(省略)

OpenWrt のインストールをして MAP-E のトンネルを張り、とりあえず IPv4 と IPv6 それぞれのウェブサイトに繋がる(ただし先頭のポートセットしか使用されない)ようにするまでの手順は、OpenWrt 21.02 の頃と同じなので割愛する。先駆者のブログなどを参照してほしい。ここではその時にハマりそうなポイントなどを紹介する。

  • R86S は Intel CPU の PC と同じなので x86_64 用のイメージ generic-ext4-combined.img.gz か EFI ブートをするのであれば generic-ext4-combined-efi.img.gz をダウンロードする。解凍した後、rufus なり dd コマンドなりで、eMMC なり TF カード[2]なり NVMe SSD なりに書きこめばブートできる。
  • map パッケージをインストールした後、直ぐにインタフェースを作成しようとしても、プロトコル一覧に MAP / LW4over6 が現れない。一度再起動する必要がある。
  • mapluci-i18n-firewall-ja などのパッケージに iptables のものっぽい dependency がある。この辺りは fw4 対応が追いついてないのかな。
  • Local address not yet configured! 気にしなくてよさそう。
  • /lib/netifd/proto/map.sh によってトンネルデバイスの MTU が 1280 に設定されているので環境によっては変更するとよい(後述)。
  • /lib/netifd/proto/map.sh のファイアウォールまわりの記述が fw4 に対応してないという噂[3] もあるが、今のところそのままでも問題ない模様。

ポートセットを全部使う設定

先述の通り、従来の fw3 で使用されていた、MAP-E(OCN バーチャルコネクト, v6 プラス)のポートセットを活用するためのルール設定は、そのままでは fw4 では通用しない。とはいえ、「トンネルを通じて出ていこうとするパケットに対し順番にマークをつけ、そのマークに応じてポートセットを振り分ける」というルールを追加すればよいという点に変わりはない。

fw4 では、ルールを記述した nft ファイルを /etc/nftables.d/ 以下に置くことで、ルールを登録することができる。だたこれだと inet/fw4 テーブル下のチェーンとして登録されるためか、うまく機能しなかった。また、IPv4 のパケットについて扱えばよいので、ip のテーブルにしたい。そこで、fw3 の時のような手動設定ルールとしてではなく、同等のルールを書くスクリプトをローカルスタートアップに置くようにした。
要するに以下のどちらか好きなほうのスクリプトを適当なディレクトリに配置して実行パーミッションを付けたうえで、以下のように [System]-[Startup]-[Local Startup] に追記する。

/etc/rc.local
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.

+ /etc/mape_setup_rule.sh

exit 0

肝心のスクリプトは以下の通りだが、生成されるルールは fw3 の頃のルールとはいくつか見栄えが異なる。
まずは、パケットマークを付けるルールが一つだけになっている。これは nftables のルールでは、「ナンバジェネレータの値をポートセットの個数で割った余り(+ オフセット)をそのままパケットマークとして設定する」という記述が可能だからである。
次に、パケットマークごとのジャンプ先チェーンの連想配列を用いていて、ジャンプ先チェーンで SNAT している。従来は、パケットを処理する際にも毎回ルールを舐めていたわけで、このようなところをまとめられるのも nftables の高速化の秘訣の一つなのだろうか。ただ、このようなルールにすると、[Status]-[Firewall] に大量のチェーンが表示されて見づらいので、気になる場合は map 使わない版を使用してほしい。

またルールにヒットしたパケットの数や合計サイズを確認するには、 nftables ではルール中に counter を明記しておく必要がある。しかし、ポートセットに分散させる処理自体には不要なので、最初にポートセットにパケットがちゃんと分散している様子を確認した後で削除しても構わない。なお、各ルールはセットを用いて異なるプロトコル(TCP, UDP, ICMP)についてひとまとめのルールになっている。プロトコルごとのパケットカウントを観察したい場合は、それぞれのルールに分けてもよい。

mape_setup_rule.sh(map 使う版)
#!/bin/sh
IP4=例の計算機で計算した IP アドレス
TUNDEV=プロトコル MAP で作成したインタフェースの名前
PSID=例の計算機で計算した PSID
PREFIX=1024 # v6 プラスなら 4096
BLOCKS=63 # v6 プラスなら 15。
# BLOCKS をあえて小さい値にして残りを自宅サーバなど用ポートにするのもアリ

nft table ip mape_nat
nft delete table ip mape_nat

nft add table ip mape_nat
nft add map ip mape_nat chain_map { type mark : verdict \; }
nft add chain ip mape_nat POSTROUTING { type nat hook postrouting priority 100 \; }
nft add rule mape_nat POSTROUTING oifname map-$TUNDEV meta l4proto { tcp, udp, icmp } mark set numgen inc mod $BLOCKS offset 0x11 counter
nft add rule mape_nat POSTROUTING oifname map-$TUNDEV meta mark vmap @chain_map;

for r in `seq 1 $BLOCKS ` ; do
    mark=$(( r + 0x10 ))
    port_l=$(( r * PREFIX + PSID * 16 ))
    port_r=$((port_l + 15))

    nft add chain ip mape_nat mape_ports$r
    nft add rule ip mape_nat mape_ports$r meta l4proto { tcp, udp, icmp } counter snat to $IP4:$port_l-$port_r persistent
    nft add element ip mape_nat chain_map { $mark : goto mape_ports$r }
done
mape_setup_rule.sh(map 使わない版)
#!/bin/sh
IP4=例の計算機で計算した IP アドレス
TUNDEV=プロトコル MAP で作成したインタフェースの名前
PSID=例の計算機で計算した PSID
PREFIX=1024 # v6 プラスなら 4096
BLOCKS=63 # v6 プラスなら 15
# BLOCKS をあえて小さい値にして残りを自宅サーバなど用ポートにするのもアリ

nft table ip mape_nat
nft delete table ip mape_nat

nft add table ip mape_nat
nft add chain ip mape_nat POSTROUTING { type nat hook postrouting priority 100 \; }
nft add rule mape_nat POSTROUTING oifname map-$TUNDEV meta l4proto { tcp, udp, icmp } mark set numgen inc mod $BLOCKS offset 0x11 counter

for r in `seq 1 $BLOCKS ` ; do
    mark=$(( r + 0x10 ))
    port_l=$(( r * PREFIX + PSID * 16 ))
    port_r=$((port_l + 15))

    nft add rule ip mape_nat POSTROUTING oifname map-$TUNDEV meta l4proto { tcp, udp, icmp } counter mark $mark snat to $IP4:$port_l-$port_r persistent
done

トンネルデバイスの MTU について

先述の通り、トンネルデバイスの MTU は /lib/netifd/proto/map.sh によって 1280 に設定されている。LuCI の [Network]-[Interfaces]-[Devices] から設定するとうまく通信できなくなるようなので、以下の行を編集するとよい。値は書く環境に応じて要調整。

/lib/netifd/proto/map.sh
proto_add_tunnel
json_add_string mode ipip6
- json_add_int mtu "${mtu:-1280}"
+ json_add_int mtu "${mtu:-1460}"
json_add_int ttl "${ttl:-64}"
json_add_string local $(eval "echo \$RULE_${k}_IPV6ADDR")

めでたしめでたし

各ポートセットへ流すチェーンに分散しているようだ。ニチバンのページも問題なく開ける。

firewall

冒頭の R86S をルータにしてスピードテストした結果。以前よりこのくらい出ていて、R86S のパワーが足りずに足を引っ張るということはなさそう。

speedtest

追記

2023/08/06:

  • 23.05-rc2 でも同様の方法が使えることを追記
  • ルール投入スクリプトでトンネルデバイス名(map- で始まる)ではなくトンネルインタフェース名を指定すればよいように修正

関連記事

脚注
  1. https://knowledge.sakura.ad.jp/22636/ ↩︎

  2. 要するに microSD ↩︎

  3. https://github.com/openwrt/openwrt/issues/11972 ↩︎

Discussion