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

Fix Android route exclusion emulation

The old route emulation would immediately stop if the 0.0.0.0/0 was
in the routes to install.

Replace the old approach by first calculation a fine grained enough
set of routes and then only install those routes from this set that
should go via the VPN.
This commit is contained in:
Arne Schwabe 2018-07-04 19:07:37 +02:00
parent daf575ff50
commit 2105b4b7c0
4 changed files with 140 additions and 56 deletions

View File

@ -22,36 +22,35 @@
// Invert a route list. Used to support excluded routes on platforms that
// don't support them natively.
#ifndef OPENVPN_ADDR_ROUTEINV_H
#define OPENVPN_ADDR_ROUTEINV_H
#pragma once
#include <openvpn/common/exception.hpp>
#include <openvpn/addr/route.hpp>
namespace openvpn {
namespace IP {
class RouteInverter : public RouteList
class AddressSpaceSplitter : public RouteList
{
public:
OPENVPN_EXCEPTION(route_inverter);
OPENVPN_EXCEPTION(address_space_splitter);
RouteInverter() {}
AddressSpaceSplitter() {}
// NOTE: when passing RouteInverter to this constructor, make sure
// NOTE: when passing AddressSpaceSplitter to this constructor, make sure
// to static_cast it to RouteList& so as to avoid matching the
// default copy constructor.
explicit RouteInverter(const RouteList& in)
: RouteInverter(in, in.version_mask())
explicit AddressSpaceSplitter(const RouteList& in)
: AddressSpaceSplitter(in, in.version_mask())
{
}
RouteInverter(const RouteList& in, const Addr::VersionMask vermask)
AddressSpaceSplitter(const RouteList& in, const Addr::VersionMask vermask)
{
in.verify_canonical();
if (vermask & Addr::V4_MASK)
descend(in, Addr::V4, Route(Addr::from_zero(Addr::V4), 0));
descend(in, Route(Addr::from_zero(Addr::V4), 0));
if (vermask & Addr::V6_MASK)
descend(in, Addr::V6, Route(Addr::from_zero(Addr::V6), 0));
descend(in, Route(Addr::from_zero(Addr::V6), 0));
}
private:
@ -60,8 +59,16 @@ namespace openvpn {
SUBROUTE,
LEAF,
};
void descend(const RouteList& in, const Addr::Version ver, const Route& route)
/**
* This method construct a non-overlapping list of routes spanning the address
* space in @param route. The routes are constructed in a way that each
* route in the returned list is smaller or equalto each route in
* parameter @param in
*
* @param route The route we currently are looking at and split if it does
* not meet the requirements
*/
void descend(const RouteList& in, const Route& route)
{
switch (find(in, route))
{
@ -70,18 +77,17 @@ namespace openvpn {
Route r1, r2;
if (route.split(r1, r2))
{
descend(in, ver, r1);
descend(in, ver, r2);
descend(in, r1);
descend(in, r2);
}
else
push_back(route);
break;
}
case EQUAL:
case LEAF:
push_back(route);
break;
case EQUAL:
break;
}
}
@ -92,14 +98,12 @@ namespace openvpn {
{
const Route& r = *i;
if (route == r)
return EQUAL;
type = EQUAL;
else if (route.contains(r))
type = SUBROUTE;
return SUBROUTE;
}
return type;
}
};
}
}
#endif

View File

