CUBE SUGAR CONTAINER

技術系のこと書きます。

nftablesは同じフックで優先度が後ろのルールがあるとaccept済みのパケットが再び評価される

nftables の公式 Wiki を眺めていたところ、気になる記述があった。 どうやら、nftables は同じフックポイントで、優先度が異なるチェーンがあるときに注意を要する振る舞いを示すようだ。

nftables の公式 Wiki の記述 1 を以下に引用する。

NOTE: If a packet is accepted and there is another chain, bearing the same hook type and with a later priority, then the packet will subsequently traverse this other chain. Hence, an accept verdict - be it by way of a rule or the default chain policy - isn't necessarily final. However, the same is not true of packets that are subjected to a drop verdict. Instead, drops take immediate effect, with no further rules or chains being evaluated.

以下に拙訳する。

注意: もしパケットが accept されても、他に同じフックタイプでより後ろの優先度のチェーンがあると、パケットはその別のチェーンを通過します。したがって、accept 判定はルールによるものであっても、デフォルトのチェーンポリシーであっても、それは必ずしも最終的なものではありません。ただし、drop 判定のパケットは同じことが当てはまりません。代わりに drop は即座に影響し、さらなるルールやチェインでは評価されません。

上記を見ると、一旦 accept されたパケットが異なるチェーンで再び評価されるらしい。 ただし、drop されたパケットについては再び評価されることはないようだ。 今回は、この振る舞いについて実際に動かして検証してみる。

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

$ 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 の設定を投入して実験していく。

同じフックで優先度が異なるルール (accept -> drop) を用意する

以下のコマンドでは Network Namespace の ns1 に nftables の設定を入れている。 まず、prerouting チェーンでは、prerouting フックでデバッグ用に ICMPv4 のパケットにトレース用のフラグを付与している。 これにより nft monitor trace でパケットを追跡できる。 input_pri0 チェーンでは、input フックの priority 0 で ICMPv4 の Echo-Request を accept している。 そして input_pri1 チェーンでは、同じ input フックの priority 1 で ICMPv4 の Echo-Request を drop している。 優先度では input_pri0 の方が input_pri1 よりも前になる。 つまり、ドキュメントの記述通りであれば input_pri0 で accept されたパケットは input_pri1 で再び評価されて drop されるはず。

$ cat << 'EOF' | sudo ip netns exec ns1 nft -f -
#!/usr/sbin/nft -f

flush ruleset

table inet filter {

    chain prerouting {
        type filter hook prerouting priority 0; policy accept
        # すべての ICMPv4 にトレース機能を有効にするメタ情報を付与する
        ip protocol icmp meta nftrace set 1
    }

   chain input_pri0 {
       # hook が input で priority 0   
       type filter hook input priority 0; policy accept;
       # ICMP Echo-Request を通す
       ip protocol icmp icmp type echo-request accept
   }

   chain input_pri1 {
       # hook が input で priority 1
       type filter hook input priority 1; policy accept;
       # ICMP Echo-Request を落とす
       ip protocol icmp icmp type echo-request drop
   }
}
EOF

設定が入ったことを確認する。

$ sudo ip netns exec ns1 nft -y list ruleset
table inet filter {
        chain prerouting {
                type filter hook prerouting priority 0; policy accept;
                ip protocol icmp meta nftrace set 1
        }

        chain input_pri0 {
                type filter hook input priority 0; policy accept;
                ip protocol icmp icmp type echo-request accept
        }

        chain input_pri1 {
                type filter hook input priority 1; policy accept;
                ip protocol icmp icmp type echo-request drop
        }
}

次に、パケットを追跡するために 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.

--- 192.0.2.1 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

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

$ sudo ip netns exec ns1 nft monitor trace
trace id 7a7d7959 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 398 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 2566 icmp sequence 1 @th,64,96 0xd893146800000000c2f80200
trace id 7a7d7959 inet filter prerouting rule ip protocol icmp meta nftrace set 1 (verdict continue)
trace id 7a7d7959 inet filter prerouting policy accept
trace id 7a7d7959 inet filter input_pri0 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 398 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 2566 icmp sequence 1 @th,64,96 0xd893146800000000c2f80200
trace id 7a7d7959 inet filter input_pri0 rule ip protocol icmp icmp type echo-request accept (verdict accept)
trace id 7a7d7959 inet filter input_pri1 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 398 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 2566 icmp sequence 1 @th,64,96 0xd893146800000000c2f80200
trace id 7a7d7959 inet filter input_pri1 rule ip protocol icmp icmp type echo-request drop (verdict drop)

