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

412 lines
12 KiB
C++

extern "C"
{
#include <wintun.h>
}
#include <iphlpapi.h>
#include "wintun.hpp"
#include "exception.hpp"
#include "dll.hpp"
#include "guid.hpp"
#include <unordered_set>
#include <map>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/util/str.hpp>
#include <llarp/util/thread/queue.hpp>
#include <llarp/util/logging.hpp>
#include <llarp/vpn/platform.hpp>
namespace llarp::win32
{
namespace
{
auto logcat = log::Cat("wintun");
constexpr auto PoolName = "lokinet";
} // namespace
using Adapter_ptr = std::shared_ptr<_WINTUN_ADAPTER>;
struct PacketWrapper
{
BYTE* data;
DWORD size;
WINTUN_SESSION_HANDLE session;
WINTUN_RELEASE_RECEIVE_PACKET_FUNC* release;
/// 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(session, data);
}
};
class WintunDLL : public DLL
{
public:
WINTUN_CREATE_ADAPTER_FUNC* create_adapter;
WINTUN_OPEN_ADAPTER_FUNC* open_adapter;
WINTUN_CLOSE_ADAPTER_FUNC* close_adapter;
WINTUN_START_SESSION_FUNC* start_session;
WINTUN_END_SESSION_FUNC* end_session;
WINTUN_GET_ADAPTER_LUID_FUNC* get_adapter_LUID;
WINTUN_GET_READ_WAIT_EVENT_FUNC* get_adapter_handle;
WINTUN_RECEIVE_PACKET_FUNC* read_packet;
WINTUN_RELEASE_RECEIVE_PACKET_FUNC* release_read;
WINTUN_ALLOCATE_SEND_PACKET_FUNC* alloc_write;
WINTUN_SEND_PACKET_FUNC* write_packet;
WINTUN_SET_LOGGER_FUNC* set_logger;
WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC* get_version;
/// read out all the wintun function pointers from a library handle
WintunDLL() : DLL{"wintun.dll"}
{
init("WintunGetRunningDriverVersion", get_version);
init("WintunCreateAdapter", create_adapter);
init("WintunOpenAdapter", open_adapter);
init("WintunCloseAdapter", close_adapter);
init("WintunStartSession", start_session);
init("WintunEndSession", end_session);
init("WintunGetAdapterLUID", get_adapter_LUID);
init("WintunReceivePacket", read_packet);
init("WintunReleaseReceivePacket", release_read);
init("WintunSendPacket", write_packet);
init("WintunAllocateSendPacket", alloc_write);
init("WintunSetLogger", set_logger);
init("WintunGetReadWaitEvent", get_adapter_handle);
log::info(logcat, fmt::format("wintun version {0:x} loaded", get_version()));
}
/// autovivify a wintun adapter handle
[[nodiscard]] auto
make_adapter(std::string adapter_name, std::string tunnel_name) const
{
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())
{
log::info(
logcat, "did not open existing adapter '{}': {}", adapter_name, error_to_string(err));
SetLastError(0);
}
const auto guid =
llarp::win32::MakeDeterministicGUID(fmt::format("{}|{}", adapter_name, tunnel_name));
log::info(logcat, "creating adapter: '{}' on pool '{}'", adapter_name, tunnel_name);
auto tunnel_name_wide = to_wide(tunnel_name);
if (auto _impl = create_adapter(adapter_name_wide.c_str(), tunnel_name_wide.c_str(), &guid))
return _impl;
throw win32::error{"failed to create wintun adapter"};
}
};
class WintunAdapter
{
WINTUN_CLOSE_ADAPTER_FUNC* _close_adapter;
WINTUN_GET_ADAPTER_LUID_FUNC* _get_adapter_LUID;
WINTUN_GET_READ_WAIT_EVENT_FUNC* _get_handle;
WINTUN_START_SESSION_FUNC* _start_session;
WINTUN_END_SESSION_FUNC* _end_session;
WINTUN_ADAPTER_HANDLE _handle;
[[nodiscard]] auto
get_adapter_LUID() const
{
NET_LUID _uid{};
_get_adapter_LUID(_handle, &_uid);
return _uid;
}
public:
WintunAdapter(const WintunDLL& dll, std::string name)
: _close_adapter{dll.close_adapter}
, _get_adapter_LUID{dll.get_adapter_LUID}
, _get_handle{dll.get_adapter_handle}
, _start_session{dll.start_session}
, _end_session{dll.end_session}
{
_handle = dll.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 = get_adapter_LUID();
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;
AddressRow.Address.Ipv4.sin_addr.S_un.S_addr = ToNet(net::TruncateV6(addr.range.addr)).n;
AddressRow.OnLinkPrefixLength = addr.range.HostmaskBits();
AddressRow.DadState = IpDadStatePreferred;
if (auto err = CreateUnicastIpAddressEntry(&AddressRow); err != ERROR_SUCCESS)
throw error{err, fmt::format("cannot set address '{}'", addr.range)};
LogDebug(fmt::format("added address: '{}'", addr.range));
}
}
/// put adapter down and close it
void
Down() const
{
_close_adapter(_handle);
}
/// auto vivify a wintun session handle and read handle off of our adapter
[[nodiscard]] std::pair<WINTUN_SESSION_HANDLE, HANDLE>
session() const
{
if (auto impl = _start_session(_handle, WINTUN_MAX_RING_CAPACITY))
{
if (auto handle = _get_handle(impl))
return {impl, handle};
_end_session(impl);
}
return {nullptr, nullptr};
}
};
class WintunSession
{
WINTUN_END_SESSION_FUNC* _end_session;
WINTUN_RECEIVE_PACKET_FUNC* _recv_pkt;
WINTUN_RELEASE_RECEIVE_PACKET_FUNC* _release_pkt;
WINTUN_ALLOCATE_SEND_PACKET_FUNC* _alloc_write;
WINTUN_SEND_PACKET_FUNC* _write_pkt;
WINTUN_SESSION_HANDLE _impl;
HANDLE _handle;
public:
WintunSession(const WintunDLL& dll)
: _end_session{dll.end_session}
, _recv_pkt{dll.read_packet}
, _release_pkt{dll.release_read}
, _alloc_write{dll.alloc_write}
, _write_pkt{dll.write_packet}
, _impl{nullptr}
, _handle{nullptr}
{}
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() const
{
_end_session(_impl);
}
void
WaitFor(std::chrono::milliseconds dur)
{
WaitForSingleObject(_handle, dur.count());
}
/// 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
{
// typedef so the return statement fits on 1 line :^D
using Pkt_ptr = std::unique_ptr<PacketWrapper>;
DWORD sz;
if (auto* ptr = _recv_pkt(_impl, &sz))
return {Pkt_ptr{new PacketWrapper{ptr, sz, _impl, _release_pkt}}, false};
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);
_write_pkt(_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
{
AbstractRouter* 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;
static inline constexpr size_t packet_queue_length = 1024;
public:
WintunInterface(const WintunDLL& dll, vpn::InterfaceInfo info, AbstractRouter* router)
: vpn::NetworkInterface{std::move(info)}
, _router{router}
, _adapter{std::make_shared<WintunAdapter>(dll, m_Info.ifname)}
, _session{std::make_shared<WintunSession>(dll)}
, _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());
}};
}
void
Stop() override
{
// 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();
}
net::IPPacket
ReadNextPacket() override
{
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;
}
int
PollFD() const override
{
return -1;
}
void
MaybeWakeUpperLayers() const override
{
_router->TriggerPump();
}
};
struct WintunContext
{
WintunDLL dll{};
};
std::shared_ptr<WintunContext>
WintunContext_new()
{
return std::make_shared<WintunContext>();
}
std::shared_ptr<vpn::NetworkInterface>
WintunInterface_new(
std::shared_ptr<llarp::win32::WintunContext> const& ctx,
const llarp::vpn::InterfaceInfo& info,
llarp::AbstractRouter* r)
{
return std::static_pointer_cast<vpn::NetworkInterface>(
std::make_shared<WintunInterface>(ctx->dll, info, r));
}
} // namespace llarp::win32