@ -26,7 +26,7 @@
#include <openvpn/common/exception.hpp>
#include <openvpn/tun/client/emuexr.hpp>
#include <openvpn/addr/routeinv.hpp>
#include <openvpn/addr/addrspacesplit.hpp>
namespace openvpn {
class EmulateExcludeRouteImpl : public EmulateExcludeRoute
@ -36,48 +36,119 @@ namespace openvpn {
typedef RCPtr<EmulateExcludeRouteImpl> Ptr;
EmulateExcludeRouteImpl(const bool exclude_server_address)
explicit EmulateExcludeRouteImpl(const bool exclude_server_address)
: exclude_server_address_(exclude_server_address)
{
}
private:
virtual void add_route(const bool add, const IP::Addr& addr, const int prefix_len)
void add_route(const bool add, const IP::Addr& addr, const int prefix_len) override
{
(add ? include : exclude).emplace_back(addr, prefix_len);
}
virtual bool enabled(const IPVerFlags& ipv) const
void add_default_routes(bool ipv4, bool ipv6) override
{
if (ipv4)
add_route(true, IP::Addr::from_zero(IP::Addr::V4), 0);
if (ipv6)
add_route(true, IP::Addr::from_zero(IP::Addr::V6), 0);
}
bool enabled(const IPVerFlags& ipv) const override
{
return exclude.size() && (ipv.rgv4() || ipv.rgv6());
}
virtual void emulate(TunBuilderBase* tb, IPVerFlags& ipv, const IP::Addr& server_addr) const
void emulate(TunBuilderBase* tb, IPVerFlags& ipv, const IP::Addr& server_addr) const override
{
const unsigned int rg_ver_flags = ipv.rg_ver_flags();
if (exclude.size() && rg_ver_flags)
const unsigned int ip_ver_flags = ipv.ip_ver_flags();
IP::RouteList rl, tempExcludeList;
rl.reserve(include.size() + exclude.size());
rl.insert(rl.end(), include.begin(), include.end());
rl.insert(rl.end(), exclude.begin(), exclude.end());
// Check if we have to exclude the server, if yes we temporarily add it to the list
// of excluded networks as small individual /32 or /128 network
const IP::RouteList* excludedRoutes = &exclude;
if (exclude_server_address_ && (server_addr.version_mask() & ip_ver_flags) &&
!exclude.contains(IP::Route(server_addr, server_addr.size())))
{
IP::RouteList rl;
rl.reserve(include.size() + exclude.size());
rl.insert(rl.end(), include.begin(), include.end());
rl.insert(rl.end(), exclude.begin(), exclude.end());
if (exclude_server_address_ && (server_addr.version_mask() & rg_ver_flags))
rl.emplace_back(server_addr, server_addr.size());
const IP::RouteInverter ri(rl, rg_ver_flags);
OPENVPN_LOG("Exclude routes emulation:\n" << ri);
for (IP::RouteInverter::const_iterator i = ri.begin(); i != ri.end(); ++i)
{
const IP::Route& r = *i;
if (!tb->tun_builder_add_route(r.addr.to_string(), r.prefix_len, -1, r.addr.version() == IP::Addr::V6))
throw emulate_exclude_route_error("tun_builder_add_route failed");
}
ipv.set_emulate_exclude_routes();
rl.emplace_back(server_addr, server_addr.size());
// Create a temporary list that includes all the routes + the server
tempExcludeList = exclude;
tempExcludeList.emplace_back(server_addr, server_addr.size());
excludedRoutes = &tempExcludeList;
}
if (excludedRoutes->empty())
{
// Samsung's Android VPN API does different things if you have
// 0.0.0.0/0 in the list of installed routes
// (even if 0.0.0.0/1 and 128.0.0.0/1 and are present it behaves different)
// We normally always split the address space, breaking a 0.0.0.0/0 into
// smaller routes. If no routes are excluded, we install the original
// routes without modifying them
for (const auto& rt: include)
{
if (rt.version() & ip_ver_flags)
{
if (!tb->tun_builder_add_route(rt.addr.to_string(), rt.prefix_len, -1, rt.addr.version() == IP::Addr::V6))
throw emulate_exclude_route_error("tun_builder_add_route failed");
}
}
return;
}
// Complete address space (0.0.0.0/0 or ::/0) split into smaller networks
// Figure out which parts of this non overlapping address we want to install
for (const auto& r: IP::AddressSpaceSplitter(rl, ip_ver_flags))
{
if (check_route_should_be_installed(r, *excludedRoutes))
if (!tb->tun_builder_add_route(r.addr.to_string(), r.prefix_len, -1, r.addr.version() == IP::Addr::V6))
throw emulate_exclude_route_error("tun_builder_add_route failed");
}
ipv.set_emulate_exclude_routes();
}
bool check_route_should_be_installed(const IP::Route& r, const IP::RouteList & excludedRoutes) const
{
// The whole address space was partioned into NON-overlapping routes that
// we get one by one with the parameter r.
// Therefore we already know that the whole route r either is included or
// excluded IPs.
// Figure out if this particular route should be installed or not
IP::Route const* bestroute = nullptr;
// Get the best (longest-prefix/smallest) route from included routes that completely
// matches this route
for (const auto& incRoute: include)
{
if (incRoute.contains(r))
{
if (!bestroute || bestroute->prefix_len < incRoute.prefix_len)
bestroute = &incRoute;
}
}
// No positive route matches the route at all, do not install it
if (!bestroute)
return false;
// Check if there is a more specific exclude route
for (const auto& exclRoute: excludedRoutes)
{
if (exclRoute.contains(r) && exclRoute.prefix_len > bestroute->prefix_len)
return false;
}
return true;
}
const bool exclude_server_address_;
IP::RouteList include;
IP::RouteList exclude;

View File

@ -36,6 +36,7 @@ namespace openvpn {
virtual void add_route(const bool add, const IP::Addr& addr, const int prefix_len) = 0;
virtual bool enabled(const IPVerFlags& ipv) const = 0;
virtual void emulate(TunBuilderBase* tb, IPVerFlags& ipv, const IP::Addr& server_addr) const = 0;
virtual void add_default_routes(bool ipv4, bool ipv6) = 0;
};
struct EmulateExcludeRouteFactory : public RC<thread_unsafe_refcount>

View File

@ -128,13 +128,20 @@ namespace openvpn {
// add routes
add_routes(tb, opt, server_addr, ipv, eer.get(), quiet);
// emulate exclude routes
if (eer && eer->enabled(ipv))
eer->emulate(tb, ipv, server_addr);
// configure redirect-gateway
if (!tb->tun_builder_reroute_gw(ipv.rgv4(), ipv.rgv6(), ipv.api_flags()))
throw tun_prop_route_error("tun_builder_reroute_gw for redirect-gateway failed");
if (eer)
{
// Route emulation needs to know if default routes are included
// from redirect-gateway
eer->add_default_routes(ipv.rgv4(), ipv.rgv6());
// emulate exclude routes
eer->emulate(tb, ipv, server_addr);
}
else
{
// configure redirect-gateway
if (!tb->tun_builder_reroute_gw(ipv.rgv4(), ipv.rgv6(), ipv.api_flags()))
throw tun_prop_route_error("tun_builder_reroute_gw for redirect-gateway failed");
}
// add DNS servers and domain prefixes
const unsigned int dhcp_option_flags = add_dhcp_options(tb, opt, quiet);
@ -335,7 +342,9 @@ namespace openvpn {
EmulateExcludeRoute* eer)
{
const std::string addr_str = addr.to_string();
if (add)
if (eer)
eer->add_route(add, addr, prefix_length);
else if (add)
{
if (!tb->tun_builder_add_route(addr_str, prefix_length, metric, ipv6))
throw tun_prop_route_error("tun_builder_add_route failed");
@ -345,8 +354,7 @@ namespace openvpn {
if (!tb->tun_builder_exclude_route(addr_str, prefix_length, metric, ipv6))
throw tun_prop_route_error("tun_builder_exclude_route failed");
}
if (eer)
eer->add_route(add, addr, prefix_length);
}
// Check the target of a route.