The filter table is best place to drop packets, agreed.

But, out of the box, Docker bypasses INPUT filter rules with PREROUTING to its own FORWARD rules making Docker containers world-accessible. Inserting DOCKER-named filter INPUT/FORWARD rules fails because when Docker is restarted they are deleted then inserted (not appended).

My best attempt is to insert another PREROUTING chain before Docker's and send unwanted packets from eth0 (WAN) to a black hole - - because you cannot DROP/REJECT in a nat table anymore.

# Route anything but TCP 80,443 and ICMP to an IPv4 black hole
iptables -t nat -N BLACKHOLE
iptables -t nat -A BLACKHOLE ! -i eth0 -j RETURN
iptables -t nat -A BLACKHOLE -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
iptables -t nat -A BLACKHOLE -p tcp --dport 80 -j RETURN
iptables -t nat -A BLACKHOLE -p tcp --dport 443 -j RETURN
iptables -t nat -A BLACKHOLE -p icmp -j RETURN
iptables -t nat -A BLACKHOLE -p all -j DNAT --to
iptables -t nat -I PREROUTING -m addrtype --dst-type LOCAL -j BLACKHOLE

Here are what the NAT chains looks like with Docker and one container running:

Nat table rules

This seems to work well, though, is there a way to explicitly reject packets before reaching the other pre-routing rule?

(Alpine Linux 3.6.2, Docker v17.05.0-ce)


I had a similar problem which was the need to "harden" network traffic even if somebody would deploy a container that was binding the application to any address:

Docker provides a DOCKER-USER filter chain but it looks like all the magic happens in the DOCKER nat chain that is referenced in PREROUTING.

So no way around this nat happens before filtering and I don't want to touch too much at the docker rules.

I don't like the idea of having to change the packet once again so I came up with a scheme that returns everything by default and jumps to another chain in the PREROUTING before DOCKER gets called.

I then selectively jump back to DOCKER when I consider the traffic good.

Here's the code:

iptables -t nat -N DOCKER-BLOCK
iptables -t nat -I PREROUTING -m addrtype --dst-type LOCAL -j RETURN
iptables -t nat -I PREROUTING -m addrtype --dst-type LOCAL -j DOCKER-BLOCK

That's it!

The packet will jump to the DOCKER-BLOCK chain, and if that chain is empty, it'll go out the chain and continue on PREROUTING jumping to RETURN and it'll be blocked.

When you enable a port:

iptables -t nat -I DOCKER-BLOCK -p tcp -m tcp --dport 1234 -j DOCKER

It'll make the packet jump back to the DOCKER chain where it is managed by docker. Docker should handle the packet and the RETURN from PREROUTING should never be reached.

The nice way about it is that you never have to touch to the PREROUTING table anymore, if you want to flush, flush directly DOCKER-BLOCK.

  • Can you please explain on "That's all, by default everything coming from egress will end up in the filter table where I do have a catchall that drops everything."? – Ram Aug 31 '18 at 2:14
  • @Ram Editing my answer, if you create the DOCKER-BLOCK chain and it is empty, all the packets will traverse that chain without being block. When they go back to the PREROUTING chain, they will jump to RETURN blocking them by default. – tehmoon Oct 1 '18 at 15:28

The issue with the DOCKER-BLOCK approach is that if you want to block traffic to the host & containers, e.g block tcp port 50, you need to add it in the DOCKER-BLOCK chain (to bypass docker rules) and in the INPUT chain (to perform the actual drop).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.