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

win: improve "add bypass route" logic

When adding bypass route to remote we always use
default gateway. This doesn't work when remote is not
reachable via default gateway (local network,
custom route - OVPN3-653).

Implement "get best gateway" logic by traversing routing
table and find gateway with longest prefix match and
highest metric.

In case of seamless tunnel and redirect-gw "get best gateway"
will return VPN gateway when adding bypass route during reconnect
to another remote. VPN tunnel is likely broken at this point
and bypass route via VPN make reconnect impossible.

Fix that by storing VPN interface index and, when finding best gateway,
filter routes which use VPN interface.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
(cherry picked from commit e8030c2a421390a10506ec5dbfc6034f949aaf07)
This commit is contained in:
Lev Stipakov 2020-09-07 16:04:13 +03:00
parent 242cdad9c9
commit 7910b5dd7e
3 changed files with 124 additions and 18 deletions

View File

@ -41,7 +41,7 @@ namespace openvpn {
inline std::string get_hwaddr()
{
#if defined(OPENVPN_PLATFORM_WIN) && !defined(OPENVPN_PLATFORM_UWP)
const TunWin::Util::DefaultGateway dg;
const TunWin::Util::BestGateway dg;
if (dg.defined())
{
const TunWin::Util::IPAdaptersInfo ai_list;

View File

@ -42,6 +42,7 @@
#include <openvpn/tun/proxy.hpp>
#include <openvpn/tun/win/tunutil.hpp>
#include <openvpn/tun/win/winproxy.hpp>
#include <openvpn/tun/win/tunutil.hpp>
#include <openvpn/tun/win/client/setupbase.hpp>
#include <openvpn/win/scoped_handle.hpp>
#include <openvpn/win/cmd.hpp>
@ -90,6 +91,7 @@ namespace openvpn {
Util::TapNameGuidPair tap;
Win::ScopedHANDLE th(Util::tap_open(guids, path_opened, tap, wintun));
const std::string msg = "Open TAP device \"" + tap.name + "\" PATH=\"" + path_opened + '\"';
vpn_interface_index_ = tap.index;
if (!th.defined())
{
@ -203,6 +205,8 @@ namespace openvpn {
}
delete_route_timer.cancel();
vpn_interface_index_ = DWORD(-1);
}
virtual ~Setup()
@ -211,17 +215,28 @@ namespace openvpn {
destroy(os);
}
static void add_bypass_route(const std::string& route,
DWORD vpn_interface_index() const
{
return vpn_interface_index_;
}
static void add_bypass_route(const Util::BestGateway& gw,
const std::string& route,
bool ipv6,
ActionList& add_cmds,
ActionList& remove_cmds_bypass_gw)
{
const Util::DefaultGateway gw;
if (!ipv6)
{
add_cmds.add(new WinCmd("netsh interface ip add route " + route + "/32 " + to_string(gw.interface_index()) + ' ' + gw.gateway_address() + " store=active"));
remove_cmds_bypass_gw.add(new WinCmd("netsh interface ip delete route " + route + "/32 " + to_string(gw.interface_index()) + ' ' + gw.gateway_address() + " store=active"));
if (!gw.local_route())
{
add_cmds.add(new WinCmd("netsh interface ip add route " + route + "/32 " + to_string(gw.interface_index()) + ' ' + gw.gateway_address() + " store=active"));
remove_cmds_bypass_gw.add(new WinCmd("netsh interface ip delete route " + route + "/32 " + to_string(gw.interface_index()) + ' ' + gw.gateway_address() + " store=active"));
}
else
{
OPENVPN_LOG("Skip bypass route to " << route << ", route is local");
}
}
}
@ -318,9 +333,6 @@ namespace openvpn {
// special IPv6 next-hop recognized by TAP driver (magic)
const std::string ipv6_next_hop = "fe80::8";
// get default gateway
const Util::DefaultGateway gw;
// 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();
@ -469,6 +481,7 @@ namespace openvpn {
// Process exclude routes
if (!pull.exclude_routes.empty())
{
const Util::BestGateway gw;
if (gw.defined())
{
bool ipv6_error = false;
@ -495,16 +508,18 @@ namespace openvpn {
// Process IPv4 redirect-gateway
if (pull.reroute_gw.ipv4)
{
// get default gateway
const Util::BestGateway gw{ pull.remote_address.address, tap.index };
// add server bypass route
if (gw.defined())
if (gw.defined() && !gw.local_route())
{
if (!pull.remote_address.ipv6 && !(pull.reroute_gw.flags & RedirectGatewayFlags::RG_LOCAL))
{
create.add(new WinCmd("netsh interface ip add route " + pull.remote_address.address + "/32 " + to_string(gw.interface_index()) + ' ' + gw.gateway_address() + " store=active"));
destroy.add(new WinCmd("netsh interface ip delete route " + pull.remote_address.address + "/32 " + to_string(gw.interface_index()) + ' ' + gw.gateway_address() + " store=active"));
add_bypass_route(gw, pull.remote_address.address, false, create, destroy);
}
}
else
else if (!gw.defined())
throw tun_win_setup("redirect-gateway error: cannot detect default gateway");
create.add(new WinCmd("netsh interface ip add route 0.0.0.0/1 " + tap_index_name + ' ' + local4->gateway + " store=active"));
@ -919,6 +934,7 @@ namespace openvpn {
std::unique_ptr<std::thread> l2_thread;
std::unique_ptr<L2State> l2_state;
DWORD vpn_interface_index_ = DWORD(-1);
ActionList::Ptr remove_cmds;
AsioTimer delete_route_timer;

View File

@ -1069,12 +1069,13 @@ namespace openvpn {
}
#endif
// Get the current default gateway
class DefaultGateway
class BestGateway
{
public:
DefaultGateway()
: index(DWORD(-1))
/**
* Construct object which represents default gateway
*/
BestGateway()
{
std::unique_ptr<const MIB_IPFORWARDTABLE> rt(windows_routing_table());
if (rt)
@ -1095,6 +1096,85 @@ namespace openvpn {
}
}
/**
* Construct object which represents best gateway to given
* destination, excluding gateway on VPN interface. Gateway is chosen
* first by the longest prefix match and then by metric. If destination
* is in local network, no gateway is selected and "local_route" flag is set.
*
* @param dest destination IPv4 address
* @param vpn_interface_index index of VPN interface which is excluded from gateway selection
*/
BestGateway(const std::string& dest, DWORD vpn_interface_index)
{
DWORD dest_addr;
auto res = inet_pton(AF_INET, dest.c_str(), &dest_addr);
switch (res)
{
case -1:
OPENVPN_THROW(tun_win_util, "GetBestGateway: error converting IPv4 address " << dest << " to int: " << ::WSAGetLastError());
case 0:
OPENVPN_THROW(tun_win_util, "GetBestGateway: " << dest << " is not a valid IPv4 address");
}
{
MIB_IPFORWARDROW row;
DWORD res2 = GetBestRoute(dest_addr, 0, &row);
if (res2 != NO_ERROR)
{
OPENVPN_THROW(tun_win_util, "GetBestGateway: error retrieving the best route for " << dest << ": " << res2);
}
if (row.dwForwardType == MIB_IPROUTE_TYPE_DIRECT)
{
local_route_ = true;
return;
}
}
std::unique_ptr<const MIB_IPFORWARDTABLE> rt(windows_routing_table());
if (rt)
{
const MIB_IPFORWARDROW* gw = nullptr;
for (size_t i = 0; i < rt->dwNumEntries; ++i)
{
const MIB_IPFORWARDROW* row = &rt->table[i];
// does route match?
if ((dest_addr & row->dwForwardMask) == (row->dwForwardDest & row->dwForwardMask))
{
// skip gateway on VPN interface
if ((vpn_interface_index != DWORD(-1)) && (row->dwForwardIfIndex == vpn_interface_index))
{
OPENVPN_LOG("GetBestGateway: skip gateway " <<
IPv4::Addr::from_uint32(ntohl(gw->dwForwardNextHop)).to_string() <<
" on VPN interface " << vpn_interface_index);
continue;
}
if (!gw)
{
gw = row;
continue;
}
auto cur_prefix = IPv4::Addr::prefix_len_32(ntohl(gw->dwForwardMask));
auto new_prefix = IPv4::Addr::prefix_len_32(ntohl(row->dwForwardMask));
auto new_metric_is_higher = row->dwForwardMetric1 > gw->dwForwardMetric1;
if ((new_prefix > cur_prefix) || ((new_prefix == cur_prefix) && (new_metric_is_higher)))
gw = row;
}
}
if (gw)
{
index = gw->dwForwardIfIndex;
addr = IPv4::Addr::from_uint32(ntohl(gw->dwForwardNextHop)).to_string();
OPENVPN_LOG("GetBestGateway: selected gateway " << addr << " on adapter " << index << " for destination " << dest);
}
}
}
bool defined() const
{
return index != DWORD(-1) && !addr.empty();
@ -1110,9 +1190,19 @@ namespace openvpn {
return addr;
}
/**
* Return true if destination, provided to constructor,
* doesn't require gateway, false otherwise.
*/
bool local_route() const
{
return local_route_;
}
private:
DWORD index;
DWORD index = -1;
std::string addr;
bool local_route_ = false;
};
// An action to delete all routes on an interface