/* 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 "exception/Except.h"
#include "exception/WinFail.h"
#include "memory/Allocator.h"
#include "interface/tuntap/windows/TAPDevice.h"
#include "interface/tuntap/windows/TAPInterface.h"
#include "interface/FramingInterface.h"
#include "util/events/EventBase.h"
#include "util/events/Pipe.h"
#include "util/platform/netdev/NetDev.h"
/*
* Portions of this code are copied from QEMU project which is licensed
* under GPLv2 or greater, further contributions are licensed under GPLv3
* or greater.
*/
/*
* TAP-Win32 -- A kernel driver to provide virtual tap device functionality
* on win32. Originally derived from the CIPE-Win32
* project by Damion K. Wilson, with extensive modifications by
* James Yonan.
*
* All source code which derives from the CIPE-Win32 project is
* Copyright (C) Damion K. Wilson, 2003, and is released under the
* GPL version 2 (see below).
*
* All other source code is Copyright (C) James Yonan, 2003-2004,
* and is released under the GPL version 2 (see below).
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 (see the file COPYING included with this
* distribution); if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include
#include
#include
//=============
// TAP IOCTLs
//=============
#define TAP_CONTROL_CODE(request,method) \
CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS)
#define TAP_IOCTL_GET_MAC TAP_CONTROL_CODE (1, METHOD_BUFFERED)
#define TAP_IOCTL_GET_VERSION TAP_CONTROL_CODE (2, METHOD_BUFFERED)
#define TAP_IOCTL_GET_MTU TAP_CONTROL_CODE (3, METHOD_BUFFERED)
#define TAP_IOCTL_GET_INFO TAP_CONTROL_CODE (4, METHOD_BUFFERED)
#define TAP_IOCTL_CONFIG_POINT_TO_POINT TAP_CONTROL_CODE (5, METHOD_BUFFERED)
#define TAP_IOCTL_SET_MEDIA_STATUS TAP_CONTROL_CODE (6, METHOD_BUFFERED)
#define TAP_IOCTL_CONFIG_DHCP_MASQ TAP_CONTROL_CODE (7, METHOD_BUFFERED)
#define TAP_IOCTL_GET_LOG_LINE TAP_CONTROL_CODE (8, METHOD_BUFFERED)
#define TAP_IOCTL_CONFIG_DHCP_SET_OPT TAP_CONTROL_CODE (9, METHOD_BUFFERED)
struct TAPInterface_Version_pvt {
unsigned long major;
unsigned long minor;
unsigned long debug;
};
static void getVersion(HANDLE tap, struct TAPInterface_Version_pvt* version, struct Except* eh)
{
ULONG version_len;
BOOL bret = DeviceIoControl(tap,
TAP_IOCTL_GET_VERSION,
version,
sizeof(struct TAPInterface_Version_pvt),
version,
sizeof(struct TAPInterface_Version_pvt),
&version_len,
NULL);
if (!bret) {
DWORD err = GetLastError();
CloseHandle(tap);
WinFail_fail(eh, "DeviceIoControl(TAP_IOCTL_GET_VERSION)", err);
}
if (version_len != sizeof(struct TAPInterface_Version_pvt)) {
CloseHandle(tap);
Except_throw(eh, "DeviceIoControl(TAP_IOCTL_GET_VERSION) out size [%d] expected [%d]",
(int)version_len, (int)sizeof(struct TAPInterface_Version_pvt));
}
}
static void setEnabled(HANDLE tap, int status, struct Except* eh)
{
unsigned long len = 0;
BOOL bret = DeviceIoControl(tap, TAP_IOCTL_SET_MEDIA_STATUS,
&status, sizeof (status),
&status, sizeof (status), &len, NULL);
if (!bret) {
DWORD err = GetLastError();
CloseHandle(tap);
WinFail_fail(eh, "DeviceIoControl(TAP_IOCTL_SET_MEDIA_STATUS)", err);
}
}
union TAPInterface_buffer {
struct {
uint32_t length_be;
// account for ethernet misallignment
uint16_t pad;
uint8_t data[2042];
} components;
uint8_t bytes[2048];
};
#define TAPInterface_FdAndOl_state_AWAITING_READ 1
#define TAPInterface_FdAndOl_state_AWAITING_WRITE 2
struct TAPInterface_FdAndOl
{
HANDLE fd;
OVERLAPPED ol;
union TAPInterface_buffer buff;
int state;
DWORD bytes;
DWORD offset;
char* name;
};
struct TAPInterface_ThreadContext
{
struct TAPInterface_FdAndOl tap;
struct TAPInterface_FdAndOl pipe;
};
#include "util/Hex.h"
/**
* Copy data from one file handle to another.
* @return the handle which has blocked.
*/
#define thread_copy_ADD_FRAMING 1
#define thread_copy_REMOVE_FRAMING 2
static HANDLE thread_copy(struct TAPInterface_FdAndOl* from,
struct TAPInterface_FdAndOl* to,
int framing)
{
for (;;) {
uint8_t* readTo = (framing == thread_copy_ADD_FRAMING)
? from->buff.components.data : from->buff.bytes;
uint8_t* writeFrom = (framing == thread_copy_REMOVE_FRAMING)
? from->buff.components.data : from->buff.bytes;
DWORD bytesToRead = 2042;
if (framing == thread_copy_REMOVE_FRAMING) {
if (!from->bytes) {
bytesToRead = 4;
} else {
bytesToRead = from->bytes;
}
}
DWORD bytesRead = 0;
if (from->state == TAPInterface_FdAndOl_state_AWAITING_READ) {
if (!GetOverlappedResult(from->fd, &from->ol, &bytesRead, FALSE)) {
switch (GetLastError()) {
case ERROR_IO_PENDING: return from->fd;
default:;
}
printf("GetOverlappedResult(read, %s): %s\n",
from->name, WinFail_strerror(GetLastError()));
Assert_true(0);
}
printf("read completed from %s with %d bytes\n", from->name, (int)bytesRead);
from->state = 0;
} else if (from->state == TAPInterface_FdAndOl_state_AWAITING_WRITE) {
DWORD bytesWritten;
if (!GetOverlappedResult(to->fd, &from->ol, &bytesWritten, FALSE)) {
switch (GetLastError()) {
case ERROR_IO_PENDING: return to->fd;
default:;
}
printf("GetOverlappedResult(write, %s): %s\n",
to->name, WinFail_strerror(GetLastError()));
Assert_true(0);
}
from->state = 0;
if (bytesWritten < from->bytes) {
from->bytes -= bytesWritten;
from->offset += bytesWritten;
goto writeMore;
} else {
Assert_true(bytesWritten == from->bytes);
from->bytes = 0;
from->offset = 0;
printf("write to %s with %d bytes completed\n", to->name, (int)bytesWritten);
// successfully finished a write, loop back and try again.
continue;
}
} else if (!ReadFile(from->fd, &readTo[from->offset], bytesToRead, &bytesRead, &from->ol)) {
switch (GetLastError()) {
case ERROR_IO_PENDING: {
from->state = TAPInterface_FdAndOl_state_AWAITING_READ;
printf("read pending from %s\n", from->name);
return from->fd;
}
default: {
printf("ReadFile(%s): %s\n", from->name, WinFail_strerror(GetLastError()));
Assert_true(0);
}
}
} else {
printf("read completed immedietly from %s with %d bytes\n",
from->name, (int)bytesRead);
}
if (framing == thread_copy_REMOVE_FRAMING) {
if (!from->bytes) {
if (bytesRead < 4) {
from->offset += bytesRead;
continue;
}
from->bytes = Endian_bigEndianToHost32(from->buff.components.length_be) + 4;
Assert_true(from->bytes <= 2042);
}
if (bytesRead < from->bytes) {
from->offset += bytesRead;
from->bytes -= bytesRead;
continue;
}
} else {
from->bytes = bytesRead;
from->bytes += 2;
from->buff.components.length_be = Endian_hostToBigEndian32(((uint32_t)from->bytes));
from->bytes += 4;
}
from->offset = 0;
writeMore:
for (;;) {
DWORD bytes;
if (!WriteFile(to->fd,
&writeFrom[from->offset],
from->bytes - (writeFrom - from->buff.bytes),
&bytes,
&from->ol))
{
switch (GetLastError()) {
case ERROR_IO_PENDING: {
from->state = TAPInterface_FdAndOl_state_AWAITING_WRITE;
return to->fd;
}
default:;
}
printf("WriteFile(%s): %s\n", to->name, WinFail_strerror(GetLastError()));
Assert_true(0);
} else {
if (bytes < from->bytes) {
from->bytes += bytes;
from->offset += bytes;
continue;
}
Assert_true(bytes == from->bytes);
printf("write to %s with %d bytes completed immedietly\n", to->name, (int)bytes);
from->bytes = 0;
from->offset = 0;
break;
}
}
}
}
static DWORD WINAPI thread_main(LPVOID param)
{
struct TAPInterface_ThreadContext* tc = (struct TAPInterface_ThreadContext*)param;
for (;;) {
HANDLE handles[2];
handles[0] = thread_copy(&tc->tap, &tc->pipe, thread_copy_ADD_FRAMING);
handles[1] = thread_copy(&tc->pipe, &tc->tap, thread_copy_REMOVE_FRAMING);
if (WaitForMultipleObjects(2, handles, FALSE, 3000) == WAIT_FAILED) {
printf("WaitForMultipleObjects(): %s\n", WinFail_strerror(GetLastError()));
}
}
return 0;
}
struct Interface* TAPInterface_new(const char* preferredName,
char** assignedName,
struct Except* eh,
struct Log* logger,
struct EventBase* base,
struct Allocator* alloc)
{
Log_debug(logger, "Getting TAP-Windows device name");
struct TAPDevice* dev = TAPDevice_find(preferredName, eh, alloc);
*assignedName = dev->name;
NetDev_flushAddresses(dev->name, eh);
Log_debug(logger, "Opening TAP-Windows device [%s] at location [%s]", dev->name, dev->path);
struct TAPInterface_ThreadContext* tc =
Allocator_calloc(alloc, sizeof(struct TAPInterface_ThreadContext), 1);
WinFail_assert(eh, (tc->tap.ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL);
WinFail_assert(eh, (tc->pipe.ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL);
tc->tap.name = "tap";
tc->pipe.name = "pipe";
tc->tap.fd = CreateFile(dev->path,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
0);
if (tc->tap.fd == INVALID_HANDLE_VALUE) {
WinFail_fail(eh, "CreateFile(tapDevice)", GetLastError());
}
struct TAPInterface_Version_pvt ver = { .major = 0 };
getVersion(tc->tap.fd, &ver, eh);
setEnabled(tc->tap.fd, 1, eh);
tc->pipe.fd =
CreateNamedPipeA("\\\\.\\pipe\\cjdns_pipe_abcdefg",
PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
65536,
65536,
0,
NULL);
struct Pipe* p = Pipe_named("abcdefg", base, eh, alloc);
p->logger = logger;
// CreateThread(NULL, 0, piper, (LPVOID)&fh[1], 0, NULL);
CreateThread(NULL, 0, thread_main, (LPVOID)tc, 0, NULL);
Log_info(logger, "Opened TAP-Windows device [%s] version [%lu.%lu.%lu] at location [%s]",
dev->name, ver.major, ver.minor, ver.debug, dev->path);
return FramingInterface_new(2048, &p->iface, alloc);
}