diff options
Diffstat (limited to 'src/iface.c')
-rw-r--r-- | src/iface.c | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/src/iface.c b/src/iface.c new file mode 100644 index 0000000..b448cbd --- /dev/null +++ b/src/iface.c @@ -0,0 +1,441 @@ +/* + Copyright (c) 2012-2015, Matthias Schiffer <mschiffer@universe-factory.net> + All rights reserved. + + Android port contributor: + Copyright (c) 2014-2015, Haofeng "Rick" Lei <ricklei@gmail.com> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + \file + + Management of the TUN/TAP interface +*/ + +#include "fastd.h" +#include "poll.h" + +#include <net/if.h> +#include <sys/ioctl.h> + +#ifdef __linux__ + +#include <linux/if_tun.h> + +#else + +#ifndef __APPLE__ +#include <net/if_tun.h> +#endif + +#ifdef __FreeBSD__ +#include <net/if_tap.h> +#endif + +#endif + + +/** Defines if the platform uses an address family header on TUN interfaces */ +#if defined(__linux__) || defined(__APPLE__) +static const bool multiaf_tun = false; +#else +static const bool multiaf_tun = true; +#endif + +#ifdef __linux__ + +/** Opens the TUN/TAP device helper shared by Android and Linux targets */ +static void open_iface_linux(fastd_iface_t *iface, const char *dev_name) { + struct ifreq ifr = {}; + + iface->fd = FASTD_POLL_FD(POLL_TYPE_IFACE, open(dev_name, O_RDWR|O_NONBLOCK)); + if (iface->fd.fd < 0) + exit_errno("could not open tun/tap device file"); + + if (conf.ifname) + strncpy(ifr.ifr_name, conf.ifname, IFNAMSIZ-1); + + switch (conf.mode) { + case MODE_TAP: + ifr.ifr_flags = IFF_TAP; + break; + + case MODE_TUN: + ifr.ifr_flags = IFF_TUN; + break; + + default: + exit_bug("invalid mode"); + } + + ifr.ifr_flags |= IFF_NO_PI; + if (ioctl(iface->fd.fd, TUNSETIFF, &ifr) < 0) + exit_errno("TUNSETIFF ioctl failed"); + + iface->name = fastd_strndup(ifr.ifr_name, IFNAMSIZ-1); + + int ctl_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (ctl_sock < 0) + exit_errno("socket"); + + if (ioctl(ctl_sock, SIOCGIFMTU, &ifr) < 0) + exit_errno("SIOCGIFMTU ioctl failed"); + + if (ifr.ifr_mtu != conf.mtu) { + ifr.ifr_mtu = conf.mtu; + if (ioctl(ctl_sock, SIOCSIFMTU, &ifr) < 0) + exit_errno("SIOCSIFMTU ioctl failed"); + } + + if (close(ctl_sock)) + pr_error_errno("close"); + +} + +#endif + +#if defined(__ANDROID__) + +/** Opens the TUN/TAP device */ +static void open_iface(fastd_iface_t *iface) { + if (conf.android_integration) { + if (conf.mode != MODE_TUN) + exit_error("Non root Android supports only TUN mode"); + + pr_debug("using android TUN fd"); + iface->fd = FASTD_POLL_FD(POLL_TYPE_IFACE, fastd_android_receive_tunfd()); + + fastd_android_send_pid(); + } else { + /* this requires root on Android */ + open_iface_linux(iface, "/dev/tun"); + } +} + +#elif defined(__linux__) + +/** Opens the TUN/TAP device */ +static void open_iface(fastd_iface_t *iface) { + open_iface_linux(iface, "/dev/net/tun"); +} + +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + +/** Sets the MTU of the TUN/TAP device */ +static void set_tun_mtu(fastd_iface_t *iface) { + struct tuninfo tuninfo; + + if (ioctl(iface->fd.fd, TUNGIFINFO, &tuninfo) < 0) + exit_errno("TUNGIFINFO ioctl failed"); + + tuninfo.mtu = conf.mtu; + + if (ioctl(iface->fd.fd, TUNSIFINFO, &tuninfo) < 0) + exit_errno("TUNSIFINFO ioctl failed"); +} + + +#ifdef __FreeBSD__ + +/** Sets the MTU of the TAP device */ +static void set_tap_mtu(fastd_iface_t *iface) { + struct tapinfo tapinfo; + + if (ioctl(iface->fd.fd, TAPGIFINFO, &tapinfo) < 0) + exit_errno("TAPGIFINFO ioctl failed"); + + tapinfo.mtu = conf.mtu; + + if (ioctl(iface->fd.fd, TAPSIFINFO, &tapinfo) < 0) + exit_errno("TAPSIFINFO ioctl failed"); +} + +/** Sets up the TUN device */ +static void setup_tun(fastd_iface_t *iface) { + int one = 1; + if (ioctl(iface->fd.fd, TUNSIFHEAD, &one) < 0) + exit_errno("TUNSIFHEAD ioctl failed"); + + set_tun_mtu(iface); +} + +/** Sets up the TAP device */ +static void setup_tap(fastd_iface_t *iface) { + struct ifreq ifr = {}; + + if (ioctl(iface->fd.fd, TAPGIFNAME, &ifr) < 0) + exit_errno("TAPGIFNAME ioctl failed"); + + free(iface->name); + iface->name = fastd_strndup(ifr.ifr_name, IFNAMSIZ-1); + + set_tap_mtu(iface); +} + +/** Opens the TUN/TAP device */ +static void open_iface(fastd_iface_t *iface) { + char ifname[5+IFNAMSIZ] = "/dev/"; + const char *type; + + switch (conf.mode) { + case MODE_TAP: + type = "tap"; + break; + + case MODE_TUN: + type = "tun"; + break; + + default: + exit_bug("invalid mode"); + } + + if (conf.ifname) { + if (strncmp(conf.ifname, type, 3) != 0) + exit_error("`%s' doesn't seem to be a %s device", conf.ifname, type); + + strncat(ifname, conf.ifname, IFNAMSIZ-1); + } + else { + strncat(ifname, type, IFNAMSIZ-1); + } + + iface->fd = FASTD_POLL_FD(POLL_TYPE_IFACE, open(ifname, O_RDWR|O_NONBLOCK)); + if (iface->fd.fd < 0) + exit_errno("could not open tun/tap device file"); + + if (!(iface->name = fdevname_r(iface->fd.fd, fastd_alloc(IFNAMSIZ), IFNAMSIZ))) + exit_errno("could not get tun/tap interface name"); + + switch (conf.mode) { + case MODE_TAP: + setup_tap(iface); + break; + + case MODE_TUN: + setup_tun(iface); + break; + + default: + exit_bug("invalid mode"); + } +} + +#else /* __OpenBSD__ */ + +static void set_link0(fastd_iface_t *iface, bool set) { + struct ifreq ifr = {}; + + int ctl_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (ctl_sock < 0) + exit_errno("socket"); + + strncpy(ifr.ifr_name, iface->name, IFNAMSIZ-1); + if (ioctl(ctl_sock, SIOCGIFFLAGS, &ifr) < 0) + exit_errno("SIOCGIFFLAGS ioctl failed"); + + if (set) + ifr.ifr_flags |= IFF_LINK0; + else + ifr.ifr_flags &= ~IFF_LINK0; + + if (ioctl(ctl_sock, SIOCSIFFLAGS, &ifr) < 0) + exit_errno("SIOCSIFFLAGS ioctl failed"); + + if (close(ctl_sock)) + pr_error_errno("close"); +} + +/** Sets up the TUN device */ +static void setup_tun(fastd_iface_t *iface) { + set_link0(iface, false); + set_tun_mtu(iface); +} + +/** Sets up the TAP device */ +static void setup_tap(fastd_iface_t *iface) { + set_link0(iface, true); + set_tun_mtu(iface); +} + +/** Opens the TUN/TAP device */ +static void open_iface(fastd_iface_t *iface) { + char ifname[5+IFNAMSIZ] = "/dev/"; + if (!conf.ifname) + exit_error("config error: no interface name given."); + else if (strncmp(conf.ifname, "tun", 3) != 0) + exit_error("config error: `%s' doesn't seem to be a tun device", conf.ifname); + else + strncat(ifname, conf.ifname, IFNAMSIZ-1); + + iface->fd = FASTD_POLL_FD(POLL_TYPE_IFACE, open(ifname, O_RDWR|O_NONBLOCK)); + if (iface->fd.fd < 0) + exit_errno("could not open tun device file"); + + iface->name = fastd_strndup(conf.ifname, IFNAMSIZ-1); + + switch (conf.mode) { + case MODE_TAP: + setup_tap(iface); + break; + + case MODE_TUN: + setup_tun(iface); + break; + + default: + exit_bug("invalid mode"); + } +} + +#endif + +#elif __APPLE__ + +/** Opens the TUN/TAP device */ +static void open_iface(fastd_iface_t *iface) { + const char *devtype; + switch (conf.mode) { + case MODE_TAP: + devtype = "tap"; + break; + + case MODE_TUN: + devtype = "tun"; + break; + + default: + exit_bug("invalid mode"); + } + + char ifname[5+IFNAMSIZ] = "/dev/"; + if (!conf.ifname) + exit_error("config error: no interface name given."); + else if (strncmp(conf.ifname, devtype, 3) != 0) + exit_error("config error: `%s' doesn't seem to be a %s device", conf.ifname, devtype); + else + strncat(ifname, conf.ifname, IFNAMSIZ-1); + + iface->fd = FASTD_POLL_FD(POLL_TYPE_IFACE, open(ifname, O_RDWR|O_NONBLOCK)); + if (iface->fd.fd < 0) + exit_errno("could not open tun device file"); + + iface->name = fastd_strndup(conf.ifname, IFNAMSIZ-1); + + int ctl_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (ctl_sock < 0) + exit_errno("socket"); + + struct ifreq ifr = {}; + strncpy(ifr.ifr_name, conf.ifname, IFNAMSIZ-1); + ifr.ifr_mtu = conf.mtu; + if (ioctl(ctl_sock, SIOCSIFMTU, &ifr) < 0) + exit_errno("SIOCSIFMTU ioctl failed"); + + if (close(ctl_sock)) + pr_error_errno("close"); +} + +#else + +#error unknown tun/tap implementation + +#endif + + +/** Reads a packet from the TUN/TAP device */ +void fastd_iface_handle(fastd_iface_t *iface) { + size_t max_len = fastd_max_payload(); + + fastd_buffer_t buffer; + if (multiaf_tun && conf.mode == MODE_TUN) + buffer = fastd_buffer_alloc(max_len+4, conf.min_encrypt_head_space+12, conf.min_encrypt_tail_space); + else + buffer = fastd_buffer_alloc(max_len, conf.min_encrypt_head_space, conf.min_encrypt_tail_space); + + ssize_t len = read(iface->fd.fd, buffer.data, max_len); + if (len < 0) + exit_errno("read"); + + buffer.len = len; + + if (multiaf_tun && conf.mode == MODE_TUN) + fastd_buffer_push_head(&buffer, 4); + + fastd_send_data(buffer, iface->peer); +} + +/** Writes a packet to the TUN/TAP device */ +void fastd_iface_write(fastd_iface_t *iface, fastd_buffer_t buffer) { + if (multiaf_tun && conf.mode == MODE_TUN) { + uint8_t version = *((uint8_t *)buffer.data) >> 4; + uint32_t af; + + switch (version) { + case 4: + af = htonl(AF_INET); + break; + + case 6: + af = htonl(AF_INET6); + break; + + default: + pr_warn("fastd_iface_write: unknown IP version %u", version); + return; + } + + fastd_buffer_pull_head(&buffer, 4); + memcpy(buffer.data, &af, 4); + } + + if (write(iface->fd.fd, buffer.data, buffer.len) < 0) + pr_debug2_errno("write"); +} + +fastd_iface_t * fastd_iface_open(fastd_peer_t *peer) { + fastd_iface_t *iface = fastd_new(fastd_iface_t); + iface->peer = peer; + + pr_debug("initializing tun/tap device..."); + open_iface(iface); + + if (iface->name) + pr_debug("tun/tap device `%s' initialized.", iface->name); + else + pr_debug("tun/tap device initialized."); + + fastd_poll_fd_register(&iface->fd); + + return iface; +} + +/** Closes the TUN/TAP device */ +void fastd_iface_close(fastd_iface_t *iface) { + if (!fastd_poll_fd_close(&iface->fd)) + pr_warn_errno("closing tun/tap: close"); + + free(iface->name); + free(iface); +} |