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

Added Relay capability, a kind of proxy function similar

to HTTP CONNECT but implemented over the OpenVPN protocol.

1. Client connects to relay server as if it were connecting
   to an ordinary OpenVPN server.

2. Client authenticates to relay server using its client
   certificate.

3. Client sends a PUSH_REQUEST method to relay server which
   then replies with a RELAY message instead of PUSH_REPLY.

4. On receiving the RELAY message, the client attempts to
   reconnect using the existing transport socket.  The
   server will proxy this new connection (at the transport
   layer) to a second server (chosen by the relay server)
   that is the target of proxy.

5. The client must establish and authenticate a new session
   from scratch with the target server, only reusing the
   transport layer socket from the original connection to
   the relay server.

6. The relay acts as a man-in-the-middle only at the
   transport layer (like most proxies), i.e. it forwards
   the encrypted session between client and target server
   without decrypting or having the capability to decrypt
   the session.

7. The client is designed to protect against potentially
   untrusted or malicious relays:

   (a) The client never transmits the target server
       username/password credentials to the relay server.

   (b) The relay forwards the encrypted OpenVPN session
       between client and target server without having
       access to the session keys.

   (c) The client configuration has a special directive
       for relay server CA (<relay-extra-ca>) and relay
       server tls-auth key (<relay-tls-auth>) to allow
       for separation of TLS/crypto configuration between
       relay and target servers.

   (d) The client will reject any PUSH_REPLY messages
       from the relay itself to prevent the relay from
       trying to establish a tunnel directly with the
       client.

Example configuring a client for relay:

  # remote addresses point to the relay server
  remote ... 1194 udp
  remote ... 443 tcp

  # include all other directives for connecting
  # to the target server

  # enable relay mode
  relay-mode

  # constrain the relay server's cert type
  relay-ns-cert-type server

  # include extra CAs that validate the relay
  # server cert (optional).
  <relay-extra-ca>
  -----BEGIN CERTIFICATE-----
  . . .
  -----END CERTIFICATE-----
  </relay-extra-ca>

  # specify the TLS auth key for the relay server
  relay-key-direction 1
  <relay-tls-auth>
  -----BEGIN OpenVPN Static key V1-----
  . . .
  -----END OpenVPN Static key V1-----
  </relay-tls-auth>
This commit is contained in:
James Yonan 2016-11-20 13:06:35 -07:00
parent c3dc14f3a0
commit 9c0397ebd3
25 changed files with 645 additions and 177 deletions

View File

