Arnt Gulbrandsen
About meAbout this blog

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 linke
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).