1
1
Fork 0
mirror of https://github.com/oxen-io/lokinet synced 2023-12-14 06:53:00 +01:00
lokinet/llarp/handlers/exit.cpp
Thomas Winget 4c630e0437 Large collection of changes to make android work
- Previous android java and jni code updated to work, but with much love
  still needed to make it work nicely, e.g. handling when the VPN is
  turned off.

- DNS handling refactored to allow android to intercept and handle DNS
  requests as we can't set the system DNS to use a high port
  (and apparently Chrome ignores system DNS settings anyway)

- add packet router structure to allow separate handling of specific
  intercepted traffic, e.g. UDP traffic to port 53 gets handled by our
  DNS handler rather than being naively forwarded as exit traffic.

- For now, android lokinet is exit-only and hard-coded to use exit.loki
  as its exit.  The exit will be configurable before release, but
  allowing to not use exit-only mode is more of a challenge.

- some old gitignore remnants which were matching to things we don't
  want them to (and are no longer relevant) removed

- some minor changes to CI configuration
2021-03-02 13:18:22 -05:00

688 lines
19 KiB
C++

#include <handlers/exit.hpp>
#include <dns/dns.hpp>
#include <net/net.hpp>
#include <path/path_context.hpp>
#include <router/abstractrouter.hpp>
#include <util/str.hpp>
#include <util/bits.hpp>
#include <cassert>
namespace llarp
{
namespace handlers
{
ExitEndpoint::ExitEndpoint(const std::string& name, AbstractRouter* r)
: m_Router(r)
, m_Resolver(std::make_shared<dns::Proxy>(r->netloop(), r->logic(), this))
, m_Name(name)
, m_LocalResolverAddr("127.0.0.1", 53)
, m_InetToNetwork(name + "_exit_rx", r->netloop(), r->netloop())
{
m_ShouldInitTun = true;
}
ExitEndpoint::~ExitEndpoint() = default;
util::StatusObject
ExitEndpoint::ExtractStatus() const
{
util::StatusObject obj{{"permitExit", m_PermitExit}, {"ip", m_IfAddr.ToString()}};
util::StatusObject exitsObj{};
for (const auto& item : m_ActiveExits)
{
exitsObj[item.first.ToString()] = item.second->ExtractStatus();
}
obj["exits"] = exitsObj;
return obj;
}
bool
ExitEndpoint::SupportsV6() const
{
return m_UseV6;
}
bool
ExitEndpoint::ShouldHookDNSMessage(const dns::Message& msg) const
{
if (msg.questions.size() == 0)
return false;
// always hook ptr for ranges we own
if (msg.questions[0].qtype == dns::qTypePTR)
{
huint128_t ip;
if (!dns::DecodePTR(msg.questions[0].qname, ip))
return false;
return m_OurRange.Contains(ip);
}
if (msg.questions[0].qtype == dns::qTypeA || msg.questions[0].qtype == dns::qTypeCNAME
|| msg.questions[0].qtype == dns::qTypeAAAA)
{
if (msg.questions[0].IsName("localhost.loki"))
return true;
if (msg.questions[0].HasTLD(".snode"))
return true;
}
return false;
}
bool
ExitEndpoint::HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)> reply)
{
if (msg.questions[0].qtype == dns::qTypePTR)
{
huint128_t ip;
if (!dns::DecodePTR(msg.questions[0].qname, ip))
return false;
if (ip == m_IfAddr)
{
RouterID us = GetRouter()->pubkey();
msg.AddAReply(us.ToString(), 300);
}
else
{
auto itr = m_IPToKey.find(ip);
if (itr != m_IPToKey.end() && m_SNodeKeys.find(itr->second) != m_SNodeKeys.end())
{
RouterID them = itr->second;
msg.AddAReply(them.ToString());
}
else
msg.AddNXReply();
}
}
else if (msg.questions[0].qtype == dns::qTypeCNAME)
{
if (msg.questions[0].IsName("random.snode"))
{
RouterID random;
if (GetRouter()->GetRandomGoodRouter(random))
msg.AddCNAMEReply(random.ToString(), 1);
else
msg.AddNXReply();
}
else if (msg.questions[0].IsName("localhost.loki"))
{
RouterID us = m_Router->pubkey();
msg.AddAReply(us.ToString(), 1);
}
else
msg.AddNXReply();
}
else if (msg.questions[0].qtype == dns::qTypeA || msg.questions[0].qtype == dns::qTypeAAAA)
{
const bool isV6 = msg.questions[0].qtype == dns::qTypeAAAA;
const bool isV4 = msg.questions[0].qtype == dns::qTypeA;
if (msg.questions[0].IsName("random.snode"))
{
RouterID random;
if (GetRouter()->GetRandomGoodRouter(random))
{
msg.AddCNAMEReply(random.ToString(), 1);
auto ip = ObtainServiceNodeIP(random);
msg.AddINReply(ip, false);
}
else
msg.AddNXReply();
reply(msg);
return true;
}
if (msg.questions[0].IsName("localhost.loki"))
{
msg.AddINReply(GetIfAddr(), isV6);
reply(msg);
return true;
}
// forward dns for snode
RouterID r;
if (r.FromString(msg.questions[0].Name()))
{
huint128_t ip;
PubKey pubKey(r);
if (isV4 && SupportsV6())
{
msg.hdr_fields |= dns::flags_QR | dns::flags_AA | dns::flags_RA;
}
else if (m_SNodeKeys.find(pubKey) == m_SNodeKeys.end())
{
// we do not have it mapped, async obtain it
ObtainSNodeSession(r, [&](std::shared_ptr<exit::BaseSession> session) {
if (session && session->IsReady())
{
msg.AddINReply(m_KeyToIP[pubKey], isV6);
}
else
{
msg.AddNXReply();
}
reply(msg);
});
return true;
}
else
{
// we have it mapped already as a service node
auto itr = m_KeyToIP.find(pubKey);
if (itr != m_KeyToIP.end())
{
ip = itr->second;
msg.AddINReply(ip, isV6);
}
else // fallback case that should never happen (probably)
msg.AddNXReply();
}
}
else
msg.AddNXReply();
}
reply(msg);
return true;
}
void
ExitEndpoint::ObtainSNodeSession(const RouterID& router, exit::SessionReadyFunc obtainCb)
{
ObtainServiceNodeIP(router);
m_SNodeSessions[router]->AddReadyHook(obtainCb);
}
llarp_time_t
ExitEndpoint::Now() const
{
return m_Router->Now();
}
bool
ExitEndpoint::VisitEndpointsFor(
const PubKey& pk, std::function<bool(exit::Endpoint* const)> visit)
{
auto range = m_ActiveExits.equal_range(pk);
auto itr = range.first;
while (itr != range.second)
{
if (visit(itr->second.get()))
++itr;
else
return true;
}
return false;
}
void
ExitEndpoint::Flush()
{
m_InetToNetwork.Process([&](Pkt_t& pkt) {
PubKey pk;
{
auto itr = m_IPToKey.find(pkt.dstv6());
if (itr == m_IPToKey.end())
{
// drop
LogWarn(Name(), " dropping packet, has no session at ", pkt.dstv6());
return;
}
pk = itr->second;
}
// check if this key is a service node
if (m_SNodeKeys.find(pk) != m_SNodeKeys.end())
{
// check if it's a service node session we made and queue it via our
// snode session that we made otherwise use an inbound session that
// was made by the other service node
auto itr = m_SNodeSessions.find(pk);
if (itr != m_SNodeSessions.end())
{
if (itr->second->QueueUpstreamTraffic(pkt, routing::ExitPadSize))
return;
}
}
auto tryFlushingTraffic = [&](exit::Endpoint* const ep) -> bool {
if (!ep->QueueInboundTraffic(ManagedBuffer{pkt.Buffer()}))
{
LogWarn(
Name(),
" dropped inbound traffic for session ",
pk,
" as we are overloaded (probably)");
// continue iteration
return true;
}
// break iteration
return false;
};
if (!VisitEndpointsFor(pk, tryFlushingTraffic))
{
// we may have all dead sessions, wtf now?
LogWarn(
Name(),
" dropped inbound traffic for session ",
pk,
" as we have no working endpoints");
}
});
{
auto itr = m_ActiveExits.begin();
while (itr != m_ActiveExits.end())
{
if (!itr->second->Flush())
{
LogWarn("exit session with ", itr->first, " dropped packets");
}
++itr;
}
}
{
auto itr = m_SNodeSessions.begin();
while (itr != m_SNodeSessions.end())
{
// TODO: move flush upstream to router event loop
if (!itr->second->FlushUpstream())
{
LogWarn("failed to flush snode traffic to ", itr->first, " via outbound session");
}
itr->second->FlushDownstream();
++itr;
}
}
m_Router->PumpLL();
}
bool
ExitEndpoint::Start()
{
// map our address
const PubKey us(m_Router->pubkey());
const huint128_t ip = GetIfAddr();
m_KeyToIP[us] = ip;
m_IPToKey[ip] = us;
m_IPActivity[ip] = std::numeric_limits<llarp_time_t>::max();
m_SNodeKeys.insert(us);
if (m_ShouldInitTun)
{
vpn::InterfaceInfo info;
info.ifname = m_ifname;
info.addrs.emplace(m_OurRange);
m_NetIf = GetRouter()->GetVPNPlatform()->ObtainInterface(std::move(info));
if (not m_NetIf)
{
llarp::LogError("Could not create interface");
return false;
}
auto loop = GetRouter()->netloop();
if (not loop->add_network_interface(
m_NetIf, [&](net::IPPacket pkt) { OnInetPacket(std::move(pkt)); }))
{
llarp::LogWarn("Could not create tunnel for exit endpoint");
return false;
}
loop->add_ticker([&]() { Flush(); });
llarp::LogInfo("Trying to start resolver ", m_LocalResolverAddr.toString());
return m_Resolver->Start(m_LocalResolverAddr.createSockAddr(), m_UpstreamResolvers);
}
return true;
}
AbstractRouter*
ExitEndpoint::GetRouter()
{
return m_Router;
}
huint128_t
ExitEndpoint::GetIfAddr() const
{
return m_IfAddr;
}
bool
ExitEndpoint::Stop()
{
for (auto& item : m_SNodeSessions)
item.second->Stop();
return true;
}
bool
ExitEndpoint::ShouldRemove() const
{
for (auto& item : m_SNodeSessions)
if (!item.second->ShouldRemove())
return false;
return true;
}
bool
ExitEndpoint::HasLocalMappedAddrFor(const PubKey& pk) const
{
return m_KeyToIP.find(pk) != m_KeyToIP.end();
}
huint128_t
ExitEndpoint::GetIPForIdent(const PubKey pk)
{
huint128_t found = {0};
if (!HasLocalMappedAddrFor(pk))
{
// allocate and map
found.h = AllocateNewAddress().h;
if (!m_KeyToIP.emplace(pk, found).second)
{
LogError(Name(), "failed to map ", pk, " to ", found);
return found;
}
if (!m_IPToKey.emplace(found, pk).second)
{
LogError(Name(), "failed to map ", found, " to ", pk);
return found;
}
if (HasLocalMappedAddrFor(pk))
LogInfo(Name(), " mapping ", pk, " to ", found);
else
LogError(Name(), "failed to map ", pk, " to ", found);
}
else
found.h = m_KeyToIP[pk].h;
MarkIPActive(found);
m_KeyToIP.rehash(0);
assert(HasLocalMappedAddrFor(pk));
return found;
}
huint128_t
ExitEndpoint::AllocateNewAddress()
{
if (m_NextAddr < m_HigestAddr)
return ++m_NextAddr;
// find oldest activity ip address
huint128_t found = {0};
llarp_time_t min = std::numeric_limits<llarp_time_t>::max();
auto itr = m_IPActivity.begin();
while (itr != m_IPActivity.end())
{
if (itr->second < min)
{
found.h = itr->first.h;
min = itr->second;
}
++itr;
}
// kick old ident off exit
// TODO: DoS
PubKey pk = m_IPToKey[found];
KickIdentOffExit(pk);
return found;
}
bool
ExitEndpoint::QueueOutboundTraffic(net::IPPacket pkt)
{
return m_NetIf && m_NetIf->WritePacket(std::move(pkt));
}
void
ExitEndpoint::KickIdentOffExit(const PubKey& pk)
{
LogInfo(Name(), " kicking ", pk, " off exit");
huint128_t ip = m_KeyToIP[pk];
m_KeyToIP.erase(pk);
m_IPToKey.erase(ip);
auto range = m_ActiveExits.equal_range(pk);
auto exit_itr = range.first;
while (exit_itr != range.second)
exit_itr = m_ActiveExits.erase(exit_itr);
}
void
ExitEndpoint::MarkIPActive(huint128_t ip)
{
m_IPActivity[ip] = GetRouter()->Now();
}
void
ExitEndpoint::OnInetPacket(net::IPPacket pkt)
{
m_InetToNetwork.Emplace(std::move(pkt));
}
bool
ExitEndpoint::QueueSNodePacket(const llarp_buffer_t& buf, huint128_t from)
{
net::IPPacket pkt;
if (!pkt.Load(buf))
return false;
// rewrite ip
if (m_UseV6)
pkt.UpdateIPv6Address(from, m_IfAddr);
else
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(from)), xhtonl(net::TruncateV6(m_IfAddr)));
return m_NetIf and m_NetIf->WritePacket(std::move(pkt));
}
exit::Endpoint*
ExitEndpoint::FindEndpointByPath(const PathID_t& path)
{
exit::Endpoint* endpoint = nullptr;
PubKey pk;
{
auto itr = m_Paths.find(path);
if (itr == m_Paths.end())
return nullptr;
pk = itr->second;
}
{
auto itr = m_ActiveExits.find(pk);
if (itr != m_ActiveExits.end())
{
if (itr->second->PubKey() == pk)
endpoint = itr->second.get();
}
}
return endpoint;
}
bool
ExitEndpoint::UpdateEndpointPath(const PubKey& remote, const PathID_t& next)
{
// check if already mapped
auto itr = m_Paths.find(next);
if (itr != m_Paths.end())
return false;
m_Paths.emplace(next, remote);
return true;
}
void
ExitEndpoint::Configure(const NetworkConfig& networkConfig, const DnsConfig& dnsConfig)
{
/*
* TODO: pre-config refactor, this was checking a couple things that were extremely vague
* these could have appeared on either [dns] or [network], but they weren't documented
* anywhere
*
if (k == "type" && v == "null")
{
m_ShouldInitTun = false;
return true;
}
if (k == "exit")
{
m_PermitExit = IsTrueValue(v.c_str());
return true;
}
*/
if (networkConfig.m_endpointType == "null")
{
m_ShouldInitTun = false;
}
m_LocalResolverAddr = dnsConfig.m_bind;
m_UpstreamResolvers = dnsConfig.m_upstreamDNS;
m_OurRange = networkConfig.m_ifaddr;
if (!m_OurRange.addr.h)
{
const auto maybe = llarp::FindFreeRange();
if (not maybe.has_value())
throw std::runtime_error("cannot find free interface range");
m_OurRange = *maybe;
}
const auto host_str = m_OurRange.BaseAddressString();
// string, or just a plain char array?
m_IfAddr = m_OurRange.addr;
m_NextAddr = m_IfAddr;
m_HigestAddr = m_OurRange.HighestAddr();
m_UseV6 = not m_OurRange.IsV4();
m_ifname = networkConfig.m_ifname;
if (m_ifname.empty())
{
const auto maybe = llarp::FindFreeTun();
if (not maybe.has_value())
throw std::runtime_error("cannot find free interface name");
m_ifname = *maybe;
}
LogInfo(Name(), " set ifname to ", m_ifname);
// TODO: "exit-whitelist" and "exit-blacklist"
// (which weren't originally implemented)
}
huint128_t
ExitEndpoint::ObtainServiceNodeIP(const RouterID& other)
{
const PubKey pubKey(other);
const PubKey us(m_Router->pubkey());
// just in case
if (pubKey == us)
return m_IfAddr;
huint128_t ip = GetIPForIdent(pubKey);
if (m_SNodeKeys.emplace(pubKey).second)
{
auto session = std::make_shared<exit::SNodeSession>(
other,
std::bind(&ExitEndpoint::QueueSNodePacket, this, std::placeholders::_1, ip),
GetRouter(),
2,
1,
true,
false);
// this is a new service node make an outbound session to them
m_SNodeSessions.emplace(other, session);
}
return ip;
}
bool
ExitEndpoint::AllocateNewExit(const PubKey pk, const PathID_t& path, bool wantInternet)
{
if (wantInternet && !m_PermitExit)
return false;
auto ip = GetIPForIdent(pk);
if (GetRouter()->pathContext().TransitHopPreviousIsRouter(path, pk.as_array()))
{
// we think this path belongs to a service node
// mark it as such so we don't make an outbound session to them
m_SNodeKeys.emplace(pk.as_array());
}
m_ActiveExits.emplace(
pk, std::make_unique<exit::Endpoint>(pk, path, !wantInternet, ip, this));
m_Paths[path] = pk;
return HasLocalMappedAddrFor(pk);
}
std::string
ExitEndpoint::Name() const
{
return m_Name;
}
void
ExitEndpoint::DelEndpointInfo(const PathID_t& path)
{
m_Paths.erase(path);
}
void
ExitEndpoint::RemoveExit(const exit::Endpoint* ep)
{
auto range = m_ActiveExits.equal_range(ep->PubKey());
auto itr = range.first;
while (itr != range.second)
{
if (itr->second->LocalPath() == ep->LocalPath())
{
itr = m_ActiveExits.erase(itr);
// now ep is gone af
return;
}
++itr;
}
}
void
ExitEndpoint::Tick(llarp_time_t now)
{
{
auto itr = m_SNodeSessions.begin();
while (itr != m_SNodeSessions.end())
{
if (itr->second->IsExpired(now))
itr = m_SNodeSessions.erase(itr);
else
{
itr->second->Tick(now);
++itr;
}
}
}
{
// expire
auto itr = m_ActiveExits.begin();
while (itr != m_ActiveExits.end())
{
if (itr->second->IsExpired(now))
itr = m_ActiveExits.erase(itr);
else
++itr;
}
// pick chosen exits and tick
m_ChosenExits.clear();
itr = m_ActiveExits.begin();
while (itr != m_ActiveExits.end())
{
// do we have an exit set for this key?
if (m_ChosenExits.find(itr->first) != m_ChosenExits.end())
{
// yes
if (m_ChosenExits[itr->first]->createdAt < itr->second->createdAt)
{
// if the iterators's exit is newer use it for the chosen exit for
// key
if (!itr->second->LooksDead(now))
m_ChosenExits[itr->first] = itr->second.get();
}
}
else if (!itr->second->LooksDead(now)) // set chosen exit if not dead for key that
// doesn't have one yet
m_ChosenExits[itr->first] = itr->second.get();
// tick which clears the tx rx counters
itr->second->Tick(now);
++itr;
}
}
}
} // namespace handlers
} // namespace llarp