Added support for macOS-specific kernel stuff.

This commit is contained in:
2025-12-08 17:01:41 +01:00
parent 842752cd88
commit b50b594d68
3 changed files with 106 additions and 17 deletions

View File

@@ -26,6 +26,7 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/poll.h>
#elif defined(_WIN32) #elif defined(_WIN32)
#include <windows.h> #include <windows.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
@@ -49,9 +50,13 @@ namespace ColumnLynx::Net {
const std::string& getName() const; const std::string& getName() const;
int getFd() const; // For ASIO integration (on POSIX) int getFd() const; // For ASIO integration (on POSIX)
static inline std::string ipv4ToString(uint32_t ip) { static inline std::string ipv4ToString(uint32_t ip, bool flip = true) {
struct in_addr addr; struct in_addr addr;
addr.s_addr = htonl(ip);
if (flip)
addr.s_addr = htonl(ip);
else
addr.s_addr = ip;
char buf[INET_ADDRSTRLEN]; char buf[INET_ADDRSTRLEN];
if (!inet_ntop(AF_INET, &addr, buf, sizeof(buf))) if (!inet_ntop(AF_INET, &addr, buf, sizeof(buf)))

View File

@@ -28,6 +28,7 @@ namespace ColumnLynx::Net {
#elif defined(__APPLE__) #elif defined(__APPLE__)
// ---- macOS: UTUN (system control socket) ---- // ---- macOS: UTUN (system control socket) ----
// TL;DR: macOS doesn't really have a "device file" for TUN/TAP like Linux. Instead we have to request a "system control socket" from the kernel.
mFd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); mFd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
if (mFd < 0) if (mFd < 0)
throw std::runtime_error("socket(PF_SYSTEM) failed: " + std::string(strerror(errno))); throw std::runtime_error("socket(PF_SYSTEM) failed: " + std::string(strerror(errno)));
@@ -42,7 +43,7 @@ namespace ColumnLynx::Net {
sc.sc_family = AF_SYSTEM; sc.sc_family = AF_SYSTEM;
sc.ss_sysaddr = AF_SYS_CONTROL; sc.ss_sysaddr = AF_SYS_CONTROL;
sc.sc_id = ctlInfo.ctl_id; sc.sc_id = ctlInfo.ctl_id;
sc.sc_unit = 0; // lynx0 (0 = auto-assign) sc.sc_unit = 0; // 0 = auto-assign next utunX
if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) { if (connect(mFd, (struct sockaddr*)&sc, sizeof(sc)) < 0) {
if (errno == EPERM) if (errno == EPERM)
@@ -50,16 +51,17 @@ namespace ColumnLynx::Net {
throw std::runtime_error("connect(AF_SYS_CONTROL) failed: " + std::string(strerror(errno))); throw std::runtime_error("connect(AF_SYS_CONTROL) failed: " + std::string(strerror(errno)));
} }
// Retrieve actual utun device name // Retrieve actual utun device name via UTUN_OPT_IFNAME
struct sockaddr_storage addr; char ifname[IFNAMSIZ];
socklen_t addrlen = sizeof(addr); socklen_t ifname_len = sizeof(ifname);
if (getsockname(mFd, (struct sockaddr*)&addr, &addrlen) == 0) { if (getsockopt(mFd, SYSPROTO_CONTROL, UTUN_OPT_IFNAME, ifname, &ifname_len) == 0) {
const struct sockaddr_ctl* addr_ctl = (const struct sockaddr_ctl*)&addr; mIfName = ifname; // Update to actual assigned name
mIfName = "utun" + std::to_string(addr_ctl->sc_unit - 1);
} else { } else {
mIfName = "utunX"; mIfName = "utun0"; // Fallback (should not happen)
} }
Utils::log("VirtualInterface: opened macOS UTUN: " + mIfName);
#elif defined(_WIN32) #elif defined(_WIN32)
// ---- Windows: Wintun (WireGuard virtual adapter) ---- // ---- Windows: Wintun (WireGuard virtual adapter) ----
WINTUN_ADAPTER_HANDLE adapter = WINTUN_ADAPTER_HANDLE adapter =
@@ -95,24 +97,73 @@ namespace ColumnLynx::Net {
// ------------------------------ Read ------------------------------ // ------------------------------ Read ------------------------------
std::vector<uint8_t> VirtualInterface::readPacket() { std::vector<uint8_t> VirtualInterface::readPacket() {
#if defined(__linux__) || defined(__APPLE__) #if defined(__linux__)
// Linux TUN: blocking read is fine, unblocks on fd close / EINTR
std::vector<uint8_t> buf(4096); std::vector<uint8_t> buf(4096);
ssize_t n = read(mFd, buf.data(), buf.size()); ssize_t n = read(mFd, buf.data(), buf.size());
if (n < 0) { if (n < 0) {
if (errno == EINTR) { if (errno == EINTR) {
return {}; // Interrupted, return empty return {}; // Interrupted, just return empty
} }
throw std::runtime_error("read() failed: " + std::string(strerror(errno))); throw std::runtime_error("read() failed: " + std::string(strerror(errno)));
} }
buf.resize(n); buf.resize(n);
return buf; return buf;
#elif defined(__APPLE__)
// macOS utun: must poll, or read() can block forever
std::vector<uint8_t> buf(4096);
struct pollfd pfd;
pfd.fd = mFd;
pfd.events = POLLIN;
// timeout in ms; keep it small so shutdown is responsive
int ret = poll(&pfd, 1, 200);
if (ret == 0) {
// No data yet
return {};
}
if (ret < 0) {
if (errno == EINTR) {
return {}; // Interrupted by signal
}
throw std::runtime_error("poll() failed: " + std::string(strerror(errno)));
}
if (!(pfd.revents & POLLIN)) {
return {};
}
ssize_t n = read(mFd, buf.data(), buf.size());
if (n <= 0) {
// 0 or -1: treat as EOF or transient; you can decide how aggressive to be
return {};
}
if (n > 4) {
// Drop macOS UTUN header (4 bytes)
std::memmove(buf.data(), buf.data() + 4, n - 4);
buf.resize(n - 4);
} else {
return {};
}
return buf;
#elif defined(_WIN32) #elif defined(_WIN32)
WINTUN_PACKET* packet = WintunReceivePacket(mSession, nullptr); WINTUN_PACKET* packet = WintunReceivePacket(mSession, nullptr);
if (!packet) return {}; if (!packet) return {};
std::vector<uint8_t> buf(packet->Data, packet->Data + packet->Length); std::vector<uint8_t> buf(packet->Data, packet->Data + packet->Length);
WintunReleaseReceivePacket(mSession, packet); WintunReleaseReceivePacket(mSession, packet);
return buf; return buf;
#else #else
return {}; return {};
#endif #endif
@@ -120,16 +171,48 @@ namespace ColumnLynx::Net {
// ------------------------------ Write ------------------------------ // ------------------------------ Write ------------------------------
void VirtualInterface::writePacket(const std::vector<uint8_t>& packet) { void VirtualInterface::writePacket(const std::vector<uint8_t>& packet) {
#if defined(__linux__) || defined(__APPLE__) #if defined(__linux__)
// Linux TUN expects raw IP packet
ssize_t n = write(mFd, packet.data(), packet.size()); ssize_t n = write(mFd, packet.data(), packet.size());
if (n < 0) if (n < 0)
throw std::runtime_error("write() failed: " + std::string(strerror(errno))); throw std::runtime_error("write() failed: " + std::string(strerror(errno)));
#elif defined(__APPLE__)
if (packet.empty())
return;
// Detect IPv4 or IPv6
uint8_t version = packet[0] >> 4;
uint32_t af;
if (version == 4) {
af = htonl(AF_INET);
} else if (version == 6) {
af = htonl(AF_INET6);
} else {
throw std::runtime_error("writePacket(): unknown IP version");
}
// Prepend 4-byte AF header
std::vector<uint8_t> out(packet.size() + 4);
memcpy(out.data(), &af, 4);
memcpy(out.data() + 4, packet.data(), packet.size());
ssize_t n = write(mFd, out.data(), out.size());
if (n < 0)
throw std::runtime_error("utun write() failed: " + std::string(strerror(errno)));
#elif defined(_WIN32) #elif defined(_WIN32)
WINTUN_PACKET* tx = WintunAllocateSendPacket(mSession, (DWORD)packet.size()); WINTUN_PACKET* tx = WintunAllocateSendPacket(mSession, (DWORD)packet.size());
if (!tx) throw std::runtime_error("WintunAllocateSendPacket failed"); if (!tx)
throw std::runtime_error("WintunAllocateSendPacket failed");
memcpy(tx->Data, packet.data(), packet.size()); memcpy(tx->Data, packet.data(), packet.size());
WintunSendPacket(mSession, tx); WintunSendPacket(mSession, tx);
#endif #endif
} }
@@ -195,7 +278,8 @@ namespace ColumnLynx::Net {
std::string ipStr = ipv4ToString(clientIP); std::string ipStr = ipv4ToString(clientIP);
std::string peerStr = ipv4ToString(serverIP); std::string peerStr = ipv4ToString(serverIP);
std::string prefixStr = ipv4ToString(prefixLen); std::string prefixStr = ipv4ToString(prefixLengthToNetmask(prefixLen), false);
Utils::debug("Prefix string: " + prefixStr);
// Reset // Reset
snprintf(cmd, sizeof(cmd), snprintf(cmd, sizeof(cmd),
@@ -212,7 +296,7 @@ namespace ColumnLynx::Net {
// Set // Set
snprintf(cmd, sizeof(cmd), snprintf(cmd, sizeof(cmd),
"ifconfig %s %s %s mtu %d netmask %s up", "ifconfig %s inet %s %s mtu %d netmask %s up",
mIfName.c_str(), ipStr.c_str(), peerStr.c_str(), mtu, prefixStr.c_str()); mIfName.c_str(), ipStr.c_str(), peerStr.c_str(), mtu, prefixStr.c_str());
system(cmd); system(cmd);