@ -75,7 +75,7 @@ namespace openvpn {
std::ostringstream os;
os << "*** AuthCreds ***" << std::endl;
os << "user: '" << username << "'" << std::endl;
//os << "pass: '" << password << "'" << std::endl;
os << "pass: (" << password.length() << " chars)" << std::endl;
os << "peer info:" << std::endl;
os << peer_info.render(Option::RENDER_BRACKET|Option::RENDER_NUMBER);
return os.str();

View File

@ -50,6 +50,7 @@
#define OPENVPN_CLIENT_CLICONNECT_H
#include <memory>
#include <utility>
#include <openvpn/common/rc.hpp>
#include <openvpn/error/excode.hpp>
@ -370,13 +371,13 @@ namespace openvpn {
}
}
void queue_restart(const unsigned int delay = 2)
void queue_restart(const unsigned int delay_ms = 2000)
{
OPENVPN_LOG("Client terminated, restarting in " << delay << "...");
OPENVPN_LOG("Client terminated, restarting in " << delay_ms << " ms...");
server_poll_timer.cancel();
interim_finalize();
client_options->remote_reset_cache_item();
restart_wait_timer.expires_at(Time::now() + Time::Duration::seconds(delay));
restart_wait_timer.expires_at(Time::now() + Time::Duration::milliseconds(delay_ms));
restart_wait_timer.async_wait([self=Ptr(this), gen=generation](const asio::error_code& error)
{
self->restart_wait_callback(gen, error);
@ -504,7 +505,7 @@ namespace openvpn {
ClientEvent::Base::Ptr ev = new ClientEvent::TransportError(client->fatal_reason());
client_options->events().add_event(std::move(ev));
client_options->stats().error(Error::TRANSPORT_ERROR);
queue_restart(5); // use a larger timeout to allow preemption from higher levels
queue_restart(5000); // use a larger timeout to allow preemption from higher levels
}
break;
case Error::TUN_ERROR:
@ -512,7 +513,24 @@ namespace openvpn {
ClientEvent::Base::Ptr ev = new ClientEvent::TunError(client->fatal_reason());
client_options->events().add_event(std::move(ev));
client_options->stats().error(Error::TUN_ERROR);
queue_restart(5);
queue_restart(5000);
}
break;
case Error::RELAY:
{
ClientEvent::Base::Ptr ev = new ClientEvent::Relay();
client_options->events().add_event(std::move(ev));
client_options->stats().error(Error::RELAY);
transport_factory_relay = client->transport_factory_relay();
queue_restart(0);
}
break;
case Error::RELAY_ERROR:
{
ClientEvent::Base::Ptr ev = new ClientEvent::RelayError(client->fatal_reason());
client_options->events().add_event(std::move(ev));
client_options->stats().error(Error::RELAY_ERROR);
stop();
}
break;
default:
@ -531,7 +549,7 @@ namespace openvpn {
client->stop(false);
interim_finalize();
}
if (generation > 1)
if (generation > 1 && !transport_factory_relay)
{
ClientEvent::Base::Ptr ev = new ClientEvent::Reconnecting();
client_options->events().add_event(std::move(ev));
@ -539,10 +557,19 @@ namespace openvpn {
if (!(client && client->reached_connected_state()))
client_options->next();
}
Client::Config::Ptr cli_config = client_options->client_config(); // client_config in cliopt.hpp
// client_config in cliopt.hpp
Client::Config::Ptr cli_config = client_options->client_config(!transport_factory_relay);
client.reset(new Client(io_context, *cli_config, this)); // build ClientProto::Session from cliproto.hpp
client_finalized = false;
// relay?
if (transport_factory_relay)
{
client->transport_factory_override(std::move(transport_factory_relay));
transport_factory_relay.reset();
}
restart_wait_timer.cancel();
if (client_options->server_poll_timeout_enabled())
{
@ -588,6 +615,7 @@ namespace openvpn {
asio::io_context& io_context;
ClientOptions::Ptr client_options;
Client::Ptr client;
TransportClientFactory::Ptr transport_factory_relay;
AsioTimer server_poll_timer;
AsioTimer restart_wait_timer;
AsioTimer conn_timer;

View File

@ -52,6 +52,7 @@ namespace openvpn {
INFO,
PAUSE,
RESUME,
RELAY,
// start of nonfatal errors, must be marked by NONFATAL_ERROR_START below
TRANSPORT_ERROR,
@ -73,6 +74,7 @@ namespace openvpn {
TUN_IFACE_DISABLED,
EPKI_ERROR, // EPKI refers to External PKI errors, i.e. errors in accessing external
EPKI_INVALID_ALIAS, // certificates or keys.
RELAY_ERROR,
N_TYPES
};
@ -99,6 +101,7 @@ namespace openvpn {
"INFO",
"PAUSE",
"RESUME",
"RELAY",
// nonfatal errors
"TRANSPORT_ERROR",
@ -120,6 +123,7 @@ namespace openvpn {
"TUN_IFACE_DISABLED",
"EPKI_ERROR",
"EPKI_INVALID_ALIAS",
"RELAY_ERROR",
};
static_assert(N_TYPES == array_size(names), "event names array inconsistency");
@ -220,6 +224,11 @@ namespace openvpn {
Resume() : Base(RESUME) {}
};
struct Relay : public Base
{
Relay() : Base(RELAY) {}
};
struct Disconnected : public Base
{
Disconnected() : Base(DISCONNECTED) {}
@ -316,6 +325,11 @@ namespace openvpn {
ClientRestart(std::string reason) : ReasonBase(CLIENT_RESTART, std::move(reason)) {}
};
struct RelayError : public ReasonBase
{
RelayError(std::string reason) : ReasonBase(RELAY_ERROR, std::move(reason)) {}
};
struct DynamicChallenge : public ReasonBase
{
DynamicChallenge(std::string reason) : ReasonBase(DYNAMIC_CHALLENGE, std::move(reason)) {}

View File

@ -209,37 +209,10 @@ namespace openvpn {
// route-nopull
pushed_options_filter.reset(new PushedOptionsFilter(opt.exists("route-nopull")));
// client SSL config
SSLLib::SSLAPI::Config::Ptr cc(new SSLLib::SSLAPI::Config());
cc->set_external_pki_callback(config.external_pki);
cc->set_frame(frame);
cc->set_flags(SSLConst::LOG_VERIFY_STATUS);
cc->set_debug_level(config.ssl_debug_level);
cc->set_rng(rng);
cc->set_local_cert_enabled(pcc.clientCertEnabled() && !config.disable_client_cert);
cc->set_private_key_password(config.private_key_password);
cc->set_force_aes_cbc_ciphersuites(config.force_aes_cbc_ciphersuites);
cc->load(opt, SSLConfigAPI::LF_PARSE_MODE);
cc->set_tls_version_min_override(config.tls_version_min_override);
if (!cc->get_mode().is_client())
throw option_error("only client configuration supported");
// client ProtoContext config
cp.reset(new Client::ProtoConfig());
cp->dc.set_factory(new CryptoDCSelect<SSLLib::CryptoAPI>(frame, cli_stats, prng));
cp->dc_deferred = true; // defer data channel setup until after options pull
cp->tls_auth_factory.reset(new CryptoOvpnHMACFactory<SSLLib::CryptoAPI>());
cp->tlsprf_factory.reset(new CryptoTLSPRFFactory<SSLLib::CryptoAPI>());
cp->ssl_factory = cc->new_factory();
cp->load(opt, *proto_context_options, config.default_key_direction, false);
cp->set_xmit_creds(!autologin || pcc.hasEmbeddedPassword() || autologin_sessions);
cp->gui_version = config.gui_version;
cp->force_aes_cbc_ciphersuites = config.force_aes_cbc_ciphersuites; // also used to disable proto V2
cp->extra_peer_info = build_peer_info(config, pcc, autologin_sessions);
cp->frame = frame;
cp->now = &now_;
cp->rng = rng;
cp->prng = prng;
// OpenVPN Protocol context (including SSL)
cp_main = proto_config(opt, config, pcc, false);
cp_relay = proto_config(opt, config, pcc, true); // may be null
layer = cp_main->layer;
#ifdef PRIVATE_TUNNEL_PROXY
if (config.alt_proxy && !dco)
@ -307,7 +280,7 @@ namespace openvpn {
if (dco)
{
DCO::TunConfig tunconf;
tunconf.tun_prop.layer = cp->layer;
tunconf.tun_prop.layer = layer;
tunconf.tun_prop.session_name = session_name;
if (tun_mtu)
tunconf.tun_prop.mtu = tun_mtu;
@ -359,7 +332,7 @@ namespace openvpn {
#elif defined(OPENVPN_PLATFORM_LINUX) && !defined(OPENVPN_FORCE_TUN_NULL)
{
TunLinux::ClientConfig::Ptr tunconf = TunLinux::ClientConfig::new_obj();
tunconf->tun_prop.layer = cp->layer;
tunconf->tun_prop.layer = layer;
tunconf->tun_prop.session_name = session_name;
if (tun_mtu)
tunconf->tun_prop.mtu = tun_mtu;
@ -373,7 +346,7 @@ namespace openvpn {
#elif defined(OPENVPN_PLATFORM_MAC) && !defined(OPENVPN_FORCE_TUN_NULL)
{
TunMac::ClientConfig::Ptr tunconf = TunMac::ClientConfig::new_obj();
tunconf->tun_prop.layer = cp->layer;
tunconf->tun_prop.layer = layer;
tunconf->tun_prop.session_name = session_name;
tunconf->tun_prop.google_dns_fallback = config.google_dns_fallback;
if (tun_mtu)
@ -392,7 +365,7 @@ namespace openvpn {
#elif defined(OPENVPN_PLATFORM_WIN) && !defined(OPENVPN_FORCE_TUN_NULL)
{
TunWin::ClientConfig::Ptr tunconf = TunWin::ClientConfig::new_obj();
tunconf->tun_prop.layer = cp->layer;
tunconf->tun_prop.layer = layer;
tunconf->tun_prop.session_name = session_name;
tunconf->tun_prop.google_dns_fallback = config.google_dns_fallback;
if (tun_mtu)
@ -418,7 +391,7 @@ namespace openvpn {
}
// verify that tun implementation can handle OSI layer declared by config
if (cp->layer == Layer(Layer::OSI_LAYER_2) && !tun_factory->layer_2_supported())
if (layer == Layer(Layer::OSI_LAYER_2) && !tun_factory->layer_2_supported())
throw ErrorCode(Error::TAP_NOT_SUPPORTED, true, "OSI layer 2 tunnels are not currently supported");
// server-poll-timeout
@ -539,13 +512,13 @@ namespace openvpn {
return false;
}
Client::Config::Ptr client_config()
Client::Config::Ptr client_config(const bool relay_mode)
{
Client::Config::Ptr cli_config = new Client::Config;
// Copy ProtoConfig so that modifications due to server push will
// not persist across client instantiations.
cli_config->proto_context_config.reset(new Client::ProtoConfig(*cp));
cli_config->proto_context_config.reset(new Client::ProtoConfig(proto_config_cached(relay_mode)));
cli_config->proto_context_options = proto_context_options;
cli_config->push_base = push_base;
@ -626,14 +599,69 @@ namespace openvpn {
}
private:
Client::ProtoConfig& proto_config_cached(const bool relay_mode)
{
if (relay_mode && cp_relay)
return *cp_relay;
else
return *cp_main;
}
Client::ProtoConfig::Ptr proto_config(const OptionList& opt,
const Config& config,
const ParseClientConfig& pcc,
const bool relay_mode)
{
// relay mode is null unless one of the below directives is defined
if (relay_mode && !opt.exists("relay-mode"))
return Client::ProtoConfig::Ptr();
// load flags
unsigned int lflags = SSLConfigAPI::LF_PARSE_MODE;
if (relay_mode)
lflags |= SSLConfigAPI::LF_RELAY_MODE;
// client SSL config
SSLLib::SSLAPI::Config::Ptr cc(new SSLLib::SSLAPI::Config());
cc->set_external_pki_callback(config.external_pki);
cc->set_frame(frame);
cc->set_flags(SSLConst::LOG_VERIFY_STATUS);
cc->set_debug_level(config.ssl_debug_level);
cc->set_rng(rng);
cc->set_local_cert_enabled(pcc.clientCertEnabled() && !config.disable_client_cert);
cc->set_private_key_password(config.private_key_password);
cc->set_force_aes_cbc_ciphersuites(config.force_aes_cbc_ciphersuites);
cc->load(opt, lflags);
cc->set_tls_version_min_override(config.tls_version_min_override);
if (!cc->get_mode().is_client())
throw option_error("only client configuration supported");
// client ProtoContext config
Client::ProtoConfig::Ptr cp(new Client::ProtoConfig());
cp->relay_mode = relay_mode;
cp->dc.set_factory(new CryptoDCSelect<SSLLib::CryptoAPI>(frame, cli_stats, prng));
cp->dc_deferred = true; // defer data channel setup until after options pull
cp->tls_auth_factory.reset(new CryptoOvpnHMACFactory<SSLLib::CryptoAPI>());
cp->tlsprf_factory.reset(new CryptoTLSPRFFactory<SSLLib::CryptoAPI>());
cp->ssl_factory = cc->new_factory();
cp->load(opt, *proto_context_options, config.default_key_direction, false);
cp->set_xmit_creds(!autologin || pcc.hasEmbeddedPassword() || autologin_sessions);
cp->gui_version = config.gui_version;
cp->force_aes_cbc_ciphersuites = config.force_aes_cbc_ciphersuites; // also used to disable proto V2
cp->extra_peer_info = build_peer_info(config, pcc, autologin_sessions);
cp->frame = frame;
cp->now = &now_;
cp->rng = rng;
cp->prng = prng;
return cp;
}
std::string load_transport_config()
{
// get current transport protocol
const Protocol& transport_protocol = remote_list->current_transport_protocol();
// set transport protocol in Client::ProtoConfig
cp->set_protocol(transport_protocol);
// If we are connecting over a proxy, and TCP protocol is required, but current
// transport protocol is NOT TCP, we will throw an internal error because this
// should have been caught earlier in RemoteList::handle_proto_override.
@ -720,8 +748,9 @@ namespace openvpn {
RandomAPI::Ptr rng;
RandomAPI::Ptr prng;
Frame::Ptr frame;
SSLLib::SSLAPI::Config cc;
Client::ProtoConfig::Ptr cp;
Layer layer;
Client::ProtoConfig::Ptr cp_main;
Client::ProtoConfig::Ptr cp_relay;
RemoteList::Ptr remote_list;
bool server_addr_float;
TransportClientFactory::Ptr transport_factory;

View File

@ -51,6 +51,7 @@
#include <openvpn/common/base64.hpp>
#include <openvpn/tun/client/tunbase.hpp>
#include <openvpn/transport/client/transbase.hpp>
#include <openvpn/transport/client/relay.hpp>
#include <openvpn/options/continuation.hpp>
#include <openvpn/options/sanitize.hpp>
#include <openvpn/client/clievent.hpp>
@ -101,6 +102,7 @@ namespace openvpn {
OPENVPN_SIMPLE_EXCEPTION(session_invalidated);
OPENVPN_SIMPLE_EXCEPTION(authentication_failed);
OPENVPN_SIMPLE_EXCEPTION(inactive_timer_expired);
OPENVPN_SIMPLE_EXCEPTION(relay_event);
OPENVPN_EXCEPTION(proxy_exception);
@ -181,12 +183,27 @@ namespace openvpn {
housekeeping_schedule.init(Time::Duration::binary_ms(512), Time::Duration::binary_ms(1024));
// initialize transport-layer packet handler
transport = transport_factory->new_transport_client_obj(io_context, *this);
transport = transport_factory->new_transport_client_obj(io_context, this);
transport_has_send_queue = transport->transport_has_send_queue();
transport->transport_start();
if (transport_factory->is_relay())
transport_connecting();
else
transport->transport_start();
}
}
TransportClientFactory::Ptr transport_factory_relay()
{
TransportClient::Ptr tc(new TransportRelayFactory::TransportClientNull(transport.get()));
tc.swap(transport);
return new TransportRelayFactory(io_context, std::move(tc), this);
}
void transport_factory_override(TransportClientFactory::Ptr factory)
{
transport_factory = std::move(factory);
}
void send_explicit_exit_notify()
{
if (!halt)
@ -425,6 +442,7 @@ namespace openvpn {
{
try {
OPENVPN_LOG("Connecting to " << server_endpoint_render());
Base::set_protocol(transport->transport_protocol());
Base::start();
Base::flush(true);
set_housekeeping_timer();
@ -526,6 +544,13 @@ namespace openvpn {
// show options
OPENVPN_LOG("OPTIONS:" << std::endl << render_options_sanitized(received_options, Option::RENDER_PASS_FMT|Option::RENDER_NUMBER|Option::RENDER_BRACKET));
// relay servers are not allowed to establish a tunnel with us
if (Base::conf().relay_mode)
{
tun_error(Error::RELAY_ERROR, "tunnel not permitted to relay server");
return;
}
// process "echo" directives
if (echo)
process_echo(received_options);
@ -616,6 +641,26 @@ namespace openvpn {
else
cli_events->add_event(std::move(ev));
}
else if (msg == "RELAY")
{
if (Base::conf().relay_mode)
{
fatal_ = Error::RELAY;
fatal_reason_ = "";
}
else
{
fatal_ = Error::RELAY_ERROR;
fatal_reason_ = "not in relay mode";
}
if (notify_callback)
{
OPENVPN_LOG(Error::name(fatal_) << ' ' << fatal_reason_);
stop(true);
}
else
throw relay_event();
}
}
virtual void tun_pre_tun_config()
@ -674,7 +719,8 @@ namespace openvpn {
// proto base class calls here to get auth credentials
virtual void client_auth(Buffer& buf)
{
if (creds)
// we never send creds to a relay server
if (creds && !Base::conf().relay_mode)
{
OPENVPN_LOG("Creds: " << creds->auth_info());
Base::write_auth_string(creds->get_username(), buf);

View File

@ -30,6 +30,8 @@
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <openvpn/common/exception.hpp>
@ -71,6 +73,13 @@ namespace openvpn {
if (::fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
throw Exception("error setting FD_CLOEXEC on file-descriptor/socket");
}
// set non-block mode on socket
static inline void set_nonblock(const int fd)
{
if (::fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
throw Exception("error setting socket to non-blocking mode");
}
}
}

View File

@ -75,6 +75,8 @@ namespace openvpn {
AUTH_FAILED, // general authentication failure
CLIENT_HALT, // HALT message from server received
CLIENT_RESTART, // RESTART message from server received
RELAY, // RELAY message from server received
RELAY_ERROR, // RELAY error
N_PAUSE, // Number of transitions to Pause state
N_RECONNECT, // Number of reconnections
N_KEY_LIMIT_RENEG, // Number of renegotiations triggered by per-key limits such as data or packet limits
@ -148,6 +150,8 @@ namespace openvpn {
"AUTH_FAILED",
"CLIENT_HALT",
"CLIENT_RESTART",
"RELAY",
"RELAY_ERROR",
"N_PAUSE",
"N_RECONNECT",
"N_KEY_LIMIT_RENEG",

View File

@ -263,7 +263,9 @@ namespace openvpn {
// ca
{
const std::string ca_txt = opt.cat("ca");
std::string ca_txt = opt.cat("ca");
if (lflags & LF_RELAY_MODE)
ca_txt += opt.cat("relay-extra-ca");
load_ca(ca_txt, true);
}
@ -292,16 +294,21 @@ namespace openvpn {
load_dh(dh_txt);
}
// relay mode
std::string relay_prefix;
if (lflags & LF_RELAY_MODE)
relay_prefix = "relay-";
// ns-cert-type
ns_cert_type = NSCert::ns_cert_type(opt);
ns_cert_type = NSCert::ns_cert_type(opt, relay_prefix);
// parse remote-cert-x options
KUParse::remote_cert_tls(opt, ku, eku);
KUParse::remote_cert_ku(opt, ku);
KUParse::remote_cert_eku(opt, eku);
KUParse::remote_cert_tls(opt, relay_prefix, ku, eku);
KUParse::remote_cert_ku(opt, relay_prefix, ku);
KUParse::remote_cert_eku(opt, relay_prefix, eku);
// parse tls-remote
tls_remote = opt.get_optional("tls-remote", 1, 256);
tls_remote = opt.get_optional(relay_prefix + "tls-remote", 1, 256);
// Parse tls-version-min option.
// Assume that presence of SSL_OP_NO_TLSvX macro indicates
@ -314,7 +321,7 @@ namespace openvpn {
# else
const TLSVersion::Type maxver = TLSVersion::V1_0;
# endif
tls_version_min = TLSVersion::parse_tls_version_min(opt, maxver);
tls_version_min = TLSVersion::parse_tls_version_min(opt, relay_prefix, maxver);
}
// unsupported cert checkers

View File

@ -405,6 +405,15 @@ namespace openvpn {
}
return false;
#endif
case 'r':
if (d == "relay-extra-ca")
return true;
if (d == "relay-tls-auth")
{
flags |= F_MAY_INCLUDE_KEY_DIRECTION;
return true;
}
return false;
case 't':
if (d == "tls-auth")
{

View File

@ -324,7 +324,9 @@ namespace openvpn {
// ca
{
const std::string ca_txt = opt.cat("ca");
std::string ca_txt = opt.cat("ca");
if (lflags & LF_RELAY_MODE)
ca_txt += opt.cat("relay-extra-ca");
load_ca(ca_txt, true);
}
@ -360,16 +362,21 @@ namespace openvpn {
load_dh(dh_txt);
}
// relay mode
std::string relay_prefix;
if (lflags & LF_RELAY_MODE)
relay_prefix = "relay-";
// parse ns-cert-type
ns_cert_type = NSCert::ns_cert_type(opt);
ns_cert_type = NSCert::ns_cert_type(opt, relay_prefix);
// parse remote-cert-x options
KUParse::remote_cert_tls(opt, ku, eku);
KUParse::remote_cert_ku(opt, ku);
KUParse::remote_cert_eku(opt, eku);
KUParse::remote_cert_tls(opt, relay_prefix, ku, eku);
KUParse::remote_cert_ku(opt, relay_prefix, ku);
KUParse::remote_cert_eku(opt, relay_prefix, eku);
// parse tls-remote
tls_remote = opt.get_optional("tls-remote", 1, 256);
tls_remote = opt.get_optional(relay_prefix + "tls-remote", 1, 256);
// parse tls-version-min option
{
@ -380,7 +387,7 @@ namespace openvpn {
# else
const TLSVersion::Type maxver = TLSVersion::V1_0;
# endif
tls_version_min = TLSVersion::parse_tls_version_min(opt, maxver);
tls_version_min = TLSVersion::parse_tls_version_min(opt, relay_prefix, maxver);
}
// unsupported cert verification options

View File

@ -103,11 +103,14 @@ namespace openvpn {
// send control channel message
virtual void post_info(BufferPtr&& info) = 0;
virtual void post_cc_msg(BufferPtr&& msg) = 0;
// set fwmark value in client instance
virtual void set_fwmark(const unsigned int fwmark) = 0;
// set up relay to target
virtual void relay(const IP::Addr& target, const int port) = 0;
// get client bandwidth stats
virtual PeerStats stats_poll() = 0;
};

View File

@ -24,6 +24,7 @@
#ifndef OPENVPN_SERVER_SERVPROTO_H
#define OPENVPN_SERVER_SERVPROTO_H
#include <memory>
#include <utility> // for std::move
#include <openvpn/common/size.hpp>
@ -196,6 +197,8 @@ namespace openvpn {
virtual bool transport_recv(BufferAllocated& buf)
{
bool ret = false;
if (!Base::primary_defined())
return false;
try {
OPENVPN_LOG_SERVPROTO("Transport RECV[" << buf.size() << "] " << client_endpoint_render() << ' ' << Base::dump_packet(buf));
@ -287,9 +290,6 @@ namespace openvpn {
TunClientInstanceFactory::Ptr tun_factory_arg)
: Base(factory.clone_proto_config(), factory.stats),
io_context(io_context_arg),
halt(false),
did_push(false),
did_client_halt_restart(false),
housekeeping_timer(io_context_arg),
disconnect_at(Time::infinite()),
stats(factory.stats),
@ -368,13 +368,38 @@ namespace openvpn {
TunLink::send->set_fwmark(fwmark);
}
virtual void relay(const IP::Addr& target, const int port)
{
Base::update_now();
if (TunLink::send && !relay_transition)
{
relay_transition = true;
TunLink::send->relay(target, port);
disconnect_in(Time::Duration::seconds(10)); // not a real disconnect, just complete transition to relay
}
if (Base::primary_defined())
{
BufferPtr buf(new BufferAllocated(64, 0));
buf_append_string(*buf, "RELAY");
buf->null_terminate();
Base::control_send(std::move(buf));
Base::flush(true);
}
set_housekeeping_timer();
}
virtual void push_reply(std::vector<BufferPtr>&& push_msgs,
const std::vector<IP::Route>& rtvec,
const unsigned int initial_fwmark)
{
if (halt)
if (halt || relay_transition || !Base::primary_defined())
return;
Base::update_now();
if (get_tun())
{
Base::init_data_channel();
@ -469,20 +494,24 @@ namespace openvpn {
disconnect_in(Time::Duration::seconds(1));
}
buf->null_terminate();
Base::control_send(std::move(buf));
Base::flush(true);
if (Base::primary_defined())
{
buf->null_terminate();
Base::control_send(std::move(buf));
Base::flush(true);
}
set_housekeeping_timer();
}
virtual void post_info(BufferPtr&& info)
virtual void post_cc_msg(BufferPtr&& msg)
{
if (halt)
if (halt || !Base::primary_defined())
return;
Base::update_now();
info->null_terminate();
Base::control_send(std::move(info));
msg->null_terminate();
Base::control_send(std::move(msg));
Base::flush(true);
set_housekeeping_timer();
}
@ -529,6 +558,8 @@ namespace openvpn {
return bool(TunLink::send);
}
// caller must ensure that update_now() was called before
// and set_housekeeping_timer() called after this method
void disconnect_in(const Time::Duration& dur)
{
disconnect_at = now() + dur;
@ -547,7 +578,12 @@ namespace openvpn {
if (Base::invalidated())
invalidation_error(Base::invalidation_reason());
else if (now() >= disconnect_at)
error("disconnect triggered");
{
if (relay_transition && !did_client_halt_restart)
Base::pre_destroy();
else
error("disconnect triggered");
}
else
set_housekeeping_timer();
}
@ -620,9 +656,11 @@ namespace openvpn {
}
asio::io_context& io_context;
bool halt;
bool did_push;
bool did_client_halt_restart;
bool halt = false;
bool did_push = false;
bool did_client_halt_restart = false;
bool relay_transition = false;
PeerAddr::Ptr peer_addr;

View File

@ -65,10 +65,13 @@ namespace openvpn {
}
}
inline void remote_cert_tls(const OptionList& opt, std::vector<unsigned int>& ku, std::string& eku)
inline void remote_cert_tls(const OptionList& opt,
const std::string& relay_prefix,
std::vector<unsigned int>& ku,
std::string& eku)
{
TLSWebType wt = TLS_WEB_NONE;
const Option* o = opt.get_ptr("remote-cert-tls");
const Option* o = opt.get_ptr(relay_prefix + "remote-cert-tls");
if (o)
{
const std::string ct = o->get_optional(1, 16);
@ -82,11 +85,13 @@ namespace openvpn {
remote_cert_tls(wt, ku, eku);
}
inline void remote_cert_ku(const OptionList& opt, std::vector<unsigned int>& ku)
inline void remote_cert_ku(const OptionList& opt,
const std::string& relay_prefix,
std::vector<unsigned int>& ku)
{
ku.clear();
const Option* o = opt.get_ptr("remote-cert-ku");
const Option* o = opt.get_ptr(relay_prefix + "remote-cert-ku");
if (o)
{
if (o->empty())
@ -107,11 +112,13 @@ namespace openvpn {
}
}
inline void remote_cert_eku(const OptionList& opt, std::string& eku)
inline void remote_cert_eku(const OptionList& opt,
const std::string& relay_prefix,
std::string& eku)
{
eku = "";
const Option* o = opt.get_ptr("remote-cert-eku");
const Option* o = opt.get_ptr(relay_prefix + "remote-cert-eku");
if (o)
eku = o->get(1, 256);
}

View File

@ -38,8 +38,8 @@ namespace openvpn {
SERVER
};
inline Type ns_cert_type(const OptionList& opt) {
const Option* o = opt.get_ptr("ns-cert-type");
inline Type ns_cert_type(const OptionList& opt, const std::string& relay_prefix) {
const Option* o = opt.get_ptr(relay_prefix + "ns-cert-type");
if (o)
{
const std::string ct = o->get_optional(1, 16);

View File

@ -270,6 +270,10 @@ namespace openvpn {
// IV and ProtoSessionID generation.
RandomAPI::Ptr prng;
// If relay mode is enabled, connect to a special OpenVPN
// server that acts as a relay/proxy to a second server.
bool relay_mode = false;
// defer data channel initialization until after client options pull
bool dc_deferred = false;
@ -396,12 +400,12 @@ namespace openvpn {
// tls-auth
{
const Option *o = opt.get_ptr("tls-auth");
const Option *o = opt.get_ptr(relay_prefix("tls-auth"));
if (o)
{
tls_auth_key.parse(o->get(1, 0));
const Option *tad = opt.get_ptr("tls-auth-digest");
const Option *tad = opt.get_ptr(relay_prefix("tls-auth-digest"));
if (tad)
digest = CryptoAlgs::lookup(tad->get(1, 128));
if (digest != CryptoAlgs::NONE)
@ -414,7 +418,7 @@ namespace openvpn {
{
if (key_direction >= -1 && key_direction <= 1)
{
const Option *o = opt.get_ptr("key-direction");
const Option *o = opt.get_ptr(relay_prefix("key-direction"));
if (o)
{
const std::string& dir = o->get(1, 16);
@ -621,12 +625,6 @@ namespace openvpn {
return tls_auth_key.defined() && tls_auth_context;
}
void validate_complete() const
{
if (!protocol.defined())
throw proto_option_error("transport protocol undefined");
}
// generate a string summarizing options that will be
// transmitted to peer for options consistency check
std::string options_string()
@ -694,6 +692,8 @@ namespace openvpn {
out << extra_peer_info->to_string();
if (is_bs64_cipher(dc.cipher()))
out << "IV_BS64DL=1\n"; // indicate support for data limits when using 64-bit block-size ciphers, version 1 (CVE-2016-6329)
if (relay_mode)
out << "IV_RELAY=1\n";
const std::string ret = out.str();
OPENVPN_LOG_PROTO("Peer Info:" << std::endl << ret);
return ret;
@ -757,6 +757,15 @@ namespace openvpn {
}
}
}
std::string relay_prefix(const char *optname) const
{
std::string ret;
if (relay_mode)
ret = "relay-";
ret += optname;
return ret;
}
};
// Used to describe an incoming network packet
@ -840,7 +849,7 @@ namespace openvpn {
// examine key ID
{
const unsigned int kid = key_id_extract(op);
if (kid == proto.primary->key_id())
if (proto.primary && kid == proto.primary->key_id())
flags |= DEFINED;
else if (proto.secondary && kid == proto.secondary->key_id())
flags |= (DEFINED | SECONDARY);
@ -1221,9 +1230,11 @@ namespace openvpn {
crypto_flags(0),
dirty(0),
key_limit_renegotiation_fired(false),
is_reliable(p.config->protocol.is_reliable()),
tlsprf(p.config->tlsprf_factory->new_obj(p.is_server()))
{
// reliable protocol?
set_protocol(proto.config->protocol);
// get key_id from parent
key_id_ = proto.next_key_id();
@ -1240,6 +1251,11 @@ namespace openvpn {
set_event(KEV_NONE, KEV_NEGOTIATE, construct_time + proto.config->handshake_window);
}
void set_protocol(const Protocol& p)
{
is_reliable = p.is_reliable(); // cache is_reliable state locally
}
// need to call only on the initiator side of the connection
void start()
{
@ -2408,9 +2424,6 @@ namespace openvpn {
{
const Config& c = *config;
// validate options
c.validate_complete();
// defer data channel initialization until after client options pull?
dc_deferred = c.dc_deferred;
@ -2463,6 +2476,15 @@ namespace openvpn {
update_last_sent(); // set timer for initial keepalive send
}
void set_protocol(const Protocol& p)
{
config->set_protocol(p);
if (primary)
primary->set_protocol(p);
if (secondary)
secondary->set_protocol(p);
}
// Free up space when parent object has been halted but
// object destruction is not immediately scheduled.
void pre_destroy()
@ -2470,6 +2492,12 @@ namespace openvpn {
reset_all();
}
// Is primary key defined
bool primary_defined()
{
return bool(primary);
}
virtual ~ProtoContext() {}
// return the PacketType of an incoming network packet
@ -2481,6 +2509,8 @@ namespace openvpn {
// start protocol negotiation
void start()
{
if (!primary)
throw proto_error("start: no primary key");
primary->start();
update_last_received(); // set an upper bound on when we expect a response
}
@ -2503,7 +2533,8 @@ namespace openvpn {
if (control_channel || process_events())
{
do {
primary->flush();
if (primary)
primary->flush();
if (secondary)
secondary->flush();
} while (process_events());
@ -2517,7 +2548,8 @@ namespace openvpn {
void housekeeping()
{
// handle control channel retransmissions on primary
primary->retransmit();
if (primary)
primary->retransmit();
// handle control channel retransmissions on secondary
if (secondary)
@ -2537,7 +2569,9 @@ namespace openvpn {
{
if (!invalidated())
{
Time ret = primary->next_retransmit();
Time ret = Time::infinite();
if (primary)
ret.min(primary->next_retransmit());
if (secondary)
ret.min(secondary->next_retransmit());
ret.min(keepalive_xmit);
@ -2588,6 +2622,8 @@ namespace openvpn {
void data_encrypt(BufferAllocated& in_out)
{
//OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " DATA ENCRYPT size=" << in_out.size());
if (!primary)
throw proto_error("data_encrypt: no primary key");
primary->encrypt(in_out);
}
@ -2620,7 +2656,8 @@ namespace openvpn {
// enter disconnected state
void disconnect(const Error::Type reason)
{
primary->invalidate(reason);
if (primary)
primary->invalidate(reason);
if (secondary)
secondary->invalidate(reason);
}
@ -2629,7 +2666,7 @@ namespace openvpn {
// they are disconnecting
void send_explicit_exit_notify()
{
if (is_client() && is_udp())
if (is_client() && is_udp() && primary)
primary->send_explicit_exit_notify();
}
@ -2640,7 +2677,7 @@ namespace openvpn {
}
// can we call data_encrypt or data_decrypt yet?
bool data_channel_ready() const { return primary->data_channel_ready(); }
bool data_channel_ready() const { return primary && primary->data_channel_ready(); }
// total number of SSL/TLS negotiations during lifetime of ProtoContext object
unsigned int negotiations() const { return n_key_ids; }
@ -2649,7 +2686,7 @@ namespace openvpn {
const Time::Duration& slowest_handshake() { return slowest_handshake_; }
// was primary context invalidated by an exception?
bool invalidated() const { return primary->invalidated(); }
bool invalidated() const { return primary && primary->invalidated(); }
// reason for invalidation if invalidated() above returns true
Error::Type invalidation_reason() const { return primary->invalidation_reason(); }
@ -2662,7 +2699,8 @@ namespace openvpn {
dc_deferred = false;
// initialize data channel (crypto & compression)
primary->init_data_channel();
if (primary)
primary->init_data_channel();
if (secondary)
secondary->init_data_channel();
}
@ -2707,7 +2745,7 @@ namespace openvpn {
const DataLimit::Mode cdl_mode,
const DataLimit::State cdl_status)
{
if (key_id == primary->key_id())
if (primary && key_id == primary->key_id())
primary->data_limit_notify(cdl_mode, cdl_status);
else if (secondary && key_id == secondary->key_id())
secondary->data_limit_notify(cdl_mode, cdl_status);
@ -2826,14 +2864,14 @@ namespace openvpn {
const unsigned int flags = type.flags & (PacketType::DEFINED|PacketType::SECONDARY|PacketType::CONTROL);
if (!control)
{
if (flags == (PacketType::DEFINED))
if (flags == (PacketType::DEFINED) && primary)
return *primary;
else if (flags == (PacketType::DEFINED|PacketType::SECONDARY) && secondary)
return *secondary;
}
else
{
if (flags == (PacketType::DEFINED|PacketType::CONTROL))
if (flags == (PacketType::DEFINED|PacketType::CONTROL) && primary)
return *primary;
else if (flags == (PacketType::DEFINED|PacketType::SECONDARY|PacketType::CONTROL) && secondary)
return *secondary;
@ -2850,6 +2888,8 @@ namespace openvpn {
KeyContext& select_control_send_context()
{
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " CONTROL SEND");
if (!primary)
throw proto_error("select_control_send_context: no primary key");
return *primary;
}
@ -2860,7 +2900,7 @@ namespace openvpn {
const Time now = *now_;
// check for keepalive timeouts
if (now >= keepalive_xmit)
if (now >= keepalive_xmit && primary)
{
primary->send_keepalive();
update_last_sent();
@ -2880,7 +2920,7 @@ namespace openvpn {
bool did_work = false;
// primary
if (primary->event_pending())
if (primary && primary->event_pending())
{
process_primary_event();
did_work = true;
@ -2913,8 +2953,10 @@ namespace openvpn {
void promote_secondary_to_primary()
{
primary.swap(secondary);
primary->rekey(CryptoDCInstance::PROMOTE_SECONDARY_TO_PRIMARY);
secondary->prepare_expire();
if (primary)
primary->rekey(CryptoDCInstance::PROMOTE_SECONDARY_TO_PRIMARY);
if (secondary)
secondary->prepare_expire();
OPENVPN_LOG_PROTO_VERBOSE(debug_prefix() << " PROMOTE_SECONDARY_TO_PRIMARY");
}
@ -2965,7 +3007,8 @@ namespace openvpn {
{
case KeyContext::KEV_ACTIVE:
secondary->rekey(CryptoDCInstance::NEW_SECONDARY);
primary->prepare_expire();
if (primary)
primary->prepare_expire();
break;
case KeyContext::KEV_BECOME_PRIMARY:
if (!secondary->invalidated())
@ -2976,7 +3019,8 @@ namespace openvpn {
secondary.reset();
break;
case KeyContext::KEV_RENEGOTIATE_QUEUE:
primary->key_limit_reneg(KeyContext::KEV_RENEGOTIATE_FORCE, secondary->become_primary_time());
if (primary)
primary->key_limit_reneg(KeyContext::KEV_RENEGOTIATE_FORCE, secondary->become_primary_time());
break;
case KeyContext::KEV_NEGOTIATE:
stats->error(Error::HANDSHAKE_TIMEOUT);

View File

@ -87,6 +87,7 @@ namespace openvpn {
enum LoadFlags {
LF_PARSE_MODE = (1<<0),
LF_ALLOW_CLIENT_CERT_NOT_REQUIRED = (1<<1),
LF_RELAY_MODE = (1<<2), // look for "relay-ca" instead of "ca" directive
};
virtual void set_mode(const Mode& mode_arg) = 0;

View File

@ -72,9 +72,11 @@ namespace openvpn {
throw option_error("tls-version-min: unrecognized TLS version");
}
inline Type parse_tls_version_min(const OptionList& opt, const Type max_version)
inline Type parse_tls_version_min(const OptionList& opt,
const std::string& relay_prefix,
const Type max_version)
{
const Option* o = opt.get_ptr("tls-version-min");
const Option* o = opt.get_ptr(relay_prefix + "tls-version-min");
if (o)
{
const std::string ver = o->get_optional(1, 16);

View File

@ -199,7 +199,7 @@ namespace openvpn {
}
virtual TransportClient::Ptr new_transport_client_obj(asio::io_context& io_context,
TransportClientParent& parent);
TransportClientParent* parent);
private:
ClientConfig()
@ -225,7 +225,7 @@ namespace openvpn {
{
if (!config->http_proxy_options)
{
parent.proxy_error(Error::PROXY_ERROR, "http_proxy_options not defined");
parent->proxy_error(Error::PROXY_ERROR, "http_proxy_options not defined");
return;
}
@ -244,7 +244,7 @@ namespace openvpn {
else
{
// resolve it
parent.transport_pre_resolve();
parent->transport_pre_resolve();
resolver.async_resolve(proxy_host, proxy_port,
[self=Ptr(this)](const asio::error_code& error, asio::ip::tcp::resolver::results_type results)
{
@ -307,6 +307,16 @@ namespace openvpn {
return IP::Addr::from_asio(server_endpoint.address());
}
virtual Protocol transport_protocol() const
{
if (server_endpoint.address().is_v4())
return Protocol(Protocol::TCPv4);
else if (server_endpoint.address().is_v6())
return Protocol(Protocol::TCPv6);
else
return Protocol();
}
virtual void stop() { stop_(); }
virtual ~Client() { stop_(); }
@ -326,7 +336,7 @@ namespace openvpn {
Client(asio::io_context& io_context_arg,
ClientConfig* config_arg,
TransportClientParent& parent_arg)
TransportClientParent* parent_arg)
: io_context(io_context_arg),
socket(io_context_arg),
config(config_arg),
@ -341,6 +351,11 @@ namespace openvpn {
{
}
virtual void transport_reparent(TransportClientParent* parent_arg)
{
parent = parent_arg;
}
bool send_const(const Buffer& cbuf)
{
if (impl)
@ -365,7 +380,7 @@ namespace openvpn {
std::ostringstream os;
os << "Transport error on '" << server_host << "' via HTTP proxy " << proxy_host << ':' << proxy_port << " : " << error;
stop();
parent.transport_error(Error::TRANSPORT_ERROR, os.str());
parent->transport_error(Error::TRANSPORT_ERROR, os.str());
}
void proxy_error(const Error::Type fatal_err, const std::string& what)
@ -373,7 +388,7 @@ namespace openvpn {
std::ostringstream os;
os << "on " << proxy_host << ':' << proxy_port << ": " << what;
stop();
parent.proxy_error(fatal_err, os.str());
parent->proxy_error(fatal_err, os.str());
}
bool tcp_read_handler(BufferAllocated& buf) // called by LinkImpl
@ -381,7 +396,7 @@ namespace openvpn {
if (proxy_established)
{
if (!html_skip)
parent.transport_recv(buf);
parent->transport_recv(buf);
else
drain_html(buf); // skip extraneous HTML after header
}
@ -401,7 +416,7 @@ namespace openvpn {
void tcp_write_queue_needs_send() // called by LinkImpl
{
if (proxy_established)
parent.transport_needs_send();
parent->transport_needs_send();
}
void tcp_eof_handler() // called by LinkImpl
@ -483,12 +498,12 @@ namespace openvpn {
void proxy_connected(BufferAllocated& buf, const bool notify_parent)
{
proxy_established = true;
if (parent.transport_is_openvpn_protocol())
if (parent->transport_is_openvpn_protocol())
{
// switch socket from HTTP proxy handshake mode to OpenVPN protocol mode
impl->set_raw_mode(false);
if (notify_parent)
parent.transport_connecting();
parent->transport_connecting();
try {
impl->inject(buf);
}
@ -501,8 +516,8 @@ namespace openvpn {
else
{
if (notify_parent)
parent.transport_connecting();
parent.transport_recv(buf);
parent->transport_connecting();
parent->transport_recv(buf);
}
}
@ -515,9 +530,9 @@ namespace openvpn {
void proxy_half_connected()
{
proxy_established = true;
if (parent.transport_is_openvpn_protocol())
if (parent->transport_is_openvpn_protocol())
impl->set_raw_mode_write(false);
parent.transport_connecting();
parent->transport_connecting();
}
void drain_html(BufferAllocated& buf)
@ -862,7 +877,7 @@ namespace openvpn {
os << "DNS resolve error on '" << proxy_host << "' for TCP (HTTP proxy): " << error.message();
config->stats->error(Error::RESOLVE_ERROR);
stop();
parent.transport_error(Error::UNDEF, os.str());
parent->transport_error(Error::UNDEF, os.str());
}
}
}
@ -891,8 +906,8 @@ namespace openvpn {
{
proxy_remote_list().get_endpoint(server_endpoint);
OPENVPN_LOG("Contacting " << server_endpoint << " via HTTP Proxy");
parent.transport_wait_proxy();
parent.ip_hole_punch(server_endpoint_addr());
parent->transport_wait_proxy();
parent->ip_hole_punch(server_endpoint_addr());
socket.open(server_endpoint.protocol());
#ifdef OPENVPN_PLATFORM_TYPE_UNIX
if (config->socket_protect)
@ -901,7 +916,7 @@ namespace openvpn {
{
config->stats->error(Error::SOCKET_PROTECT_ERROR);
stop();
parent.transport_error(Error::UNDEF, "socket_protect error (HTTP Proxy)");
parent->transport_error(Error::UNDEF, "socket_protect error (HTTP Proxy)");
return;
}
}
@ -920,7 +935,7 @@ namespace openvpn {
{
if (!error)
{
parent.transport_wait();
parent->transport_wait();
impl.reset(new LinkImpl(this,
socket,
0, // send_queue_max_size is unlimited because we regulate size in cliproto.hpp
@ -942,7 +957,7 @@ namespace openvpn {
os << "TCP connect error on '" << proxy_host << ':' << proxy_port << "' (" << server_endpoint << ") for TCP-via-HTTP-proxy session: " << error.message();
config->stats->error(Error::TCP_CONNECT_ERROR);
stop();
parent.transport_error(Error::UNDEF, os.str());
parent->transport_error(Error::UNDEF, os.str());
}
}
}
@ -991,7 +1006,7 @@ namespace openvpn {
asio::io_context& io_context;
asio::ip::tcp::socket socket;
ClientConfig::Ptr config;
TransportClientParent& parent;
TransportClientParent* parent;
LinkImpl::Ptr impl;
asio::ip::tcp::resolver resolver;
LinkImpl::protocol::endpoint server_endpoint;
@ -1011,7 +1026,7 @@ namespace openvpn {
std::unique_ptr<HTTP::HTMLSkip> html_skip;
};
inline TransportClient::Ptr ClientConfig::new_transport_client_obj(asio::io_context& io_context, TransportClientParent& parent)
inline TransportClient::Ptr ClientConfig::new_transport_client_obj(asio::io_context& io_context, TransportClientParent* parent)
{
return TransportClient::Ptr(new Client(io_context, this, parent));
}

View File

@ -0,0 +1,166 @@
// 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-2016 OpenVPN Technologies, 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/>.
// Create a special transport factory that persists an existing
// transport client. This is used to preserve the transport
// socket when other client components are restarted after
// a RELAY message is received from the server.
#ifndef OPENVPN_TRANSPORT_CLIENT_RELAY_H
#define OPENVPN_TRANSPORT_CLIENT_RELAY_H
#include <memory>
#include <openvpn/transport/client/transbase.hpp>
namespace openvpn {
class TransportRelayFactory : public TransportClientFactory
{
public:
TransportRelayFactory(asio::io_context& io_context,
TransportClient::Ptr transport,
TransportClientParent* old_parent)
: io_context_(io_context),
transport_(std::move(transport)),
null_parent_(new NullParent(old_parent))
{
// Temporarily point transport to our null parent
transport_->transport_reparent(null_parent_.get());
}
class TransportClientNull : public TransportClient
{
public:
TransportClientNull(TransportClient* old)
: endpoint_(old->server_endpoint_addr()),
protocol_(old->transport_protocol())
{
old->server_endpoint_info(host_, port_, proto_, ip_addr_);
}
private:
virtual void transport_start() {}
virtual void stop() {}
virtual bool transport_send_const(const Buffer& buf) { return false; }
virtual bool transport_send(BufferAllocated& buf) { return false; }
virtual bool transport_send_queue_empty() { return false; }
virtual bool transport_has_send_queue() { return false; }
virtual unsigned int transport_send_queue_size() { return 0; }
virtual void reset_align_adjust(const size_t align_adjust) {}
virtual void transport_reparent(TransportClientParent* parent) {}
virtual IP::Addr server_endpoint_addr() const
{
return endpoint_;
}
virtual Protocol transport_protocol() const
{
return protocol_;
}
virtual void server_endpoint_info(std::string& host, std::string& port, std::string& proto, std::string& ip_addr) const
{
host = host_;
port = port_;
proto = proto_;
ip_addr = ip_addr_;
}
IP::Addr endpoint_;
Protocol protocol_;
std::string host_;
std::string port_;
std::string proto_;
std::string ip_addr_;
};
private:
class NullParent : public TransportClientParent
{
public:
NullParent(TransportClientParent* old_parent)
: is_openvpn_protocol(old_parent->transport_is_openvpn_protocol())
{
}
private:
virtual void transport_recv(BufferAllocated& buf) {}
virtual void transport_needs_send() {}
virtual void transport_error(const Error::Type fatal_err, const std::string& err_text)
{
OPENVPN_LOG("TransportRelayFactory: Transport Error in null parent: " << Error::name(fatal_err) << " : " << err_text);
}
virtual void proxy_error(const Error::Type fatal_err, const std::string& err_text)
{
OPENVPN_LOG("TransportRelayFactory: Proxy Error in null parent: " << Error::name(fatal_err) << " : " << err_text);
}
virtual void ip_hole_punch(const IP::Addr& addr) {}
// Return true if we are transporting OpenVPN protocol
virtual bool transport_is_openvpn_protocol() { return is_openvpn_protocol; }
// progress notifications
virtual void transport_pre_resolve() {}
virtual void transport_wait_proxy() {}
virtual void transport_wait() {}
virtual void transport_connecting() {}
// Return true if keepalive parameter(s) are enabled.
virtual bool is_keepalive_enabled() const { return false; }
// Disable keepalive for rest of session, but fetch
// the keepalive parameters (in seconds).
virtual void disable_keepalive(unsigned int& keepalive_ping, unsigned int& keepalive_timeout)
{
keepalive_ping = 0;
keepalive_timeout = 0;
}
bool is_openvpn_protocol;
};
virtual TransportClient::Ptr new_transport_client_obj(asio::io_context& io_context,
TransportClientParent* parent) override
{
// io_context MUST stay consistent
if (&io_context != &io_context_)
throw Exception("TransportRelayFactory: inconsistent io_context");
transport_->transport_reparent(parent);
return transport_;
}
virtual bool is_relay() override
{
return true;
}
asio::io_context& io_context_; // only used to verify consistency
TransportClient::Ptr transport_; // the persisted transport
std::unique_ptr<TransportClientParent> null_parent_; // placeholder for TransportClient parent before reparenting
};
}
#endif

View File

@ -58,7 +58,7 @@ namespace openvpn {
}
virtual TransportClient::Ptr new_transport_client_obj(asio::io_context& io_context,
TransportClientParent& parent);
TransportClientParent* parent);
private:
ClientConfig()
@ -88,7 +88,7 @@ namespace openvpn {
}
else
{
parent.transport_pre_resolve();
parent->transport_pre_resolve();
resolver.async_resolve(server_host, server_port,
[self=Ptr(this)](const asio::error_code& error, asio::ip::tcp::resolver::results_type results)
{
@ -150,13 +150,23 @@ namespace openvpn {
return IP::Addr::from_asio(server_endpoint.address());
}
virtual Protocol transport_protocol() const
{
if (server_endpoint.address().is_v4())
return Protocol(Protocol::TCPv4);
else if (server_endpoint.address().is_v6())
return Protocol(Protocol::TCPv6);
else
return Protocol();
}
virtual void stop() { stop_(); }
virtual ~Client() { stop_(); }
private:
Client(asio::io_context& io_context_arg,
ClientConfig* config_arg,
TransportClientParent& parent_arg)
TransportClientParent* parent_arg)
: io_context(io_context_arg),
socket(io_context_arg),
config(config_arg),
@ -166,6 +176,11 @@ namespace openvpn {
{
}
virtual void transport_reparent(TransportClientParent* parent_arg)
{
parent = parent_arg;
}
bool send_const(const Buffer& cbuf)
{
if (impl)
@ -193,13 +208,13 @@ namespace openvpn {
bool tcp_read_handler(BufferAllocated& buf) // called by LinkImpl
{
parent.transport_recv(buf);
parent->transport_recv(buf);
return true;
}
void tcp_write_queue_needs_send() // called by LinkImpl
{
parent.transport_needs_send();
parent->transport_needs_send();
}
void tcp_error_handler(const char *error) // called by LinkImpl
@ -207,7 +222,7 @@ namespace openvpn {
std::ostringstream os;
os << "Transport error on '" << server_host << ": " << error;
stop();
parent.transport_error(Error::TRANSPORT_ERROR, os.str());
parent->transport_error(Error::TRANSPORT_ERROR, os.str());
}
void stop_()
@ -241,7 +256,7 @@ namespace openvpn {
os << "DNS resolve error on '" << server_host << "' for TCP session: " << error.message();
config->stats->error(Error::RESOLVE_ERROR);
stop();
parent.transport_error(Error::UNDEF, os.str());
parent->transport_error(Error::UNDEF, os.str());
}
}
}
@ -251,8 +266,8 @@ namespace openvpn {
{
config->remote_list->get_endpoint(server_endpoint);
OPENVPN_LOG("Contacting " << server_endpoint << " via TCP");
parent.transport_wait();
parent.ip_hole_punch(server_endpoint_addr());
parent->transport_wait();
parent->ip_hole_punch(server_endpoint_addr());
socket.open(server_endpoint.protocol());
#ifdef OPENVPN_PLATFORM_TYPE_UNIX
if (config->socket_protect)
@ -261,7 +276,7 @@ namespace openvpn {
{
config->stats->error(Error::SOCKET_PROTECT_ERROR);
stop();
parent.transport_error(Error::UNDEF, "socket_protect error (TCP)");
parent->transport_error(Error::UNDEF, "socket_protect error (TCP)");
return;
}
}
@ -290,9 +305,9 @@ namespace openvpn {
impl->gremlin_config(config->gremlin_config);
#endif
impl->start();
if (!parent.transport_is_openvpn_protocol())
if (!parent->transport_is_openvpn_protocol())
impl->set_raw_mode(true);
parent.transport_connecting();
parent->transport_connecting();
}
else
{
@ -300,7 +315,7 @@ namespace openvpn {
os << "TCP connect error on '" << server_host << ':' << server_port << "' (" << server_endpoint << "): " << error.message();
config->stats->error(Error::TCP_CONNECT_ERROR);
stop();
parent.transport_error(Error::UNDEF, os.str());
parent->transport_error(Error::UNDEF, os.str());
}
}
}
@ -311,7 +326,7 @@ namespace openvpn {
asio::io_context& io_context;
asio::ip::tcp::socket socket;
ClientConfig::Ptr config;
TransportClientParent& parent;
TransportClientParent* parent;
LinkImpl::Ptr impl;
asio::ip::tcp::resolver resolver;
LinkImpl::protocol::endpoint server_endpoint;
@ -319,7 +334,7 @@ namespace openvpn {
};
inline TransportClient::Ptr ClientConfig::new_transport_client_obj(asio::io_context& io_context,
TransportClientParent& parent)
TransportClientParent* parent)
{
return TransportClient::Ptr(new Client(io_context, this, parent));
}

View File

@ -35,8 +35,10 @@
#include <openvpn/addr/ip.hpp>
#include <openvpn/error/error.hpp>
#include <openvpn/crypto/cryptodc.hpp>
#include <openvpn/transport/protocol.hpp>
namespace openvpn {
struct TransportClientParent;
// Base class for client transport object.
struct TransportClient : public virtual RC<thread_unsafe_refcount>
@ -53,6 +55,8 @@ namespace openvpn {
virtual void reset_align_adjust(const size_t align_adjust) = 0;
virtual IP::Addr server_endpoint_addr() const = 0;
virtual void server_endpoint_info(std::string& host, std::string& port, std::string& proto, std::string& ip_addr) const = 0;
virtual Protocol transport_protocol() const = 0;
virtual void transport_reparent(TransportClientParent* parent) = 0;
};
// Base class for parent of client transport object, used by client transport
@ -94,7 +98,8 @@ namespace openvpn {
typedef RCPtr<TransportClientFactory> Ptr;
virtual TransportClient::Ptr new_transport_client_obj(asio::io_context& io_context,
TransportClientParent& parent) = 0;
TransportClientParent* parent) = 0;
virtual bool is_relay() { return false; }
};
} // namespace openvpn

View File

@ -61,7 +61,7 @@ namespace openvpn {
}
virtual TransportClient::Ptr new_transport_client_obj(asio::io_context& io_context,
TransportClientParent& parent);
TransportClientParent* parent);
private:
ClientConfig()
@ -92,7 +92,7 @@ namespace openvpn {
}
else
{
parent.transport_pre_resolve();
parent->transport_pre_resolve();
resolver.async_resolve(server_host, server_port,
[self=Ptr(this)](const asio::error_code& error, asio::ip::udp::resolver::results_type results)
{
@ -148,13 +148,23 @@ namespace openvpn {
return IP::Addr::from_asio(server_endpoint.address());
}
virtual Protocol transport_protocol() const
{
if (server_endpoint.address().is_v4())
return Protocol(Protocol::UDPv4);
else if (server_endpoint.address().is_v6())
return Protocol(Protocol::UDPv6);
else
return Protocol();
}
virtual void stop() { stop_(); }
virtual ~Client() { stop_(); }
private:
Client(asio::io_context& io_context_arg,
ClientConfig* config_arg,
TransportClientParent& parent_arg)
TransportClientParent* parent_arg)
: io_context(io_context_arg),
socket(io_context_arg),
config(config_arg),
@ -164,6 +174,11 @@ namespace openvpn {
{
}
virtual void transport_reparent(TransportClientParent* parent_arg)
{
parent = parent_arg;
}
bool send(const Buffer& buf)
{
if (impl)
@ -177,7 +192,7 @@ namespace openvpn {
if (err == EADDRNOTAVAIL)
{
stop();
parent.transport_error(Error::TRANSPORT_ERROR, "EADDRNOTAVAIL: Can't assign requested address");
parent->transport_error(Error::TRANSPORT_ERROR, "EADDRNOTAVAIL: Can't assign requested address");
}
#endif
return false;
@ -192,7 +207,7 @@ namespace openvpn {
void udp_read_handler(PacketFrom::SPtr& pfp) // called by LinkImpl
{
if (config->server_addr_float || pfp->sender_endpoint == server_endpoint)
parent.transport_recv(pfp->buf);
parent->transport_recv(pfp->buf);
else
config->stats->error(Error::BAD_SRC_ADDR);
}
@ -227,7 +242,7 @@ namespace openvpn {
os << "DNS resolve error on '" << server_host << "' for UDP session: " << error.message();
config->stats->error(Error::RESOLVE_ERROR);
stop();
parent.transport_error(Error::UNDEF, os.str());
parent->transport_error(Error::UNDEF, os.str());
}
}
}
@ -237,8 +252,8 @@ namespace openvpn {
{
config->remote_list->get_endpoint(server_endpoint);
OPENVPN_LOG("Contacting " << server_endpoint << " via UDP");
parent.transport_wait();
parent.ip_hole_punch(server_endpoint_addr());
parent->transport_wait();
parent->ip_hole_punch(server_endpoint_addr());
socket.open(server_endpoint.protocol());
#ifdef OPENVPN_PLATFORM_TYPE_UNIX
if (config->socket_protect)
@ -247,7 +262,7 @@ namespace openvpn {
{
config->stats->error(Error::SOCKET_PROTECT_ERROR);
stop();
parent.transport_error(Error::UNDEF, "socket_protect error (UDP)");
parent->transport_error(Error::UNDEF, "socket_protect error (UDP)");
return;
}
}
@ -273,7 +288,7 @@ namespace openvpn {
impl->gremlin_config(config->gremlin_config);
#endif
impl->start(config->n_parallel);
parent.transport_connecting();
parent->transport_connecting();
}
else
{
@ -281,7 +296,7 @@ namespace openvpn {
os << "UDP connect error on '" << server_host << ':' << server_port << "' (" << server_endpoint << "): " << error.message();
config->stats->error(Error::UDP_CONNECT_ERROR);
stop();
parent.transport_error(Error::UNDEF, os.str());
parent->transport_error(Error::UNDEF, os.str());
}
}
}
@ -292,7 +307,7 @@ namespace openvpn {
asio::io_context& io_context;
asio::ip::udp::socket socket;
ClientConfig::Ptr config;
TransportClientParent& parent;
TransportClientParent* parent;
LinkImpl::Ptr impl;
asio::ip::udp::resolver resolver;
UDPTransport::AsioEndpoint server_endpoint;
@ -300,7 +315,7 @@ namespace openvpn {
};
inline TransportClient::Ptr ClientConfig::new_transport_client_obj(asio::io_context& io_context,
TransportClientParent& parent)
TransportClientParent* parent)
{
return TransportClient::Ptr(new Client(io_context, this, parent));
}

View File

@ -51,6 +51,7 @@ namespace openvpn {
virtual void start() = 0;
virtual void stop() = 0;
virtual std::string local_endpoint_info() const = 0;
virtual IP::Addr local_endpoint_addr() const = 0;
};
// Factory for server transport object.

View File

@ -143,7 +143,7 @@ namespace openvpn {
return;
}
const int eno = errno;
OPENVPN_THROW(tun_ioctl_error, "failed to open tun device '" << name << "' after trying " << max_units << "units : " << errinfo(eno));
OPENVPN_THROW(tun_ioctl_error, "failed to open tun device '" << name << "' after trying " << max_units << " units : " << errinfo(eno));
}
else
{

View File

@ -72,6 +72,9 @@ namespace openvpn {
// set fwmark
virtual void set_fwmark(const unsigned int fwmark) = 0;
// set up relay to target
virtual void relay(const IP::Addr& target, const int port) = 0;
virtual const std::string& tun_info() const = 0;
};