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

Windows core : added layer 2 support.

We use the DHCP model where the connecting client talks
directly to the server-side DHCP server to configure
the TAP adapter.
This commit is contained in:
James Yonan 2016-04-09 01:04:16 -06:00
parent 0f40e47f9c
commit bd4a994b6d
13 changed files with 905 additions and 90 deletions

View File

@ -383,6 +383,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.session_name = session_name;
tunconf->tun_prop.google_dns_fallback = config.google_dns_fallback;
if (tun_mtu)

90
openvpn/ip/dhcp.hpp Normal file
View File

@ -0,0 +1,90 @@
// 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-2015 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/>.
#ifndef OPENVPN_IP_DHCP_H
#define OPENVPN_IP_DHCP_H
#include <openvpn/ip/eth.hpp>
#include <openvpn/ip/ip.hpp>
#include <openvpn/ip/udp.hpp>
#pragma pack(push)
#pragma pack(1)
namespace openvpn {
struct DHCP {
enum {
/* DHCP Option types */
DHCP_PAD = 0,
DHCP_NETMASK = 1,
DHCP_ROUTER = 3,
DHCP_DNS = 6,
DHCP_MSG_TYPE = 53 /* message type (u8) */,
DHCP_END = 255,
/* DHCP Messages types */
DHCPDISCOVER = 1,
DHCPOFFER = 2,
DHCPREQUEST = 3,
DHCPDECLINE = 4,
DHCPACK = 5,
DHCPNAK = 6,
DHCPRELEASE = 7,
DHCPINFORM = 8,
/* DHCP UDP port numbers */
BOOTPS_PORT = 67,
BOOTPC_PORT = 68,
/* DHCP message op */
BOOTREQUEST = 1,
BOOTREPLY = 2,
};
std::uint8_t op; /* message op */
std::uint8_t htype; /* hardware address type (e.g. '1' = 10Mb Ethernet) */
std::uint8_t hlen; /* hardware address length (e.g. '6' for 10Mb Ethernet) */
std::uint8_t hops; /* client sets to 0, may be used by relay agents */
std::uint32_t xid; /* transaction ID, chosen by client */
std::uint16_t secs; /* seconds since request process began, set by client */
std::uint16_t flags;
std::uint32_t ciaddr; /* client IP address, client sets if known */
std::uint32_t yiaddr; /* 'your' IP address -- server's response to client */
std::uint32_t siaddr; /* server IP address */
std::uint32_t giaddr; /* relay agent IP address */
std::uint8_t chaddr[16]; /* client hardware address */
std::uint8_t sname[64]; /* optional server host name */
std::uint8_t file[128]; /* boot file name */
std::uint32_t magic; /* must be 0x63825363 (network order) */
};
struct DHCPPacket {
EthHeader eth;
IPHeader ip;
UDPHeader udp;
DHCP dhcp;
std::uint8_t options[];
};
}
#pragma pack(pop)
#endif

42
openvpn/ip/eth.hpp Normal file
View File

@ -0,0 +1,42 @@
// 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-2015 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/>.
// Define the Ethernet header
#ifndef OPENVPN_IP_ETH_H
#define OPENVPN_IP_ETH_H
#include <cstdint> // for std::uint32_t, uint16_t, uint8_t
#pragma pack(push)
#pragma pack(1)
namespace openvpn {
struct EthHeader {
std::uint8_t dest_mac[6];
std::uint8_t src_mac[6];
std::uint16_t ethertype;
};
}
#pragma pack(pop)
#endif

83
openvpn/ip/udp.hpp Normal file
View File

