NetfilterとフォワーディングチェーンはARPを追跡しますが、他のパケットは追跡しません。

NetfilterとフォワーディングチェーンはARPを追跡しますが、他のパケットは追跡しません。

Dockerコンテナでnetfilterを試しています。 3つのコンテナがあります。 1つは「ルーター」、2つは「エンドポイント」です。それらはそれぞれ以下を介して接続されます。pipeworkしたがって、各エンドポイント<->ルーター接続に対して外部(ホスト)ブリッジがあります。このような:

containerA (eth1) -- hostbridgeA -- (eth1) containerR
containerB (eth1) -- hostbridgeB -- (eth2) containerR

containerRその後、「ルーター」コンテナには次のように構成されたブリッジがあります。br0

bridge name bridge id          STP enabled     interfaces
br0         8000.3a047f7a7006  no              eth1
                                               eth2

net.bridge.bridge-nf-call-iptables=0他のテストの一部が邪魔になったため、ホストコンピュータでこれを行いました。

containerAIPがあり、あり192.168.10.1/24ます。containerB192.168.10.2/24

その後、転送されたパケットを追跡する非常に単純なルールセットがあります。

flush ruleset

table bridge filter {
    chain forward {
        type filter hook forward priority 0; policy accept;
        meta nftrace set 1
    }
}

これにより、ICMPパケットは追跡されず、ARPパケットのみが追跡されることがわかりました。つまり、is pingnft monitor中に実行すると、追跡されたARPパケットを見ることができますが、ICMPパケットは見ることができません。私の理解では、これは私を驚かせます。containerAcontainerBnftables用ブリッジフィルタチェーンタイプforward、パケットがこのステップを通過できない唯一の場合は、inputホスト(この場合containerR)を介して送信される場合です。 Linuxパケットフローチャートによると:

NetfilterとLinuxのパケットフロー

私はまだICMPパケットがARPのように転送パスを使用したいと思います。私する事前パスとポストパスを追跡したら、パケットを見てください。それで私の質問は、ここで何が起こっているのでしょうか?私が認識していないFlowtableやその他の段落はありますか?コンテナネットワーキングおよび/またはDockerに固有のものですか?コンテナの代わりにVMを確認することもできますが、他の人がこれを知っていたり、この問題を経験した場合は興味があるかもしれません。

編集する:その後、Alpine仮想マシンセットを使用してVirtualBoxで同様の設定を作成しました。 ICMPパケットするforwardチェーンに達するので、ホストやDockerの何かが私の期待を妨げているようです。私または他の人が原因を特定できるまで、この質問に答えないでください。誰かがこの方法が有用であることを知っている場合に備えているからです。

ありがとうございます!

再現可能な最小例

これを行うには、仮想マシンでAlpine Linux 3.19.1を使用し、次の場所communityでリポジトリを有効にします/etc/apk/respositories

# Prerequisites of host
apk add bridge bridge-utils iproute2 docker openrc
service docker start

# When using linux bridges instead of openvswitch, disable iptables on bridges
sysctl net.bridge.bridge-nf-call-iptables=0

# Pipework to let me avoid docker's IPAM
git clone https://github.com/jpetazzo/pipework.git
cp pipework/pipework /usr/local/bin/

# Create two containers each on their own network (bridge)
pipework brA $(docker create -itd --name hostA alpine:3.19) 192.168.10.1/24
pipework brB $(docker create -itd --name hostB alpine:3.19) 192.168.10.2/24

# Create bridge-filtering container then connect it to both of the other networks
R=$(docker create --cap-add NET_ADMIN -itd --name hostR alpine:3.19)
pipework brA -i eth1 $R 0/0
pipework brB -i eth2 $R 0/0
# Note: `hostR` doesn't have/need an IP address on the bridge for this example

# Add bridge tools and netfilter to the bridging container
docker exec hostR apk add bridge bridge-utils nftables
docker exec hostR brctl addbr br
docker exec hostR brctl addif br eth1 eth2
docker exec hostR ip link set dev br up

# hostA should be able to ping hostB
docker exec hostA ping -c 1 192.168.10.2
# 64 bytes from 192.168.10.2...

# Set nftables rules
docker exec hostR nft add table bridge filter
docker exec hostR nft add chain bridge filter forward '{type filter hook forward priority 0;}'
docker exec hostR nft add rule bridge filter forward meta nftrace set 1

# Now ping hostB from hostA while nft monitor is running...
docker exec hostA ping -c 4 192.168.10.2 & docker exec hostR nft monitor

