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 // Invert a route list. Used to support excluded routes on platforms that
// don't support them natively. // don't support them natively.
#ifndef OPENVPN_ADDR_ROUTEINV_H #pragma once
#define OPENVPN_ADDR_ROUTEINV_H
#include <openvpn/common/exception.hpp> #include <openvpn/common/exception.hpp>
#include <openvpn/addr/route.hpp> #include <openvpn/addr/route.hpp>
namespace openvpn { namespace openvpn {
namespace IP { namespace IP {
class RouteInverter : public RouteList class AddressSpaceSplitter : public RouteList
{ {
public: 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 // to static_cast it to RouteList& so as to avoid matching the
// default copy constructor. // default copy constructor.
explicit RouteInverter(const RouteList& in) explicit AddressSpaceSplitter(const RouteList& in)
: RouteInverter(in, in.version_mask()) : AddressSpaceSplitter(in, in.version_mask())
{ {
} }
RouteInverter(const RouteList& in, const Addr::VersionMask vermask) AddressSpaceSplitter(const RouteList& in, const Addr::VersionMask vermask)
{ {
in.verify_canonical(); in.verify_canonical();
if (vermask & Addr::V4_MASK) 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) 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: private:
@ -60,8 +59,16 @@ namespace openvpn {
SUBROUTE, SUBROUTE,
LEAF, 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)) switch (find(in, route))
{ {
@ -70,18 +77,17 @@ namespace openvpn {
Route r1, r2; Route r1, r2;
if (route.split(r1, r2)) if (route.split(r1, r2))
{ {
descend(in, ver, r1); descend(in, r1);
descend(in, ver, r2); descend(in, r2);
} }
else else
push_back(route); push_back(route);
break; break;
} }
case EQUAL:
case LEAF: case LEAF:
push_back(route); push_back(route);
break; break;
case EQUAL:
break;
} }
} }
@ -92,14 +98,12 @@ namespace openvpn {
{ {
const Route& r = *i; const Route& r = *i;
if (route == r) if (route == r)
return EQUAL; type = EQUAL;
else if (route.contains(r)) else if (route.contains(r))
type = SUBROUTE; return SUBROUTE;
} }
return type; return type;
} }
}; };
} }
} }
#endif

View File

@ -26,7 +26,7 @@
#include <openvpn/common/exception.hpp> #include <openvpn/common/exception.hpp>
#include <openvpn/tun/client/emuexr.hpp> #include <openvpn/tun/client/emuexr.hpp>
#include <openvpn/addr/routeinv.hpp> #include <openvpn/addr/addrspacesplit.hpp>
namespace openvpn { namespace openvpn {
class EmulateExcludeRouteImpl : public EmulateExcludeRoute class EmulateExcludeRouteImpl : public EmulateExcludeRoute
@ -36,48 +36,119 @@ namespace openvpn {
typedef RCPtr<EmulateExcludeRouteImpl> Ptr; typedef RCPtr<EmulateExcludeRouteImpl> Ptr;
EmulateExcludeRouteImpl(const bool exclude_server_address) explicit EmulateExcludeRouteImpl(const bool exclude_server_address)
: exclude_server_address_(exclude_server_address) : exclude_server_address_(exclude_server_address)
{ {
} }
private: 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); (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()); 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(); const unsigned int ip_ver_flags = ipv.ip_ver_flags();
if (exclude.size() && rg_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.emplace_back(server_addr, server_addr.size());
rl.reserve(include.size() + exclude.size()); // Create a temporary list that includes all the routes + the server
rl.insert(rl.end(), include.begin(), include.end()); tempExcludeList = exclude;
rl.insert(rl.end(), exclude.begin(), exclude.end()); tempExcludeList.emplace_back(server_addr, server_addr.size());
excludedRoutes = &tempExcludeList;
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();
} }
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_; const bool exclude_server_address_;
IP::RouteList include; IP::RouteList include;
IP::RouteList exclude; 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 void add_route(const bool add, const IP::Addr& addr, const int prefix_len) = 0;
virtual bool enabled(const IPVerFlags& ipv) const = 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 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> struct EmulateExcludeRouteFactory : public RC<thread_unsafe_refcount>

View File

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