2

I'm trying to setup a VPN router using Ubuntu Server 16.04.

I'm configuring the router to be secure so that no traffic can leak or if the VPN fails it will fail closed.

The router has a wireless interface (wlp2s0) that connects to my wireless network for internet connectivity and an ethernet interface (enp1s0) where I plug my laptop into.

Ideally, openvpn would run on startup and use wlp2s0 to create a tun0 interface and then some iptable rules would forward enp1s0 traffic to tun0.

To accomplish my goal of preventing traffic leakage I made a startup script that flushes all routes and then adds a local subnet route for wlp2s0 to the MAIN table. It also adds a default route out wlp2s0 for table 100.

I added an iptables mangle rule that marks openvpn traffic and then a fw rule that makes the marked traffic use table 100.

So basically, I only want openvpn traffic to be able to use the wireless default route (table 100) so that it can create the tunnel. All other traffic will be local or through the tunnel.

My startup script works fine, it creates the proper routes on startup. My iptable rules and fw rule seem to be correct as well.

The issue is that openvpn traffic just doesn't seem to hit the iptables mangle rule.

iptable rule that marks the openvpn traffic.

iptables -t mangle -A OUTPUT -p tcp --dport 501 -j MARK --set-mark 2

Startup script.

ip route add table 100 default via 192.168.0.1 dev wlp2s0
ip rule add fwmark 0x2 table 100
ip route flush table main
ip route add 192.168.0.0/24 dev wlp2s0

Output of various commands that most of you will ask for

administrator@ubuntu-svr:~$ sudo iptables -t mangle -L -v
Chain PREROUTING (policy ACCEPT 6153 packets, 1859K bytes)
 pkts bytes target     prot opt in     out     source               destination                  

Chain INPUT (policy ACCEPT 1961 packets, 297K bytes)
 pkts bytes target     prot opt in     out     source               destination       

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination        

Chain OUTPUT (policy ACCEPT 1348 packets, 128K bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MARK       tcp  --  any    any     anywhere             anywhere             tcp dpt:501 MARK set 0x2                  

Chain POSTROUTING (policy ACCEPT 1348 packets, 128K bytes)
 pkts bytes target     prot opt in     out     source               destination         

administrator@ubuntu-svr:~$ ip rule
0:  from all lookup local 
32765:  from all fwmark 0x2 lookup 100 
32766:  from all lookup main 
32767:  from all lookup default 

administrator@ubuntu-svr:~$ ip route show table main
192.168.0.0/24 dev wlp2s0  scope link 

administrator@ubuntu-svr:~$ ip route show table 100
default via 192.168.0.1 dev wlp2s0 

administrator@ubuntu-svr:~$ ip route get 8.8.8.8 mark 0x2
8.8.8.8 via 192.168.0.1 dev wlp2s0  src 192.168.0.209  mark 2

So from the above output you can see that the proper routes, iptable mangle rule, and fw rule are all in place.

When I issue the "ip route get" get command you can see that the proper route is selected for marked traffic.

However, the iptables mangle rule is NOT getting hit. I issued "telnet 8.8.8.8 501" and still the rule is not hit! Even though the traffic is locally generated and hits tcp 501, which should match the rule.

The openvpn traffic is generated from the router itself. Per, the man page for iptables the OUTPUT chain for mangle is for modifying LOCALLY generated traffic PREROUTE decision.

I'm at a complete loss here. I've read other posts of people having similar issues but can't seem to find a solution.

1

The problem is a missing default route in table main. This can't be detected when checking with ip route get ... mark ... because the whole path is short-circuited with the "solution" handed directly.

Because there's no default route, a route lookup to 8.8.8.8 fails with "Network is unreachable" before the packet has a chance to reach the mangle OUTPUT chain at all giving:

Adding any default route in table main (even using a non-existing router as long as it's a valid syntax) would allow the intended flow:

  • packet is generated,
  • route is checked a first time in the ip rule table, deemed existing,
  • traverses the mangle OUTPUT chain, inherits the mark,
  • gets rerouted (reroute check in Packet flow in Netfilter and General Networking) and thus triggers for a second time a lookup in the ip rule table,
  • grabs the early lookup on table 100 and gets its final route "via 192.168.0.1 dev wlp2s0".

So, picking in the LAN an IP that doesn't belong to any host, let's say 192.168.0.250 and adding it in the script, thus giving:

ip route add table 100 default via 192.168.0.1 dev wlp2s0
ip rule add fwmark 0x2 table 100
ip route flush table main
ip route add 192.168.0.0/24 dev wlp2s0
ip route add default via 192.168.0.250 dev wlp2s0

will solve the test made in the question:

  • any connection not made with destination tcp port 501 will trigger ARP requests to 192.168.0.250, which should time out 3 seconds later with the message "No route to host" (instead of "Network is unreachable")
  • any connection made with destination tcp port 501 will (resolve the ARP request and) go via 192.168.0.1

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.