# Ping will succeed, nft monitor will not show any echo-request/-response packets traced, only arps
# Example:
trace id abc bridge filter forward packet: iif "eth2" oif "eth1" ether saddr ... daddr ... arp operation request
trace id abc bridge filter forward rule meta nfrtrace set 1 (verdict continue)
trace id abc bridge filter forward verdict continue
trace id abc bridge filter forward policy accept
...
trace id def bridge filter forward packet: iif "eth1" oif "eth2" ether saddr ... daddr ... arp operation reply
trace id def bridge filter forward rule meta nfrtrace set 1 (verdict continue)
trace id def bridge filter forward verdict continue
trace id def bridge filter forward policy accept

# Add tracing in prerouting and the icmp packets are visible:
docker exec hostR nft add chain bridge filter prerouting '{type filter hook prerouting priority 0;}'
docker exec hostR nft add rule bridge filter prerouting meta nftrace set 1

# Run again
docker exec hostA ping -c 4 192.168.10.2 & docker exec hostR nft monitor
# Ping still works (obviously), but we can see its packets in prerouting, which then disappear from the forward chain, but ARP shows up in both.
# Example:
trace id abc bridge filter prerouting packet: iif "eth1" ether saddr ... daddr ... ... icmp type echo-request ...
trace id abc bridge filter prerouting rule meta nfrtrace set 1 (verdict continue)
trace id abc bridge filter prerouting verdict continue
trace id abc bridge filter prerouting policy accept
...
trace id def bridge filter prerouting packet:  iif "eth2" ether saddr ... daddr ... ... icmp type echo-reply ...
trace id def bridge filter prerouting rule meta nfrtrace set 1 (verdict continue)
trace id def bridge filter prerouting verdict continue
trace id def bridge filter prerouting policy accept
...
trace id 123 bridge filter prerouting packet: iif "eth1" ether saddr ... daddr ... ... arp operation request
trace id 123 bridge filter prerouting rule meta nfrtrace set 1 (verdict continue)
trace id 123 bridge filter prerouting verdict continue
trace id 123 bridge filter prerouting policy accept
trace id 123 bridge filter forward packet: iif "eth1" oif "eth2" ether saddr ... daddr ... arp operation request
trace id 123 bridge filter forward rule meta nfrtrace set 1 (verdict continue)
trace id 123 bridge filter forward verdict continue
trace id 123 bridge filter forward policy accept
...
trace id 456 bridge filter prerouting packet:  iif "eth2" ether saddr ... daddr ... ... arp operation reply
trace id 456 bridge filter prerouting rule meta nfrtrace set 1 (verdict continue)
trace id 456 bridge filter prerouting verdict continue
trace id 456 bridge filter prerouting policy accept
trace id 456 bridge filter forward packet: iif "eth2" oif "eth1" ether saddr ... daddr ... arp operation reply
trace id 456 bridge filter forward rule meta nfrtrace set 1 (verdict continue)
trace id 456 bridge filter forward verdict continue
trace id 456 bridge filter forward policy accept
# Note the trace id matching across prerouting and forward chains

openvswitchでも試しましたが、単純化のためにLinuxブリッジの例を使用しましたが、関係なく同じ結果が生成されます。 openvswitchとの唯一の違いは、net.bridge.bridge-nf-call-iptables=0IIRCが必要ないことです。

答え1

導入および簡略化されたレンダラー設定

ドッカーロードbr_netfilter基準寸法。読み込まれると、既存のすべてのアイテムに影響します。そして未来ネットワークネームスペース。これは、の説明に従って歴史的および互換性の理由からです。この質問に対する私の答え

したがって、これがホストシステムで完了すると、次のようになります。

service docker start

# When using linux bridges instead of openvswitch, disable iptables on bridges
sysctl net.bridge.bridge-nf-call-iptables=0

これはホストネットワークの名前空間にのみ影響します。将来生成されるネットワーク名前空間は、次のようになりhostRます。

# docker exec hostR sysctl net.bridge.bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 1

以下は、OPよりはるかに簡単なエラー再現です。 Dockerや仮想マシンはまったく必要ありません。現在、Linuxホストで実行でき、影響を受ける名前付きネットワーク名前空間iproute2内にブリッジをパッケージ化して作成するだけです。hostR

#!/bin/sh

modprobe br_netfilter # as would have done Docker

sysctl net.bridge.bridge-nf-call-iptables=0 # actually it won't matter: netns hostR will still get 1 when created

ip netns add hostA
ip netns add hostB
ip netns add hostR

