/* vim: set expandtab ts=4 sw=4: */ /* * You may redistribute this program and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "interface/tuntap/TUNInterface.h" #include "util/AddrTools.h" #include "util/Identity.h" #include "util/events/Socket.h" #include "wire/Ethernet.h" #include "wire/Error.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * Since some illumos distributions (namely SmartOS) don't ship `net/if_tun.h`, * define those IOCTL constants here. */ #define TUNNEWPPA (('T'<<16) | 0x0001) #define TUNSETPPA (('T'<<16) | 0x0002) struct TUNInterface_Illumos_pvt { struct Iface internalIf; struct Iface externalIf; Identity }; /** * Illumos has no concept of packet info, it only supports IPv4 and IPv6 * through TUN devices and it detects it by reading the version byte. */ static uint16_t ethertypeForPacketType(uint8_t highByte) { return ((highByte >> 4) == 6) ? Ethernet_TYPE_IP6 : Ethernet_TYPE_IP4; } static Iface_DEFUN incomingFromWire(Message_t* message, struct Iface* externalIf) { struct TUNInterface_Illumos_pvt* ctx = Identity_containerOf(externalIf, struct TUNInterface_Illumos_pvt, externalIf); if (Message_getLength(message) < 4) { return 0; } Er_assert(Message_eshift(message, 4)); ((uint16_t*) Message_bytes(message))[0] = 0; ((uint16_t*) Message_bytes(message))[1] = ethertypeForPacketType(Message_bytes(message)[4]); return Iface_next(&ctx->internalIf, message); } static Iface_DEFUN incomingFromUs(Message_t* message, struct Iface* internalIf) { struct TUNInterface_Illumos_pvt* ctx = Identity_containerOf(internalIf, struct TUNInterface_Illumos_pvt, internalIf); Er_assert(Message_eshift(message, -4)); uint16_t ethertype = ((uint16_t*) Message_bytes(message))[-1]; if (ethertype != Ethernet_TYPE_IP6 && ethertype != Ethernet_TYPE_IP4) { Assert_true(!"Unsupported ethertype"); } return Iface_next(&ctx->externalIf, message); } Er_DEFUN(struct Iface* TUNInterface_new(const char* interfaceName, char assignedInterfaceName[TUNInterface_IFNAMSIZ], int isTapMode, EventBase_t* base, struct Log* logger, struct Allocator* alloc)) { // tap mode is not supported at all by the sunos tun driver. if (isTapMode) { Er_raise(alloc, "tap mode not supported on this platform"); } // Extract the number eg: 0 from tun0 int ppa = 0; if (interfaceName) { for (uint32_t i = 0; i < strlen(interfaceName); i++) { if (isdigit(interfaceName[i])) { ppa = atoi(interfaceName); } } } // Open the descriptor int tunFd = open("/dev/tun", O_RDWR); // Either the name is specified and we use TUNSETPPA, // or it's not specified and we just want a TUNNEWPPA if (ppa) { ppa = ioctl(tunFd, TUNSETPPA, ppa); } else { ppa = ioctl(tunFd, TUNNEWPPA, -1); } int ipFd = open("/dev/ip6", O_RDWR, 0); int tunFd2 = open("/dev/tun", O_RDWR, 0); if (tunFd < 0 || ipFd < 0 || ppa < 0 || tunFd2 < 0) { int err = errno; close(tunFd); close(ipFd); close(tunFd2); char* error = NULL; if (tunFd < 0) { error = "open(\"/dev/tun\")"; } else if (ipFd < 0) { error = "open(\"/dev/ip6\")"; } else if (ppa < 0) { error = "ioctl(TUNNEWPPA)"; } else if (tunFd2 < 0) { error = "open(\"/dev/tun\") (opening for plumbing interface)"; } Er_raise(alloc, "%s [%s]", error, strerror(err)); } struct lifreq ifr = { .lifr_ppa = ppa, .lifr_flags = IFF_IPV6 }; // Since devices are numbered rather than named, it's not possible to have tun0 and cjdns0 // so we'll skip the pretty names and call everything tunX int maxNameSize = (LIFNAMSIZ < TUNInterface_IFNAMSIZ) ? LIFNAMSIZ : TUNInterface_IFNAMSIZ; if (assignedInterfaceName) { snprintf(assignedInterfaceName, maxNameSize, "tun%d", ppa); } snprintf(ifr.lifr_name, maxNameSize, "tun%d", ppa); char* error = NULL; if (ioctl(tunFd, I_SRDOPT, RMSGD) < 0) { error = "putting tun into message-discard mode"; } else if (ioctl(tunFd2, I_PUSH, "ip") < 0) { // add the ip module error = "ioctl(I_PUSH)"; } else if (ioctl(tunFd2, SIOCSLIFNAME, &ifr) < 0) { // set the name of the interface and specify it as ipv6 error = "ioctl(SIOCSLIFNAME)"; } else if (ioctl(ipFd, I_LINK, tunFd2) < 0) { // link the device to the ipv6 router error = "ioctl(I_LINK)"; } if (error) { int err = errno; close(ipFd); close(tunFd2); close(tunFd); Er_raise(alloc, "%s [%s]", error, strerror(err)); } close(ipFd); struct Iface* s = Er(Socket_forFd(tunFd, Socket_forFd_FRAMES, alloc)); struct TUNInterface_Illumos_pvt* ctx = Allocator_clone(alloc, (&(struct TUNInterface_Illumos_pvt) { .externalIf = { .send = incomingFromWire }, .internalIf = { .send = incomingFromUs }, })); Iface_plumb(&ctx->externalIf, s); Identity_set(ctx); // TODO(cjd): This needs to be tested Er_ret(&ctx->internalIf); }