CUBE SUGAR CONTAINER

技術系のこと書きます。

nftables の処理をトレース機能 (meta nftrace) で追跡する

nftables は、Linux の Netfilter サブシステムをバックエンドに実装されたフレームワークのひとつ。 nftables を使うことで、パケットフィルタリングや NAT、パケット分類などを統一的に管理できる。 nftables は、xtables (iptables, ip6tables など) を置き換える後継として開発された。

今回は、その nftables が管理しているルールがどのように動いているかをトレース機能 (meta nftrace) を使って調べる方法について。 nftables のルールをデバッグする際、素朴なやり方ではログやカウンタを用いるやり方がある。 それに比べてトレース機能を使うと、より詳細は情報が得られる。

使った環境は次のとおり。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.2 LTS
Release:    24.04
Codename:   noble
$ uname -srm
Linux 6.8.0-58-generic x86_64
$ nft --version
nftables v1.0.9 (Old Doc Yak #3)

もくじ

下準備

あらかじめ、必要なパッケージをインストールしておく。

$ sudo apt-get -y install nftables iproute2 iputils-ping

実験用の Network Namespace を用意する

ホストを直接使って nftables の実験をすると不都合が多い。 そこで、Network Namespace を使って隔離されたネットワークスタックを用意する。 今回は 2 つの Network Namespace を用意して、それぞれを veth でつなぐ。

まずは Network Namespace を用意する。

$ sudo ip netns add ns1
$ sudo ip netns add ns2

両者をつなぐための veth を作る。

$ sudo ip link add ns1-veth0 type veth peer name ns2-veth0

veth の両端を Network Namespace に所属させる。

$ sudo ip link set ns1-veth0 netns ns1
$ sudo ip link set ns2-veth0 netns ns2

veth デバイスの MAC アドレスをドキュメンテーションアドレスに変更しておく。

$ sudo ip netns exec ns1 ip link set dev ns1-veth0 address 00:00:5E:00:53:01
$ sudo ip netns exec ns2 ip link set dev ns2-veth0 address 00:00:5E:00:53:02

インターフェイスの状態を UP にする。

$ sudo ip netns exec ns1 ip link set ns1-veth0 up
$ sudo ip netns exec ns2 ip link set ns2-veth0 up

インターフェイスにドキュメンテーションアドレスの IP アドレスを付与する。

$ sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0
$ sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0

この状態で、一旦 ping による疎通があるかを確認しておく。

$ sudo ip netns exec ns1 ping -c 3 192.0.2.2 -I 192.0.2.1
PING 192.0.2.2 (192.0.2.2) from 192.0.2.1 : 56(84) bytes of data.
64 bytes from 192.0.2.2: icmp_seq=1 ttl=64 time=0.034 ms
64 bytes from 192.0.2.2: icmp_seq=2 ttl=64 time=0.017 ms
64 bytes from 192.0.2.2: icmp_seq=3 ttl=64 time=0.030 ms

--- 192.0.2.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2074ms
rtt min/avg/max/mdev = 0.017/0.027/0.034/0.007 ms

以降は Network Namespace の ns1 に nftables の設定を投入して実験していく。

トレース対象のルールを追加する

まず、初期状態では nftables に何も設定が入っていない。 nftables に設定されているルールは nft list ruleset コマンドで確認できる。

$ sudo ip netns exec ns1 nft list ruleset

ここに nft -f で設定を投入する。 引数にハイフンを指定することで、標準入力から設定を読み込むことができる。

以下の設定では、inet アドレスファミリの filter テーブルの中に、input チェインがある。 input チェインは type filter hook input なので、Netfilter の input hook 経由でパケットが入ってくる。 そして、input チェインには ip protocol icmp icmp type echo-request というルールが含まれる。 これは ICMPv4 の Echo Request、つまりは Ping の往路と一致するルールになっている。

$ cat << 'EOF' | sudo ip netns exec ns1 nft -f -
table inet filter {
    chain input {
        type filter hook input priority 0; policy accept
        ip protocol icmp icmp type echo-request
    }
}
EOF

上記を実行すると、Network Namespace の ns1 に nftables の設定が入る。 なお、このルールは一致したとしても何もしない。 また、チェインのデフォルトのポリシーが accept なので、パケットはそのまま通過する。

$ sudo ip netns exec ns1 nft list ruleset
table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;
        ip protocol icmp icmp type echo-request
    }
}

上記のルールによってパケットが処理される様子をトレース機能で追跡したい。

トレース用のルールを追加する

続いては、トレース機能を使うためのルールを追加する。 nftables のトレース機能 (meta nftrace) を使うには、パケットにトレースを有効にするメタ情報のフラグをつける必要がある。 トレース機能のメタ情報のフラグが有効になったパケットは、以降の処理でモニター用の機能を使って追跡できるようになる。 そこで、メタ情報のフラグを付与するためのルールが必要になる。

