You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lokinet/llarp/win32/wintun.cpp

359 lines
13 KiB
C++

extern "C"
{
#include <wintun.h>
}
#include "dll.hpp"
#include "exception.hpp"
#include "guid.hpp"
#include "wintun.hpp"
#include <llarp/router/router.hpp>
#include <llarp/util/logging.hpp>
#include <llarp/util/str.hpp>
#include <llarp/util/thread/queue.hpp>
#include <llarp/vpn/platform.hpp>
#include <iphlpapi.h>
#include <map>
#include <unordered_set>
namespace llarp::win32
{
8 months ago
namespace
{
static auto logcat = log::Cat("wintun");
8 months ago
constexpr auto PoolName = "lokinet";
WINTUN_CREATE_ADAPTER_FUNC* create_adapter = nullptr;
WINTUN_CLOSE_ADAPTER_FUNC* close_adapter = nullptr;
WINTUN_OPEN_ADAPTER_FUNC* open_adapter = nullptr;
WINTUN_GET_ADAPTER_LUID_FUNC* get_adapter_LUID = nullptr;
WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC* get_version = nullptr;
WINTUN_DELETE_DRIVER_FUNC* delete_driver = nullptr;
WINTUN_SET_LOGGER_FUNC* set_logger = nullptr;
WINTUN_START_SESSION_FUNC* start_session = nullptr;
WINTUN_END_SESSION_FUNC* end_session = nullptr;
WINTUN_GET_READ_WAIT_EVENT_FUNC* get_adapter_handle = nullptr;
WINTUN_RECEIVE_PACKET_FUNC* read_packet = nullptr;
WINTUN_RELEASE_RECEIVE_PACKET_FUNC* release_read = nullptr;
WINTUN_ALLOCATE_SEND_PACKET_FUNC* alloc_write = nullptr;
WINTUN_SEND_PACKET_FUNC* send_packet = nullptr;
void WintunInitialize()
{
if (create_adapter)
return;
8 months ago
// clang-format off
load_dll_functions(
"wintun.dll",
"WintunCreateAdapter", create_adapter,
"WintunCloseAdapter", close_adapter,
"WintunOpenAdapter", open_adapter,
"WintunGetAdapterLUID", get_adapter_LUID,
"WintunGetRunningDriverVersion", get_version,
"WintunDeleteDriver", delete_driver,
"WintunSetLogger", set_logger,
"WintunStartSession", start_session,
"WintunEndSession", end_session,
"WintunGetReadWaitEvent", get_adapter_handle,
"WintunReceivePacket", read_packet,
"WintunReleaseReceivePacket", release_read,
"WintunAllocateSendPacket", alloc_write,
"WintunSendPacket", send_packet);
8 months ago
// clang-format on
}
8 months ago
using Adapter_ptr = std::shared_ptr<_WINTUN_ADAPTER>;
8 months ago
struct PacketWrapper
{
8 months ago
BYTE* data;
DWORD size;
WINTUN_SESSION_HANDLE session;
/// copy our data into an ip packet struct
net::IPPacket copy() const
{
net::IPPacket pkt{size};
std::copy_n(data, size, pkt.data());
return pkt;
}
~PacketWrapper() { release_read(session, data); }
8 months ago
};
/// autovivify a wintun adapter handle
[[nodiscard]] auto make_adapter(std::string adapter_name, std::string tunnel_name)
{
8 months ago
auto adapter_name_wide = to_wide(adapter_name);
if (auto _impl = open_adapter(adapter_name_wide.c_str()))
{
log::info(logcat, "opened existing adapter: '{}'", adapter_name);
return _impl;
}
if (auto err = GetLastError())
{
8 months ago
log::info(logcat, "did not open existing adapter '{}': {}", adapter_name, error_to_string(err));
8 months ago
SetLastError(0);
}
8 months ago
const auto guid = llarp::win32::MakeDeterministicGUID(fmt::format("{}|{}", adapter_name, tunnel_name));
8 months ago
log::info(logcat, "creating adapter: '{}' on pool '{}'", adapter_name, tunnel_name);
auto tunnel_name_wide = to_wide(tunnel_name);
8 months ago
if (auto _impl = create_adapter(adapter_name_wide.c_str(), tunnel_name_wide.c_str(), &guid))
8 months ago
{
if (auto v = get_version())
8 months ago
log::info(logcat, "created adapter (wintun v{}.{})", (v >> 16) & 0xff, v & 0xff);
8 months ago
else
8 months ago
log::warning(logcat, "failed to query wintun driver version: {}!", error_to_string(GetLastError()));
8 months ago
return _impl;
}
throw win32::error{"failed to create wintun adapter"};
}
8 months ago
class WintunAdapter
{
8 months ago
WINTUN_ADAPTER_HANDLE _handle;
[[nodiscard]] auto GetAdapterLUID() const
{
NET_LUID _uid{};
get_adapter_LUID(_handle, &_uid);
return _uid;
}
public:
8 months ago
explicit WintunAdapter(std::string name)
{
_handle = make_adapter(std::move(name), PoolName);
if (_handle == nullptr)
throw std::runtime_error{"failed to create wintun adapter"};
}
/// put adapter up
void Up(const vpn::InterfaceInfo& info) const
{
const auto luid = GetAdapterLUID();
for (const auto& addr : info.addrs)
{
// TODO: implement ipv6
if (addr.fam != AF_INET)
continue;
MIB_UNICASTIPADDRESS_ROW AddressRow;
InitializeUnicastIpAddressEntry(&AddressRow);
AddressRow.InterfaceLuid = luid;
AddressRow.Address.Ipv4.sin_family = AF_INET;
8 months ago
AddressRow.Address.Ipv4.sin_addr.S_un.S_addr = ToNet(net::TruncateV6(addr.range.addr)).n;
8 months ago
AddressRow.OnLinkPrefixLength = addr.range.HostmaskBits();
AddressRow.DadState = IpDadStatePreferred;
if (auto err = CreateUnicastIpAddressEntry(&AddressRow); err != ERROR_SUCCESS)
throw win32::error{err, fmt::format("cannot set address '{}'", addr.range)};
log::debug(logcat, "Added address: {}", addr.range);
8 months ago
}
}
/// put adapter down and close it
void Down() const { close_adapter(_handle); }
8 months ago
/// auto vivify a wintun session handle and read handle off of our adapter
[[nodiscard]] std::pair<WINTUN_SESSION_HANDLE, HANDLE> session() const
{
if (auto wintun_ver = get_version())
log::info(
logcat,
8 months ago
fmt::format("wintun version {}.{} loaded", (wintun_ver >> 16) & 0xff, wintun_ver & 0xff));
8 months ago
else
throw win32::error{"Failed to load wintun"};
if (auto impl = start_session(_handle, WINTUN_MAX_RING_CAPACITY))
{
if (auto handle = get_adapter_handle(impl))
return {impl, handle};
end_session(impl);
}
return {nullptr, nullptr};
}
};
class WintunSession
{
8 months ago
WINTUN_SESSION_HANDLE _impl;
HANDLE _handle;
std::atomic<bool> ended{false};
static_assert(std::atomic<bool>::is_always_lock_free);
public:
WintunSession() : _impl{nullptr}, _handle{nullptr} {}
8 months ago
void Start(const std::shared_ptr<WintunAdapter>& adapter)
{
if (auto [impl, handle] = adapter->session(); impl and handle)
{
_impl = impl;
_handle = handle;
return;
}
throw error{GetLastError(), "could not create wintun session"};
}
void Stop()
{
ended = true;
end_session(_impl);
}
void WaitFor(std::chrono::milliseconds dur) { WaitForSingleObject(_handle, dur.count()); }
8 months ago
/// read a unique pointer holding a packet read from wintun, returns the packet if we
/// read one and a bool, set to true if our adapter is now closed
[[nodiscard]] std::pair<std::unique_ptr<PacketWrapper>, bool> ReadPacket() const
{
if (ended)
return {nullptr, true};
DWORD sz;
if (auto* ptr = read_packet(_impl, &sz))
8 months ago
return {std::unique_ptr<PacketWrapper>{new PacketWrapper{ptr, sz, _impl}}, false};
8 months ago
const auto err = GetLastError();
if (err == ERROR_NO_MORE_ITEMS or err == ERROR_HANDLE_EOF)
{
SetLastError(0);
return {nullptr, err == ERROR_HANDLE_EOF};
}
throw error{err, "failed to read packet"};
}
/// write an ip packet to the interface, return 2 bools, first is did we write the
/// packet, second if we are terminating
std::pair<bool, bool> WritePacket(net::IPPacket pkt) const
{
if (auto* buf = alloc_write(_impl, pkt.size()))
{
std::copy_n(pkt.data(), pkt.size(), buf);
send_packet(_impl, buf);
return {true, false};
}
const auto err = GetLastError();
if (err == ERROR_BUFFER_OVERFLOW or err == ERROR_HANDLE_EOF)
{
SetLastError(0);
return {err != ERROR_BUFFER_OVERFLOW, err == ERROR_HANDLE_EOF};
}
throw error{err, "failed to write packet"};
}
};
class WintunInterface : public vpn::NetworkInterface
{
8 months ago
Router* const _router;
std::shared_ptr<WintunAdapter> _adapter;
std::shared_ptr<WintunSession> _session;
thread::Queue<net::IPPacket> _recv_queue;
thread::Queue<net::IPPacket> _send_queue;
std::thread _recv_thread;
std::thread _send_thread;
inline static constexpr size_t packet_queue_length = 1024;
8 months ago
public:
8 months ago
WintunInterface(vpn::InterfaceInfo info, Router* router)
: vpn::NetworkInterface{std::move(info)},
_router{router},
_adapter{std::make_shared<WintunAdapter>(m_Info.ifname)},
_session{std::make_shared<WintunSession>()},
_recv_queue{packet_queue_length},
_send_queue{packet_queue_length}
{}
void Start() override
{
m_Info.index = 0;
// put the adapter and set addresses
_adapter->Up(m_Info);
// start up io session
_session->Start(_adapter);
// start read packet loop
_recv_thread = std::thread{[session = _session, this]() {
do
{
// read all our packets this iteration
bool more{true};
do
{
auto [pkt, done] = session->ReadPacket();
// bail if we are closing
if (done)
return;
if (pkt)
_recv_queue.pushBack(pkt->copy());
else
more = false;
} while (more);
// wait for more packets
session->WaitFor(5s);
} while (true);
}};
// start write packet loop
_send_thread = std::thread{[this, session = _session]() {
do
{
if (auto maybe = _send_queue.popFrontWithTimeout(100ms))
{
auto [written, done] = session->WritePacket(std::move(*maybe));
if (done)
return;
}
} while (_send_queue.enabled());
}};
}
8 months ago
void Stop() override
{
8 months ago
// end writing packets
_send_queue.disable();
_send_thread.join();
// end reading packets
_session->Stop();
_recv_thread.join();
// close session
_session.reset();
// put adapter down
_adapter->Down();
_adapter.reset();
}
3 weeks ago
net::IPPacket read_next_packet() override
{
8 months ago
net::IPPacket pkt{};
if (auto maybe_pkt = _recv_queue.tryPopFront())
pkt = std::move(*maybe_pkt);
return pkt;
}
bool WritePacket(net::IPPacket pkt) override
{
return _send_queue.tryPushBack(std::move(pkt)) == thread::QueueReturn::Success;
}
8 months ago
int PollFD() const override { return -1; }
8 months ago
void MaybeWakeUpperLayers() const override { _router->TriggerPump(); }
8 months ago
};
} // namespace
namespace wintun
{
8 months ago
std::shared_ptr<vpn::NetworkInterface> make_interface(const llarp::vpn::InterfaceInfo& info, llarp::Router* r)
8 months ago
{
WintunInitialize();
8 months ago
return std::static_pointer_cast<vpn::NetworkInterface>(std::make_shared<WintunInterface>(info, r));
8 months ago
}
} // namespace wintun
} // namespace llarp::win32