wg: Linux WireGuard client — /dev/net/tun + boringtun FFI
This commit is contained in:
parent
77b8d616eb
commit
38a5527a61
1 changed files with 260 additions and 0 deletions
260
src/wg/wgclient_linux.cpp
Normal file
260
src/wg/wgclient_linux.cpp
Normal file
|
|
@ -0,0 +1,260 @@
|
||||||
|
// src/wg/wgclient_linux.cpp — Linux WireGuard tunnel for Artemis.
|
||||||
|
//
|
||||||
|
// Opens /dev/net/tun (IFF_TUN|IFF_NO_PI) — requires CAP_NET_ADMIN or root.
|
||||||
|
// Routing is configured via `ip addr` / `ip route` subprocesses.
|
||||||
|
// Links against: libboringtun.a, pthread
|
||||||
|
//
|
||||||
|
// Packet format on Linux TUN: raw IP (IFF_NO_PI strips the protocol header).
|
||||||
|
|
||||||
|
#include "wgclient.h"
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/if.h>
|
||||||
|
#include <linux/if_tun.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstring>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "boringtun_ffi.h"
|
||||||
|
|
||||||
|
namespace wg {
|
||||||
|
|
||||||
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void shell(const std::string &cmd) {
|
||||||
|
// Fire-and-forget; ignore return value (route may already exist)
|
||||||
|
::system(cmd.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ClientImpl ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class ClientImpl {
|
||||||
|
public:
|
||||||
|
int tunFd = -1;
|
||||||
|
int udpFd = -1;
|
||||||
|
int wakeR = -1; // self-pipe read end
|
||||||
|
int wakeW = -1; // self-pipe write end
|
||||||
|
wireguard_tunnel *wg = nullptr;
|
||||||
|
std::atomic<bool> live {false};
|
||||||
|
std::thread tTunToUdp, tUdpToTun, tTicker;
|
||||||
|
Config cfg;
|
||||||
|
char ifName[IFNAMSIZ] {};
|
||||||
|
std::string localIPStr;
|
||||||
|
LogFn log;
|
||||||
|
ErrorFn err;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Open TUN device ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static int openTun(char *nameOut) {
|
||||||
|
int fd = open("/dev/net/tun", O_RDWR);
|
||||||
|
if (fd < 0) return -1;
|
||||||
|
struct ifreq ifr {};
|
||||||
|
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
||||||
|
// Let the kernel pick a name (tun0, tun1, …)
|
||||||
|
if (ioctl(fd, TUNSETIFF, &ifr) < 0) { close(fd); return -1; }
|
||||||
|
strncpy(nameOut, ifr.ifr_name, IFNAMSIZ - 1);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── I/O threads ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void tunToUdp(ClientImpl *p) {
|
||||||
|
struct sockaddr_in peer {}; peer.sin_family = AF_INET;
|
||||||
|
inet_pton(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr);
|
||||||
|
peer.sin_port = htons((uint16_t)p->cfg.endpointPort());
|
||||||
|
|
||||||
|
std::vector<uint8_t> plain(65536), enc(65536);
|
||||||
|
fd_set fds;
|
||||||
|
|
||||||
|
while (p->live) {
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(p->tunFd, &fds);
|
||||||
|
FD_SET(p->wakeR, &fds);
|
||||||
|
int nfds = std::max(p->tunFd, p->wakeR) + 1;
|
||||||
|
if (select(nfds, &fds, nullptr, nullptr, nullptr) <= 0) continue;
|
||||||
|
if (FD_ISSET(p->wakeR, &fds)) break;
|
||||||
|
|
||||||
|
ssize_t n = read(p->tunFd, plain.data(), plain.size());
|
||||||
|
if (n <= 0) continue;
|
||||||
|
|
||||||
|
size_t elen = enc.size();
|
||||||
|
wireguard_result r = wireguard_write(p->wg, plain.data(), (size_t)n,
|
||||||
|
enc.data(), &elen);
|
||||||
|
if (r.op == WRITE_TO_NETWORK && elen > 0)
|
||||||
|
sendto(p->udpFd, enc.data(), elen, 0,
|
||||||
|
(sockaddr *)&peer, sizeof(peer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void udpToTun(ClientImpl *p) {
|
||||||
|
struct sockaddr_in peer {}; peer.sin_family = AF_INET;
|
||||||
|
inet_pton(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr);
|
||||||
|
peer.sin_port = htons((uint16_t)p->cfg.endpointPort());
|
||||||
|
|
||||||
|
std::vector<uint8_t> enc(65536), plain(65536);
|
||||||
|
fd_set fds;
|
||||||
|
|
||||||
|
while (p->live) {
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(p->udpFd, &fds);
|
||||||
|
FD_SET(p->wakeR, &fds);
|
||||||
|
int nfds = std::max(p->udpFd, p->wakeR) + 1;
|
||||||
|
if (select(nfds, &fds, nullptr, nullptr, nullptr) <= 0) continue;
|
||||||
|
if (FD_ISSET(p->wakeR, &fds)) break;
|
||||||
|
|
||||||
|
struct sockaddr_in from {}; socklen_t fl = sizeof(from);
|
||||||
|
ssize_t n = recvfrom(p->udpFd, enc.data(), enc.size(), 0,
|
||||||
|
(sockaddr *)&from, &fl);
|
||||||
|
if (n <= 0) continue;
|
||||||
|
|
||||||
|
size_t plen = plain.size();
|
||||||
|
wireguard_result r = wireguard_read(p->wg, enc.data(), (size_t)n,
|
||||||
|
plain.data(), &plen);
|
||||||
|
if ((r.op == WRITE_TO_TUNNEL_IPV4 || r.op == WRITE_TO_TUNNEL_IPV6) && plen > 0)
|
||||||
|
write(p->tunFd, plain.data(), plen);
|
||||||
|
else if (r.op == WRITE_TO_NETWORK && plen > 0)
|
||||||
|
sendto(p->udpFd, plain.data(), plen, 0,
|
||||||
|
(sockaddr *)&peer, sizeof(peer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ticker(ClientImpl *p) {
|
||||||
|
struct sockaddr_in peer {}; peer.sin_family = AF_INET;
|
||||||
|
inet_pton(AF_INET, p->cfg.endpointHost().c_str(), &peer.sin_addr);
|
||||||
|
peer.sin_port = htons((uint16_t)p->cfg.endpointPort());
|
||||||
|
|
||||||
|
std::vector<uint8_t> buf(512);
|
||||||
|
fd_set fds;
|
||||||
|
|
||||||
|
while (p->live) {
|
||||||
|
struct timeval tv { 0, 100000 }; // 100 ms
|
||||||
|
FD_ZERO(&fds); FD_SET(p->wakeR, &fds);
|
||||||
|
select(p->wakeR + 1, &fds, nullptr, nullptr, &tv);
|
||||||
|
if (FD_ISSET(p->wakeR, &fds)) break;
|
||||||
|
|
||||||
|
size_t len = buf.size();
|
||||||
|
wireguard_result r = wireguard_tick(p->wg, buf.data(), &len);
|
||||||
|
if (r.op == WRITE_TO_NETWORK && len > 0)
|
||||||
|
sendto(p->udpFd, buf.data(), len, 0,
|
||||||
|
(sockaddr *)&peer, sizeof(peer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Client public API ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Client::Client() = default;
|
||||||
|
Client::~Client() { stop(); }
|
||||||
|
|
||||||
|
void Client::start(const Config &cfg) {
|
||||||
|
if (m_impl && m_impl->live) return;
|
||||||
|
|
||||||
|
auto p = std::make_unique<ClientImpl>();
|
||||||
|
p->cfg = cfg;
|
||||||
|
p->log = m_log;
|
||||||
|
p->err = m_error;
|
||||||
|
|
||||||
|
// boringtun tunnel
|
||||||
|
p->wg = new_tunnel(
|
||||||
|
cfg.privateKey().c_str(),
|
||||||
|
cfg.peerPublicKey().c_str(),
|
||||||
|
cfg.presharedKey().empty() ? nullptr : cfg.presharedKey().c_str(),
|
||||||
|
cfg.persistentKeepalive(), 0);
|
||||||
|
if (!p->wg) throw std::runtime_error("boringtun: tunnel creation failed");
|
||||||
|
|
||||||
|
// TUN device
|
||||||
|
p->tunFd = openTun(p->ifName);
|
||||||
|
if (p->tunFd < 0) {
|
||||||
|
tunnel_free(p->wg);
|
||||||
|
throw std::runtime_error(std::string("openTun: ") + strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDP socket
|
||||||
|
p->udpFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (p->udpFd < 0) {
|
||||||
|
close(p->tunFd); tunnel_free(p->wg);
|
||||||
|
throw std::runtime_error("socket: " + std::string(strerror(errno)));
|
||||||
|
}
|
||||||
|
struct sockaddr_in local {}; local.sin_family = AF_INET;
|
||||||
|
bind(p->udpFd, (sockaddr *)&local, sizeof(local));
|
||||||
|
|
||||||
|
// Self-pipe for clean shutdown
|
||||||
|
int pipefds[2]; pipe(pipefds);
|
||||||
|
p->wakeR = pipefds[0];
|
||||||
|
p->wakeW = pipefds[1];
|
||||||
|
|
||||||
|
// Configure interface address + routes via `ip` commands
|
||||||
|
std::string iface = p->ifName;
|
||||||
|
shell("ip link set " + iface + " up");
|
||||||
|
shell("ip addr add " + cfg.address() + " dev " + iface);
|
||||||
|
for (const auto &cidr : cfg.allowedIPs())
|
||||||
|
shell("ip route add " + cidr + " dev " + iface);
|
||||||
|
|
||||||
|
p->localIPStr = cfg.addressIP();
|
||||||
|
|
||||||
|
// Initial handshake
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> hs(512); size_t len = hs.size();
|
||||||
|
wireguard_force_handshake(p->wg, hs.data(), &len);
|
||||||
|
if (len > 0) {
|
||||||
|
struct sockaddr_in peer {}; peer.sin_family = AF_INET;
|
||||||
|
inet_pton(AF_INET, cfg.endpointHost().c_str(), &peer.sin_addr);
|
||||||
|
peer.sin_port = htons((uint16_t)cfg.endpointPort());
|
||||||
|
sendto(p->udpFd, hs.data(), len, 0, (sockaddr *)&peer, sizeof(peer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p->live = true;
|
||||||
|
p->tTunToUdp = std::thread(tunToUdp, p.get());
|
||||||
|
p->tUdpToTun = std::thread(udpToTun, p.get());
|
||||||
|
p->tTicker = std::thread(ticker, p.get());
|
||||||
|
|
||||||
|
m_impl = std::move(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::stop() {
|
||||||
|
if (!m_impl) return;
|
||||||
|
auto *p = m_impl.get();
|
||||||
|
p->live = false;
|
||||||
|
// Wake all threads via self-pipe
|
||||||
|
char b = 0; write(p->wakeW, &b, 1);
|
||||||
|
if (p->tTunToUdp.joinable()) p->tTunToUdp.join();
|
||||||
|
if (p->tUdpToTun.joinable()) p->tUdpToTun.join();
|
||||||
|
if (p->tTicker.joinable()) p->tTicker.join();
|
||||||
|
|
||||||
|
// Remove routes before bringing interface down
|
||||||
|
std::string iface = p->ifName;
|
||||||
|
for (const auto &cidr : p->cfg.allowedIPs())
|
||||||
|
shell("ip route del " + cidr + " dev " + iface + " 2>/dev/null");
|
||||||
|
shell("ip link set " + iface + " down 2>/dev/null");
|
||||||
|
|
||||||
|
close(p->tunFd);
|
||||||
|
close(p->udpFd);
|
||||||
|
close(p->wakeR);
|
||||||
|
close(p->wakeW);
|
||||||
|
tunnel_free(p->wg);
|
||||||
|
m_impl.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::running() const { return m_impl && m_impl->live; }
|
||||||
|
std::string Client::localIP() const {
|
||||||
|
return (m_impl && m_impl->live) ? m_impl->localIPStr : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace wg
|
||||||
|
|
||||||
|
#endif // __linux__
|
||||||
Loading…
Reference in a new issue