Md at

tales of a debian maintainer

Per-process netfilter rules

This article documents how the traffic of specific Linux processes can be subjected to a custom firewall or routing configuration, thanks to the magic of cgroups. We will use the Network classifier cgroup, which allows tagging the packets sent by specific processes.

To create the cgroup which will be used to identify the processes I added something like this to /etc/rc.local:

mkdir /sys/fs/cgroup/net_cls/unlocator
/bin/echo 42 > /sys/fs/cgroup/net_cls/unlocator/net_cls.classid
chown md: /sys/fs/cgroup/net_cls/unlocator/tasks

The tasks file, which controls the membership of processes in a cgroup, is made writeable by my user: this way I can add new processes without becoming root. 42 is the arbitrary class identifier that the kernel will associate with the packets generated by the member processes.

A command like systemd-cgls /sys/fs/cgroup/net_cls/ can be used to explore which processes are in which cgroup.

I use a simple shell wrapper to start a shell or a new program as members of this cgroup:

#!/bin/sh -e

if [ ! -d /sys/fs/cgroup/net_cls/$CGROUP_NAME/ ]; then
  echo "The $CGROUP_NAME net_cls cgroup does not exist!" >&2
  exit 1

/bin/echo $$ > /sys/fs/cgroup/net_cls/$CGROUP_NAME/tasks

if [ $# = 0 ]; then
  exec ${SHELL:-/bin/sh}

exec "$@"

My first goal is to use a special name server for the DNS queries of some processes, thanks to a second dnsmasq process which acts as a caching forwarder.





Description=dnsmasq - Second instance

ExecStartPre=/usr/sbin/dnsmasq --test
ExecStart=/usr/sbin/dnsmasq --keep-in-foreground --conf-file=/etc/dnsmasq2.conf
ExecReload=/bin/kill -HUP $MAINPID


Do not forget to enable the new service:

systemctl enable dnsmasq2
systemctl start dnsmasq2

Since the cgroup match extension is not yet available in a released version of iptables, you will first need to build and install it manually:

git clone git://
cd iptables
make -k
sudo cp extensions/ /lib/xtables/
sudo chmod -x /lib/xtables/

The netfilter configuration required is very simple: all DNS traffic from the marked processes is redirected to the port of the local dnsmasq2:

iptables -t nat -A OUTPUT -m cgroup --cgroup 42 -p udp --dport 53 -j REDIRECT --to-ports 5354
iptables -t nat -A OUTPUT -m cgroup --cgroup 42 -p tcp --dport 53 -j REDIRECT --to-ports 5354

For related reasons, I also need to disable IPv6 for these processes:

ip6tables -A OUTPUT -m cgroup --cgroup 42 -j REJECT

I use a different cgroup to force some programs to use my office VPN by first setting a netfilter packet mark on their traffic:

iptables -t mangle -A OUTPUT -m cgroup --cgroup 43 -j MARK --set-mark 43

The packet mark is then used to policy-route this traffic using a dedicate VRF, i.e. routing table 43:

ip rule add fwmark 43 table 43

This VPN VRF just contains a default route for the VPN interface:

ip route add default dev tun0 table 43

Depending on your local configuration it may be a good idea to also add to the VPN VRF the routes of your local interfaces:

ip route show scope link proto kernel \
  | xargs -I ROUTE ip route add ROUTE table 43

Since the source address selection happens before the traffic is diverted to the VPN, we also need to source-NAT to the VPN address the marked packets:

iptables -t nat -A POSTROUTING -m mark --mark 43 --out-interface tun0 -j MASQUERADE


This is the blog of Marco d'Itri.

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          

See also:

My blogroll:

W3C HTML 4.01
W3C CSS 2.0     

Powered by