上記で、以下はトレース機能のメタ情報を付与している様子 (meta nftrace set 1) を表している。

trace id 7a7d7959 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 398 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 2566 icmp sequence 1 @th,64,96 0xd893146800000000c2f80200
trace id 7a7d7959 inet filter prerouting rule ip protocol icmp meta nftrace set 1 (verdict continue)
trace id 7a7d7959 inet filter prerouting policy accept

そして、以下で input_pri0 でパケットが accept されている。

trace id 7a7d7959 inet filter input_pri0 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 398 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 2566 icmp sequence 1 @th,64,96 0xd893146800000000c2f80200
trace id 7a7d7959 inet filter input_pri0 rule ip protocol icmp icmp type echo-request accept (verdict accept)

しかし、同じ input フックで、より優先度が後ろの input_pri1 があるためパケットが再び評価される。 以下では input_pri1 で ICMPv4 の Echo Request が drop されている。

trace id 7a7d7959 inet filter input_pri1 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 398 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 2566 icmp sequence 1 @th,64,96 0xd893146800000000c2f80200
trace id 7a7d7959 inet filter input_pri1 rule ip protocol icmp icmp type echo-request drop (verdict drop)

ドキュメントにある通りの振る舞いを示すことが上記から確認できた。

同じフックで優先度が異なるルール (drop -> accept) を用意する

念の為、逆のパターンも確認しておこう。 同じフックで、より優先度が前のチェーンで drop されると、後ろのチェーンでは評価されないはず。

以下のように、先ほどと accept と drop するチェーンを入れ替えた設定を投入する。 今回は input_pri0 で drop して、input_pri1 で accept している。

$ cat << 'EOF' | sudo ip netns exec ns1 nft -f -
#!/usr/sbin/nft -f

flush ruleset

table inet filter {

    chain prerouting {
        type filter hook prerouting priority 0; policy accept
        # すべての ICMPv4 にトレース機能を有効にするメタ情報を付与する
        ip protocol icmp meta nftrace set 1
    }

   chain input_pri0 {
       # hook が input で priority 0   
       type filter hook input priority 0; policy accept;
       # ICMP Echo-Request を落とす
       ip protocol icmp icmp type echo-request drop
   }

   chain input_pri1 {
       # hook が input で priority 1
       type filter hook input priority 1; policy accept;
       # ICMP Echo-Request を通す
       ip protocol icmp icmp type echo-request accept
   }
}
EOF

投入した設定を確認する。

$ sudo ip netns exec ns1 nft -y list ruleset
table inet filter {
        chain prerouting {
                type filter hook prerouting priority 0; policy accept;
                ip protocol icmp meta nftrace set 1
        }

        chain input_pri0 {
                type filter hook input priority 0; policy accept;
                ip protocol icmp icmp type echo-request drop
        }

        chain input_pri1 {
                type filter hook input priority 1; policy accept;
                ip protocol icmp icmp type echo-request accept
        }
}

再び nft monitor torace コマンドを実行しておく。

$ 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.

--- 192.0.2.1 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

nft monitor trace に次のような出力が得られる。

$ sudo ip netns exec ns1 nft monitor trace
trace id ea548659 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 52494 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 10208 icmp sequence 1 @th,64,96 0x9fb71968000000002fb80000
trace id ea548659 inet filter prerouting rule ip protocol icmp meta nftrace set 1 (verdict continue)
trace id ea548659 inet filter prerouting policy accept
trace id ea548659 inet filter input_pri0 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 52494 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 10208 icmp sequence 1 @th,64,96 0x9fb71968000000002fb80000
trace id ea548659 inet filter input_pri0 rule ip protocol icmp icmp type echo-request drop (verdict drop)

以下では先ほどと同じようにトレース機能のメタ情報をパケットに付与している。

trace id ea548659 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 52494 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 10208 icmp sequence 1 @th,64,96 0x9fb71968000000002fb80000
trace id ea548659 inet filter prerouting rule ip protocol icmp meta nftrace set 1 (verdict continue)
trace id ea548659 inet filter prerouting policy accept

次に以下では input_pri0 でパケットが drop されている。

trace id ea548659 inet filter input_pri0 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 52494 ip protocol icmp ip length 84 icmp type echo-request icmp code net-unreachable icmp id 10208 icmp sequence 1 @th,64,96 0x9fb71968000000002fb80000
trace id ea548659 inet filter input_pri0 rule ip protocol icmp icmp type echo-request drop (verdict drop)

そして、以降はトレース情報の出力がない。 したがって、drop された後は別のチェーンで処理されていない。 ドキュメント通りの振る舞いが確認できた。

いじょう。