lokinet/llarp/vpn/win32.hpp
Jason Rhinelander 5b555ee5aa Replace libuv with uvw & related refactoring
- removes all the llarp_ev_* functions, replacing with methods/classes/functions in the llarp
  namespace.
- banish ev/ev.h to the void
- Passes various things by const lvalue ref, especially shared_ptr's that don't need to be copied
  (to avoid an atomic refcount increment/decrement).
- Add a llarp::UDPHandle abstract class for UDP handling
- Removes the UDP tick handler; code that needs tick can just do a separate handler on the event
  loop outside the UDP socket.
- Adds an "OwnedBuffer" which owns its own memory but is implicitly convertible to a llarp_buffer_t.
  This is mostly needed to take over ownership of buffers from uvw without copying them as,
  currently, uvw does its own allocation (pending some open upstream issues/PRs).
- Logic:
  - add `make_caller`/`call_forever`/`call_every` utility functions to abstract Call wrapping and
    dependent timed tasks.
  - Add inLogicThread() so that code can tell its inside the logic thread (typically for
    debugging assertions).
  - get rid of janky integer returns and dealing with cancellations on call_later: the other methods
    added here and the event loop code remove the need for them.
- Event loop:
  - redo everything with uvw instead of libuv
  - rename EventLoopWakeup::Wakeup to EventLoopWakeup::Trigger to better reflect what it does.
  - add EventLoopRepeater for repeated events, and replace the code that reschedules itself every
    time it is called with a repeater.
  - Split up `EventLoop::run()` into a non-virtual base method and abstract `run_loop()` methods;
    the base method does a couple extra setup/teardown things that don't need to be in the derived class.
  - udp_listen is replaced with ev->udp(...) which returns a new UDPHandle object rather that
    needing gross C-style-but-not-actually-C-compatible structs.
  - Remove unused register_poll_fd_(un)readable
  - Use shared_ptr for EventLoopWakeup rather than returning a raw pointer; uvw lets us not have to
    worry about having the event loop class maintain ownership of it.
  - Add factory EventLoop::create() function to create a default (uvw-based) event loop (previously
    this was one of the llarp_ev_blahblah unnamespaced functions).
  - ev_libuv: this is mostly rewritten; all of the glue code/structs, in particular, are gone as
    they are no longer needed with uvw.
- DNS:
  - Rename DnsHandler to DnsInterceptor to better describe what it does (this is the code that
    intercepts all DNS to the tun IP range for Android).
- endpoint:
  - remove unused "isolated network" code
  - remove distinct (but actually always the same) variables for router/endpoint logic objects
- llarp_buffer_t
  - make constructors type-safe against being called with points to non-size-1 values
- tun packet reading:
  - read all available packets off the device/file descriptor; previously we were reading one packet
    at a time then returning to the event loop to poll again.
  - ReadNextPacket() now returns a 0-size packet if the read would block (so that we can implement
    the previous point).
  - ReadNextPacket() now throws on I/O error
- Miscellaneous code cleanups/simplifications
2021-03-04 16:51:18 -04:00

449 lines
12 KiB
C++

