From da887dc559086f0aac0ac87d84b340911b42a48d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 1 Dec 2021 13:12:44 -0500 Subject: [PATCH] implement exit node pooling, allows users to use multiple exits for an address range. mappings per ip stick to the same exit, each new ip is mapped to a random exit in the specified pool. make exit-auth multi value --- llarp/config/config.cpp | 4 +- llarp/handlers/tun.cpp | 94 +++++++++++++++++++---------------------- llarp/handlers/tun.hpp | 13 ++++++ 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 047427631..b625b9e78 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -454,6 +454,7 @@ namespace llarp "network", "exit-node", ClientOnly, + MultiValue, Comment{ "Specify a `.loki` address and an optional ip range to use as an exit broker.", "Example:", @@ -496,12 +497,13 @@ namespace llarp "network", "exit-auth", ClientOnly, + MultiValue, Comment{ "Specify an optional authentication code required to use a non-public exit node.", "For example:", " exit-auth=myfavouriteexit.loki:abc", "uses the authentication code `abc` whenever myfavouriteexit.loki is accessed.", - "Can be specified multiple time to store codes for different exit nodes.", + "Can be specified multiple times to store codes for different exit nodes.", }, [this](std::string arg) { if (arg.empty()) diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 8736f2125..4a4ad117f 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -1,7 +1,5 @@ #include #include -// harmless on other platforms -#define __USE_MINGW_ANSI_STDIO 1 #include "tun.hpp" #include #ifndef _WIN32 @@ -1023,6 +1021,38 @@ namespace llarp return llarp::service::Endpoint::Stop(); } + std::optional + TunEndpoint::ObtainExitAddressFor( + huint128_t ip, + std::function)> exitSelectionStrat) + { + // is it already mapped? return the mapping + if (auto itr = m_ExitIPToExitAddress.find(ip); itr != m_ExitIPToExitAddress.end()) + return itr->second; + // build up our candidates to choose + std::unordered_set candidates; + for (const auto& entry : m_ExitMap.FindAllEntries(ip)) + { + // make sure it is allowed by the range if the ip is a bogon + if (not IsBogon(ip) or entry.first.BogonContains(ip)) + candidates.emplace(entry.second); + } + // no candidates? bail. + if (candidates.empty()) + return std::nullopt; + if (not exitSelectionStrat) + { + // default strat to random choice + exitSelectionStrat = [](auto candidates) { + auto itr = candidates.begin(); + std::advance(itr, llarp::randint() % candidates.size()); + return *itr; + }; + } + // map the exit and return the endpoint we mapped it to + return m_ExitIPToExitAddress.emplace(ip, exitSelectionStrat(candidates)).first->second; + } + void TunEndpoint::HandleGotUserPacket(net::IPPacket pkt) { @@ -1037,25 +1067,7 @@ namespace llarp dst = pkt.dstv6(); src = pkt.srcv6(); } - // this is for ipv6 slaac on ipv6 exits - /* - constexpr huint128_t ipv6_multicast_all_nodes = - huint128_t{uint128_t{0xff01'0000'0000'0000UL, 1UL}}; - constexpr huint128_t ipv6_multicast_all_routers = - huint128_t{uint128_t{0xff01'0000'0000'0000UL, 2UL}}; - if (dst == ipv6_multicast_all_nodes and m_state->m_ExitEnabled) - { - // send ipv6 multicast - for (const auto& [ip, addr] : m_IPToAddr) - { - (void)ip; - SendToOrQueue( - service::Address{addr.as_array()}, pkt.ConstBuffer(), service::ProtocolType::Exit); - } - return; - } - */ if (m_state->m_ExitEnabled) { dst = net::ExpandV4(net::TruncateV6(dst)); @@ -1063,28 +1075,18 @@ namespace llarp auto itr = m_IPToAddr.find(dst); if (itr == m_IPToAddr.end()) { - // find all ranges that match the destination ip - const auto exitEntries = m_ExitMap.FindAllEntries(dst); - if (exitEntries.empty()) + service::Address addr{}; + + if (auto maybe = ObtainExitAddressFor(dst)) + addr = *maybe; + else { // send icmp unreachable as we dont have any exits for this ip if (const auto icmp = pkt.MakeICMPUnreachable()) - { HandleWriteIPPacket(icmp->ConstBuffer(), dst, src, 0); - } + return; } - service::Address addr{}; - for (const auto& [range, exitAddr] : exitEntries) - { - if (not IsBogon(dst) or range.BogonContains(dst)) - { - addr = exitAddr; - } - // we do not permit bogons when they don't explicitly match a permitted bogon range - } - if (addr.IsZero()) // drop becase no exit was found that matches our rules - return; pkt.ZeroSourceAddress(); MarkAddressOutbound(addr); EnsurePathToService( @@ -1266,24 +1268,14 @@ namespace llarp } else // don't allow snode return false; - const auto mapped = m_ExitMap.FindAllEntries(src); - bool allow = false; - for (const auto& [range, exitAddr] : mapped) + // make sure the mapping matches + if (auto itr = m_ExitIPToExitAddress.find(src); itr != m_ExitIPToExitAddress.end()) { - if (not IsBogon(src) or range.BogonContains(src)) - { - // allow if this address matches the endpoint we think it should be - allow = exitAddr == fromAddr; - break; - } + if (itr->second != fromAddr) + return false; } - if (not allow) - { - var::visit( - [&](auto&& address) { LogWarn(Name(), " does not allow ", src, " from ", address); }, - addr); + else return false; - } } else { diff --git a/llarp/handlers/tun.hpp b/llarp/handlers/tun.hpp index a5173c17d..ee7a64b5f 100644 --- a/llarp/handlers/tun.hpp +++ b/llarp/handlers/tun.hpp @@ -222,7 +222,20 @@ namespace llarp /// a hidden service std::unordered_map, bool> m_SNodes; + /// maps ip address to an exit endpoint, useful when we have multiple exits on a range + std::unordered_map m_ExitIPToExitAddress; + private: + /// given an ip address that is not mapped locally find the address it shall be forwarded to + /// optionally provide a custom selection strategy, if none is provided it will choose a + /// random entry from the available choices + /// return std::nullopt if we cannot route this address to an exit + std::optional + ObtainExitAddressFor( + huint128_t ip, + std::function)> exitSelectionStrat = + nullptr); + template void SendDNSReply(