aboutsummaryrefslogtreecommitdiff
path: root/src/guide/iproute2tun.md
diff options
context:
space:
mode:
Diffstat (limited to 'src/guide/iproute2tun.md')
-rw-r--r--src/guide/iproute2tun.md132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/guide/iproute2tun.md b/src/guide/iproute2tun.md
new file mode 100644
index 0000000..3c049d9
--- /dev/null
+++ b/src/guide/iproute2tun.md
@@ -0,0 +1,132 @@
+% Write your own ip-tunnel
+
+# Introduction
+
+In this post we're going to explore how to create IP-in-IP tunnels
+without writing a userspace encapsulation driver.
+The main advantage here is keeping the userspace code clean and simple.
+
+# IP-in-IP tunnels
+
+This is a type of managed tunnel. It works by simply adding an outer IP header
+to the datagrams. The local and remote IP addresses have to be configured
+beforehand. There is no encryption or handshaking.
+It's about as simple as it gets.
+
+You can tunnel a version of IP inside of itself or the other version.
+This allows for the following combinations to be implemented:
+
+* IPv4 in IPv4: ipip / 4in4
+* IPv6 in IPv6: ip6ip6 / 6in6
+* IPv6 in IPv4: sit / 6in4
+* IPv4 in IPv6: ipip6 / 4in6
+
+Inter-protocol tunnels are much more common than their intra-protocol variants.
+IPv6-in-IPv4 lies at the core of many IPv6 transition mechanisms
+including 6in4 using a tunnel broker or legacy 6to4. IPv4-in-IPv6
+is a core component of DS-Lite.
+
+These tunnels require kernel support and can be configured
+using the `ip tunnel` subcommand.
+
+Support is enabled by building Linux with the following flags:
+
+```
+CONFIG_NET_IP_TUNNEL=y
+CONFIG_INET_TUNNEL=y
+CONFIG_INET6_TUNNEL=y
+CONFIG_IPV6_TUNNEL=y
+CONFIG_IPV6_SIT=y
+CONFIG_NFT_TUNNEL=y
+```
+
+# So how does it work?
+
+Unlike most other types of network interfaces tunnels don't require netlink
+configuration. Creating links like VLANs does require this,
+but tunnels don't. Netlink is still used to change configuration parameters
+like the IP addresses but it's not involved in providing the creation
+and deletion API.
+
+# Let's reimplement it
+
+This could probably be implemented using unsafe Rust code without the need
+for C bindings, but they do make our life much easier.
+
+Tunnel control works using the `ioctl` syscall on any dummy IPv4 or IPv6 socket.
+You should probably use the outer protocol, though IPv6 might work
+for everything.
+
+The control socket is created as follows:
+
+```
+int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+```
+
+not including any error handling. `IPPROTO_IP` is zero.
+
+Here's the `ioctl` we have to prepare to create a 4in4 tunnel:
+
+```
+struct ip_tunnel_parm p;
+
+strcpy(p.name, "footnl0");
+p.iph.version = 4; // outer protocol
+p.iph.ihl = 5;
+p.iph.ttl = 64;
+p.iph.protocol = IPPROTO_IPIP; // inner protocol
+p.iph.saddr = /* our address as big-endian u32 */
+p.iph.daddr = /* remote address as big-endian u32 */
+p.link = if_nametoindex("ppp0"); // parent interface
+
+struct ifreq ifr;
+
+strcpy(ifr.ifr_name, "tunl0"); // default name for our tunnel type
+ifr.ifr_ifru.ifru_data = (char *) &p;
+
+ioctl(fd, SIOCADDTUNNEL, &ifr);
+```
+
+This will create the tunnel assuming you have the required permissions.
+The default interface name for 4in4 is `tunl0`. This is different
+for the other tunnel types. From what I understand `sit0`
+is used if the inner protocol is IPv6, but if it doesn't work
+you'll have to read the iproute2 source code and test it for yourself.
+One easy way to do this is making a debug build and attaching `gdb`
+to set a breakpoint at the `ioctl` call and inspect the `ifr` struct.
+
+The MTU is set automatically but bringing the interface up
+and configuring the internal addressing is up to you.
+Also unlike the more complex PPP driver the tunneling code
+does not delete the interface when your application exits.
+If this is unwanted behavior you need to call a deletion function
+before exiting which can even be automated using Rust's destructors.
+
+An interesting fact to note is that the MTU is automatically calculated
+from the overhead and the parent MTU. Because of this it's always going to be
+correct, even if you're tunneling through PPPoE.
+
+# Deletion
+
+Initialise a socket like you did when creating the tunnel.
+The request and `ioctl` call are different though:
+
+```
+struct ip_tunnel_parm p;
+
+strcpy(p.name, "footnl0");
+
+struct ifreq ifr;
+
+strcpy(ifr.ifr_name, "footnl0");
+ifr.ifr_ifru.ifru_data = (char *) &p;
+
+ioctl(fd, SIOCDELTUNNEL, &ifr);
+```
+
+Thankfully all of this is much simpler than PPP
+once you figure out how it works. Have fun with your cleanly created tunnels!
+
+[Return to Guide List](/cgi-bin/guides.lua)
+
+[Return to Index Page](/cgi-bin/index.lua)