Let’s say I’ve got a server with lo
and eth0
(1.1.1.1
) interfaces. I’ve just installed docker
(no swarm mode). When I start it, it adds the docker0
interface (172.17.0.1
) and the following iptables
rules:
*nat
:PREROUTING ACCEPT
:INPUT ACCEPT
:OUTPUT ACCEPT
:POSTROUTING ACCEPT
:DOCKER -
# (nat.1)
# when receiving a connection targeting a local address
# from the outside world to 1.1.1.1,
# or from a container to 172.17.0.1, 1.1.1.1
# jump to the DOCKER chain
-A PREROUTING -m addrtype --dst-type LOCAL
-j DOCKER
# (nat.2)
# when establishing a connection from the host
# to a local address (1.1.1.1, 172.17.0.1),
# jump to the DOCKER chain
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype
--dst-type LOCAL -j DOCKER
# (nat.3)
# when receiving a connection
# from a container to the outside world,
# or establishing from the host to 172.17.0.1
# do SNAT
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0
-j MASQUERADE
# (nat.4)
# return if a connection is coming from a container
-A DOCKER -i docker0 -j RETURN
# here we're left with connections coming from the outside world to 1.1.1.1,
# and from the host to 1.1.1.1, 172.17.0.1
# and here's where DNAT rules will be added
*filter
:INPUT ACCEPT
:FORWARD DROP # DROP policy
:OUTPUT ACCEPT
:DOCKER -
:DOCKER-ISOLATION-STAGE-1 -
:DOCKER-ISOLATION-STAGE-2 -
:DOCKER-USER -
# (filter.1)
-A FORWARD -j DOCKER-USER
# (filter.2)
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
# (filter.3)
# accept established and related connections
# to a container
# from the outside world (in case they are forwarded, none by default),
# or from another container
-A FORWARD -o docker0 -m conntrack
--ctstate RELATED,ESTABLISHED -j ACCEPT
# (filter.4)
# jump to the DOCKER chain
# for packets coming
# to a container
# from the outside world
# or from another container
-A FORWARD -o docker0 -j DOCKER
# (filter.5)
# accept packets coming
# from a container
# to the outside world
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
# (filter.6)
# accept packets between containers
-A FORWARD -i docker0 -o docker0 -j ACCEPT
# (filter.7)
# jump to DOCKER-ISOLATION-STAGE-2
# for packets coming
# from a container
# to the outside world
-A DOCKER-ISOLATION-STAGE-1 -i docker0
! -o docker0 -j DOCKER-ISOLATION-STAGE-2
# (filter.8)
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
# (filter.9)
# drop packets coming
# to a container
# from the outside world,
# or from another container
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
# (filter.10)
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
# (filter.11)
# placeholder for user rules
# https://docs.docker.com/network/iptables/
-A DOCKER-USER -j RETURN
The same in a more succinct way. And on a separate page to not scroll up and down continuously.
And so I’m trying to see the big picture.
It adds SNAT for connections coming from containers (nat.3
). The case from the host to 172.17.0.1
is most likely unintentional.
Then supposedly makes provisions for DNAT rules (nat.1
, nat.2
, nat.4
).
DOCKER-USER
chain is supposedly for rules added manually by a user (filter.1
, filter.11
).
DOCKER-ISOLATION-STAGE-2
seems to be useless (filter.9
, filter.10
), at least right after the start. It drops packets coming to a container, but is jumped to for packets coming from a container (filter.7
, filter.8
).
filter.3
, filter.4
supposedly deal with connections to containers. filter.4
is probably for rules restricting access to containers from the outside world (new connections), filter.3
allows further communication if filter.4
was passed for a new connection.
filter.5
, filter.6
accept packets from containers to the outside world and between containers.
Then, when I publish a port (-p 111:222
), I get a couple more rules:
*nat
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
# SNAT connections coming from a container to itself to port 222
# but this rule never matches (these packets don't reach the host)
+-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 222 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
# DNAT connections coming to port 111
# from the outside world to 1.1.1.1,
# and from the host to 1.1.1.1, 172.17.0.1
# but actually it works without this rule from the host
# supposedly owing to docker-proxy listening to *:111
+-A DOCKER ! -i docker0 -p tcp -m tcp --dport 111 -j DNAT --to-destination 172.17.0.2:222
* filter
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
# allow DNAT'ed connections from the outside world to the container
# only FORWARD's policy is DROP
+-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 222 -j ACCEPT
When you additionally specify an IP address (-p 127.0.0.1:111:222
), you get the same 3 rules but with a slight change:
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 222 -j MASQUERADE
--A DOCKER ! -i docker0 -p tcp -m tcp --dport 111 -j DNAT --to-destination 172.17.0.2:222
+-A DOCKER -d 127.0.0.1/32 ! -i docker0 -p tcp -m tcp --dport 111 -j DNAT --to-destination 172.17.0.2:222
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 222 -j ACCEPT
And again it works without DNAT rule.
Adding a user-defined bridge network duplicates docker0
-specific rules for the new interface:
*nat
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
+-A POSTROUTING -s 172.18.0.0/16 ! -o br-b215ed0febb5 -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
+-A DOCKER -i br-b215ed0febb5 -j RETURN
-A DOCKER -i docker0 -j RETURN
*filter
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
+-A FORWARD -o br-b215ed0febb5 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A FORWARD -o br-b215ed0febb5 -j DOCKER
+-A FORWARD -i br-b215ed0febb5 ! -o br-b215ed0febb5 -j ACCEPT
+-A FORWARD -i br-b215ed0febb5 -o br-b215ed0febb5 -j ACCEPT
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
+-A DOCKER-ISOLATION-STAGE-1 -i br-b215ed0febb5 ! -o br-b215ed0febb5 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
+-A DOCKER-ISOLATION-STAGE-2 -o br-b215ed0febb5 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
Do you have anything to add?