Background
I have a linux machine with bridge interfaces as shown below...
---{prenat}--> ---{postnat}-->
source: 172.25.0.3 source: 192.0.2.1
+--------------------+ +-----------------------+
| br-2f0c8e39d468 |------{linux}----| br-dee49672169b |
| 172.25.0.0/16 | | 192.0.2.0/24 |
| Docker Compose | | containerlab Docker |
| Hosts | | Hosts |
+--------------------+ +-----------------------+
I want to NAT the IPv4 source address of any traffic from br-2f0c8e39d468
to br-dee49672169b
using the interface IP address of br-dee49672169b
(192.0.2.1).
$ ip route show
default via 10.100.50.1 dev ens160 proto static
10.100.50.0/28 dev ens160 proto kernel scope link src 10.100.50.5
172.16.0.0/16 via 192.0.2.2 dev br-dee49672169b
172.25.0.0/16 dev br-2f0c8e39d468 proto kernel scope link src 172.25.0.1
192.0.2.0/24 dev br-dee49672169b proto kernel scope link src 192.0.2.1
$
Docker Compose bridge
This is my Docker Compose yaml for the zabbix br-2f0c8e39d468
segment...
version: '3.3'
services:
# Zabbix database
zabbix-db:
container_name: zabbix-db
image: mariadb:10.11.4
restart: always
volumes:
- ${ZABBIX_DATA_PATH}/zabbix-db/mariadb:/var/lib/mysql:rw
- ${ZABBIX_DATA_PATH}/zabbix-db/backups:/backups
command:
- mariadbd
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_bin
- --default-authentication-plugin=mysql_native_password
environment:
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
stop_grace_period: 1m
networks:
- statics
# Zabbix server
zabbix-server:
container_name: zabbix-server
image: zabbix/zabbix-server-mysql:ubuntu-6.4-latest
restart: always
ports:
- 10051:10051
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ZABBIX_DATA_PATH}/zabbix-server/alertscripts:/usr/lib/zabbix/alertscripts:ro
- ${ZABBIX_DATA_PATH}/zabbix-server/externalscripts:/usr/lib/zabbix/externalscripts:ro
- ${ZABBIX_DATA_PATH}/zabbix-server/dbscripts:/var/lib/zabbix/dbscripts:ro
- ${ZABBIX_DATA_PATH}/zabbix-server/export:/var/lib/zabbix/export:rw
- ${ZABBIX_DATA_PATH}/zabbix-server/modules:/var/lib/zabbix/modules:ro
- ${ZABBIX_DATA_PATH}/zabbix-server/enc:/var/lib/zabbix/enc:ro
- ${ZABBIX_DATA_PATH}/zabbix-server/ssh_keys:/var/lib/zabbix/ssh_keys:ro
- ${ZABBIX_DATA_PATH}/zabbix-server/mibs:/var/lib/zabbix/mibs:ro
environment:
- MYSQL_ROOT_USER=root
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- DB_SERVER_HOST=zabbix-db
- ZBX_STARTPINGERS=${ZBX_STARTPINGERS}
depends_on:
- zabbix-db
stop_grace_period: 30s
sysctls:
- net.ipv4.ip_local_port_range=1024 65000
- net.ipv4.conf.all.accept_redirects=0
- net.ipv4.conf.all.secure_redirects=0
- net.ipv4.conf.all.send_redirects=0
networks:
- statics
# Zabbix web UI
zabbix-web:
build:
context: .
dockerfile: Dockerfile
container_name: zabbix-web
image: zabbix/zabbix-web-nginx-mysql:ubuntu-6.4-latest
restart: always
ports:
- 9000:8080
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ZABBIX_DATA_PATH}/zabbix-web/nginx:/etc/ssl/nginx:ro
- ${ZABBIX_DATA_PATH}/zabbix-web/modules/:/usr/share/zabbix/modules/:ro
environment:
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- DB_SERVER_HOST=zabbix-db
- ZBX_SERVER_HOST=zabbix-server
- ZBX_SERVER_NAME=Zabbix Docker
- PHP_TZ=America/Chicago
depends_on:
- zabbix-db
- zabbix-server
stop_grace_period: 10s
networks:
- statics
networks:
statics:
driver: macvlan
containerlab bridge
This is the yaml for the Cisco CSR1000V containerlab bridged segment on 192.0.2.0/24...
name: rr
mgmt:
network: statics
ipv4-subnet: 192.0.2.0/24
ipv4-range: 192.0.2.0/24
# ACCESS for linux:
# docker exec -it <container_name> bash
# ACCESS for frr:
# docker exec -it <container_name> vtysh
# ACCESS for srlinux:
# docker exec -it <container_name> sr_cli
# ACCESS for vr-csr:
# telnet <container_ip> 5000
topology:
nodes:
csr01:
kind: vr-csr
image: vrnetlab/vr-csr:16.12.08
startup-config: config/csr01/config.txt
mgmt-ipv4: 192.0.2.2
csr02:
kind: vr-csr
image: vrnetlab/vr-csr:16.12.08
startup-config: config/csr02/config.txt
mgmt-ipv4: 192.0.2.3
csr03:
kind: vr-csr
image: vrnetlab/vr-csr:16.12.08
startup-config: config/csr03/config.txt
mgmt-ipv4: 192.0.2.6
PC01:
kind: linux
image: ubuntu:22.04
mgmt-ipv4: 192.0.2.4
PC02:
kind: linux
image: ubuntu:22.04
mgmt-ipv4: 192.0.2.5
#image: netenglabs/suzieq:latest
# Manual creation of bridge required before deploying the topology
# sudo brctl addbr br-clab
br-clab:
kind: bridge
links:
- endpoints: ["csr01:eth3", "csr02:eth3"]
- endpoints: ["csr01:eth4", "csr02:eth4"]
- endpoints: ["csr03:eth3", "csr01:eth5"]
- endpoints: ["csr03:eth4", "csr02:eth5"]
- endpoints: ["PC01:eth1", "csr01:eth6"]
- endpoints: ["PC02:eth1", "csr02:eth6"]
- endpoints: ["br-clab:eth1", "csr01:eth2"]
- endpoints: ["br-clab:eth2", "csr02:eth2"]
- endpoints: ["br-clab:eth3", "csr03:eth2"]
What I tried
To circumvent the DOCKER-ISOLATION-*
chains, I used this...
sudo iptables -I INPUT -i br-2f0c8e39d468 -j ACCEPT
sudo iptables -I FORWARD -i br-2f0c8e39d468 -j ACCEPT
sudo iptables -I FORWARD -o br-2f0c8e39d468 -j ACCEPT
This results in the following iptables
rules
$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
Chain FORWARD (policy DROP)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
DOCKER-USER all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
DOCKER all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (4 references)
target prot opt source destination
ACCEPT tcp -- anywhere 172.25.0.2 tcp dpt:http-alt
ACCEPT tcp -- anywhere 172.25.0.3 tcp dpt:zabbix-trapper
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
RETURN all -- anywhere anywhere
Chain DOCKER-ISOLATION-STAGE-2 (4 references)
target prot opt source destination
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
DROP all -- anywhere anywhere
RETURN all -- anywhere anywhere
Chain DOCKER-USER (1 references)
target prot opt source destination
ACCEPT all -- anywhere anywhere /* set by containerlab */
I used:
$ sudo sysctl net.bridge.bridge-nf-call-iptables=1
$ sudo sysctl net.bridge.bridge-nf-call-arptables=1
$ sudo sysctl -w net.ipv4.ip_forward=1
I also used sudo iptables -t nat -A POSTROUTING -o br-dee49672169b -j MASQUERADE
.
When I ping a 192.0.2.2 Docker containerlab container from 172.25.0.3 Docker Compose system and I sniff the 192.0.2.0/24 bridge interface on the {linux}
host, I see:
19:32:31.870772 IP 192.0.2.1 > 192.0.2.2: ICMP echo request, id 10775, seq 0, length 64
19:32:31.870807 IP 192.0.2.2 > 172.25.0.3: ICMP echo reply, id 10775, seq 0, length 64
19:32:32.871777 IP 192.0.2.1 > 192.0.2.2: ICMP echo request, id 10775, seq 1, length 64
19:32:32.871811 IP 192.0.2.2 > 172.25.0.3: ICMP echo reply, id 10775, seq 1, length 64
19:32:33.871761 IP 192.0.2.1 > 192.0.2.2: ICMP echo request, id 10775, seq 2, length 64
19:32:33.871794 IP 192.0.2.2 > 172.25.0.3: ICMP echo reply, id 10775, seq 2, length 64
As you can see, the NAT is applied when sending to 192.0.2.2, but the reply goes to 172.25.0.3, so something is rather broken here.
What commands should I use to implement this NAT correctly?
tcpdump
? Where are you running theping
command?macvlan
driver (without specifying a parent device explicitly)? You neglected to mention that in your question earlier. That is critical information that completely changes the traffic flow. Using the macvlan driver means there isn't any bridge device associated with that network. (I'm not even sure that using macvlan without an explicit parent device leads to a useful configuration.)