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:
parent
daf575ff50
commit
2105b4b7c0
@ -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
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user