@ -0,0 +1,83 @@
// 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-2015 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/>.
// Define the UDP header
#ifndef OPENVPN_IP_UDP_H
#define OPENVPN_IP_UDP_H
#include <openvpn/ip/ip.hpp>
namespace openvpn {
#pragma pack(push)
#pragma pack(1)
struct UDPHeader {
std::uint16_t source;
std::uint16_t dest;
std::uint16_t len;
std::uint16_t check;
};
#pragma pack(pop)
inline std::uint16_t udp_checksum (const std::uint8_t *buf,
const unsigned int len_udp,
const std::uint8_t *src_addr,
const std::uint8_t *dest_addr)
{
std::uint32_t sum = 0;
/* make 16 bit words out of every two adjacent 8 bit words and */
/* calculate the sum of all 16 bit words */
for (unsigned int i = 0; i < len_udp; i += 2)
{
std::uint16_t word16 = ((buf[i] << 8) & 0xFF00) + ((i + 1 < len_udp) ? (buf[i+1] & 0xFF) : 0);
sum += word16;
}
/* add the UDP pseudo header which contains the IP source and destination addresses */
for (unsigned int i = 0; i < 4; i += 2)
{
std::uint16_t word16 =((src_addr[i] << 8) & 0xFF00) + (src_addr[i+1] & 0xFF);
sum += word16;
}
for (unsigned int i = 0; i < 4; i += 2)
{
std::uint16_t word16 =((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i+1] & 0xFF);
sum += word16;
}
/* the protocol number and the length of the UDP packet */
sum += (std::uint16_t)IPHeader::UDP + (std::uint16_t)len_udp;
/* keep only the last 16 bits of the 32 bit calculated sum and add the carries */
while (sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
/* take the one's complement of sum */
return std::uint16_t(~sum);
}
}
#endif

View File

@ -610,14 +610,15 @@ namespace openvpn {
std::ostringstream out;
const bool server = ssl_factory->mode().is_server();
const unsigned int l2extra = (layer() == Layer::OSI_LAYER_2 ? 32 : 0);
out << "V4";
out << ",dev-type " << layer.dev_type();
out << ",link-mtu " << tun_mtu + link_mtu_adjust();
out << ",tun-mtu " << tun_mtu;
out << ",link-mtu " << tun_mtu + link_mtu_adjust() + l2extra;
out << ",tun-mtu " << tun_mtu + l2extra;
out << ",proto " << protocol.str_client(true);
{
const char *compstr = comp_ctx.options_string();
if (compstr)

View File

@ -48,6 +48,13 @@ namespace openvpn {
return false;
}
// Optional callback that indicates OSI layer, should be 2 or 3.
// Defaults to 3.
virtual bool tun_builder_set_layer(int layer)
{
return true;
}
// Callback to set address of remote server
// Never called more than once per tun_builder session.
virtual bool tun_builder_set_remote_address(const std::string& address, bool ipv6)

View File

@ -39,6 +39,7 @@
#include <openvpn/addr/ip.hpp>
#include <openvpn/addr/route.hpp>
#include <openvpn/http/urlparse.hpp>
#include <openvpn/tun/layer.hpp>
#ifdef HAVE_JSONCPP
#include <openvpn/common/jsonhelper.hpp>
@ -550,6 +551,12 @@ namespace openvpn {
return true;
}
virtual bool tun_builder_set_layer(int layer) override
{
this->layer = Layer::from_value(layer);
return true;
}
virtual bool tun_builder_set_mtu(int mtu) override
{
this->mtu = mtu;
@ -604,6 +611,18 @@ namespace openvpn {
return true;
}
void reset_tunnel_addresses()
{
tunnel_addresses.clear();
tunnel_address_index_ipv4 = -1;
tunnel_address_index_ipv6 = -1;
}
void reset_dns_servers()
{
dns_servers.clear();
}
const RouteAddress* vpn_ipv4() const
{
if (tunnel_address_index_ipv4 >= 0)
@ -635,6 +654,7 @@ namespace openvpn {
void validate() const
{
validate_layer("root");
validate_mtu("root");
remote_address.validate("remote_address");
validate_list(tunnel_addresses, "tunnel_addresses");
@ -654,6 +674,7 @@ namespace openvpn {
{
std::ostringstream os;
os << "Session Name: " << session_name << std::endl;
os << "Layer: " << layer.str() << std::endl;
if (mtu)
os << "MTU: " << mtu << std::endl;
os << "Remote Address: " << remote_address.to_string() << std::endl;
@ -684,6 +705,7 @@ namespace openvpn {
Json::Value root(Json::objectValue);
root["session_name"] = Json::Value(session_name);
root["mtu"] = Json::Value(mtu);
root["layer"] = Json::Value(layer.value());
if (remote_address.defined())
root["remote_address"] = remote_address.to_json();
json::from_vector(root, tunnel_addresses, "tunnel_addresses");
@ -712,6 +734,7 @@ namespace openvpn {
TunBuilderCapture::Ptr tbc(new TunBuilderCapture);
json::assert_dict(root, title);
json::to_string(root, tbc->session_name, "session_name", title);
tbc->layer = Layer::from_value(json::get_int(root, "layer", title));
json::to_int(root, tbc->mtu, "mtu", title);
tbc->remote_address.from_json(root["remote_address"], "remote_address");
json::to_vector(root, tbc->tunnel_addresses, "tunnel_addresses", title);
@ -736,6 +759,7 @@ namespace openvpn {
// builder data
std::string session_name;
int mtu = 0;
Layer layer{Layer::OSI_LAYER_3}; // OSI layer
RemoteAddress remote_address; // real address of server
std::vector<RouteAddress> tunnel_addresses; // local tunnel addresses
int tunnel_address_index_ipv4 = -1; // index into tunnel_addresses for IPv4 entry (or -1 if undef)
@ -801,6 +825,11 @@ namespace openvpn {
OPENVPN_THROW_EXCEPTION(title << ".mtu : MTU out of range: " << mtu);
}
void validate_layer(const std::string& title) const
{
if (!layer.defined())
OPENVPN_THROW_EXCEPTION(title << ": layer undefined");
}
};
}

View File

@ -0,0 +1,314 @@
// 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-2015 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/>.
#ifndef OPENVPN_TUN_CLIENT_DHCP_CAPTURE_H
#define OPENVPN_TUN_CLIENT_DHCP_CAPTURE_H
#include <cstring>
#include <openvpn/common/socktypes.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/addr/ipv4.hpp>
#include <openvpn/ip/dhcp.hpp>
#include <openvpn/tun/builder/capture.hpp>
namespace openvpn {
class DHCPCapture
{
public:
// We take a TunBuilderCapture object with previously pushed
// options and augment it with additional options sniffed
// from the DHCP reply.
DHCPCapture(const TunBuilderCapture::Ptr& props_arg)
: props(props_arg)
{
if (props->vpn_ipv4() || props->vpn_ipv4())
OPENVPN_LOG("NOTE: pushed ifconfig directive is ignored in layer 2 mode");
if (!props->dns_servers.empty())
OPENVPN_LOG("NOTE: pushed DNS servers are ignored in layer 2 mode");
reset();
}
// returns true when router addr and DNS servers are captured
bool mod_reply(Buffer& buf)
{
if (buf.size() < sizeof(DHCPPacket))
return false;
DHCPPacket* dhcp = (DHCPPacket*)buf.data();
if (dhcp->ip.protocol == IPHeader::UDP
&& dhcp->udp.source == htons(DHCP::BOOTPS_PORT)
&& dhcp->udp.dest == htons(DHCP::BOOTPC_PORT)
&& dhcp->dhcp.op == DHCP::BOOTREPLY)
{
const unsigned int optlen = buf.size() - sizeof(DHCPPacket);
const int message_type = dhcp_message_type(dhcp, optlen);
if (message_type == DHCP::DHCPACK || message_type == DHCP::DHCPOFFER)
{
/* get host IP address/netmask */
const IPv4::Addr host = IPv4::Addr::from_uint32_net(dhcp->dhcp.yiaddr);
const IPv4::Addr netmask = get_netmask(dhcp, optlen);
/* get the router IP address while padding out all DHCP router options */
const IPv4::Addr router = extract_router(dhcp, optlen);
/* get DNS server addresses */
const std::vector<IPv4::Addr> dns_servers = get_dns(dhcp, optlen);
/* recompute the UDP checksum */
dhcp->udp.check = 0;
dhcp->udp.check = htons(udp_checksum((uint8_t *)&dhcp->udp,
sizeof(UDPHeader) + sizeof(DHCP) + optlen,
(uint8_t *)&dhcp->ip.saddr,
(uint8_t *)&dhcp->ip.daddr));
/* only capture the extracted Router address if DHCPACK */
if (message_type == DHCP::DHCPACK && !configured)
{
bool complete = true;
if (host.unspecified())
{
OPENVPN_LOG("NOTE: failed to obtain host address via DHCP");
complete = false;
}
if (netmask.unspecified())
{
OPENVPN_LOG("NOTE: failed to obtain netmask via DHCP");
complete = false;
}
if (router.unspecified())
{
OPENVPN_LOG("NOTE: failed to obtain router via DHCP");
complete = false;
}
if (complete)
{
reset();
props->tun_builder_add_address(host.to_string(), netmask.prefix_len(), router.to_string(), false, false);
if (dns_servers.empty())
OPENVPN_LOG("NOTE: failed to obtain DNS servers via DHCP");
else
{
for (const auto &a : dns_servers)
props->tun_builder_add_dns_server(a.to_string(), false);
}
}
return configured = complete;
}
}
}
return false;
}
const TunBuilderCapture& get_props() const
{
return *props;
}
private:
void reset()
{
props->reset_tunnel_addresses();
props->reset_dns_servers();
}
static int dhcp_message_type(const DHCPPacket* dhcp, const unsigned int optlen)
{
const std::uint8_t* p = dhcp->options;
for (unsigned int i = 0; i < optlen; ++i)
{
const std::uint8_t type = p[i];
const unsigned int room = optlen - i;
if (type == DHCP::DHCP_END) /* didn't find what we were looking for */
return -1;
else if (type == DHCP::DHCP_PAD) /* no-operation */
;
else if (type == DHCP::DHCP_MSG_TYPE) /* what we are looking for */
{
if (room >= 3)
{
if (p[i+1] == 1) /* option length should be 1 */
return p[i+2]; /* return message type */
}
return -1;
}
else /* some other option */
{
if (room >= 2)
{
const unsigned int len = p[i+1]; /* get option length */
i += (len + 1); /* advance to next option */
}
}
}
return -1;
}
static IPv4::Addr extract_router(DHCPPacket* dhcp, const unsigned int optlen)
{
std::uint8_t* p = dhcp->options;
IPv4::Addr ret = IPv4::Addr::from_zero();
for (unsigned int i = 0; i < optlen; )
{
const std::uint8_t type = p[i];
const unsigned int room = optlen - i;
if (type == DHCP::DHCP_END)
break;
else if (type == DHCP::DHCP_PAD)
++i;
else if (type == DHCP::DHCP_ROUTER)
{
if (room >= 2)
{
const unsigned int len = p[i+1]; /* get option length */
if (len <= (room-2))
{
/* get router IP address */
if (ret.unspecified() && len >= 4 && (len & 3) == 0)
ret = IPv4::Addr::from_bytes_net(p + i + 2);
/* delete the router option */
std::uint8_t *dest = p + i;
const unsigned int owlen = len + 2; /* len of data to overwrite */
std::uint8_t *src = dest + owlen;
std::uint8_t *end = p + optlen;
const int movlen = end - src;
if (movlen > 0)
std::memmove(dest, src, movlen); /* overwrite router option */
std::memset(end - owlen, DHCP::DHCP_PAD, owlen); /* pad tail */
}
else
break;
}
else
break;
}
else /* some other option */
{
if (room >= 2)
{
const unsigned int len = p[i+1]; /* get option length */
i += (len + 2); /* advance to next option */
}
else
break;
}
}
return ret;
}
static IPv4::Addr get_netmask(const DHCPPacket* dhcp, const unsigned int optlen)
{
const std::uint8_t* p = dhcp->options;
IPv4::Addr ret = IPv4::Addr::from_zero();
for (unsigned int i = 0; i < optlen; )
{
const std::uint8_t type = p[i];
const unsigned int room = optlen - i;
if (type == DHCP::DHCP_END)
break;
else if (type == DHCP::DHCP_PAD)
++i;
else if (type == DHCP::DHCP_NETMASK)
{
if (room >= 2)
{
const unsigned int len = p[i+1]; /* get option length */
if (len <= (room-2) && len == 4)
return IPv4::Addr::from_bytes_net(p + i + 2);
else
break;
}
else
break;
}
else /* some other option */
{
if (room >= 2)
{
const unsigned int len = p[i+1]; /* get option length */
i += (len + 2); /* advance to next option */
}
else
break;
}
}
return ret;
}
static std::vector<IPv4::Addr> get_dns(const DHCPPacket* dhcp, const unsigned int optlen)
{
const std::uint8_t* p = dhcp->options;
std::vector<IPv4::Addr> ret;
for (unsigned int i = 0; i < optlen; )
{
const std::uint8_t type = p[i];
const unsigned int room = optlen - i;
if (type == DHCP::DHCP_END)
break;
else if (type == DHCP::DHCP_PAD)
++i;
else if (type == DHCP::DHCP_DNS)
{
if (room >= 2)
{
const unsigned int len = p[i+1]; /* get option length */
if (len <= (room-2) && (len & 3) == 0)
{
/* get DNS addresses */
for (unsigned int j = 0; j < len; j += 4)
ret.push_back(IPv4::Addr::from_bytes_net(p + i + j + 2));
i += (len + 2); /* advance to next option */
}
else
break;
}
else
break;
}
else /* some other option */
{
if (room >= 2)
{
const unsigned int len = p[i+1]; /* get option length */
i += (len + 2); /* advance to next option */
}
else
break;
}
}
return ret;
}
TunBuilderCapture::Ptr props;
bool configured = false;
};
}
#endif

View File

@ -36,6 +36,7 @@
#include <openvpn/client/remotelist.hpp>
#include <openvpn/client/ipverflags.hpp>
#include <openvpn/tun/client/emuexr.hpp>
#include <openvpn/tun/layer.hpp>
namespace openvpn {
class TunProp {
@ -56,11 +57,10 @@ namespace openvpn {
struct Config
{
Config() : mtu(0), google_dns_fallback(false), remote_bypass(false) {}
std::string session_name;
int mtu;
bool google_dns_fallback;
int mtu = 0;
bool google_dns_fallback = false;
Layer layer{Layer::OSI_LAYER_3};
// If remote_bypass is true, obtain cached remote IPs from
// remote_list, and preconfigure exclude route rules for them.
@ -72,7 +72,7 @@ namespace openvpn {
// servers in the remote list after the routing configuration
// for the initial connection has taken effect.
RemoteList::Ptr remote_list;
bool remote_bypass;
bool remote_bypass = false;
};
struct State : public RC<thread_unsafe_refcount>
@ -100,7 +100,15 @@ namespace openvpn {
eer = eer_factory->new_obj();
// do ifconfig
const IP::Addr::VersionMask ip_ver_flags = tun_ifconfig(tb, state, opt);
IP::Addr::VersionMask ip_ver_flags = tun_ifconfig(tb, state, opt);
// with layer 2, either IPv4 or IPv6 might be supported
if (config.layer() == Layer::OSI_LAYER_2)
ip_ver_flags |= (IP::Addr::V4_MASK|IP::Addr::V6_MASK);
// verify IPv4/IPv6
if (!ip_ver_flags)
throw tun_prop_error("one of ifconfig or ifconfig-ipv6 must be specified");
// get IP version and redirect-gateway flags
IPVerFlags ipv(opt, ip_ver_flags);
@ -135,7 +143,7 @@ namespace openvpn {
OPENVPN_LOG("Google DNS fallback enabled");
add_google_dns(tb);
}
else if (stats)
else if (stats && (config.layer() != Layer::OSI_LAYER_2))
stats->error(Error::REROUTE_GW_NO_DNS);
}
@ -144,6 +152,10 @@ namespace openvpn {
server_addr.version() == IP::Addr::V6))
throw tun_prop_error("tun_builder_set_remote_address failed");
// set layer
if (!tb->tun_builder_set_layer(config.layer.value()))
throw tun_prop_error("tun_builder_set_layer failed");
// set MTU
if (config.mtu)
{
@ -175,7 +187,9 @@ namespace openvpn {
return ret;
}
static IP::Addr::VersionMask tun_ifconfig(TunBuilderBase* tb, State* state, const OptionList& opt)
static IP::Addr::VersionMask tun_ifconfig(TunBuilderBase* tb,
State* state,
const OptionList& opt)
{
enum Topology {
NET30,
@ -270,8 +284,6 @@ namespace openvpn {
ip_ver_flags |= IP::Addr::V6_MASK;
}
if (!ip_ver_flags)
throw tun_prop_error("one of ifconfig or ifconfig-ipv6 must be specified");
return ip_ver_flags;
}
}

View File

@ -40,6 +40,11 @@ namespace openvpn {
explicit Layer(const Type t) : type_(t) {}
Type operator()() const { return type_; }
bool defined() const
{
return type_ != NONE;
}
const char *dev_type() const
{
switch (type_)
@ -68,6 +73,21 @@ namespace openvpn {
}
}
int value() const
{
switch (type_)
{
case NONE:
return 0;
case OSI_LAYER_2:
return 2;
case OSI_LAYER_3:
return 3;
default:
throw Exception("Layer: unrecognized layer type");
}
}
static Layer from_str(const std::string& str)
{
if (str == "OSI_LAYER_3")
@ -80,6 +100,18 @@ namespace openvpn {
throw Exception("Layer: unrecognized layer string");
}
static Layer from_value(const int value)
{
if (value == 3)
return Layer(OSI_LAYER_3);
else if (value == 2)
return Layer(OSI_LAYER_2);
else if (value == 0)
return Layer(NONE);
else
throw Exception("Layer: unrecognized layer value");
}
bool operator==(const Layer& other) const
{
return type_ == other.type_;

View File

@ -47,6 +47,12 @@ namespace openvpn {
Stop* stop,
std::ostream& os) = 0;
virtual bool l2_ready(const TunBuilderCapture& pull) = 0;
virtual void l2_finish(const TunBuilderCapture& pull,
Stop* stop,
std::ostream& os) = 0;
virtual void confirm()
{
}

View File

@ -31,8 +31,10 @@
#include <openvpn/common/format.hpp>
#include <openvpn/common/scoped_asio_stream.hpp>
#include <openvpn/common/cleanup.hpp>
#include <openvpn/time/asiotimer.hpp>
#include <openvpn/tun/client/tunbase.hpp>
#include <openvpn/tun/client/tunprop.hpp>
#include <openvpn/tun/client/dhcp_capture.hpp>
#include <openvpn/tun/persist/tunpersist.hpp>
#include <openvpn/tun/persist/tunwrapasio.hpp>
#include <openvpn/tun/tunio.hpp>
@ -120,6 +122,11 @@ namespace openvpn {
if (disconnected)
tun_persist.reset();
}
virtual bool layer_2_supported() const override
{
return true;
}
};
class Client : public TunClient
@ -172,7 +179,7 @@ namespace openvpn {
OPENVPN_LOG("CAPTURED OPTIONS:" << std::endl << po->to_string());
// create new tun setup object
TunWin::SetupBase::Ptr tun_setup(config->new_setup_obj(io_context));
tun_setup = config->new_setup_obj(io_context);
// open/config TAP
HANDLE th;
@ -200,6 +207,10 @@ namespace openvpn {
// assert ownership over TAP device handle
tun_setup->confirm();
// if layer 2, set up to capture DHCP messages over the tunnel
if (config->tun_prop.layer() == Layer::OSI_LAYER_2)
dhcp_capture.reset(new DHCPCapture(po));
}
// configure tun interface packet forwarding
@ -212,8 +223,8 @@ namespace openvpn {
));
impl->start(config->n_parallel);
// signal that we are connected
parent.tun_connected();
if (!dhcp_capture)
parent.tun_connected(); // signal that we are connected
}
catch (const std::exception& e)
{
@ -272,15 +283,23 @@ namespace openvpn {
: io_context(io_context_arg),
config(config_arg),
parent(parent_arg),
halt(false),
state(new TunProp::State())
state(new TunProp::State()),
l2_timer(io_context_arg),
halt(false)
{
}
bool send(Buffer& buf)
{
if (impl)
return impl->write(buf);
{
if (dhcp_capture && dhcp_capture->mod_reply(buf))
{
OPENVPN_LOG("DHCP PROPS:" << std::endl << dhcp_capture->get_props().to_string());
layer_2_schedule_timer(1);
}
return impl->write(buf);
}
else
return false;
#ifdef OPENVPN_DEBUG_TAPWIN
@ -311,6 +330,8 @@ namespace openvpn {
{
halt = true;
l2_timer.cancel();
// stop tun
if (impl)
impl->stop();
@ -336,13 +357,51 @@ namespace openvpn {
Util::tap_process_logging(h);
}
void layer_2_schedule_timer(const unsigned int seconds)
{
l2_timer.expires_at(Time::now() + Time::Duration::seconds(seconds));
l2_timer.async_wait([self=Ptr(this)](const asio::error_code& error)
{
if (!error && !self->halt)
self->layer_2_timer_callback();
});
}
// Normally called once per second by l2_timer while we are waiting
// for layer 2 DHCP handshake to complete.
void layer_2_timer_callback()
{
if (dhcp_capture && tun_setup)
{
if (tun_setup->l2_ready(dhcp_capture->get_props()))
{
std::ostringstream os;
tun_setup->l2_finish(dhcp_capture->get_props(), config->stop, os);
OPENVPN_LOG_STRING(os.str());
parent.tun_connected();
dhcp_capture.reset();
}
else
{
OPENVPN_LOG("L2: Waiting for DHCP handshake...");
layer_2_schedule_timer(1);
}
}
}
asio::io_context& io_context;
TunPersist::Ptr tun_persist; // contains the TAP device HANDLE
ClientConfig::Ptr config;
TunClientParent& parent;
TunImpl::Ptr impl;
bool halt;
TunProp::State::Ptr state;
TunWin::SetupBase::Ptr tun_setup;
// Layer 2 DHCP stuff
std::unique_ptr<DHCPCapture> dhcp_capture;
AsioTimer l2_timer;
bool halt;
};
inline TunClient::Ptr ClientConfig::new_tun_client_obj(asio::io_context& io_context,

View File

@ -27,13 +27,16 @@
#include <string>
#include <sstream>
#include <ostream>
#include <memory>
#include <utility>
#include <thread>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/rc.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/common/size.hpp>
#include <openvpn/common/arraysize.hpp>
#include <openvpn/time/time.hpp>
#include <openvpn/error/excode.hpp>
#include <openvpn/win/scoped_handle.hpp>
#include <openvpn/win/cmd.hpp>
@ -54,6 +57,7 @@ namespace openvpn {
public:
typedef RCPtr<Setup> Ptr;
// Set up the TAP device
virtual HANDLE establish(const TunBuilderCapture& pull,
const std::wstring& openvpn_app_path,
Stop* stop,
@ -87,8 +91,17 @@ namespace openvpn {
remove_cmds.reset(new ActionList());
// populate add/remove lists with actions
adapter_config(th(), openvpn_app_path, tap, pull, *add_cmds, *remove_cmds, os);
switch (pull.layer())
{
case Layer::OSI_LAYER_3:
adapter_config(th(), openvpn_app_path, tap, pull, false, *add_cmds, *remove_cmds, os);
break;
case Layer::OSI_LAYER_2:
adapter_config_l2(th(), openvpn_app_path, tap, pull, *add_cmds, *remove_cmds, os);
break;
default:
throw tun_win_setup("layer undefined");
}
// execute the add actions
add_cmds->execute(os);
@ -96,11 +109,72 @@ namespace openvpn {
// enable the remove actions
remove_cmds->enable_destroy(true);
// if layer 2, save state
if (pull.layer() == Layer::OSI_LAYER_2)
l2_state.reset(new L2State(tap, openvpn_app_path));
return th.release();
}
// In layer 2 mode, return true route_delay seconds after
// the adapter properties matches the data given in pull.
// This method is usually called once per second until it
// returns true.
virtual bool l2_ready(const TunBuilderCapture& pull) override
{
const unsigned int route_delay = 5;
if (l2_state)
{
if (l2_state->props_ready.defined())
{
if (Time::now() >= l2_state->props_ready)
return true;
}
else
{
const Util::IPNetmask4 vpn_addr(pull, "VPN IP");
const Util::IPAdaptersInfo ai;
if (ai.is_up(l2_state->tap.index, vpn_addr))
l2_state->props_ready = Time::now() + Time::Duration::seconds(route_delay);
}
}
return false;
}
// Finish the layer 2 configuration, should be called
// after l2_ready() returns true.
virtual void l2_finish(const TunBuilderCapture& pull,
Stop* stop,
std::ostream& os) override
{
std::unique_ptr<L2State> l2s(std::move(l2_state));
if (l2s)
{
Win::ScopedHANDLE nh;
ActionList::Ptr add_cmds(new ActionList());
adapter_config(nh(), l2s->openvpn_app_path, l2s->tap, pull, true, *add_cmds, *remove_cmds, os);
add_cmds->execute(os);
}
}
virtual void destroy(std::ostream& os) override // defined by DestructorBase
{
// l2_state
l2_state.reset();
// l2_thread
if (l2_thread)
{
try {
l2_thread->join();
}
catch (...)
{
}
l2_thread.reset();
}
// remove_cmds
if (remove_cmds)
{
remove_cmds->destroy(os);
@ -115,12 +189,27 @@ namespace openvpn {
}
private:
struct L2State
{
L2State(const Util::TapNameGuidPair& tap_arg,
const std::wstring& openvpn_app_path_arg)
: tap(tap_arg),
openvpn_app_path(openvpn_app_path_arg)
{
}
Util::TapNameGuidPair tap;
std::wstring openvpn_app_path;
Time props_ready;
};
#if _WIN32_WINNT >= 0x0600
// Configure TAP adapter on Vista and higher
void adapter_config(HANDLE th,
const std::wstring& openvpn_app_path,
const Util::TapNameGuidPair& tap,
const TunBuilderCapture& pull,
const bool l2_post,
ActionList& create,
ActionList& destroy,
std::ostream& os)
@ -139,7 +228,8 @@ namespace openvpn {
const TunBuilderCapture::RouteAddress* local6 = pull.vpn_ipv6();
// set TAP media status to CONNECTED
Util::tap_set_media_status(th, true);
if (!l2_post)
Util::tap_set_media_status(th, true);
// try to delete any stale routes on interface left over from previous session
create.add(new Util::ActionDeleteAllRoutesOnInterface(tap.index));
@ -156,7 +246,7 @@ namespace openvpn {
// Usage: delete address [name=]<string> [[address=]<IPv4 address>]
// [[gateway=]<IPv4 address>|all]
// [[store=]active|persistent]
if (local4)
if (local4 && !l2_post)
{
// Process ifconfig and topology
const std::string netmask = IPv4::Addr::netmask_from_prefix_len(local4->prefix_length).to_string();
@ -193,7 +283,7 @@ namespace openvpn {
// [[store=]active|persistent]
//Usage: delete address [interface=]<string> [address=]<IPv6 address>
// [[store=]active|persistent]
if (local6 && !pull.block_ipv6)
if (local6 && !pull.block_ipv6 && !l2_post)
{
create.add(new WinCmd("netsh interface ipv6 set address " + tap_index_name + ' ' + local6->address + " store=active"));
destroy.add(new WinCmd("netsh interface ipv6 delete address " + tap_index_name + ' ' + local6->address + " store=active"));
@ -351,7 +441,7 @@ namespace openvpn {
continue;
const std::string proto = ds.ipv6 ? "ipv6" : "ip";
const int idx = indices[bool(ds.ipv6)]++;
if (add_netsh_rules)
if (add_netsh_rules && !l2_post)
{
if (idx)
create.add(new WinCmd("netsh interface " + proto + " add " + dns_servers_cmd + " " + tap_index_name + ' ' + ds.address + " " + to_string(idx+1) + validate_cmd));
@ -460,6 +550,7 @@ namespace openvpn {
const std::wstring& openvpn_app_path,
const Util::TapNameGuidPair& tap,
const TunBuilderCapture& pull,
const bool l2_post,
ActionList& create,
ActionList& destroy,
std::ostream& os)
@ -473,74 +564,80 @@ namespace openvpn {
// set local4 to point to IPv4 route configurations
const TunBuilderCapture::RouteAddress* local4 = pull.vpn_ipv4();
// Make sure the TAP adapter is set for DHCP
{
const Util::IPAdaptersInfo ai;
if (!ai.is_dhcp_enabled(tap.index))
{
os << "TAP: DHCP is disabled, attempting to enable" << std::endl;
ActionList::Ptr cmds(new ActionList());
cmds->add(new Util::ActionEnableDHCP(tap));
cmds->execute(os);
}
}
// Set IPv4 Interface
if (local4)
// This section skipped on layer 2 post-config
if (!l2_post)
{
// Process ifconfig and topology
const std::string netmask = IPv4::Addr::netmask_from_prefix_len(local4->prefix_length).to_string();
const IP::Addr localaddr = IP::Addr::from_string(local4->address);
if (local4->net30)
Util::tap_configure_topology_net30(th, localaddr, local4->prefix_length);
else
Util::tap_configure_topology_subnet(th, localaddr, local4->prefix_length);
// Make sure the TAP adapter is set for DHCP
{
const Util::IPAdaptersInfo ai;
if (!ai.is_dhcp_enabled(tap.index))
{
os << "TAP: DHCP is disabled, attempting to enable" << std::endl;
ActionList::Ptr cmds(new ActionList());
cmds->add(new Util::ActionEnableDHCP(tap));
cmds->execute(os);
}
}
// Set IPv4 Interface
if (local4)
{
// Process ifconfig and topology
const std::string netmask = IPv4::Addr::netmask_from_prefix_len(local4->prefix_length).to_string();
const IP::Addr localaddr = IP::Addr::from_string(local4->address);
if (local4->net30)
Util::tap_configure_topology_net30(th, localaddr, local4->prefix_length);
else
Util::tap_configure_topology_subnet(th, localaddr, local4->prefix_length);
}
// On pre-Vista, set up TAP adapter DHCP masquerade for
// configuring adapter properties.
{
os << "TAP: configure DHCP masquerade" << std::endl;
Util::TAPDHCPMasquerade dhmasq;
dhmasq.init_from_capture(pull);
dhmasq.ioctl(th);
}
// set TAP media status to CONNECTED
Util::tap_set_media_status(th, true);
// ARP
Util::flush_arp(tap.index, os);
// DHCP release/renew
{
const Util::InterfaceInfoList ii;
Util::dhcp_release(ii, tap.index, os);
Util::dhcp_renew(ii, tap.index, os);
}
// Wait for TAP adapter to come up
{
bool succeed = false;
const Util::IPNetmask4 vpn_addr(pull, "VPN IP");
for (int i = 1; i <= 30; ++i)
{
os << '[' << i << "] waiting for TAP adapter to receive DHCP settings..." << std::endl;
const Util::IPAdaptersInfo ai;
if (ai.is_up(tap.index, vpn_addr))
{
succeed = true;
break;
}
::Sleep(1000);
}
if (!succeed)
throw tun_win_setup("TAP adapter DHCP handshake failed");
}
// Pre route-add sleep
os << "Sleeping 5 seconds prior to adding routes..." << std::endl;
::Sleep(5000);
}
// On pre-Vista, set up TAP adapter DHCP masquerade for
// configuring adapter properties.
{
os << "TAP: configure DHCP masquerade" << std::endl;
Util::TAPDHCPMasquerade dhmasq;
dhmasq.init_from_capture(pull);
dhmasq.ioctl(th);
}
// set TAP media status to CONNECTED
Util::tap_set_media_status(th, true);
// ARP
Util::flush_arp(tap.index, os);
// DHCP release/renew
{
const Util::InterfaceInfoList ii;
Util::dhcp_release(ii, tap.index, os);
Util::dhcp_renew(ii, tap.index, os);
}
// Wait for TAP adapter to come up
{
bool succeed = false;
const Util::IPNetmask4 vpn_addr(pull, "VPN IP");
for (int i = 1; i <= 30; ++i)
{
os << '[' << i << "] waiting for TAP adapter to receive DHCP settings..." << std::endl;
const Util::IPAdaptersInfo ai;
if (ai.is_up(tap.index, vpn_addr))
{
succeed = true;
break;
}
::Sleep(1000);
}
if (!succeed)
throw tun_win_setup("TAP adapter DHCP handshake failed");
}
// Process routes
os << "Sleeping 5 seconds prior to adding routes..." << std::endl;
::Sleep(5000);
for (auto &route : pull.add_routes)
{
if (!route.ipv6)
@ -605,10 +702,52 @@ namespace openvpn {
}
#endif
void adapter_config_l2(HANDLE th,
const std::wstring& openvpn_app_path,
const Util::TapNameGuidPair& tap,
const TunBuilderCapture& pull,
ActionList& create,
ActionList& destroy,
std::ostream& os)
{
// Make sure the TAP adapter is set for DHCP
{
const Util::IPAdaptersInfo ai;
if (!ai.is_dhcp_enabled(tap.index))
{
os << "TAP: DHCP is disabled, attempting to enable" << std::endl;
ActionList::Ptr cmds(new ActionList());
cmds->add(new Util::ActionEnableDHCP(tap));
cmds->execute(os);
}
}
// set TAP media status to CONNECTED
Util::tap_set_media_status(th, true);
// ARP
Util::flush_arp(tap.index, os);
// We must do DHCP release/renew in a background thread
// so the foreground can forward the DHCP negotiation packets
// over the tunnel.
l2_thread.reset(new std::thread([this, logwrap=Log::Context::Wrapper(), tap]() {
Log::Context logctx(logwrap);
std::ostringstream os;
const Util::InterfaceInfoList ii;
Util::dhcp_release(ii, tap.index, os);
Util::dhcp_renew(ii, tap.index, os);
OPENVPN_LOG_STRING(os.str());
}));
}
#if _WIN32_WINNT >= 0x0600 // Vista+
TunWin::WFPContext::Ptr wfp{new TunWin::WFPContext};
#endif
std::unique_ptr<std::thread> l2_thread;
std::unique_ptr<L2State> l2_state;
ActionList::Ptr remove_cmds;
};
}