Arnt Gulbrandsen
About meAbout this blog
2009-10-14

Mikrotik RouterOS, OpenVPN and IPv6

Mikrotik makes a series of small, neat routers. I have a 433UAH (with indoor case), which has a VPN tunnel to OpenVPN running on a rented server at vollmar.net. This describes how to build an IPv4 and IPv6 VPN tunnel between a Mikrotik router with a dynamic IP address and a Linux server running OpenVPN with fixed IP addresses.

Getting IPv4 running was easy. I made myself some certificates and uploaded the CA and one certificate to the Mikrotik:

[admin@coco] > certificate print Flags: K - decrypted-private-key, Q - private-key, R - rsa, D - dsa 0 D name="arntca" subject=C=DE,ST=Bayern,L=Muenchen,O=Arnt Gulbrandsen,CN=Arnt Gulbrandsen CA,emailAddress=arnt@gulbrandsen.priv.no issuer=C=DE,ST=Bayern,L=Muenchen,O=Arnt Gulbrandsen,CN=Arnt Gulbrandsen CA,emailAddress=arnt@gulbrandsen.priv.no serial-number="8B534F428719D91F" email=arnt@gulbrandsen.priv.no invalid-before=sep/09/2009 11:09:51 invalid-after=sep/07/2019 11:09:51 ca=yes 1 KR name="coco" subject=C=DE,ST=Bayern,L=Muenchen,O=Arnt Gulbrandsen, CN=coco.gulbrandsen.priv.no,emailAddress=arnt@gulbrandsen.priv.no issuer=C=DE,ST=Bayern,L=Muenchen,O=Arnt Gulbrandsen,CN=Arnt Gulbrandsen CA,emailAddress=arnt@gulbrandsen.priv.no serial-number="01" email=arnt@gulbrandsen.priv.no invalid-before=sep/09/2009 11:16:16 invalid-after=sep/07/2019 11:16:16 ca=yes

The names are just the file names I used when FTPing the files. Next, I set up an openvpn client on the Mikrotik using that certificate:

[admin@coco] > interface ovpn-client print detail Flags: X - disabled, R - running 0 R name="strange-tunnel-3g" mac-address=FE:20:E6:62:62:EE max-mtu=1500 connect-to=80.244.248.170 port=1194 mode=ip user="coco-3g" password="" profile=default certificate=coco auth=sha1 cipher=blowfish128 add-default-route=no

Meanwhile, on the OpenVPN side, I wrote this configuration file:

port 1194 syslog openvpn proto tcp-server tls-server dev tun ca /etc/openvpn/keys/ca.crt cert /etc/openvpn/keys/strangecert.crt key /etc/openvpn/keys/strangecert.key dh /etc/openvpn/keys/dh1536.pem persist-key persist-tun verb 3 keepalive 20 120 server 79.140.39.152 255.255.255.248 client-config-dir /etc/openvpn/clients route 79.140.39.160 255.255.255.224

proto tcp-server corresponds to mode ip. When the Mikrotik connects, it'll present a certificate signed by /etc/openvpn/keys/ca.crt (the same CA as on the Mikrotik), and openvpn opens a per-client file in /etc/openvpn/clients, where it finds:

iroute 79.140.39.160 255.255.255.224

Thus, the configuration file tells OpenVPN to accept responsibility for 79.140.39.160/27, and the per-client file tells it to send packets onwards to this particular client. That's enough to make inbound IPv4 packets work. Outbound packets still do not work.

In order to send most outbound IPv4 via the VPN, but route some traffic directly towards their target, I added some mangling rules to be processed before routing:

[admin@coco] > ip firewall mangle print detail Flags: X - disabled, I - invalid, D - dynamic 0 chain=prerouting action=mark-routing new-routing-mark=must-vpn passthrough=yes src-address=79.140.39.160/27 1 chain=prerouting action=mark-routing new-routing-mark=main passthrough=yes protocol=tcp dst-address=80.244.248.170 port=22 2 chain=prerouting action=mark-routing new-routing-mark=main passthrough=yes protocol=tcp src-address=79.140.39.160/27 dst-port=80 3 chain=prerouting action=mark-routing new-routing-mark=main passthrough=yes protocol=udp src-address=79.140.39.160/27 port=53 4 chain=output action=mark-routing new-routing-mark=must-vpn passthrough=yes protocol=41