ip -n hostR link add name br address 02:00:00:00:01:00 up type bridge
ip -n hostR link add name eth1 up master br type veth peer netns hostA name eth1
ip -n hostR link add name eth2 up master br type veth peer netns hostB name eth1

ip -n hostA addr add dev eth1 192.168.10.1/24
ip -n hostA link set eth1 up
ip -n hostB addr add dev eth1 192.168.10.2/24
ip -n hostB link set eth1 up

ip netns exec hostR nft -f - <<'EOF'
table bridge filter        # for idempotence
delete table bridge filter # for idempotence

table bridge filter {
    chain forward {
        type filter hook forward priority 0;
        meta nftrace set 1
    }
}
EOF

ネットワークネームスペースにはbr_netfilterまだデフォルト設定があります。hostR

# ip netns exec hostR sysctl net.bridge.bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 1

片側で実行:

ip netns exec hostR nft monitor trace

そして他の場所では:

ip netns exec hostA ping -c 4 192.168.10.2

問題が発生します。 IPv4は表示されず、ARPのみが表示されます(通常の遅延ARPアップデートでは通常数秒の遅延が発生します)。これはカーネル6.6.x以下では常にトリガーされ、カーネル6.7.x以降ではトリガーされる場合とそうでない場合があります(下記参照)。


影響br_netfilter

このモジュールは通常ルーティングルートに使用されますが、現在はブリッジルートにも使用されているブリッジルートとIPv4のNetfilterフックとの間の相互作用を生成します。ここIPv4フックみんなiptablesそしてnftables自宅でip(これはARPとIPv6でも発生します。IPv6は使用されず、これ以上議論されません)。

これは、フレームがNetfilterフックに到達することを意味します。Linux ベースのブリッジでの ebtables/iptables 対話: 5. ブリッジされた IP パケットのチェーンパス:

  1. ブリッジされたIPパケットのチェーンパス

    ブリッジされたパケットは、レイヤ1(リンクレイヤ)の上のネットワークコードには決して入りません。したがって、ブリッジされたIPパケット/フレームにはIPコードは入力されません。したがって、IPパケットがブリッジコードにある場合は、すべてのiptablesチェーンを通過します。チェーンパトロールは次のとおりです。

    図 5. ブリッジされた IP パケットのチェーンパス

    図 5. ブリッジされた IP パケットのチェーンパス

まずbridge filter forward(青色)到着し、次にip filter forward(緑色)到着する必要があります...

...しかし、元のフックの優先順位が変更され、上記のボックスの順序が変更された場合はそうではありません。ブリッジシリーズの元のフック優先順位は次のとおりです。nft(8):

表 7. ブリッジファミリの標準優先順位名とフック互換性

名前 つながる
宛先アドレス -300 事前ルーティング
フィルター -200 みんな
出る 100 出力
srcnat 300 ポストルーティング

したがって、上記の回路図では、フィルタ転送が優先順位0ではなく-200に接続されることが予想されます。 0を使用すると、すべての賭けがキャンセルされます。

実際、次のオプションでコンパイルされたカーネルを実行するときCONFIG_NETFILTER_NETLINK_HOOKnft list hooks'を含む、現在の名前空間で使用されているすべてのフックを照会するために使用できますbr_netfilter。カーネル 6.6.x 以下の場合:

# ip netns exec hostR nft list hooks
family ip {
    hook prerouting {
        -2147483648 ip_sabotage_in [br_netfilter]
    }
    hook postrouting {
        -0000000225 apparmor_ip_postroute
    }
}
family ip6 {
    hook prerouting {
        -2147483648 ip_sabotage_in [br_netfilter]
    }
    hook postrouting {
        -0000000225 apparmor_ip_postroute
    }
}
family bridge {
    hook prerouting {
         0000000000 br_nf_pre_routing [br_netfilter]
    }
    hook input {
        +2147483647 br_nf_local_in [br_netfilter]
    }
    hook forward {
        -0000000001 br_nf_forward_ip [br_netfilter]
         0000000000 chain bridge filter forward [nf_tables]
         0000000000 br_nf_forward_arp [br_netfilter]
    }
    hook postrouting {
        +2147483647 br_nf_post_routing [br_netfilter]
    }
}

br_netfilterこのネットワークネームスペースで無効になっていないカーネルモジュールがIPv4では-1にマウントされ、ARPでは再び0にマウントされることがわかります。予想されるマウント順序が満たされず、bridge filter forwardOP優先順位で割り込みが0で発生しました。

