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:
parent
c9315c7dc1
commit
8a502f3b61
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user