diff options
Diffstat (limited to 'src/guide/iproute2tun.md')
-rw-r--r-- | src/guide/iproute2tun.md | 132 |
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) |