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
다른 테스트 중 일부를 방해하므로 호스트 컴퓨터에서 이 작업을 수행했습니다 .
containerA
IP가 있고 192.168.10.1/24
가 containerB
있습니다 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 패킷은 볼 수 없습니다. 내 이해로는 이것이 나를 놀라게 한다.containerA
containerB
nftables용 브리지 필터 체인 유형forward
, 패킷이 이 단계를 통과하지 못하는 유일한 경우 는 input
호스트(이 경우 containerR
)를 통해 전송되는 경우입니다. 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=0
IIRC가 필요하지 않다는 것입니다.
답변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 패킷의 체인 통과:
먼저 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 forward
OP 우선순위에서 인터럽트가 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.11
CONFIG_NETFILTER_NETLINK_HOOK
openvswitch 브리지에서는 테스트되지 않았습니다.
br_netfilter
최종 참고 사항: 가능하면 실행 시 Docker 또는 커널 모듈을 사용하지 마세요.회로망실험. 내 재생산에서 볼 수 있듯이 ip netns
네트워킹만 포함된 경우 실험만 단독으로 사용하는 것은 매우 쉽습니다(실험에 데몬이 필요한 경우(예: OpenVPN) 이는 더 어려울 수 있습니다).