#pragma once
#include <windows.h>
#include <iphlpapi.h>
#include <io.h>
#include <fcntl.h>
#include <util/thread/queue.hpp>
#include <ev/vpn.hpp>
// DDK macros
#define CTL_CODE(DeviceType, Function, Method, Access) \
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
#define FILE_DEVICE_UNKNOWN 0x00000022
#define FILE_ANY_ACCESS 0x00000000
#define METHOD_BUFFERED 0
/* From OpenVPN tap driver, common.h */
#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)
#define TAP_IOCTL_CONFIG_TUN TAP_CONTROL_CODE(10, METHOD_BUFFERED)
/* Windows registry crap */
#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383
#define NETWORK_ADAPTERS \
"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-" \
"08002BE10318}"
typedef unsigned long IPADDR;
namespace llarp::vpn
{
static char*
reg_query(char* key_name)
{
HKEY adapters, adapter;
DWORD i, ret, len;
char* deviceid = nullptr;
DWORD sub_keys = 0;
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(key_name), 0, KEY_READ, &adapters);
if (ret != ERROR_SUCCESS)
{
return nullptr;
}
ret = RegQueryInfoKey(
adapters, NULL, NULL, NULL, &sub_keys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (ret != ERROR_SUCCESS)
{
return nullptr;
}
if (sub_keys <= 0)
{
return nullptr;
}
/* Walk througt all adapters */
for (i = 0; i < sub_keys; i++)
{
char new_key[MAX_KEY_LENGTH];
char data[256];
TCHAR key[MAX_KEY_LENGTH];
DWORD keylen = MAX_KEY_LENGTH;
/* Get the adapter key name */
ret = RegEnumKeyEx(adapters, i, key, &keylen, NULL, NULL, NULL, NULL);
if (ret != ERROR_SUCCESS)
{
continue;
}
/* Append it to NETWORK_ADAPTERS and open it */
snprintf(new_key, sizeof new_key, "%s\\%s", key_name, key);
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(new_key), 0, KEY_READ, &adapter);
if (ret != ERROR_SUCCESS)
{
continue;
}
/* Check its values */
len = sizeof data;
ret = RegQueryValueEx(adapter, "ComponentId", NULL, NULL, (LPBYTE)data, &len);
if (ret != ERROR_SUCCESS)
{
/* This value doesn't exist in this adaptater tree */
goto clean;
}
/* If its a tap adapter, its all good */
if (strncmp(data, "tap0901", 7) == 0)
{
DWORD type;
len = sizeof data;
ret = RegQueryValueEx(adapter, "NetCfgInstanceId", NULL, &type, (LPBYTE)data, &len);
if (ret != ERROR_SUCCESS)
{
goto clean;
}
deviceid = strdup(data);
break;
}
clean:
RegCloseKey(adapter);
}
RegCloseKey(adapters);
return deviceid;
}
class Win32Interface : public NetworkInterface
{
std::atomic<bool> m_Run;
HANDLE m_Device, m_IOCP;
std::vector<std::thread> m_Threads;
thread::Queue<net::IPPacket> m_ReadQueue;
const InterfaceInfo m_Info;
static std::wstring
get_win_sys_path()
{
wchar_t win_sys_path[MAX_PATH] = {0};
const wchar_t* default_sys_path = L"C:\\Windows\\system32";
if (!GetSystemDirectoryW(win_sys_path, _countof(win_sys_path)))
{
wcsncpy(win_sys_path, default_sys_path, _countof(win_sys_path));
win_sys_path[_countof(win_sys_path) - 1] = L'\0';
}
return win_sys_path;
}
static std::string
NetSHCommand()
{
std::wstring wcmd = get_win_sys_path() + L"\\netsh.exe";
using convert_type = std::codecvt_utf8<wchar_t>;
std::wstring_convert<convert_type, wchar_t> converter;
return converter.to_bytes(wcmd);
}
static void
NetSH(std::string commands)
{
commands = NetSHCommand() + " interface IPv6 " + commands;
LogInfo(commands);
::system(commands.c_str());
}
public:
Win32Interface(InterfaceInfo info) : m_ReadQueue{1024}, m_Info{std::move(info)}
{
DWORD len;
const auto device_id = reg_query(NETWORK_ADAPTERS);
if (device_id == nullptr)
{
LogError("cannot query registry");
throw std::invalid_argument{"cannot query registery"};
}
std::stringstream ss;
ss << "\\\\.\\Global\\" << device_id << ".tap";
const auto fname = ss.str();
m_Device = CreateFile(
fname.c_str(),
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
0);
if (m_Device == INVALID_HANDLE_VALUE)
{
LogError("failed to open device");
throw std::invalid_argument{"cannot open " + fname};
}
LogInfo("putting interface up...");
ULONG flag = 1;
// put the interface up
if (not DeviceIoControl(
m_Device,
TAP_IOCTL_SET_MEDIA_STATUS,
&flag,
sizeof(flag),
&flag,
sizeof(flag),
&len,
nullptr))
{
LogError("cannot up interface up");
throw std::invalid_argument{"cannot put interface up"};
}
LogInfo("setting addresses");
huint32_t ip{};
// set ipv4 addresses
for (const auto& ifaddr : m_Info.addrs)
{
if (ifaddr.fam == AF_INET)
{
IPADDR sock[3]{};
const nuint32_t addr = xhtonl(net::TruncateV6(ifaddr.range.addr));
ip = net::TruncateV6(ifaddr.range.addr);
const nuint32_t mask = xhtonl(net::TruncateV6(ifaddr.range.netmask_bits));
LogInfo("address ", addr, " netmask ", mask);
sock[0] = addr.n;
sock[1] = addr.n & mask.n;
sock[2] = mask.n;
if (not DeviceIoControl(
m_Device,
TAP_IOCTL_CONFIG_TUN,
&sock,
sizeof(sock),
&sock,
sizeof(sock),
&len,
nullptr))
{
LogError("cannot set address");
throw std::invalid_argument{"cannot configure tun interface address"};
}
IPADDR ep[4]{};
ep[0] = addr.n;
ep[1] = mask.n;
ep[2] = (addr.n | ~mask.n) - htonl(1);
ep[3] = 31536000;
if (not DeviceIoControl(
m_Device,
TAP_IOCTL_CONFIG_DHCP_MASQ,
ep,
sizeof(ep),
ep,
sizeof(ep),
&len,
nullptr))
{
LogError("cannot set dhcp masq");
throw std::invalid_argument{"Cannot configure tun interface dhcp"};
}
#pragma pack(push)
#pragma pack(1)
struct opt
{
uint8_t dhcp_opt;
uint8_t length;
uint32_t value;
} dns, gateway;
#pragma pack(pop)
const nuint32_t dnsaddr{xhtonl(m_Info.dnsaddr)};
dns.dhcp_opt = 6;
dns.length = 4;
dns.value = dnsaddr.n;
gateway.dhcp_opt = 3;
gateway.length = 4;
gateway.value = addr.n + htonl(1);
if (not DeviceIoControl(
m_Device,
TAP_IOCTL_CONFIG_DHCP_SET_OPT,
&gateway,
sizeof(gateway),
&gateway,
sizeof(gateway),
&len,
nullptr))
{
LogError("cannot set gateway");
throw std::invalid_argument{"cannot set tun gateway"};
}
if (not DeviceIoControl(
m_Device,
TAP_IOCTL_CONFIG_DHCP_SET_OPT,
&dns,
sizeof(dns),
&dns,
sizeof(dns),
&len,
nullptr))
{
LogError("cannot set dns");
throw std::invalid_argument{"cannot set tun dns"};
}
}
}
// set ipv6 addresses
for (const auto& ifaddr : m_Info.addrs)
{
if (ifaddr.fam == AF_INET6)
{
const auto maybe = net::GetInterfaceIndex(ip);
if (maybe.has_value())
{
NetSH(
"add address interface=" + std::to_string(*maybe) + " " + ifaddr.range.ToString());
}
}
}
}
~Win32Interface()
{
ULONG flag = 0;
DWORD len;
// put the interface down
DeviceIoControl(
m_Device,
TAP_IOCTL_SET_MEDIA_STATUS,
&flag,
sizeof(flag),
&flag,
sizeof(flag),
&len,
nullptr);
m_Run = false;
CloseHandle(m_IOCP);
// close the handle
CloseHandle(m_Device);
// close the reader threads
for (auto& thread : m_Threads)
thread.join();
}
int
PollFD() const override
{
return -1;
}
std::string
IfName() const override
{
return "";
}
void
Start()
{
m_Run = true;
const auto numThreads = std::thread::hardware_concurrency();
// allocate packets
for (size_t idx = 0; idx < numThreads; ++idx)
m_Packets.emplace_back(new asio_evt_pkt{true});
// create completion port
m_IOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0);
// attach the handle or some shit
CreateIoCompletionPort(m_Device, m_IOCP, 0, 0);
// spawn reader threads
for (size_t idx = 0; idx < numThreads; ++idx)
m_Threads.emplace_back([this, idx]() { ReadLoop(idx); });
}
net::IPPacket
ReadNextPacket() override
{
if (m_ReadQueue.empty())
return net::IPPacket{};
return m_ReadQueue.popFront();
}
struct asio_evt_pkt
{
explicit asio_evt_pkt(bool _read) : read{_read}
{}
OVERLAPPED hdr = {0, 0, 0, 0, nullptr}; // must be first, since this is part of the IO call
bool read;
net::IPPacket pkt;
void
Read(HANDLE dev)
{
ReadFile(dev, pkt.buf, sizeof(pkt.buf), nullptr, &hdr);
}
};
std::vector<std::unique_ptr<asio_evt_pkt>> m_Packets;
bool
WritePacket(net::IPPacket pkt)
{
LogDebug("write packet ", pkt.sz);
asio_evt_pkt* ev = new asio_evt_pkt{false};
std::copy_n(pkt.buf, pkt.sz, ev->pkt.buf);
ev->pkt.sz = pkt.sz;
WriteFile(m_Device, ev->pkt.buf, ev->pkt.sz, nullptr, &ev->hdr);
return true;
}
void
ReadLoop(size_t packetIndex)
{
auto& ev = m_Packets[packetIndex];
ev->Read(m_Device);
while (m_Run)
{
DWORD size;
ULONG_PTR user;
OVERLAPPED* ovl = nullptr;
if (not GetQueuedCompletionStatus(m_IOCP, &size, &user, &ovl, 1000))
continue;
asio_evt_pkt* pkt = (asio_evt_pkt*)ovl;
LogDebug("got iocp event size=", size, " read=", pkt->read);
if (pkt->read)
{
pkt->pkt.sz = size;
m_ReadQueue.pushBack(pkt->pkt);
pkt->Read(m_Device);
}
else
delete pkt;
}
}
};
class Win32Platform : public Platform
{
public:
std::shared_ptr<NetworkInterface>
ObtainInterface(InterfaceInfo info) override
{
auto netif = std::make_shared<Win32Interface>(std::move(info));
netif->Start();
return netif;
};
};
} // namespace llarp::vpn