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

[OVPN3-354] tun linux: support for round-robin DNS and redirect gw

When profile contains several remotes or single remote which
is resolved into multiple IP addresses AND all traffic is redirected
to the VPN, client will reconnect to the next remote if connection
is broken. Since all traffic is redirected to VPN, except traffic to
current remote, reconnect fails.

Currently this problem is solved by creating bypass routes
to all remotes before establishing connection, so that reconnect
won't go via broken VPN. This solution is sub-optimal, since
it leaks traffic to other remotes.

This patch implements a better approach. Before connecting to
remote, we create a bypass route just for this remote. On reconnect
we replace an old route with a new one for the new remote.
We piggyback on socket_protect() method of OpenVPNClient
which is called before opening connection to remote.

Connection to a new remote usually means a new IP address etc,
so to prevent traffic leakage we first create a new tun interface,
set up routes and then remove old routes and tear down old tun interface.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
This commit is contained in:
Lev Stipakov 2019-04-19 10:46:24 +03:00
parent c9315c7dc1
commit 8a502f3b61
6 changed files with 112 additions and 27 deletions

View File

@ -721,7 +721,7 @@ err:
const std::string& iface, const uint32_t table,
const int metric)
{
return sitnl_route_set(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_REPLACE, iface,
return sitnl_route_set(RTM_NEWROUTE, NLM_F_CREATE, iface,
route, gw,
(enum rt_class_t)(!table ? RT_TABLE_MAIN : table),
metric, RT_SCOPE_UNIVERSE, RTPROT_BOOT, RTN_UNICAST);

View File

@ -196,6 +196,7 @@ namespace openvpn {
tsconf.layer = config->tun_prop.layer;
tsconf.dev_name = config->dev_name;
tsconf.txqueuelen = config->txqueuelen;
tsconf.add_bypass_routes_on_establish = true;
// open/config tun
{

View File

@ -88,7 +88,7 @@ namespace openvpn {
add->argv.push_back("/sbin/ip");
add->argv.push_back("-6");
add->argv.push_back("route");
add->argv.push_back("add");
add->argv.push_back("prepend");
add->argv.push_back(net.to_string() + '/' + openvpn::to_string(prefix_len));
add->argv.push_back("via");
add->argv.push_back(gateway_str);
@ -121,10 +121,15 @@ namespace openvpn {
add->argv.push_back("/sbin/ip");
add->argv.push_back("-4");
add->argv.push_back("route");
add->argv.push_back("add");
add->argv.push_back("prepend");
add->argv.push_back(net.to_string() + '/' + openvpn::to_string(prefix_len));
add->argv.push_back("via");
add->argv.push_back(gateway_str);
if (!dev.empty())
{
add->argv.push_back("dev");
add->argv.push_back(dev);
}
create = add;
// for the destroy command, copy the add command but replace "add" with "delete"
@ -248,7 +253,8 @@ namespace openvpn {
const TunBuilderCapture& pull,
std::vector<IP::Route>* rtvec,
ActionList& create,
ActionList& destroy)
ActionList& destroy,
bool add_bypass_routes = true)
{
const LinuxGW46 gw(true);
@ -301,7 +307,7 @@ namespace openvpn {
if (pull.reroute_gw.ipv4)
{
// add bypass route
if (!pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL))
if (add_bypass_routes && !pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL) && gw.v4.defined())
add_del_route(pull.remote_address.address, 32, gw.v4.addr().to_string(), gw.v4.dev(), R_ADD_SYS, rtvec, create, destroy);
add_del_route("0.0.0.0", 1, local4->gateway, iface_name, R_ADD_ALL, rtvec, create, destroy);
@ -312,7 +318,7 @@ namespace openvpn {
if (pull.reroute_gw.ipv6 && !pull.block_ipv6)
{
// add bypass route
if (pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL))
if (add_bypass_routes && pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL) && gw.v4.defined())
add_del_route(pull.remote_address.address, 128, gw.v6.addr().to_string(), gw.v6.dev(), R_ADD_SYS|R_IPv6, rtvec, create, destroy);
add_del_route("0000::", 1, local6->gateway, iface_name, R_ADD_ALL|R_IPv6, rtvec, create, destroy);
@ -323,6 +329,22 @@ namespace openvpn {
// fixme -- Handle pushed DNS servers
}
static inline void add_bypass_route(const std::string& tun_iface_name,
const std::string& address,
bool ipv6,
std::vector<IP::Route>* rtvec,
ActionList& create,
ActionList& destroy)
{
LinuxGW46 gw(true);
if (!ipv6 && gw.v4.defined())
add_del_route(address, 32, gw.v4.addr().to_string(), gw.dev(), R_ADD_SYS, rtvec, create, destroy);
if (ipv6 && gw.v6.defined())
add_del_route(address, 128, gw.v6.addr().to_string(), gw.dev(), R_ADD_SYS, rtvec, create, destroy);
}
};
}
} // namespace openvpn

View File

@ -599,13 +599,12 @@ namespace openvpn {
struct TunMethods
{
static inline void tun_config(const std::string& iface_name,
const TunBuilderCapture& pull,
std::vector<IP::Route>* rtvec,
ActionList& create,
ActionList& destroy)
const TunBuilderCapture& pull,
std::vector<IP::Route>* rtvec,
ActionList& create,
ActionList& destroy,
bool add_bypass_routes)
{
const LinuxGW46Netlink gw("", iface_name);
// set local4 and local6 to point to IPv4/6 route configurations
const TunBuilderCapture::RouteAddress* local4 = pull.vpn_ipv4();
const TunBuilderCapture::RouteAddress* local6 = pull.vpn_ipv6();
@ -634,7 +633,10 @@ namespace openvpn {
}
// Process exclude routes
if (!pull.exclude_routes.empty())
{
LinuxGW46Netlink gw(iface_name);
for (const auto &route : pull.exclude_routes)
{
if (route.ipv6)
@ -655,8 +657,8 @@ namespace openvpn {
if (pull.reroute_gw.ipv4)
{
// add bypass route
if (!pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL))
add_del_route(pull.remote_address.address, 32, gw.v4.addr().to_string(), gw.v4.dev(), R_ADD_SYS, rtvec, create, destroy);
if (add_bypass_routes && !pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL))
add_bypass_route(iface_name, pull.remote_address.address, false, rtvec, create, destroy);
add_del_route("0.0.0.0", 1, local4->gateway, iface_name, R_ADD_ALL, rtvec, create, destroy);
add_del_route("128.0.0.0", 1, local4->gateway, iface_name, R_ADD_ALL, rtvec, create, destroy);
@ -666,8 +668,8 @@ namespace openvpn {
if (pull.reroute_gw.ipv6 && !pull.block_ipv6)
{
// add bypass route
if (pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL))
add_del_route(pull.remote_address.address, 128, gw.v6.addr().to_string(), gw.v6.dev(), R_ADD_SYS|R_IPv6, rtvec, create, destroy);
if (add_bypass_routes && pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL))
add_bypass_route(iface_name, pull.remote_address.address, true, rtvec, create, destroy);
add_del_route("0000::", 1, local6->gateway, iface_name, R_ADD_ALL|R_IPv6, rtvec, create, destroy);
add_del_route("8000::", 1, local6->gateway, iface_name, R_ADD_ALL|R_IPv6, rtvec, create, destroy);
@ -677,6 +679,22 @@ namespace openvpn {
// fixme -- Handle pushed DNS servers
}
static inline void add_bypass_route(const std::string& tun_iface_name,
const std::string& address,
bool ipv6,
std::vector<IP::Route>* rtvec,
ActionList& create,
ActionList& destroy)
{
LinuxGW46Netlink gw(tun_iface_name, address);
if (!ipv6 && gw.v4.defined())
add_del_route(address, 32, gw.v4.addr().to_string(), gw.dev(), R_ADD_SYS, rtvec, create, destroy);
if (ipv6 && gw.v6.defined())
add_del_route(address, 128, gw.v6.addr().to_string(), gw.dev(), R_ADD_SYS, rtvec, create, destroy);
}
};
}
} // namespace openvpn

View File

@ -69,6 +69,7 @@ namespace openvpn {
Layer layer; // OSI layer
std::string dev_name;
int txqueuelen;
bool add_bypass_routes_on_establish; // required when not using tunbuilder
#ifdef HAVE_JSON
virtual Json::Value to_json() override
@ -92,17 +93,39 @@ namespace openvpn {
#endif
};
virtual void destroy(std::ostream &os) override
void destroy(std::ostream &os) override
{
// remove added routes
if (remove_cmds)
remove_cmds->execute(std::cout);
remove_cmds->execute(os);
// remove bypass route
remove_cmds_bypass_gw->execute(os);
}
virtual int establish(const TunBuilderCapture& pull, // defined by TunBuilderSetup::Base
TunBuilderSetup::Config* config,
Stop* stop,
std::ostream& os) override
bool add_bypass_route(const std::string& address,
bool ipv6,
std::ostream& os)
{
// nothing to do if we reconnect to the same gateway
if (connected_gw == address)
return true;
// remove previous bypass route
remove_cmds_bypass_gw->execute(os);
remove_cmds_bypass_gw->clear();
ActionList::Ptr add_cmds = new ActionList();
TUNMETHODS::add_bypass_route(tun_iface_name, address, ipv6, nullptr, *add_cmds, *remove_cmds_bypass_gw);
// add gateway bypass route
add_cmds->execute(os);
return true;
}
int establish(const TunBuilderCapture& pull, // defined by TunBuilderSetup::Base
TunBuilderSetup::Config* config,
Stop* stop,
std::ostream& os) override
{
// get configuration
Config *conf = dynamic_cast<Config *>(config);
@ -149,15 +172,22 @@ namespace openvpn {
}
conf->iface_name = ifr.ifr_name;
tun_iface_name = ifr.ifr_name;
ActionList::Ptr add_cmds = new ActionList();
remove_cmds.reset(new ActionListReversed()); // remove commands executed in reversed order
ActionList::Ptr remove_cmds_new = new ActionListReversed();
// configure tun properties
TUNMETHODS::tun_config(ifr.ifr_name, pull, nullptr, *add_cmds, *remove_cmds);
TUNMETHODS::tun_config(ifr.ifr_name, pull, nullptr, *add_cmds, *remove_cmds_new, conf->add_bypass_routes_on_establish);
// execute commands to bring up interface
add_cmds->execute(std::cout);
add_cmds->execute(os);
// tear down old routes
remove_cmds->execute(os);
std::swap(remove_cmds, remove_cmds_new);
connected_gw = pull.remote_address.to_string();
return fd.release();
}
@ -193,7 +223,12 @@ namespace openvpn {
}
}
ActionListReversed::Ptr remove_cmds;
ActionList::Ptr remove_cmds_bypass_gw = new ActionList();
ActionListReversed::Ptr remove_cmds = new ActionListReversed();
std::string connected_gw;
std::string tun_iface_name; // used to skip tun-based default gw when add bypass route
};
}
} // namespace openvpn

View File

@ -64,6 +64,7 @@ namespace openvpn {
std::string iface_name;
Layer layer; // OSI layer
bool tun_prefix = false;
bool add_bypass_routes_on_establish = false;
#ifdef HAVE_JSON
virtual Json::Value to_json() override
@ -85,6 +86,14 @@ namespace openvpn {
#endif
};
bool add_bypass_route(const std::string& address,
bool ipv6,
std::ostream& os)
{
// not yet implemented
return true;
}
virtual int establish(const TunBuilderCapture& pull, // defined by TunBuilderSetup::Base
TunBuilderSetup::Config* config,
Stop* stop,