.. _hack_tinc: Some finer points of Tinc VPN on FreeBSD ======================================== I know this will sound like a tall tale, but there was a time when the Inter-Net, yes, it was hyphenated back then, was largely devoid of idiots. Not devoid of people doing `stupid `_ `things `_ and of course the `first idiots `_ soon materialized, but still... If you want to experience that kind of peace and calm these days, you have to build a VPN, which of course I have, but I ran into some interesting corners, which deserves to be documented. I use the refreshingly competent VPN implementation `Tinc `_, available from all well stocked ports collections as ``security/tinc-devel``. ``Tinc`` is a Mesh network, which means that if you can get to just one node in the network, you can get traffic through to all the nodes in the network. (port 53 and 443 he said knowingly, nudge, nudge, wink, wink, know what a' mean ?) Tinc and FreeBSD ---------------- Load ``if_tap`` at boot:: $ grep if_tap /boot/loader.conf if_tap_load=YES You need a ``tinc.conf`` file, mine looks like this:: $ cd /usr/local/etc/tinc/VPN $ cat tinc.conf Name = $HOST Device = /dev/tap21 Forwarding = kernel ConnectTo = firewall_example_com I picked a high if_tap number to reduce the likelyhood of accidentally occupying it with any other stuff I do on the machine. Next you need a ``tinc-up`` script to configure the interface:: $ cd /usr/local/etc/tinc/VPN $ cat tinc-up #!/bin/sh /sbin/ifconfig ${INTERFACE} inet 192.168.21.3/24 /sbin/ifconfig ${INTERFACE} inet6 -ifdisabled /sbin/ifconfig ${INTERFACE} inet6 -auto_linklocal /sbin/ifconfig ${INTERFACE} inet6 fc00:192:168:21::3/120 You will want to fix the IP-numbers if you copy & paste. It would be nice of ``/etc/rc.conf`` could do that, but it almost but not quite entirely works in practice. You also want a ``tinc-down`` script to tear the interface down again:: $ cd /usr/local/etc/tinc/VPN $ cat tinc-down #!/bin/sh /sbin/ifconfig ${INTERFACE} down || true /sbin/ifconfig ${INTERFACE} delete || true /sbin/ifconfig ${INTERFACE} destroy & The destroy call goes in background, because it does not complete until ``tincd`` closes the tap device. Remember to make the scripts excutable. You also need to explicitly announce your hosts IP# to the rest of the VPN:: $ cd /usr/local/etc/tinc/VPN $ grep Subnet hosts/laptop Subnet = 192.168.21.3 Subnet = fc00:192:168:21::3 You will want to fix the IP-numbers if you copy & paste. You of course still need to do all the usual non-FreeBSD specific ``Tinc`` stuff, generate keys, add ``Address = ...`` lines &c. If you have unattended or exposed nodes on your VPN, you should add firewall rules on the VPN interface. Just because somebody manages to break into your beach-house or remote data collection computer, should not give them free run of your network. Tinc and extra networks ----------------------- If a ``Tinc`` node wants to advertise a subnet, it does so, but unless you have a ``subnet-up`` (and ``subnet-down``) script, you will not see those routes. However, just because some node advertises a subnet is not the same as you should accept it, so ``subnet-{up|down}`` should be a little bit paranoid. This is how I have done it:: $ cd /usr/local/etc/tinc/VPN $ cat subnet-up #!/bin/sh set -e # exec >/dev/console 2>&1 ME="`basename $0`" if [ "x${NODE}" = "x${NAME}" ] ; then # logger -t tinc "$ME ${NETNAME} OWN ${SUBNET}" exit 0 elif ! grep -iq "^[ ]*Subnet[ ]*=[ ]*${SUBNET}[ ]*$" hosts/${NODE} ; then # Only install routes to networks if they are in the local hosts/* files logger -t tinc "$ME ${NETNAME} UNAUTH ${SUBNET} ${NODE}/${REMOTEADDRESS}" exit 0 else logger -t tinc "${ME} ${NETNAME} OK $SUBNET ${NODE}/${REMOTEADDRESS}" fi if expr "x${SUBNET}" : 'x.*:.*' > /dev/null ; then IPV=6 else IPV=4 fi if [ "${ME}" = "subnet-up" ] ; then /sbin/route add -${IPV} ${SUBNET} -iface ${INTERFACE} else /sbin/route delete -${IPV} ${SUBNET} -iface ${INTERFACE} || true fi The result is that routes will only be installed to subnets listed in the ``/usr/local/etc/tinc/$NETNAME/hosts/$NODE`` files *local* to the machine. This way other nodes can advertize all the subnets they want, you get to decide which ones you adopt. Why ``StrictSubnets`` is not the same ------------------------------------- ``Tinc`` has an option called ``StrictSubnets`` which sounds like it does the same thing, but it is a little bit too brutal for my taste. Say you have three machines without full reachability, for instance two laptops on the internet who cannot connect directly to each other, but both connected to the same firewall. If one laptop advertises a subnet, which the firewall does not want to adopt, and the firewall uses ``StrictSubnets``, the firewall will not pass this traffic through to the other laptop which does want to adopt that subnet. At least, that is how I think it works... And now for the tricky bit -------------------------- Assume the following network topology: .. image:: tincery_01.svg Sometimes my laptop is on one of the two home networks, sometimes I leave the house and God knows what I am connected to. Because my laptop can be on either of the two local networks *and* because I advertise those as subnets with tinc in the VPN, things can go amusingly wrong. On my laptop I have a hosts file for the firewall:: $ cd /usr/local/etc/tinc/VPN $ cat hosts/firewall_example_com Ed25519PublicKey = BlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlab Subnet = 192.168.60.0/24 Subnet = 192.168.87.0/24 Address = Address = 192.168.87.1 Address = 192.168.60.1 Assume my laptop is somewhere out on the internet, it uses the public IP address to get hold of the firewall, the firewall announces the two subnets which the laptop accepts and everything is just nice and wonderful. Until, at some point, for reasons not fully understood, my laptop decides that it is a better idea to contact the firewall using one of the other two addresses. Tl;dr: Recursion: See recursion. To prevent this from happening, I had to add ``Forwarding = kernel`` to the ``tinc.conf`` file (see above), and add PF rules to stop these bogo-packets:: $ grep tinc_non_services /etc/pf.conf tinc_non_services = "{ 655 }" block in on tap21 proto tcp from any to any port $tinc_non_services block in on tap21 proto udp from any to any port $tinc_non_services block out on tap21 proto tcp from any to any port $tinc_non_services block out on tap21 proto udp from any to any port $tinc_non_services I *suspect* the trigger for this trouble may be meta-data packet loss which makes ``tincd`` try the next ``Address =`` line, to see if that works better, but I have not had patience to fully prove that. Ideally ``tincd`` should never send metadata through the tunnel the metadata is about, but I saw no sensible way of adding that check, so ``PF`` got the job instead. Another fine point ------------------ If you have two VPN nodes behind the same NAT device, tinc's outside the NAT can get confused by them using same IP+PORT. The obvious solution is to give them different port numbers, in which case you need to add the "extra" port numbers to `tinc_non_services` above. Ohh, and one final thing... --------------------------- Release 1.1.pre17 of ``tinc`` has an annoying bug where it only tries the first ``Address =`` record. Hopefully a new release will fix that soon-ish. *phk*