今回は、input hook よりも早く処理される prerouting hook に、トレース用のルールを追加しよう。 以下の設定では、inet アドレスファミリの filter テーブルの中に、prerouting チェインを作っている。 prerouting チェインは type filter hook prerouting なので、Netfilter の prerouting hook 経由でパケットが入ってくる。 そして、prerouting チェインには ip protocol icmp meta nftrace set 1 というルールが含まれる。 これは ICMPv4 のパケットにトレース機能のフラグを付与している。

$ cat << 'EOF' | sudo ip netns exec ns1 nft -f -
table inet filter {
    chain prerouting {
        type filter hook prerouting priority 0; policy accept
        ip protocol icmp meta nftrace set 1
    }
}
EOF

設定を投入すると、ルールセットは次のようになる。

$ sudo ip netns exec ns1 nft list ruleset
table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;
        ip protocol icmp icmp type echo-request
    }

    chain prerouting {
        type filter hook prerouting priority filter; policy accept;
        ip protocol icmp meta nftrace set 1
    }
}

処理の流れをモニターする

この状態で nft monitor trace コマンドを実行しよう。 実行すると出力を待ち受けた状態になる。

$ sudo ip netns exec ns1 nft monitor trace

ここで、別のターミナルを開いて ns2 から ns1 に向けて Ping を打ってみよう。

$ sudo ip netns exec ns2 ping -c 1 192.0.2.1
PING 192.0.2.1 (192.0.2.1) 56(84) bytes of data.
64 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=0.051 ms

--- 192.0.2.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.051/0.051/0.051/0.000 ms

すると、先ほど実行した nft monitor trace コマンドに出力が得られる。

$ sudo ip netns exec ns1 nft monitor trace
trace id 2d55095a inet filter prerouting packet: iif "ns1-veth0" ether saddr 00:00:5e:00:53:02 ether daddr 00:00:5e:00:53:01 ip saddr 192.0.2.2 ip daddr 192.0.2.1 ip dscp cs0 ip ecn not-ect ip ttl 64 ip id 23062 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 3186 icmp sequence 1 @th,64,96 0x455136800000000f21b0100 
trace id 2d55095a inet filter prerouting rule ip protocol icmp meta nftrace set 1 (verdict continue)
trace id 2d55095a inet filter prerouting policy accept 
trace id 2d55095a inet filter input packet: iif "ns1-veth0" ether saddr 00:00:5e:00:53:02 ether daddr 00:00:5e:00:53:01 ip saddr 192.0.2.2 ip daddr 192.0.2.1 ip dscp cs0 ip ecn not-ect ip ttl 64 ip id 23062 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 3186 icmp sequence 1 @th,64,96 0x455136800000000f21b0100 
trace id 2d55095a inet filter input rule ip protocol icmp icmp type echo-request (verdict continue)
trace id 2d55095a inet filter input policy accept 

上記から、以下の部分で ICMP の Echo Request のパケットに prerouting のルールでメタ情報が付与され、そのまま通過してチェインのデフォルトのポリシーで accept された様子が確認できる。

trace id 2d55095a inet filter prerouting packet: iif "ns1-veth0" ether saddr 00:00:5e:00:53:02 ether daddr 00:00:5e:00:53:01 ip saddr 192.0.2.2 ip daddr 192.0.2.1 ip dscp cs0 ip ecn not-ect ip ttl 64 ip id 23062 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 3186 icmp sequence 1 @th,64,96 0x455136800000000f21b0100 
trace id 2d55095a inet filter prerouting rule ip protocol icmp meta nftrace set 1 (verdict continue)
trace id 2d55095a inet filter prerouting policy accept 

同様に、以下の部分では input のルールに一致した後に、そのまま通過してチェインのデフォルトのポリシーで accept されたことが確認できる。

trace id 2d55095a inet filter input packet: iif "ns1-veth0" ether saddr 00:00:5e:00:53:02 ether daddr 00:00:5e:00:53:01 ip saddr 192.0.2.2 ip daddr 192.0.2.1 ip dscp cs0 ip ecn not-ect ip ttl 64 ip id 23062 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 3186 icmp sequence 1 @th,64,96 0x455136800000000f21b0100 
trace id 2d55095a inet filter input rule ip protocol icmp icmp type echo-request (verdict continue)
trace id 2d55095a inet filter input policy accept 

このように、ルールでトレース機能を有効化して、それをモニターすることで nftables の処理の流れを把握できる。

まとめ

今回は nftables で処理の流れを追跡してルールのデバッグに活かすことのできるトレース機能 (meta nftrace) について扱った。

今回の例では、メタ情報を付与するための専用のチェインとルールを追加していた。 しかし、必要な区間でピンポイントにメタ情報を付与して、必要なくなったらメタ情報を取り除くといったことも考えられる。

また、nft monitor trace の出力は場合によっては大量になることから grep(1) などを用いて必要な内容だけに絞り込むのも良いようだ。

参考

wiki.nftables.org