Netfilter 및 전달 체인은 ARP를 추적하지만 다른 패킷은 추적하지 않습니다.

Netfilter 및 전달 체인은 ARP를 추적하지만 다른 패킷은 추적하지 않습니다.

Docker 컨테이너에서 netfilter를 실험하고 있습니다. 세 개의 컨테이너가 있습니다. 하나는 "라우터"이고 두 개는 "엔드포인트"입니다. 그들은 각각 다음을 통해 연결됩니다.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/24containerB있습니다 192.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 pinging nft 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_HOOK,nft 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 문제를 일으키지 않습니다.

동일한 우선순위를 가진 두 개의 후크는 정의되지 않은 동작으로 간주되므로 이는 취약한 설정입니다. 간단히 다음 명령을 실행하여 여기(적어도 커널 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는 일부 설정이 컨테이너에 의해 변조되는 것을 방지하기 위해 보호하기 때문입니다.

    대신, 컨테이너의 네트워크 네임스페이스는 마운트되도록 바인딩되어야(used ) 다음과 같은 방법으로 마운트 네임스페이스를 얻지 않고도 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
  • 데비안 12.5 및
    • 재고 데비안 커널 6.1.x
    • 6.6.x 및CONFIG_NETFILTER_NETLINK_HOOK
    • 6.7.11CONFIG_NETFILTER_NETLINK_HOOK

openvswitch 브리지에서는 테스트되지 않았습니다.


br_netfilter최종 참고 사항: 가능하면 실행 시 Docker 또는 커널 모듈을 사용하지 마세요.회로망실험. 내 재생산에서 볼 수 있듯이 ip netns네트워킹만 포함된 경우 실험만 단독으로 사용하는 것은 매우 쉽습니다(실험에 데몬이 필요한 경우(예: OpenVPN) 이는 더 어려울 수 있습니다).

관련 정보