This warrants explanation. Rule 0: All packets must go through the VPN, except when later rules override that. 1: SSH traffic to the VPN server are not sent via the VPN, but instead directly. This way it's easy to ssh to the VPN server if something breaks (the Mikrotik has been stable, but OpenVPN has broken a few times). 2 and 3: DNS lookups and web browsing don't use the VPN, but instead are routed directly towards the general internet. 4: IPv6 packets tunneled in IPv4 are sent via the VPN, always.

Now that the outgoing packets are marked, they can be routed easily:

[admin@coco] > ip route print detail Flags: X - disabled, A - active, D - dynamic, C - connect, S - static, r - rip, b - bgp, o - ospf, m - mme, B - blackhole, U - unreachable, P - prohibit 0 A S dst-address=0.0.0.0/0 gateway=strange-tunnel-3g interface=strange-tunnel-3g gateway-state=reachable distance=1 routing-mark=must-vpn 1 ADS dst-address=0.0.0.0/0 gateway=10.112.114.49 interface=vodafone-1und1-3g gateway-state=reachable distance=1 scope=30 target-scope=10 …more routes…

Route 0 instructs the Mikrotik to send all must-vpn packets via the openvpn tunnel. Route 1 is the default route for other packets. In my case those other packets will be NATed, then sent towards their target:

[admin@coco] > ip firewall nat print detail Flags: X - disabled, I - invalid, D - dynamic 0 chain=srcnat action=masquerade src-address=79.140.39.160/27 out-interface=vodafone-1und1-3g

If a packet is not marked must-vpn, it will be sent out on interface vodafone-1und1-3g, and if it's sent out on that interface and (still) has a VPN address as source, then it's NATed. Simple.

IPv6 was more of a challenge. There's SIT, 6to4 and 6in4. The first two are the same, the last almost. Anyway, outgoing packets:

[admin@coco] > ipv6 route print detail Flags: X - disabled, A - active, D - dynamic, C - connect, S - static, r - rip, o - ospf, b - bgp, U - unreachable 0 A S dst-address=::/0 gateway=schw6 interface=schw6 gateway-state=reachable distance=1 …more routes… [admin@coco] > interface 6to4 print detail Flags: X - disabled, R - running 0 R ;;; ipv6 tunnel to strange name="schw6" mtu=1400 local-address=79.140.39.161 remote-address=80.244.248.170 [admin@coco] > ipv6 address print detail Flags: X - disabled, I - invalid, D - dynamic, G - global, L - link-local …more routes… 1 G address=2001:4d88:100c:2::2/112 interface=schw6 actual-interface=schw6 eui-64=no advertise=no …more routes…

On openvpn (linux, actually) the same kind of interface is called SIT:

$ ip link show schw6 23: schw6@NONE: mtu 1480 qdisc noqueue state UNKNOWN link/sit 80.244.248.170 peer 79.140.39.161 $ ip addr show schw6 23: schw6@NONE: mtu 1480 qdisc noqueue state UNKNOWN link/sit 80.244.248.170 peer 79.140.39.161 inet6 2001:4d88:100c:2::1/112 scope global valid_lft forever preferred_lft forever inet6 fe80::50f4:f8aa/128 scope link valid_lft forever preferred_lft forever $ ip -6 route show dev schw6 2001:4d88:100c:1::/64 via 2001:4d88:100c:2::2 metric 1024 mtu 1480 advmss 1420 hoplimit 4294967295 2001:4d88:100c:2::/112 via :: metric 256 mtu 1480 advmss 1420 hoplimit 4294967295 fe80::/64 via :: metric 256 mtu 1480 advmss 1420 hoplimit 4294967295

When a packet to a host on the Mikrotik's ethernet arrives, linux sends it to its sch6 device, which wraps it in an IPv4 type 41 packet (this type of IPv4 packet is called an IP6 packet, which you should not confuse with an IPv6 packet, lovely naming there) bound for the Mikrotik's main IPv4 address. Linux then routes that packet to OpenVPN, which encrypts the packet and sends it to the Mikrotik via TCP. The Mikrotik receives, decrypts, sees that the packet is an IPv4 packet to itself, undoes the type-41 wrapping, sees the actual IPv6 destination, and forwards.

This isn't even nearly as simple as it should be. OpenVPN's IPv6 support is shoddy, so the simpler approaches break mysteriously, and you have to know that SIT is 6to4, not 6in4, and keep in mind that IP6 is and isn't IPv6. But it works.

Versions involved: RouterOS 3.28, Linux 2.6.26, openvpn 2.1 (an ancient Debian build).