mirror of
https://github.com/deneraraujo/OpenVPNAdapter.git
synced 2026-02-22 00:00:06 +08:00
319 lines
7.7 KiB
C++
319 lines
7.7 KiB
C++
#pragma once
|
|
|
|
#include <openvpn/tun/client/tunbase.hpp>
|
|
#include <openvpn/tun/persist/tunpersist.hpp>
|
|
#include <openvpn/tun/win/client/setupbase.hpp>
|
|
#include <openvpn/tun/win/client/clientconfig.hpp>
|
|
#include <openvpn/win/modname.hpp>
|
|
|
|
namespace openvpn {
|
|
namespace TunWin {
|
|
|
|
class WintunClient : public TunClient
|
|
{
|
|
typedef RCPtr<WintunClient> Ptr;
|
|
|
|
public:
|
|
WintunClient(openvpn_io::io_context& io_context_arg,
|
|
ClientConfig* config_arg,
|
|
TunClientParent& parent_arg)
|
|
: io_context(io_context_arg),
|
|
config(config_arg),
|
|
parent(parent_arg),
|
|
state(new TunProp::State()),
|
|
frame(config_arg->frame)
|
|
{
|
|
}
|
|
|
|
// Inherited via TunClient
|
|
void tun_start(const OptionList& opt, TransportClient& transcli, CryptoDCSettings&) override
|
|
{
|
|
halt = false;
|
|
if (config->tun_persist)
|
|
tun_persist = config->tun_persist; // long-term persistent
|
|
else
|
|
tun_persist.reset(new TunPersist(false, false, nullptr)); // short-term
|
|
|
|
try {
|
|
|
|
const IP::Addr server_addr = transcli.server_endpoint_addr();
|
|
|
|
// Check if persisted tun session matches properties of to-be-created session
|
|
if (tun_persist->use_persisted_tun(server_addr, config->tun_prop, opt))
|
|
{
|
|
state = tun_persist->state().state;
|
|
ring_buffer = tun_persist->state().ring_buffer;
|
|
OPENVPN_LOG("TunPersist: reused tun context");
|
|
}
|
|
else
|
|
{
|
|
// notify parent
|
|
parent.tun_pre_tun_config();
|
|
|
|
// close old TAP handle if persisted
|
|
tun_persist->close();
|
|
|
|
// parse pushed options
|
|
TunBuilderCapture::Ptr po(new TunBuilderCapture());
|
|
TunProp::configure_builder(po.get(),
|
|
state.get(),
|
|
config->stats.get(),
|
|
server_addr,
|
|
config->tun_prop,
|
|
opt,
|
|
nullptr,
|
|
false);
|
|
OPENVPN_LOG("CAPTURED OPTIONS:" << std::endl << po->to_string());
|
|
|
|
// create new tun setup object
|
|
tun_setup = config->new_setup_obj(io_context);
|
|
|
|
ring_buffer.reset(new RingBuffer(io_context));
|
|
|
|
// open/config TAP
|
|
HANDLE th;
|
|
{
|
|
std::ostringstream os;
|
|
auto os_print = Cleanup([&os]() { OPENVPN_LOG_STRING(os.str()); });
|
|
th = tun_setup->establish(*po, Win::module_name(), config->stop, os, ring_buffer);
|
|
}
|
|
|
|
// create ASIO wrapper for HANDLE
|
|
TAPStream* ts = new TAPStream(io_context, th);
|
|
|
|
// persist tun settings state
|
|
if (tun_persist->persist_tun_state(ts, { state, ring_buffer }))
|
|
OPENVPN_LOG("TunPersist: saving tun context:" << std::endl << tun_persist->options());
|
|
|
|
// enable tun_setup destructor
|
|
tun_persist->add_destructor(tun_setup);
|
|
|
|
// assert ownership over TAP device handle
|
|
tun_setup->confirm();
|
|
}
|
|
|
|
openvpn_io::post(io_context, [self=Ptr(this)](){
|
|
self->read();
|
|
});
|
|
|
|
parent.tun_connected(); // signal that we are connected
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
stop();
|
|
Error::Type err = Error::TUN_SETUP_FAILED;
|
|
const ExceptionCode* ec = dynamic_cast<const ExceptionCode*>(&e);
|
|
if (ec && ec->code_defined())
|
|
err = ec->code();
|
|
parent.tun_error(err, e.what());
|
|
}
|
|
}
|
|
|
|
void stop() override
|
|
{
|
|
if (!halt)
|
|
{
|
|
halt = true;
|
|
|
|
tun_persist.reset();
|
|
}
|
|
}
|
|
|
|
void set_disconnect() override
|
|
{
|
|
|
|
}
|
|
|
|
bool tun_send(BufferAllocated& buf) override
|
|
{
|
|
TUN_RING* receive_ring = ring_buffer->receive_ring();
|
|
|
|
ULONG head = receive_ring->head.load(std::memory_order_acquire);
|
|
if (head > WINTUN_RING_CAPACITY)
|
|
{
|
|
if (head == 0xFFFFFFFF)
|
|
parent.tun_error(Error::TUN_WRITE_ERROR, "invalid ring head/tail or bogus packet received");
|
|
return false;
|
|
}
|
|
|
|
ULONG tail = receive_ring->tail.load(std::memory_order_acquire);
|
|
if (tail >= WINTUN_RING_CAPACITY)
|
|
return false;
|
|
|
|
ULONG aligned_packet_size = packet_align(sizeof(TUN_PACKET_HEADER) + buf.size());
|
|
ULONG buf_space = wrap(head - tail - WINTUN_PACKET_ALIGN);
|
|
if (aligned_packet_size > buf_space)
|
|
{
|
|
OPENVPN_LOG("ring is full");
|
|
return false;
|
|
}
|
|
|
|
// copy packet size and data into ring
|
|
TUN_PACKET* packet = (TUN_PACKET*)& receive_ring->data[tail];
|
|
packet->size = buf.size();
|
|
std::memcpy(packet->data, buf.data(), buf.size());
|
|
|
|
// move ring tail
|
|
receive_ring->tail.store(wrap(tail + aligned_packet_size), std::memory_order_release);
|
|
if (receive_ring->alertable.load(std::memory_order_acquire) != 0)
|
|
SetEvent(ring_buffer->receive_ring_tail_moved());
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string tun_name() const override
|
|
{
|
|
return "wintun";
|
|
}
|
|
|
|
std::string vpn_ip4() const override
|
|
{
|
|
if (state->vpn_ip4_addr.specified())
|
|
return state->vpn_ip4_addr.to_string();
|
|
else
|
|
return "";
|
|
}
|
|
|
|
std::string vpn_ip6() const override
|
|
{
|
|
if (state->vpn_ip6_addr.specified())
|
|
return state->vpn_ip6_addr.to_string();
|
|
else
|
|
return "";
|
|
}
|
|
|
|
std::string vpn_gw4() const override
|
|
{
|
|
if (state->vpn_ip4_gw.specified())
|
|
return state->vpn_ip4_gw.to_string();
|
|
else
|
|
return "";
|
|
}
|
|
|
|
std::string vpn_gw6() const override
|
|
{
|
|
if (state->vpn_ip6_gw.specified())
|
|
return state->vpn_ip6_gw.to_string();
|
|
else
|
|
return "";
|
|
}
|
|
|
|
private:
|
|
void read()
|
|
{
|
|
TUN_RING* send_ring = ring_buffer->send_ring();
|
|
|
|
if (halt)
|
|
return;
|
|
|
|
ULONG head = send_ring->head.load(std::memory_order_acquire);
|
|
if (head >= WINTUN_RING_CAPACITY)
|
|
{
|
|
parent.tun_error(Error::TUN_ERROR, "ring head exceeds ring capacity");
|
|
return;
|
|
}
|
|
|
|
ULONG tail = send_ring->tail.load(std::memory_order_acquire);
|
|
if (tail >= WINTUN_RING_CAPACITY)
|
|
{
|
|
parent.tun_error(Error::TUN_ERROR, "ring tail exceeds ring capacity");
|
|
return;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
// tail has moved?
|
|
if (head == tail)
|
|
{
|
|
ring_buffer->send_tail_moved_asio_event().async_wait([self = Ptr(this)](const openvpn_io::error_code& error) {
|
|
if (!error)
|
|
self->read();
|
|
else
|
|
{
|
|
if (!self->halt)
|
|
self->parent.tun_error(Error::TUN_ERROR, "error waiting on ring send tail moved");
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// read buffer content
|
|
ULONG content_len = wrap(tail - head);
|
|
if (content_len < sizeof(TUN_PACKET_HEADER))
|
|
{
|
|
parent.tun_error(Error::TUN_ERROR, "incomplete packet header in send ring");
|
|
return;
|
|
}
|
|
|
|
TUN_PACKET* packet = (TUN_PACKET *)&send_ring->data[head];
|
|
if (packet->size > WINTUN_MAX_PACKET_SIZE)
|
|
{
|
|
parent.tun_error(Error::TUN_ERROR, "packet too big in send ring");
|
|
return;
|
|
}
|
|
|
|
ULONG aligned_packet_size = packet_align(sizeof(TUN_PACKET_HEADER) + packet->size);
|
|
if (aligned_packet_size > content_len)
|
|
{
|
|
parent.tun_error(Error::TUN_ERROR, "incomplete packet in send ring");
|
|
return;
|
|
}
|
|
|
|
frame->prepare(Frame::READ_TUN, buf);
|
|
|
|
buf.write(packet->data, packet->size);
|
|
|
|
head = wrap(head + aligned_packet_size);
|
|
send_ring->head.store(head, std::memory_order_release);
|
|
|
|
parent.tun_recv(buf);
|
|
|
|
if (halt)
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct TUN_PACKET_HEADER
|
|
{
|
|
uint32_t size;
|
|
};
|
|
|
|
struct TUN_PACKET
|
|
{
|
|
uint32_t size;
|
|
UCHAR data[WINTUN_MAX_PACKET_SIZE];
|
|
};
|
|
|
|
ULONG packet_align(ULONG size)
|
|
{
|
|
return (size + (WINTUN_PACKET_ALIGN - 1)) & ~(WINTUN_PACKET_ALIGN - 1);
|
|
}
|
|
|
|
ULONG wrap(ULONG value)
|
|
{
|
|
return value & (WINTUN_RING_CAPACITY - 1);
|
|
}
|
|
|
|
openvpn_io::io_context& io_context;
|
|
TunPersist::Ptr tun_persist; // contains the TAP device HANDLE
|
|
ClientConfig::Ptr config;
|
|
TunClientParent& parent;
|
|
TunProp::State::Ptr state;
|
|
TunWin::SetupBase::Ptr tun_setup;
|
|
|
|
TUN_RING* receive_ring = nullptr;
|
|
TUN_RING* send_ring = nullptr;
|
|
|
|
BufferAllocated buf;
|
|
|
|
Frame::Ptr frame;
|
|
|
|
bool halt = false;
|
|
|
|
ScopedHANDLE driver_handle;
|
|
|
|
RingBuffer::Ptr ring_buffer;
|
|
};
|
|
}
|
|
}
|