カーネル6.7.x以降では、犯罪、レンダラーの実行後、デフォルトの順序が変更されます。

# ip netns exec hostR nft list hooks
[...]
family bridge {
    hook prerouting {
         0000000000 br_nf_pre_routing [br_netfilter]
    }
    hook input {
        +2147483647 br_nf_local_in [br_netfilter]
    }
    hook forward {
         0000000000 chain bridge filter forward [nf_tables]
         0000000000 br_nf_forward [br_netfilter]
    }
    hook postrouting {
        +2147483647 br_nf_post_routing [br_netfilter]
    }
}

転送を処理するために優先順位0でのみフックするように単純化されていますが、br_netfilter今重要なのは後ろに bridge filter forward:予想される順序であり、OPの問題は発生しません。

同じ優先順位を持つ2つのフックは未定義の動作と見なされるため、これは脆弱な設定です。単に次のコマンドを実行して、ここ(少なくともカーネル6.7.x)で問題をトリガーできます。

rmmod br_netfilter
modprobe br_netfilter

これで順序が変更されました。

[...]
    hook forward {
         0000000000 br_nf_forward [br_netfilter]
         0000000000 chain bridge filter forward [nf_tables]
    }
[...]

問題を再び触発させるのは、br_netfilterまた過去だからだbridge filter forward

これを避ける方法

ネットワーク名前空間(またはコンテナ)でこの問題を解決するには、次のいずれかのオプションを選択します。

  • br_netfilterまったくロードされません

    ホストマシンから:

    rmmod br_netfilter
    
  • br_netfilterまたは、追加のネットワーク名前空間で効果を無効にします。

    説明したように、すべての新しいネットワーク名前空間は再びこの機能は作成時に有効になります。重要な場所(hostRネットワークネームスペース)では無効にする必要があります。

    ip netns exec hostR sysctl net.bridge.bridge-nf-call-iptables=0
    

    完了すると、すべてのbr_netfilterフックが消えて、hostR予期しない注文が発生してももはや中断されません。

    注意事項があります。 Dockerのみを使用している場合は機能しません。

    # docker exec hostR sysctl net.bridge.bridge-nf-call-iptables=0
    sysctl: error setting key 'net.bridge.bridge-nf-call-iptables': Read-only file system
    # docker exec --privileged hostR sysctl net.bridge.bridge-nf-call-iptables=0
    sysctl: error setting key 'net.bridge.bridge-nf-call-iptables': Read-only file system
    

    これは、Dockerは一部の設定がコンテナによって改ざんされるのを防ぐために保護するためです。

    代わりに、コンテナのネットワーク名前空間をマウントするようにバインドする必要があります。次の方法でマウント名前空間を取得しなくてもip netns attach ...使用できます。ip netns exec ...

    ip netns attach hostR $(docker inspect --format '{{.State.Pid}}' hostR)
    

    これで、以前のコマンドが実行され、コンテナに影響を与えることができます。

    ip netns exec hostR sysctl net.bridge.bridge-nf-call-iptables=0
    
  • bridge filter forwardまたは、最初に発生するように保証された優先順位を使用してください。

    上の表に示すように、priority forwardブリッジファミリのデフォルト優先順位()は-200です。したがって、-200 を使用するか、-2 の最大値は常にbr_netfilterカーネルバージョンの前に発生します。

    ip netns exec hostR nft delete chain bridge filter forward
    ip netns exec hostR nft add chain bridge filter forward '{ type filter hook forward priority -200; }'
    ip netns exec hostR nft add rule bridge filter forward meta nftrace set 1
    

    あるいは、Dockerを使用している場合も同様です。

    docker exec hostR nft delete chain bridge filter forward
    docker exec hostR nft add chain bridge filter forward '{ type filter hook forward priority -200; }'
    docker exec hostR nft add rule bridge filter forward meta nftrace set 1
    

テスト対象:

  • (OP's) アルパイン 3.19.1
  • Debian 12.5 と
    • ストック Debian カーネル 6.1.x
    • 6.6.xとCONFIG_NETFILTER_NETLINK_HOOK
    • 6.7.11CONFIG_NETFILTER_NETLINK_HOOK

openvswitch ブリッジではテストされていません。


br_netfilter最後の注意:可能であれば、実行時にDockerまたはカーネルモジュールを使用しないでください。ネットワーク実験。私の再現に見られるように、ip netnsネットワーキングだけが含まれている場合、実験のみを単独で使用するのは非常に簡単です(実験にデーモンが必要な場合(OpenVPNなど)、これはより難しいかもしれません)。

関連情報