/* 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);
}