diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3f1a474 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,110 @@ +cmake_minimum_required(VERSION 3.20) +project(hans VERSION 1.1 LANGUAGES C CXX) + +# Remove all debug information from Release binaries +set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "") +string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") +string(REPLACE "/Zi" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") +string(REPLACE "/ZI" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") +string(REPLACE "/ZI" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/INCREMENTAL:NO /DEBUG:NONE /EMITPOGOPHASEINFO:NO") + +# --------------------------------------------------------------------------- +# Language standard +# --------------------------------------------------------------------------- +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# --------------------------------------------------------------------------- +# MSVC-specific flags (supports x64 and ARM64) +# --------------------------------------------------------------------------- +if(MSVC) + add_compile_options( + /W3 + /wd4996 # deprecated CRT functions (_CRT_SECURE_NO_WARNINGS) + /wd4267 # size_t → int conversion (64-bit) + /wd4244 # intptr_t → int conversion (fd casts) + /wd4200 # nonstandard zero-size array (not used but silences noise) + ) + add_compile_definitions( + WIN32 # legacy guard used in original code + _WIN32 # standard MSVC pre-define (explicit) + _CRT_SECURE_NO_WARNINGS + _WINSOCK_DEPRECATED_NO_WARNINGS + WIN32_LEAN_AND_MEAN + NOMINMAX # prevent windows.h min/max macros + ) +endif() + +# --------------------------------------------------------------------------- +# Source files common to all platforms +# --------------------------------------------------------------------------- +set(COMMON_SOURCES + src/main.cpp + src/client.cpp + src/server.cpp + src/worker.cpp + src/tun.cpp + src/echo.cpp + src/auth.cpp + src/sha1.cpp + src/hans_time.cpp + src/exception.cpp + src/utility.cpp +) + +# --------------------------------------------------------------------------- +# Platform-specific TUN/TAP driver +# --------------------------------------------------------------------------- +if(WIN32) + # Native MSVC build — uses TAP-Windows (OpenVPN TAP driver) + list(APPEND COMMON_SOURCES + src/win32_compat.cpp + src/tun_dev_win32.c + ) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + list(APPEND COMMON_SOURCES src/tun_dev_linux.c) + add_compile_definitions(HAVE_LINUX_IF_TUN_H LINUX) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + list(APPEND COMMON_SOURCES src/tun_dev_freebsd.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + list(APPEND COMMON_SOURCES src/tun_dev_openbsd.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + list(APPEND COMMON_SOURCES src/tun_dev_utun.c) +else() + list(APPEND COMMON_SOURCES src/tun_dev_generic.c) +endif() + +# --------------------------------------------------------------------------- +# Executable +# --------------------------------------------------------------------------- +add_executable(hans ${COMMON_SOURCES}) + +target_include_directories(hans PRIVATE src) + +set_target_properties(hans PROPERTIES + LINK_FLAGS_RELEASE "/INCREMENTAL:NO /DEBUG:NONE" +) + +# --------------------------------------------------------------------------- +# Linker dependencies +# --------------------------------------------------------------------------- +if(WIN32) + target_link_libraries(hans PRIVATE + ws2_32 # Winsock2 + iphlpapi # IP helper API (used indirectly by Winsock) + ) +elseif(UNIX) + find_package(Threads REQUIRED) + target_link_libraries(hans PRIVATE Threads::Threads) + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # tunemu mode would also need -lpcap, but utun mode does not + endif() +endif() + +# --------------------------------------------------------------------------- +# Install +# --------------------------------------------------------------------------- +install(TARGETS hans RUNTIME DESTINATION bin) diff --git a/WIN-ARM64.md b/WIN-ARM64.md new file mode 100644 index 0000000..8bf5b8f --- /dev/null +++ b/WIN-ARM64.md @@ -0,0 +1,110 @@ +# Building Hans -- Windows ARM64 (MSVC) + +This document describes how to build Hans natively on **Windows ARM64** using Visual Studio and CMake. This replaces the legacy CYGWIN/Makefile build for x86. + +--- + +## Requirements + +| Tool | Version | Notes | +|---|---|---| +| Visual Studio | 2026 Community (or 2022) | With **Desktop development with C++** workload | +| CMake | Bundled with Visual Studio | Must be added to PATH | +| TAP-Windows driver | Any | Included with [OpenVPN](https://openvpn.net/community-downloads/) | + +--- + +## 1. Add CMake to PATH + +CMake ships with Visual Studio but is not added to PATH automatically. + +Open PowerShell as **Administrator** and run: + +```powershell +$cmakePath = "C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin" +[Environment]::SetEnvironmentVariable("Path", $env:Path + ";$cmakePath", "Machine") +``` + +> For Visual Studio 2022, replace `\18\` with `\17\` or `\2022\` in the path above. + +Close and reopen PowerShell, then verify: + +```powershell +cmake --version +``` + +--- + +## 2. Configure the Build + +Open PowerShell, navigate to the project folder and run: + +```powershell +cd C:\path\to\hans +cmake -B build -A ARM64 +``` + +You should see output ending with: +``` +-- Build files have been written to: ...\hans\build +``` + +--- + +## 3. Compile + +```powershell +cmake --build build --config Release +``` + +The executable will be at: +``` +build\Release\hans.exe +``` + +--- + +## 4. Clean Build (if needed) + +If you are recompiling after source changes: + +```powershell +Remove-Item -Recurse -Force build +cmake -B build -A ARM64 +cmake --build build --config Release +``` + +--- + +## 5. Verify No Debug Info (optional) + +To confirm the Release binary has no embedded debug information: + +```powershell +& "C:\Program Files\Microsoft Visual Studio\18\Community\VC\Tools\MSVC\14.50.35717\bin\Hostarm64\arm64\dumpbin.exe" /headers build\Release\hans.exe | Select-String "debug" +``` + +Expected output: +``` +0 [ 0] RVA [size] of Debug Directory +``` + +--- + +## Runtime Requirements + +The **TAP-Windows** driver must be installed before running `hans.exe`. It is bundled with [OpenVPN](https://openvpn.net/community-downloads/) -- installing OpenVPN is sufficient. + +--- + +## Windows-specific Files + +These files were added for the Windows/MSVC port and have no effect on Linux builds: + +| File | Purpose | +|---|---| +| `src/win32_compat.h` / `.cpp` | POSIX compatibility layer (sockets, threads) | +| `src/tun_dev_win32.c` | TAP-Windows adapter driver with Overlapped I/O | +| `CMakeLists.txt` | CMake build system replacing the original Makefile | + +> `src/hans_time.h` / `hans_time.cpp` were renamed from `time.h` / `time.cpp` to avoid a naming conflict with the MSVC system header ``. diff --git a/src/auth.cpp b/src/auth.cpp index 095a7fc..b309896 100644 --- a/src/auth.cpp +++ b/src/auth.cpp @@ -17,11 +17,18 @@ * */ +/* win32_compat.h must be the first include on Windows. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "auth.h" #include "sha1.h" #include "utility.h" -#include +#ifndef _WIN32 +# include +#endif Auth::Auth(const std::string &passphrase) : passphrase(passphrase) diff --git a/src/client.cpp b/src/client.cpp index b47e29a..20c5ed6 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -17,6 +17,11 @@ * */ +/* win32_compat.h must be the first include on Windows. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "client.h" #include "server.h" #include "exception.h" @@ -24,9 +29,12 @@ #include "utility.h" #include -#include -#include -#include + +#ifndef _WIN32 +# include +# include +# include +#endif using std::vector; using std::string; diff --git a/src/echo.cpp b/src/echo.cpp index c931d04..fbc8511 100644 --- a/src/echo.cpp +++ b/src/echo.cpp @@ -17,28 +17,39 @@ * */ +/* win32_compat.h must be the first include on Windows. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "echo.h" #include "exception.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#ifdef _WIN32 + /* struct ip defined in win32_compat.h; winsock2 provides socket API */ +# define CLOSE_SOCKET(fd) closesocket((SOCKET)(uintptr_t)(fd)) +#else +# include +# include +# include +# include +# include +# include +# include +# include +# include +# define CLOSE_SOCKET(fd) close(fd) +#endif + #include #include -#include typedef ip IpHeader; Echo::Echo(int maxPayloadSize) { - fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); - if (fd == -1) + fd = (hans_fd_t)socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (fd == (hans_fd_t)-1) throw Exception("creating icmp socket", true); bufferSize = maxPayloadSize + headerSize(); @@ -48,7 +59,7 @@ Echo::Echo(int maxPayloadSize) Echo::~Echo() { - close(fd); + CLOSE_SOCKET(fd); } int Echo::headerSize() @@ -73,7 +84,10 @@ void Echo::send(int payloadLength, uint32_t realIp, bool reply, uint16_t id, uin header->chksum = 0; header->chksum = icmpChecksum(sendBuffer.data() + sizeof(IpHeader), payloadLength + sizeof(EchoHeader)); - int result = sendto(fd, sendBuffer.data() + sizeof(IpHeader), payloadLength + sizeof(EchoHeader), 0, (struct sockaddr *)&target, sizeof(struct sockaddr_in)); + int result = sendto((SOCKET)(uintptr_t)fd, + sendBuffer.data() + sizeof(IpHeader), + payloadLength + (int)sizeof(EchoHeader), 0, + (struct sockaddr *)&target, sizeof(struct sockaddr_in)); if (result == -1) syslog(LOG_ERR, "error sending icmp packet: %s", strerror(errno)); } @@ -83,7 +97,10 @@ int Echo::receive(uint32_t &realIp, bool &reply, uint16_t &id, uint16_t &seq) struct sockaddr_in source; int source_addr_len = sizeof(struct sockaddr_in); - int dataLength = recvfrom(fd, receiveBuffer.data(), bufferSize, 0, (struct sockaddr *)&source, (socklen_t *)&source_addr_len); + int dataLength = recvfrom((SOCKET)(uintptr_t)fd, + receiveBuffer.data(), bufferSize, 0, + (struct sockaddr *)&source, + (socklen_t *)&source_addr_len); if (dataLength == -1) { syslog(LOG_ERR, "error receiving icmp packet: %s", strerror(errno)); diff --git a/src/echo.h b/src/echo.h index 944a299..8891ed4 100644 --- a/src/echo.h +++ b/src/echo.h @@ -24,13 +24,23 @@ #include #include +/* hans_fd_t: intptr_t on Windows (covers 64-bit SOCKET), int on POSIX */ +#ifdef _WIN32 +# include "win32_compat.h" +#else +# ifndef HANS_FD_T_DEFINED +# define HANS_FD_T_DEFINED + typedef int hans_fd_t; +# endif +#endif + class Echo { public: Echo(int maxPayloadSize); ~Echo(); - int getFd() { return fd; } + hans_fd_t getFd() { return fd; } void send(int payloadLength, uint32_t realIp, bool reply, uint16_t id, uint16_t seq); int receive(uint32_t &realIp, bool &reply, uint16_t &id, uint16_t &seq); @@ -51,7 +61,7 @@ class Echo uint16_t icmpChecksum(const char *data, int length); - int fd; + hans_fd_t fd; int bufferSize; std::vector sendBuffer; std::vector receiveBuffer; diff --git a/src/exception.cpp b/src/exception.cpp index 253ab23..d116913 100644 --- a/src/exception.cpp +++ b/src/exception.cpp @@ -17,6 +17,11 @@ * */ +/* win32_compat.h must be the first include on Windows. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "exception.h" #include diff --git a/src/time.cpp b/src/hans_time.cpp similarity index 94% rename from src/time.cpp rename to src/hans_time.cpp index abdffae..e22135f 100644 --- a/src/time.cpp +++ b/src/hans_time.cpp @@ -17,7 +17,12 @@ * */ -#include "time.h" +/* win32_compat.h must be the first include on Windows. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + +#include "hans_time.h" const Time Time::ZERO = Time(0); diff --git a/src/time.h b/src/hans_time.h similarity index 88% rename from src/time.h rename to src/hans_time.h index 878db78..7653891 100644 --- a/src/time.h +++ b/src/hans_time.h @@ -17,10 +17,14 @@ * */ -#ifndef TIME_H -#define TIME_H +#ifndef HANS_TIME_H +#define HANS_TIME_H -#include +#ifdef _WIN32 +# include "win32_compat.h" /* provides struct timeval via winsock2.h */ +#else +# include +#endif class Time { diff --git a/src/main.cpp b/src/main.cpp index ff22550..e4cc72c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,23 +17,33 @@ * */ +/* win32_compat.h must be the first include on Windows so that is + * processed before windows.h can corrupt the _INC_TIME include guard. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "client.h" #include "server.h" #include "exception.h" +#ifdef _WIN32 + /* winsock2, syslog, getopt, gettimeofday, daemon already via win32_compat */ +#else +# include +# include +# include +# include +# include +# include +# include +# include +#endif + #include -#include -#include -#include #include -#include -#include -// #include #include #include -#include -#include -#include #include #include @@ -94,6 +104,9 @@ static void usage() int main(int argc, char *argv[]) { +#ifdef _WIN32 + hans_winsock_init(); +#endif string serverName; string userName; string passphrase; @@ -189,10 +202,10 @@ int main(int argc, char *argv[]) if (!userName.empty()) { -#ifdef WIN32 +#ifdef _WIN32 syslog(LOG_ERR, "dropping privileges is not supported on Windows"); return 1; -#endif +#else passwd *pw = getpwnam(userName.data()); if (pw != NULL) @@ -205,6 +218,7 @@ int main(int argc, char *argv[]) syslog(LOG_ERR, "user not found"); return 1; } +#endif } if (!verbose) @@ -247,8 +261,12 @@ int main(int argc, char *argv[]) if (!foreground) { +#ifdef _WIN32 + syslog(LOG_INFO, "daemon mode not supported on Windows; running in foreground"); +#else syslog(LOG_INFO, "detaching from terminal"); daemon(0, 0); +#endif } worker->run(); diff --git a/src/server.cpp b/src/server.cpp index d427bd9..3665148 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -17,16 +17,24 @@ * */ +/* win32_compat.h must be the first include on Windows. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "server.h" #include "client.h" #include "config.h" #include "utility.h" #include -#include -#include #include +#ifndef _WIN32 +# include +# include +#endif + using std::string; using std::cout; using std::endl; diff --git a/src/tun.cpp b/src/tun.cpp index 8bfeb58..eab25a9 100644 --- a/src/tun.cpp +++ b/src/tun.cpp @@ -17,35 +17,45 @@ * */ +/* win32_compat.h must be the first include on Windows. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "tun.h" #include "exception.h" #include "utility.h" -#include -#include -#include -#include -#include -#include +#ifdef _WIN32 + /* struct ip, winsock2, windows.h already included via win32_compat.h above */ +#else +# include +# include +# include +# include +# include +# include +#endif + #include #include #include #include - -#ifdef WIN32 -#include -#endif +#include typedef ip IpHeader; using std::string; -#ifdef WIN32 -static void winsystem(char *cmd) +#ifdef _WIN32 +static void winsystem(const char *cmd) { - STARTUPINFO info = { sizeof(info) }; + /* CreateProcessA requires a mutable command-line buffer */ + std::vector buf(cmd, cmd + strlen(cmd) + 1); + STARTUPINFOA info = { sizeof(info) }; PROCESS_INFORMATION processInfo; - if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &info, &processInfo)) + if (CreateProcessA(NULL, buf.data(), NULL, NULL, TRUE, 0, + NULL, NULL, &info, &processInfo)) { WaitForSingleObject(processInfo.hProcess, INFINITE); CloseHandle(processInfo.hProcess); @@ -72,7 +82,7 @@ Tun::Tun(const string *device, int mtu) std::stringstream cmdline; -#ifdef WIN32 +#ifdef _WIN32 cmdline << "netsh interface ipv4 set subinterface \"" << this->device << "\" mtu=" << mtu; winsystem(cmdline.str().data()); @@ -94,14 +104,14 @@ void Tun::setIp(uint32_t ip, uint32_t destIp) string ips = Utility::formatIp(ip); string destIps = Utility::formatIp(destIp); -#ifdef WIN32 +#ifdef _WIN32 cmdline << "netsh interface ip set address name=\"" << device << "\" " << "static " << ips << " 255.255.255.0"; winsystem(cmdline.str().data()); if (!tun_set_ip(fd, ip, ip & 0xffffff00, 0xffffff00)) syslog(LOG_ERR, "could not set tun device driver ip address: %s", tun_last_error()); -#elif LINUX +#elif defined(LINUX) cmdline << "/sbin/ifconfig " << device << " " << ips << " netmask 255.255.255.0"; if (system(cmdline.str().data()) != 0) syslog(LOG_ERR, "could not set tun device ip address"); diff --git a/src/tun.h b/src/tun.h index 3e57d00..5a69438 100644 --- a/src/tun.h +++ b/src/tun.h @@ -20,7 +20,7 @@ #ifndef TUN_H #define TUN_H -#include "tun_dev.h" +#include "tun_dev.h" /* also defines hans_fd_t */ #include #include @@ -31,7 +31,7 @@ class Tun Tun(const std::string *device, int mtu); ~Tun(); - int getFd() { return fd; } + hans_fd_t getFd() { return fd; } int read(char *buffer); int read(char *buffer, uint32_t &sourceIp, uint32_t &destIp); @@ -43,7 +43,7 @@ class Tun std::string device; int mtu; - int fd; + hans_fd_t fd; }; #endif diff --git a/src/tun_dev.h b/src/tun_dev.h index f8c16b1..466eadb 100644 --- a/src/tun_dev.h +++ b/src/tun_dev.h @@ -2,47 +2,55 @@ * Hans - IP over ICMP * Copyright (C) 2013 Friedrich Schöller * 1998-2000 Maxim Krasnyansky - * + * * 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 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 . - * + * */ -#ifdef WIN32 +#ifdef _WIN32 -#include -#include +#include "win32_compat.h" /* provides hans_fd_t, uint32_t, bool, VTUN_DEV_LEN */ #define VTUN_DEV_LEN 100 -#else +#else /* !_WIN32 */ -#define VTUN_DEV_LEN 20 +#include +#include +/* hans_fd_t = int on POSIX */ +#ifndef HANS_FD_T_DEFINED +#define HANS_FD_T_DEFINED +typedef int hans_fd_t; #endif +#define VTUN_DEV_LEN 20 + +#endif /* _WIN32 */ + #ifdef __cplusplus extern "C" { #endif - int tun_open(char *dev); - int tun_close(int fd, char *dev); - int tun_write(int fd, char *buf, int len); - int tun_read(int fd, char *buf, int len); - const char *tun_last_error(); + hans_fd_t tun_open(char *dev); + int tun_close(hans_fd_t fd, char *dev); + int tun_write(hans_fd_t fd, char *buf, int len); + int tun_read(hans_fd_t fd, char *buf, int len); + const char *tun_last_error(void); -#ifdef WIN32 - bool tun_set_ip(int fd, uint32_t local, uint32_t network, uint32_t netmask); +#ifdef _WIN32 + bool tun_set_ip(hans_fd_t fd, uint32_t local, uint32_t network, uint32_t netmask); #endif #ifdef __cplusplus diff --git a/src/tun_dev_win32.c b/src/tun_dev_win32.c new file mode 100644 index 0000000..f8644cd --- /dev/null +++ b/src/tun_dev_win32.c @@ -0,0 +1,455 @@ +/* + * Hans - IP over ICMP + * TAP adapter driver for native Windows / MSVC + * + * Replaces tun_dev_cygwin.c for builds with MSVC (no Cygwin layer). + * + * Architecture: + * - tun_open() opens a TAP-Windows adapter and creates a TCP + * loopback socket-pair so the main select()-loop can + * wait on a real SOCKET descriptor. + * - A dedicated reader thread issues OVERLAPPED ReadFile() calls on + * the TAP handle and forwards each packet to the write + * end of the socket-pair via send(). + * - tun_read() calls recv() on the read end of the socket-pair. + * - tun_write() calls WriteFile() (with OVERLAPPED) on the TAP handle. + * + * Requires TAP-Windows (OpenVPN TAP driver) to be installed. + * + * Copyright (C) 2013 Friedrich Schöller + * Windows native port (c) 2024 + */ + +#ifdef _WIN32 + +#include "win32_compat.h" +#include /* CTL_CODE, FILE_DEVICE_UNKNOWN, METHOD_BUFFERED, FILE_ANY_ACCESS */ +#include "tun_dev.h" + +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* TAP-Windows IOCTL codes */ +/* ------------------------------------------------------------------ */ +#define TAP_WIN_CONTROL_CODE(req, method) \ + CTL_CODE(FILE_DEVICE_UNKNOWN, (req), (method), FILE_ANY_ACCESS) + +#define TAP_WIN_IOCTL_GET_MAC TAP_WIN_CONTROL_CODE(1, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_VERSION TAP_WIN_CONTROL_CODE(2, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_MTU TAP_WIN_CONTROL_CODE(3, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_INFO TAP_WIN_CONTROL_CODE(4, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT TAP_WIN_CONTROL_CODE(5, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_SET_MEDIA_STATUS TAP_WIN_CONTROL_CODE(6, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_CONFIG_DHCP_MASQ TAP_WIN_CONTROL_CODE(7, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_LOG_LINE TAP_WIN_CONTROL_CODE(8, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT TAP_WIN_CONTROL_CODE(9, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_CONFIG_TUN TAP_WIN_CONTROL_CODE(10, METHOD_BUFFERED) + +#define NETWORK_CONNECTIONS_KEY \ + "SYSTEM\\CurrentControlSet\\Control\\Network\\" \ + "{4D36E972-E325-11CE-BFC1-08002BE10318}" + +#define USERMODEDEVICEDIR "\\\\.\\Global\\" +#define TAP_WIN_SUFFIX ".tap" + +/* ------------------------------------------------------------------ */ +/* Error buffer */ +/* ------------------------------------------------------------------ */ +#define ERROR_BUF_SIZE 1024 +static char s_error_buf[ERROR_BUF_SIZE]; + +static void set_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(s_error_buf, ERROR_BUF_SIZE, fmt, ap); + va_end(ap); +} + +static void clear_error(void) { s_error_buf[0] = '\0'; } + +static const char *winerr_str(DWORD code) +{ + static char buf[512]; + char *p = buf; + int written = sprintf_s(buf, sizeof(buf), "(%lu) ", (unsigned long)code); + if (written > 0) p += written; + + if (!FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, code, 0, p, + (DWORD)(sizeof(buf) - (size_t)(p - buf)), NULL)) + strcpy_s(p, sizeof(buf) - (size_t)(p - buf), "(unknown error)"); + + /* strip trailing CR/LF */ + { + size_t n = strlen(p); + while (n && (p[n-1] == '\r' || p[n-1] == '\n')) + p[--n] = '\0'; + } + return buf; +} + +/* ------------------------------------------------------------------ */ +/* Per-adapter state (single instance — Hans opens one TUN device) */ +/* ------------------------------------------------------------------ */ +static struct { + SOCKET reader_sock; /* main code reads packets here (select) */ + SOCKET writer_sock; /* reader thread writes packets here */ + HANDLE reader_thread; /* NULL = not running */ + HANDLE adapter_handle; + HANDLE stop_event; /* signaled by tun_close to request exit */ +} g_adapter = { + INVALID_SOCKET, + INVALID_SOCKET, + NULL, + INVALID_HANDLE_VALUE, + NULL +}; + +/* ------------------------------------------------------------------ */ +/* open_tap_adapter */ +/* */ +/* Enumerates network adapters in the Windows registry and opens the */ +/* first TAP-Windows adapter found (or the one matching 'name'). */ +/* On success fills 'name' with the adapter's friendly name and */ +/* returns a valid HANDLE; returns INVALID_HANDLE_VALUE on failure. */ +/* ------------------------------------------------------------------ */ +static HANDLE open_tap_adapter(char *name) +{ + HKEY conn_key = NULL; + HKEY adap_key = NULL; + DWORD idx, len; + char adapter_id [VTUN_DEV_LEN]; + char adapter_name[VTUN_DEV_LEN]; + char reg_path [512]; + char dev_path [512]; + HANDLE handle = INVALID_HANDLE_VALUE; + + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, + 0, KEY_READ, &conn_key) != ERROR_SUCCESS) { + set_error("opening registry: %s", winerr_str(GetLastError())); + return INVALID_HANDLE_VALUE; + } + + for (idx = 0; ; idx++) { + len = sizeof(adapter_id); + if (RegEnumKeyExA(conn_key, idx, adapter_id, &len, + NULL, NULL, NULL, NULL) != ERROR_SUCCESS) + break; + + sprintf_s(reg_path, sizeof(reg_path), + "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapter_id); + + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, reg_path, + 0, KEY_READ, &adap_key) != ERROR_SUCCESS) + continue; + + len = sizeof(adapter_name); + BOOL ok = (RegQueryValueExA(adap_key, "Name", 0, 0, + (LPBYTE)adapter_name, &len) == ERROR_SUCCESS); + RegCloseKey(adap_key); + adap_key = NULL; + + if (!ok) continue; + + /* If a name was requested, match against friendly name or GUID */ + if (name && name[0] && + strcmp(name, adapter_name) != 0 && + strcmp(name, adapter_id) != 0) + continue; + + sprintf_s(dev_path, sizeof(dev_path), + USERMODEDEVICEDIR "%s" TAP_WIN_SUFFIX, adapter_id); + + handle = CreateFileA(dev_path, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, + NULL); + if (handle != INVALID_HANDLE_VALUE) { + strncpy_s(name, VTUN_DEV_LEN, adapter_name, _TRUNCATE); + break; + } + } + + RegCloseKey(conn_key); + + if (handle == INVALID_HANDLE_VALUE) + set_error("could not open tap adapter (is TAP-Windows installed?)"); + + return handle; +} + +/* ------------------------------------------------------------------ */ +/* reader_thread_proc */ +/* */ +/* Reads packets from the TAP adapter using OVERLAPPED I/O and */ +/* forwards them to the socket-pair write end via send(). */ +/* ------------------------------------------------------------------ */ +static DWORD WINAPI reader_thread_proc(LPVOID param) +{ + (void)param; + + char buf[0xFFFF]; /* maximum IPv4 packet size */ + OVERLAPPED ov; + DWORD len; + HANDLE events[2]; + + memset(&ov, 0, sizeof(ov)); + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ov.hEvent) { + hans_syslog(LOG_ERR, "tap reader: CreateEvent failed: %s", + winerr_str(GetLastError())); + return 1; + } + + events[0] = ov.hEvent; + events[1] = g_adapter.stop_event; + + for (;;) { + ResetEvent(ov.hEvent); + + /* Issue the async read. NULL for lpNumberOfBytesRead: on an + overlapped handle that field is unreliable on sync completion; + the real count always comes from GetOverlappedResult below. */ + if (!ReadFile(g_adapter.adapter_handle, buf, sizeof(buf), NULL, &ov)) { + DWORD err = GetLastError(); + if (err != ERROR_IO_PENDING) { + hans_syslog(LOG_ERR, "tap reader: ReadFile error: %s", + winerr_str(err)); + break; + } + } + + /* Wait for either packet arrival or a shutdown request. + This keeps the thread alive while the ReadFile is pending — + TerminateThread is never called, so no ERROR_OPERATION_ABORTED. */ + DWORD w = WaitForMultipleObjects(2, events, FALSE, INFINITE); + + if (w == WAIT_OBJECT_0 + 1) { + /* stop_event: cancel the pending read and exit cleanly */ + CancelIo(g_adapter.adapter_handle); + GetOverlappedResult(g_adapter.adapter_handle, &ov, &len, TRUE); + break; + } + if (w != WAIT_OBJECT_0) { + hans_syslog(LOG_ERR, "tap reader: WaitForMultipleObjects error: %s", + winerr_str(GetLastError())); + break; + } + + if (!GetOverlappedResult(g_adapter.adapter_handle, &ov, &len, FALSE)) { + hans_syslog(LOG_ERR, "tap reader: GetOverlappedResult error: %s", + winerr_str(GetLastError())); + break; + } + + if (send(g_adapter.writer_sock, buf, (int)len, 0) == SOCKET_ERROR) { + hans_syslog(LOG_ERR, "tap reader: send error: %d", + WSAGetLastError()); + break; + } + } + + CloseHandle(ov.hEvent); + return 0; +} + +/* ================================================================== */ +/* Public API */ +/* ================================================================== */ + +hans_fd_t tun_open(char *dev) +{ + SOCKET sv[2]; + + clear_error(); + + if (win32_socketpair(sv) != 0) { + set_error("creating socket pair: WSA %d", WSAGetLastError()); + return (hans_fd_t)INVALID_SOCKET; + } + g_adapter.reader_sock = sv[0]; + g_adapter.writer_sock = sv[1]; + + g_adapter.stop_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!g_adapter.stop_event) { + set_error("creating stop event: %s", winerr_str(GetLastError())); + tun_close((hans_fd_t)g_adapter.reader_sock, NULL); + return (hans_fd_t)INVALID_SOCKET; + } + + g_adapter.adapter_handle = open_tap_adapter(dev); + if (g_adapter.adapter_handle == INVALID_HANDLE_VALUE) { + tun_close((hans_fd_t)g_adapter.reader_sock, NULL); + return (hans_fd_t)INVALID_SOCKET; + } + + /* Reader thread is NOT started here. The TAP driver aborts any + ReadFile while media status is FALSE. Thread is started in + tun_set_ip() only after TAP_WIN_IOCTL_SET_MEDIA_STATUS succeeds. */ + + return (hans_fd_t)g_adapter.reader_sock; +} + +int tun_close(hans_fd_t fd, char *dev) +{ + (void)fd; + (void)dev; + + if (g_adapter.reader_thread != NULL) { + /* Signal the reader thread instead of killing it with TerminateThread. + TerminateThread cancels pending I/O with ERROR_OPERATION_ABORTED. + SetEvent lets the thread notice via WaitForMultipleObjects, call + CancelIo itself, and exit cleanly. */ + if (g_adapter.stop_event != NULL) + SetEvent(g_adapter.stop_event); + WaitForSingleObject(g_adapter.reader_thread, 3000); + CloseHandle(g_adapter.reader_thread); + g_adapter.reader_thread = NULL; + } + if (g_adapter.stop_event != NULL) { + CloseHandle(g_adapter.stop_event); + g_adapter.stop_event = NULL; + } + if (g_adapter.writer_sock != INVALID_SOCKET) { + closesocket(g_adapter.writer_sock); + g_adapter.writer_sock = INVALID_SOCKET; + } + if (g_adapter.reader_sock != INVALID_SOCKET) { + closesocket(g_adapter.reader_sock); + g_adapter.reader_sock = INVALID_SOCKET; + } + if (g_adapter.adapter_handle != INVALID_HANDLE_VALUE) { + CloseHandle(g_adapter.adapter_handle); + g_adapter.adapter_handle = INVALID_HANDLE_VALUE; + } + return 0; +} + +int tun_write(hans_fd_t fd, char *buf, int len) +{ + (void)fd; + + OVERLAPPED ov; + DWORD written; + + memset(&ov, 0, sizeof(ov)); + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ov.hEvent) { + set_error("tap write: CreateEvent: %s", winerr_str(GetLastError())); + return -1; + } + + /* Pass NULL for lpNumberOfBytesWritten — unreliable on overlapped handles. */ + if (!WriteFile(g_adapter.adapter_handle, buf, (DWORD)len, NULL, &ov)) { + DWORD err = GetLastError(); + if (err != ERROR_IO_PENDING) { + set_error("tap write: WriteFile: %s", winerr_str(err)); + CloseHandle(ov.hEvent); + return -1; + } + } + + /* Always obtain the final byte count via GetOverlappedResult. */ + if (!GetOverlappedResult(g_adapter.adapter_handle, &ov, &written, TRUE)) { + set_error("tap write: GetOverlappedResult: %s", winerr_str(GetLastError())); + CloseHandle(ov.hEvent); + return -1; + } + + CloseHandle(ov.hEvent); + return (int)written; +} + +int tun_read(hans_fd_t fd, char *buf, int len) +{ + int n = recv((SOCKET)(uintptr_t)fd, buf, len, 0); + if (n == SOCKET_ERROR) { + set_error("tun_read: recv WSA %d", WSAGetLastError()); + return -1; + } + return n; +} + +const char *tun_last_error(void) +{ + return s_error_buf; +} + +bool tun_set_ip(hans_fd_t fd, uint32_t local, uint32_t network, uint32_t netmask) +{ + (void)fd; + + uint32_t addresses[3]; + DWORD status, len; + OVERLAPPED ov; + + addresses[0] = htonl(local); + addresses[1] = htonl(network); + addresses[2] = htonl(netmask); + + /* The adapter handle was opened with FILE_FLAG_OVERLAPPED, so every + I/O call — including DeviceIoControl — must supply an OVERLAPPED + structure. Passing NULL would be undefined behaviour per MSDN. */ + memset(&ov, 0, sizeof(ov)); + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ov.hEvent) { + set_error("tap set_ip: CreateEvent: %s", winerr_str(GetLastError())); + return false; + } + + if (!DeviceIoControl(g_adapter.adapter_handle, + TAP_WIN_IOCTL_CONFIG_TUN, + addresses, sizeof(addresses), + addresses, sizeof(addresses), NULL, &ov)) { + DWORD err = GetLastError(); + if (err != ERROR_IO_PENDING) { + set_error("configuring tap addresses: %s", winerr_str(err)); + CloseHandle(ov.hEvent); + return false; + } + } + if (!GetOverlappedResult(g_adapter.adapter_handle, &ov, &len, TRUE)) { + set_error("configuring tap addresses: %s", winerr_str(GetLastError())); + CloseHandle(ov.hEvent); + return false; + } + + ResetEvent(ov.hEvent); + status = TRUE; + if (!DeviceIoControl(g_adapter.adapter_handle, + TAP_WIN_IOCTL_SET_MEDIA_STATUS, + &status, sizeof(status), + &status, sizeof(status), NULL, &ov)) { + DWORD err = GetLastError(); + if (err != ERROR_IO_PENDING) { + set_error("enabling tap device: %s", winerr_str(err)); + CloseHandle(ov.hEvent); + return false; + } + } + if (!GetOverlappedResult(g_adapter.adapter_handle, &ov, &len, TRUE)) { + set_error("enabling tap device: %s", winerr_str(GetLastError())); + CloseHandle(ov.hEvent); + return false; + } + + CloseHandle(ov.hEvent); + + /* Media status is now TRUE — safe to start reading from the adapter. */ + g_adapter.reader_thread = CreateThread( + NULL, 0, reader_thread_proc, NULL, 0, NULL); + if (g_adapter.reader_thread == NULL) { + set_error("creating reader thread: %s", winerr_str(GetLastError())); + return false; + } + + clear_error(); + return true; +} + +#endif /* _WIN32 */ diff --git a/src/utility.cpp b/src/utility.cpp index 87521a7..e431149 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -17,6 +17,12 @@ * */ +/* win32_compat.h must be the first include on Windows so that is + * processed before windows.h corrupts the _INC_TIME include guard. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "utility.h" #include diff --git a/src/win32_compat.cpp b/src/win32_compat.cpp new file mode 100644 index 0000000..c8295f4 --- /dev/null +++ b/src/win32_compat.cpp @@ -0,0 +1,239 @@ +/* + * Hans - IP over ICMP + * Windows (MSVC) compatibility implementation + */ + +#ifdef _WIN32 + +#include "win32_compat.h" + +#include +#include +#include + +/* ================================================================== */ +/* syslog */ +/* ================================================================== */ + +static const char *s_ident = "hans"; +static int s_logmask = 0xFF; /* all priorities enabled */ + +void hans_openlog(const char *ident, int /*option*/, int /*facility*/) +{ + if (ident && *ident) + s_ident = ident; +} + +void hans_syslog(int priority, const char *format, ...) +{ + if (priority > s_logmask) + return; + + const char *level; + switch (priority) { + case LOG_EMERG: level = "EMERG"; break; + case LOG_ALERT: level = "ALERT"; break; + case LOG_CRIT: level = "CRIT"; break; + case LOG_ERR: level = "ERROR"; break; + case LOG_WARNING: level = "WARNING"; break; + case LOG_NOTICE: level = "NOTICE"; break; + case LOG_INFO: level = "INFO"; break; + case LOG_DEBUG: level = "DEBUG"; break; + default: level = "?"; break; + } + + va_list ap; + va_start(ap, format); + fprintf(stderr, "[%s] %s: ", s_ident, level); + vfprintf(stderr, format, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +void hans_closelog(void) { } + +void hans_setlogmask(int mask) +{ + s_logmask = mask; +} + +/* ================================================================== */ +/* gettimeofday */ +/* Uses GetSystemTimeAsFileTime for ~100 ns precision. */ +/* ================================================================== */ + +int hans_gettimeofday(struct timeval *tv, void * /*tz*/) +{ + /* Windows FILETIME: 100-ns intervals since 1601-01-01. + Unix epoch offset (1601-01-01 → 1970-01-01): 11644473600 seconds + = 116444736000000000 × 100 ns intervals */ + static const ULONGLONG EPOCH = 116444736000000000ULL; + + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + ULONGLONG t = ((ULONGLONG)ft.dwHighDateTime << 32) | ft.dwLowDateTime; + t -= EPOCH; + + tv->tv_sec = (long)(t / 10000000ULL); + tv->tv_usec = (long)((t % 10000000ULL) / 10ULL); + return 0; +} + +/* ================================================================== */ +/* getopt */ +/* Simple POSIX-compatible getopt() implementation. */ +/* ================================================================== */ + +char *hans_optarg = NULL; +int hans_optind = 1; +int hans_opterr = 1; +int hans_optopt = 0; + +int hans_getopt(int argc, char * const argv[], const char *optstring) +{ + static int sp = 1; + int c; + const char *cp; + + if (sp == 1) { + if (hans_optind >= argc || + argv[hans_optind][0] != '-' || + argv[hans_optind][1] == '\0') + return -1; + if (strcmp(argv[hans_optind], "--") == 0) { + hans_optind++; + return -1; + } + } + + hans_optopt = c = (unsigned char)argv[hans_optind][sp]; + + if (c == ':' || (cp = strchr(optstring, c)) == NULL) { + if (hans_opterr) + fprintf(stderr, "%s: illegal option -- %c\n", argv[0], c); + if (argv[hans_optind][++sp] == '\0') { + hans_optind++; + sp = 1; + } + return '?'; + } + + if (*++cp == ':') { + if (argv[hans_optind][sp + 1] != '\0') { + hans_optarg = &argv[hans_optind++][sp + 1]; + } else if (++hans_optind >= argc) { + if (hans_opterr) + fprintf(stderr, "%s: option requires an argument -- %c\n", + argv[0], c); + sp = 1; + return '?'; + } else { + hans_optarg = argv[hans_optind++]; + } + sp = 1; + } else { + if (argv[hans_optind][++sp] == '\0') { + sp = 1; + hans_optind++; + } + hans_optarg = NULL; + } + + return c; +} + +/* ================================================================== */ +/* daemon() stub */ +/* Windows does not support Unix-style daemonisation. Return -1 so */ +/* the caller (main.cpp) stays in foreground mode. */ +/* ================================================================== */ + +int hans_daemon(int /*nochdir*/, int /*noclose*/) +{ + return -1; +} + +/* ================================================================== */ +/* Winsock init / cleanup */ +/* ================================================================== */ + +void hans_winsock_init(void) +{ + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { + fprintf(stderr, "WSAStartup failed: %d\n", WSAGetLastError()); + exit(1); + } +} + +void hans_winsock_cleanup(void) +{ + WSACleanup(); +} + +/* ================================================================== */ +/* win32_socketpair — TCP loopback pair */ +/* */ +/* Creates two connected TCP sockets via 127.0.0.1 so that both ends */ +/* can be used with select() (unlike anonymous pipes). */ +/* sv[0] = "read" end (main code), sv[1] = "write" end (thread). */ +/* ================================================================== */ + +int win32_socketpair(SOCKET sv[2]) +{ + SOCKET listen_sock = INVALID_SOCKET; + struct sockaddr_in addr; + int addrlen = sizeof(addr); + DWORD last_err = 0; + + sv[0] = sv[1] = INVALID_SOCKET; + + listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_sock == INVALID_SOCKET) + goto fail; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; /* OS picks a free port */ + + if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) + goto fail; + if (getsockname(listen_sock, (struct sockaddr *)&addr, &addrlen) == SOCKET_ERROR) + goto fail; + if (listen(listen_sock, 1) == SOCKET_ERROR) + goto fail; + + sv[0] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sv[0] == INVALID_SOCKET) + goto fail; + + if (connect(sv[0], (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) + goto fail; + + sv[1] = accept(listen_sock, NULL, NULL); + if (sv[1] == INVALID_SOCKET) + goto fail; + + closesocket(listen_sock); + + /* Disable Nagle algorithm for low latency */ + { + BOOL nodelay = TRUE; + setsockopt(sv[0], IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)); + setsockopt(sv[1], IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)); + } + + return 0; + +fail: + last_err = WSAGetLastError(); + if (listen_sock != INVALID_SOCKET) closesocket(listen_sock); + if (sv[0] != INVALID_SOCKET) { closesocket(sv[0]); sv[0] = INVALID_SOCKET; } + if (sv[1] != INVALID_SOCKET) { closesocket(sv[1]); sv[1] = INVALID_SOCKET; } + WSASetLastError(last_err); + return -1; +} + +#endif /* _WIN32 */ diff --git a/src/win32_compat.h b/src/win32_compat.h new file mode 100644 index 0000000..67ff65c --- /dev/null +++ b/src/win32_compat.h @@ -0,0 +1,212 @@ +/* + * Hans - IP over ICMP + * Windows (MSVC) compatibility layer + * + * Provides POSIX stubs and type definitions needed by the codebase + * when building with MSVC on Windows (including ARM64). + * + * Include this header before any other project header in files + * that use POSIX APIs. + */ + +#ifndef WIN32_COMPAT_H +#define WIN32_COMPAT_H + +#ifdef _WIN32 + +/* + * CRITICAL INCLUDE ORDER FOR MSVC + * ================================ + * 1. must be first (before windows.h), and also provides + * struct timeval needed by src/time.h. + * 2. must come AFTER winsock2.h (so timeval is available) but + * BEFORE windows.h sets the _INC_TIME guard. Once _INC_TIME is set, + * can no longer pull in time.h and its "using ::clock_t" + * declarations fail with "'clock_t' is not a member of global namespace". + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +# define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif +#ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include /* FIRST: defines struct timeval; must precede windows.h */ +#include + +#ifdef __cplusplus +# include /* BEFORE windows.h sets _INC_TIME guard */ +# include +# include +# include +# include +#else +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Portable fd/socket type */ +/* SOCKET is UINT_PTR (64-bit on ARM64); intptr_t covers it safely. */ +/* INVALID_SOCKET (all-bits-set) maps to (intptr_t)-1, matching -1. */ +/* ------------------------------------------------------------------ */ +typedef intptr_t hans_fd_t; + +/* ------------------------------------------------------------------ */ +/* POSIX uid / gid (not used on Windows — kept for API compatibility) */ +/* ------------------------------------------------------------------ */ +typedef int uid_t; +typedef int gid_t; + +/* ------------------------------------------------------------------ */ +/* IPv4 header — replaces / struct ip */ +/* Layout matches the standard 20-byte IPv4 header. */ +/* ------------------------------------------------------------------ */ +#ifndef _STRUCT_IP_DEFINED +#define _STRUCT_IP_DEFINED +struct ip { + uint8_t ip_vhl; /* version (4 bits) + IHL (4 bits) */ + uint8_t ip_tos; /* type of service */ + uint16_t ip_len; /* total length */ + uint16_t ip_id; /* identification */ + uint16_t ip_off; /* fragment offset field */ + uint8_t ip_ttl; /* time to live */ + uint8_t ip_p; /* protocol */ + uint16_t ip_sum; /* checksum */ + struct in_addr ip_src; /* source address */ + struct in_addr ip_dst; /* destination address */ +}; /* 20 bytes */ +#endif /* _STRUCT_IP_DEFINED */ + +/* ------------------------------------------------------------------ */ +/* syslog */ +/* ------------------------------------------------------------------ */ +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 + +#define LOG_DAEMON (3 << 3) +#define LOG_PERROR 0x20 + +/* LOG_UPTO: keep priorities <= x; simplified to identity on Windows */ +#define LOG_UPTO(x) (x) + +#ifdef __cplusplus +extern "C" { +#endif + +void hans_openlog(const char *ident, int option, int facility); +void hans_syslog(int priority, const char *format, ...); +void hans_closelog(void); +void hans_setlogmask(int mask); + +#ifdef __cplusplus +} +#endif + +#define openlog(id, opt, fac) hans_openlog((id), (opt), (fac)) +#define syslog hans_syslog +#define closelog() hans_closelog() +#define setlogmask(m) hans_setlogmask(m) + +/* ------------------------------------------------------------------ */ +/* gettimeofday */ +/* struct timeval is provided by */ +/* ------------------------------------------------------------------ */ +#ifdef __cplusplus +extern "C" { +#endif +int hans_gettimeofday(struct timeval *tv, void *tz); +#ifdef __cplusplus +} +#endif + +#define gettimeofday hans_gettimeofday + +/* ------------------------------------------------------------------ */ +/* getopt */ +/* ------------------------------------------------------------------ */ +#ifdef __cplusplus +extern "C" { +#endif + +extern char *hans_optarg; +extern int hans_optind; +extern int hans_opterr; +extern int hans_optopt; +int hans_getopt(int argc, char * const argv[], const char *optstring); + +#ifdef __cplusplus +} +#endif + +#define getopt hans_getopt +#define optarg hans_optarg +#define optind hans_optind +#define opterr hans_opterr +#define optopt hans_optopt + +/* ------------------------------------------------------------------ */ +/* daemon() — Windows has no background process fork mechanism. */ +/* Returns -1 so the caller stays in foreground mode. */ +/* ------------------------------------------------------------------ */ +#ifdef __cplusplus +extern "C" { +#endif +int hans_daemon(int nochdir, int noclose); +#ifdef __cplusplus +} +#endif + +#define daemon hans_daemon + +/* ------------------------------------------------------------------ */ +/* Winsock initialisation / cleanup */ +/* ------------------------------------------------------------------ */ +#ifdef __cplusplus +extern "C" { +#endif +void hans_winsock_init(void); +void hans_winsock_cleanup(void); + +/* TCP loopback socket-pair (replacement for UNIX socketpair) */ +int win32_socketpair(SOCKET sv[2]); +#ifdef __cplusplus +} +#endif + +/* ------------------------------------------------------------------ */ +/* close() on sockets: Winsock requires closesocket() */ +/* Use this inline wrapper where socket fds are closed. */ +/* ------------------------------------------------------------------ */ +#ifdef __cplusplus +extern "C" +#endif +static __inline int hans_close_socket(hans_fd_t fd) +{ + return closesocket((SOCKET)(uintptr_t)fd); +} + +#else /* !_WIN32 */ + +/* On POSIX, hans_fd_t is just int */ +typedef int hans_fd_t; + +#endif /* _WIN32 */ +#endif /* WIN32_COMPAT_H */ diff --git a/src/worker.cpp b/src/worker.cpp index c6f8dc5..70559a8 100644 --- a/src/worker.cpp +++ b/src/worker.cpp @@ -17,19 +17,34 @@ * */ +/* win32_compat.h must be the first include on Windows. */ +#ifdef _WIN32 +# include "win32_compat.h" +#endif + #include "worker.h" #include "tun.h" #include "exception.h" #include "config.h" #include -#include -#include -#include -#include -#include #include +#ifdef _WIN32 + /* select(), fd_set, FD_* from winsock2.h (via win32_compat.h above) */ +#else +# include +# include +# include +# include +# include +# include + /* On POSIX, SOCKET is just int; uintptr_t casts are identity ops */ +# ifndef SOCKET +# define SOCKET int +# endif +#endif + using std::cout; using std::endl; @@ -92,7 +107,13 @@ void Worker::run() now = Time::now(); alive = true; + /* On POSIX, select() needs maxFd+1. On Windows the first argument + is ignored, but we compute it anyway for source compatibility. */ +#ifdef _WIN32 + int maxFd = 0; /* ignored by Winsock select() */ +#else int maxFd = echo.getFd() > tun.getFd() ? echo.getFd() : tun.getFd(); +#endif while (alive) { @@ -100,8 +121,9 @@ void Worker::run() Time timeout; FD_ZERO(&fs); - FD_SET(tun.getFd(), &fs); - FD_SET(echo.getFd(), &fs); + /* On Windows, FD_SET expects SOCKET (UINT_PTR); cast via uintptr_t */ + FD_SET((SOCKET)(uintptr_t)tun.getFd(), &fs); + FD_SET((SOCKET)(uintptr_t)echo.getFd(), &fs); if (nextTimeout != Time::ZERO) { @@ -112,7 +134,7 @@ void Worker::run() // wait for data or timeout timeval *timeval = nextTimeout != Time::ZERO ? &timeout.getTimeval() : NULL; - int result = select(maxFd + 1 , &fs, NULL, NULL, timeval); + int result = select(maxFd + 1, &fs, NULL, NULL, timeval); if (result == -1) { if (alive) @@ -131,7 +153,7 @@ void Worker::run() } // icmp data - if (FD_ISSET(echo.getFd(), &fs)) + if (FD_ISSET((SOCKET)(uintptr_t)echo.getFd(), &fs)) { bool reply; uint16_t id, seq; @@ -163,7 +185,7 @@ void Worker::run() } // data from tun - if (FD_ISSET(tun.getFd(), &fs)) + if (FD_ISSET((SOCKET)(uintptr_t)tun.getFd(), &fs)) { uint32_t sourceIp, destIp; diff --git a/src/worker.h b/src/worker.h index 3ac0367..127f4cb 100644 --- a/src/worker.h +++ b/src/worker.h @@ -20,12 +20,17 @@ #ifndef WORKER_H #define WORKER_H -#include "time.h" +#include "hans_time.h" #include "echo.h" #include "tun.h" #include -#include + +#ifdef _WIN32 +# include "win32_compat.h" /* provides uid_t, gid_t, hans_fd_t */ +#else +# include +#endif class Worker {