0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 04:02:15 +02:00

dco: implement new ovpn-dco logic

With the recent changes in ovpn-dco, aimed at accommodating the new
multi-peer logic, userspace has to adapt as well in order to follow the
expected flow. In particular we require userspace to act as following:
1) create TCP/UDP transport socket towards peer/server;
2) perform initial handshare over TCP/UDP socket
3) when handshake is complete, add peer/server to ovpn-dco kernel module
   via CMD_NEW_PEER by passing peer address, port and socket fd;
4) handle further control channel communication over netlink.

To implement the logic above, the OvpnDcoClient has lost its
"Protowrapper" template and now it basically implemente the
TransportClient and TransportParent at the same time.
This way, it can instantiate a TCP/UDP TransportClient internally and
use it for point 1 and 2. This change greatly simplify the OvpnDcoClient
itself as several operations are directly handled by the TCP/UDP
TransportClient.

Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
This commit is contained in:
Antonio Quartulli 2021-02-16 22:26:18 +01:00
parent 2c2fdaa7c5
commit e30146bfb6
No known key found for this signature in database
GPG Key ID: 20C8E22F5B1610AB
3 changed files with 252 additions and 311 deletions

View File

@ -52,12 +52,14 @@
#endif
#include <openvpn/dco/korekey.hpp>
#include <openvpn/dco/protowrapper.hpp>
// client-side DCO (Data Channel Offload) module for Linux/kovpn
namespace openvpn {
namespace DCOTransport {
enum {
OVPN_PEER_ID_UNDEF = 0x00FFFFFF,
};
OPENVPN_EXCEPTION(dco_error);
@ -165,27 +167,6 @@ namespace openvpn {
ip_addr = addr.to_string();
}
virtual IP::Addr server_endpoint_addr() const override
{
if (proto)
return proto->server_endpoint_addr();
else
return IP::Addr();
}
virtual Protocol transport_protocol() const override
{
switch (server_endpoint_addr().version())
{
case IP::Addr::V4:
return Protocol(Protocol::UDPv4);
case IP::Addr::V6:
return Protocol(Protocol::UDPv6);
default:
return Protocol();
}
}
virtual void stop() override
{
stop_();
@ -245,7 +226,7 @@ namespace openvpn {
config(config_arg),
transport_parent(parent_arg),
tun_parent(nullptr),
peer_id(-1)
peer_id(OVPN_PEER_ID_UNDEF)
{
}
@ -254,86 +235,6 @@ namespace openvpn {
transport_parent = parent_arg;
}
void transport_start_udp()
{
transport_start_impl(false);
}
void transport_start_tcp()
{
transport_start_impl(true);
}
void transport_start_impl(bool tcp)
{
if (tcp)
proto.reset(new TCP(io_context));
else
proto.reset(new UDP(io_context));
if (config->transport.remote_list->endpoint_available(&server_host, &server_port, nullptr))
{
start_connect();
}
else
{
transport_parent->transport_pre_resolve();
async_resolve_name(server_host, server_port);
}
}
// called after DNS resolution has succeeded or failed
void resolve_callback(const openvpn_io::error_code& error,
openvpn_io::ip::udp::resolver::results_type results) override
{
if (!halt)
{
if (!error)
{
// save resolved endpoint list in remote_list
config->transport.remote_list->set_endpoint_range(results);
start_connect();
}
else
{
std::ostringstream os;
os << "DNS resolve error on '" << server_host << "' for session: " << error.message();
config->transport.stats->error(Error::RESOLVE_ERROR);
stop_();
transport_parent->transport_error(Error::UNDEF, os.str());
}
}
}
void start_connect()
{
proto->get_endpoint(config->transport.remote_list);
OPENVPN_LOG("Contacting " << proto->server_endpoint_addr() << " via " << proto->proto());
transport_parent->transport_wait();
proto->open();
if (config->transport.socket_protect)
{
if (!config->transport.socket_protect->socket_protect(proto->native_handle(), server_endpoint_addr()))
{
stop();
std::ostringstream os;
os << "socket_protect error (";
os << proto->proto();
os << ")";
transport_parent->transport_error(Error::UNDEF, os.str());
return;
}
}
proto->async_connect([self=Ptr(this)](const openvpn_io::error_code &error) {
self->start_impl_udp(error);
});
}
// start I/O on UDP socket
virtual void start_impl_udp(const openvpn_io::error_code& error) = 0;
virtual void stop_() = 0;
openvpn_io::io_context& io_context;
@ -345,14 +246,12 @@ namespace openvpn {
TransportClientParent* transport_parent;
TunClientParent* tun_parent;
ProtoBase::Ptr proto;
ActionList::Ptr remove_cmds;
std::string server_host;
std::string server_port;
int peer_id;
uint32_t peer_id;
};
#ifdef ENABLE_KOVPN

View File

@ -22,10 +22,14 @@
// tun/transport client for ovpn-dco
class OvpnDcoClient : public Client, public KoRekey::Receiver {
#include <openvpn/dco/dcocli.hpp>
class OvpnDcoClient : public Client, public KoRekey::Receiver, public TransportClientParent {
friend class ClientConfig;
friend class GeNL;
OPENVPN_EXCEPTION(dcocli_error);
typedef RCPtr<OvpnDcoClient> Ptr;
typedef GeNL<OvpnDcoClient *> GeNLImpl;
@ -37,6 +41,26 @@ class OvpnDcoClient : public Client, public KoRekey::Receiver {
public:
virtual void tun_start(const OptionList &opt, TransportClient &transcli,
CryptoDCSettings &dc_settings) override {
// extract peer ID from pushed options
try {
const Option *o = opt.get_ptr("peer-id");
if (o)
{
bool status = parse_number_validate<uint32_t>(o->get(1, 16), 16, 0, OVPN_PEER_ID_UNDEF - 1,
&peer_id);
if (!status)
OPENVPN_THROW(dcocli_error, "Parse/range issue with pushed peer-id");
}
else
{
OPENVPN_THROW(dcocli_error, "No peer-id pushed by server");
}
}
catch (const std::exception& e)
{
OPENVPN_THROW(dcocli_error, "Cannot extract peer-id: " << e.what());
}
// notify parent
tun_parent->tun_pre_tun_config();
@ -78,111 +102,135 @@ public:
dc_settings.set_factory(CryptoDCFactory::Ptr(new KoRekey::Factory(
dc_settings.factory(), this, config->transport.frame)));
// add peer in ovpn-dco
add_peer(peer_id, state->vpn_ip4_gw.to_ipv4_zero(), state->vpn_ip6_gw.to_ipv6_zero());
// signal that we are connected
tun_parent->tun_connected();
}
virtual std::string tun_name() const override { return "ovpn-dco"; }
virtual IP::Addr server_endpoint_addr() const override
{
if (transport)
return transport->server_endpoint_addr();
else
return IP::Addr();
}
virtual unsigned short server_endpoint_port() const override
{
if (transport)
return transport->server_endpoint_port();
else
return 0;
}
virtual Protocol transport_protocol() const override
{
return transport->transport_protocol();
}
virtual void transport_start() override {
if (config->transport.protocol.is_udp())
transport_start_udp();
else if (config->transport.protocol.is_tcp())
transport_start_tcp();
else {
stop();
std::ostringstream os;
os << "unsupported protocol: " << config->transport.protocol.str();
transport_parent->transport_error(Error::UNDEF, os.str());
TransportClientFactory::Ptr transport_factory;
if (!config->transport.protocol.is_tcp())
{
UDPTransport::ClientConfig::Ptr udpconf = UDPTransport::ClientConfig::new_obj();
udpconf->remote_list = config->transport.remote_list;
udpconf->frame = config->transport.frame;
udpconf->stats = config->transport.stats;
udpconf->socket_protect = config->transport.socket_protect;
udpconf->server_addr_float = config->transport.server_addr_float;
transport_factory = udpconf;
}
else
{
TCPTransport::ClientConfig::Ptr tcpconf = TCPTransport::ClientConfig::new_obj();
tcpconf->remote_list = config->transport.remote_list;
tcpconf->frame = config->transport.frame;
tcpconf->stats = config->transport.stats;
tcpconf->socket_protect = config->transport.socket_protect;
transport_factory = tcpconf;
}
transport = transport_factory->new_transport_client_obj(io_context, this);
transport->transport_start();
}
virtual bool transport_send_const(const Buffer &buf) override {
if (peer_id == OVPN_PEER_ID_UNDEF)
return transport->transport_send_const(buf);
return send(buf);
}
virtual bool transport_send(BufferAllocated &buf) override {
if (peer_id == OVPN_PEER_ID_UNDEF)
return transport->transport_send(buf);
return send(buf);
}
bool send(const Buffer &buf) {
if (config->builder) {
if (config->builder)
pipe->write_some(buf.const_buffer());
} else {
genl->send_data(buf.c_data(), buf.size());
}
else
genl->send_data(peer_id, buf.c_data(), buf.size());
return true;
}
virtual void start_impl_udp(const openvpn_io::error_code &error) override {
start_impl(error);
void get_remote_sockaddr(struct sockaddr_storage &sa, socklen_t &salen)
{
memset(&sa, 0, sizeof(sa));
struct sockaddr_in *sa4 = (struct sockaddr_in *)&sa;
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&sa;
IP::Addr remote_addr = transport->server_endpoint_addr();
if (remote_addr.version() == IP::Addr::V4) {
salen = sizeof(*sa4);
*sa4 = remote_addr.to_ipv4().to_sockaddr(transport->server_endpoint_port());
} else {
salen = sizeof(*sa6);
*sa6 = remote_addr.to_ipv6().to_sockaddr(transport->server_endpoint_port());
}
}
void start_impl(const openvpn_io::error_code &error) {
if (halt)
void del_peer(uint32_t peer_id)
{
TunBuilderBase *tb = config->builder;
if (tb) {
tb->tun_builder_dco_del_peer(peer_id);
return;
if (!error) {
openvpn_io::ip::address local_addr = proto->local_address();
openvpn_io::ip::address remote_addr = proto->remote_address();
unsigned short local_port = proto->local_port();
unsigned short remote_port = proto->remote_port();
auto conf_proto = config->transport.protocol;
ovpn_proto p = OVPN_PROTO_UNDEF;
if (conf_proto.is_tcp()) {
p = conf_proto.is_ipv6() ? OVPN_PROTO_TCP6 : OVPN_PROTO_TCP4;
} else {
p = conf_proto.is_ipv6() ? OVPN_PROTO_UDP6 : OVPN_PROTO_UDP4;
}
TunBuilderBase *tb = config->builder;
if (tb) {
tb->tun_builder_new();
// pipe fd which is used to communicate to kernel
int fd = tb->tun_builder_dco_enable(proto->native_handle(), p,
config->dev_name);
if (fd == -1) {
stop_();
transport_parent->transport_error(Error::TUN_IFACE_CREATE,
"error creating ovpn-dco device");
return;
}
pipe.reset(new openvpn_io::posix::stream_descriptor(io_context, fd));
tb->tun_builder_dco_new_peer(local_addr.to_string(), local_port,
remote_addr.to_string(), remote_port);
queue_read_pipe(nullptr);
transport_parent->transport_connecting();
} else {
std::ostringstream os;
int res = TunNetlink::iface_new(os, config->dev_name, "ovpn-dco");
if (res != 0) {
stop_();
transport_parent->transport_error(Error::TUN_IFACE_CREATE, os.str());
} else {
genl.reset(new GeNLImpl(
io_context, if_nametoindex(config->dev_name.c_str()), this));
genl->start_vpn(proto->native_handle(), p);
genl->register_packet();
genl->new_peer(local_addr, local_port, remote_addr, remote_port);
transport_parent->transport_connecting();
}
}
} else {
std::ostringstream os;
os << proto->proto();
os << " connect error on '" << server_host << ':' << server_port << "' ("
<< proto->server_endpoint_addr() << "): " << error.message();
config->transport.stats->error(config->transport.protocol.is_tcp()
? Error::TCP_CONNECT_ERROR
: Error::UDP_CONNECT_ERROR);
stop_();
transport_parent->transport_error(Error::UNDEF, os.str());
}
genl->del_peer(peer_id);
}
void add_peer(uint32_t peer_id, IPv4::Addr ipv4, IPv6::Addr ipv6)
{
struct sockaddr_storage sa;
socklen_t salen;
get_remote_sockaddr(sa, salen);
TunBuilderBase *tb = config->builder;
if (tb) {
tb->tun_builder_dco_new_peer(peer_id, transport->native_handle(), (struct sockaddr *)&sa, salen,
ipv4, ipv6);
queue_read_pipe(nullptr);
return;
}
genl->new_peer(peer_id, transport->native_handle(), (struct sockaddr *)&sa, salen, ipv4, ipv6);
}
virtual void resolve_callback(const openvpn_io::error_code& error,
openvpn_io::ip::udp::resolver::results_type results) override
{
}
virtual void stop_() override {
@ -197,11 +245,15 @@ public:
std::ostringstream os;
if (genl)
genl->stop();
int res = TunNetlink::iface_del(os, config->dev_name);
if (res != 0) {
OPENVPN_LOG("ovpndcocli: error deleting iface ovpn:" << os.str());
}
}
if (transport)
transport->stop();
}
}
@ -229,15 +281,18 @@ public:
break;
case CryptoDCInstance::NEW_SECONDARY:
genl->new_key(OVPN_KEY_SLOT_SECONDARY, kc);
break;
case CryptoDCInstance::PRIMARY_SECONDARY_SWAP:
genl->swap_keys();
genl->swap_keys(peer_id);
break;
case CryptoDCInstance::DEACTIVATE_SECONDARY:
genl->del_key(OVPN_KEY_SLOT_SECONDARY);
genl->del_key(peer_id, OVPN_KEY_SLOT_SECONDARY);
break;
case CryptoDCInstance::DEACTIVATE_ALL:
@ -270,11 +325,11 @@ public:
break;
case CryptoDCInstance::PRIMARY_SECONDARY_SWAP:
tb->tun_builder_dco_swap_keys();
tb->tun_builder_dco_swap_keys(peer_id);
break;
case CryptoDCInstance::DEACTIVATE_SECONDARY:
tb->tun_builder_dco_del_key(OVPN_KEY_SLOT_SECONDARY);
tb->tun_builder_dco_del_key(peer_id, OVPN_KEY_SLOT_SECONDARY);
break;
case CryptoDCInstance::DEACTIVATE_ALL:
@ -288,6 +343,10 @@ public:
}
}
virtual void transport_recv(BufferAllocated& buf) override {
transport_parent->transport_recv(buf);
}
bool tun_read_handler(BufferAllocated &buf) {
if (halt)
return false;
@ -301,26 +360,44 @@ public:
break;
case OVPN_CMD_DEL_PEER: {
stop_();
int8_t reason = -1;
uint32_t peer_id;
buf.read(&peer_id, sizeof(peer_id));
uint8_t reason;
buf.read(&reason, sizeof(reason));
std::ostringstream os;
Error::Type err;
switch (reason) {
case OVPN_DEL_PEER_REASON_EXPIRED:
transport_parent->transport_error(Error::TRANSPORT_ERROR,
"keepalive timeout");
break;
err = Error::TRANSPORT_ERROR;
os << "keepalive timeout";
break;
case OVPN_DEL_PEER_REASON_TRANSPORT_ERROR:
transport_parent->transport_error(Error::TRANSPORT_ERROR,
"transport error");
break;
err = Error::TRANSPORT_ERROR;
os << "transport error";
break;
case OVPN_DEL_PEER_REASON_TEARDOWN:
err = Error::TRANSPORT_ERROR;
os << "peer deleted, id=" << peer_id << ", teardown";
break;
case OVPN_DEL_PEER_REASON_USERSPACE:
// volountary delete - do not stop client
OPENVPN_LOG("peer deleted, id=" << peer_id << ", requested by userspace");
peer_id = OVPN_PEER_ID_UNDEF;
return true;
default:
std::ostringstream os;
os << "peer deleted, reason " << reason;
transport_parent->transport_error(Error::TUN_HALT, os.str());
break;
err = Error::TUN_HALT;
os << "peer deleted, id=" << peer_id << ", reason=" << reason;
break;
}
stop_();
transport_parent->transport_error(err, os.str());
break;
}
@ -339,10 +416,80 @@ public:
return true;
}
virtual void transport_needs_send() override {
transport_parent->transport_needs_send();
}
virtual void transport_error(const Error::Type fatal_err, const std::string& err_text) override {
transport_parent->transport_error(fatal_err, err_text);
}
virtual void proxy_error(const Error::Type fatal_err, const std::string& err_text) override {
transport_parent->proxy_error(fatal_err, err_text);
}
virtual bool transport_is_openvpn_protocol() override {
return transport_parent->transport_is_openvpn_protocol();
}
virtual void transport_pre_resolve() override {
transport_parent->transport_pre_resolve();
}
virtual void transport_wait_proxy() override {
transport_parent->transport_wait_proxy();
}
virtual void transport_wait() override {
transport_parent->transport_wait();
}
virtual void transport_connecting() override {
transport_parent->transport_connecting();
}
virtual bool is_keepalive_enabled() const override {
return transport_parent->is_keepalive_enabled();
}
virtual void disable_keepalive(unsigned int& keepalive_ping,
unsigned int &keepalive_timeout) override {
transport_parent->disable_keepalive(keepalive_ping, keepalive_timeout);
}
private:
OvpnDcoClient(openvpn_io::io_context &io_context_arg,
ClientConfig *config_arg, TransportClientParent *parent_arg)
: Client(io_context_arg, config_arg, parent_arg) {}
: Client(io_context_arg, config_arg, parent_arg)
{
TunBuilderBase *tb = config->builder;
if (tb) {
tb->tun_builder_new();
// pipe fd which is used to communicate to kernel
int fd = tb->tun_builder_dco_enable(config->dev_name);
if (fd == -1) {
stop_();
transport_parent->transport_error(Error::TUN_IFACE_CREATE,
"error creating ovpn-dco device");
return;
}
pipe.reset(new openvpn_io::posix::stream_descriptor(io_context, fd));
return;
}
std::ostringstream os;
int res = TunNetlink::iface_new(os, config->dev_name, "ovpn-dco");
if (res != 0) {
stop_();
transport_parent->transport_error(Error::TUN_IFACE_CREATE, os.str());
return;
}
genl.reset(new GeNLImpl(io_context_arg, if_nametoindex(config_arg->dev_name.c_str()), this));
genl->register_packet();
}
void handle_keepalive() {
// since userspace doesn't know anything about presense or
@ -363,11 +510,11 @@ private:
keepalive_timeout = config->ping_restart_override;
if (config->builder) {
config->builder->tun_builder_dco_set_peer(keepalive_interval,
config->builder->tun_builder_dco_set_peer(peer_id, keepalive_interval,
keepalive_timeout);
} else {
// enable keepalive in kernel
genl->set_peer(keepalive_interval, keepalive_timeout);
genl->set_peer(peer_id, keepalive_interval, keepalive_timeout);
}
}
}
@ -404,4 +551,5 @@ private:
std::unique_ptr<openvpn_io::posix::stream_descriptor> pipe;
GeNLImpl::Ptr genl;
};
TransportClient::Ptr transport;
};

View File

@ -1,106 +0,0 @@
// OpenVPN -- An application to securely tunnel IP networks
// over a single port, with support for SSL/TLS-based
// session authentication and key exchange,
// packet encryption, packet authentication, and
// packet compression.
//
// Copyright (C) 2012-2020 OpenVPN Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License Version 3
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program in the COPYING file.
// If not, see <http://www.gnu.org/licenses/>.
namespace openvpn {
namespace DCOTransport {
class ProtoBase : public virtual RC<thread_unsafe_refcount> {
public:
typedef RCPtr<ProtoBase> Ptr;
virtual IP::Addr server_endpoint_addr() const = 0;
virtual void close() = 0;
virtual void get_endpoint(RemoteList::Ptr remote_list) = 0;
virtual void open() = 0;
virtual const char *proto() = 0;
virtual int native_handle() = 0;
virtual void
async_connect(std::function<void(const openvpn_io::error_code &)>&&) = 0;
virtual openvpn_io::ip::address local_address() = 0;
virtual openvpn_io::ip::address remote_address() = 0;
virtual unsigned short local_port() = 0;
virtual unsigned short remote_port() = 0;
};
template <class ENDPOINT, class SOCKET> class ProtoImpl : public ProtoBase {
public:
explicit ProtoImpl(openvpn_io::io_context &io_context)
: resolver(io_context), socket(io_context) {}
virtual IP::Addr server_endpoint_addr() const override {
return IP::Addr::from_asio(server_endpoint.address());
}
virtual void close() override {
socket.close();
resolver.cancel();
}
virtual void get_endpoint(RemoteList::Ptr remote_list) override {
remote_list->get_endpoint(server_endpoint);
}
virtual void open() override { socket.open(server_endpoint.protocol()); }
virtual const char *proto() override { return "UDP"; }
virtual int native_handle() override { return socket.native_handle(); }
virtual void async_connect(
std::function<void(const openvpn_io::error_code &)>&& func) override {
socket.async_connect(server_endpoint, func);
}
virtual openvpn_io::ip::address local_address() override {
return socket.local_endpoint().address();
}
virtual openvpn_io::ip::address remote_address() override {
return socket.remote_endpoint().address();
}
virtual unsigned short local_port() override {
return socket.local_endpoint().port();
}
virtual unsigned short remote_port() override {
return socket.remote_endpoint().port();
}
protected:
openvpn_io::ip::udp::resolver resolver;
SOCKET socket;
ENDPOINT server_endpoint;
};
class UDP : public ProtoImpl<UDPTransport::AsioEndpoint,
openvpn_io::ip::udp::socket> {
public:
explicit UDP(openvpn_io::io_context &io_context) : ProtoImpl(io_context) {}
};
class TCP : public ProtoImpl<openvpn_io::ip::tcp::endpoint,
openvpn_io::ip::tcp::socket> {
public:
explicit TCP(openvpn_io::io_context &io_context) : ProtoImpl(io_context) {}
};
} // namespace DCOTransport
} // namespace openvpn