|
|
|
#include <algorithm>
|
|
|
|
#include <iterator>
|
|
|
|
#include <variant>
|
|
|
|
#include "tun.hpp"
|
|
|
|
#include <sys/types.h>
|
|
|
|
#ifndef _WIN32
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <llarp/dns/dns.hpp>
|
|
|
|
#include <llarp/ev/ev.hpp>
|
QUIC lokinet integration refactor
Refactors how quic packets get handled: the actual tunnels now live in
tunnel.hpp's TunnelManager which holds and manages all the quic<->tcp
tunnelling. service::Endpoint now holds a TunnelManager rather than a
quic::Server. We only need one quic server, but we need a separate quic
client instance per outgoing quic tunnel, and TunnelManager handles all
that glue now.
Adds QUIC packet handling to get to the right tunnel code. This
required multiplexing incoming quic packets, as follows:
Adds a very small quic tunnel packet header of 4 bytes:
[1, SPORT, ECN] for client->server packets, where SPORT is our
source "port" (really: just a uint16_t unique quic instance
identifier)
or
[2, DPORT, ECN] for server->client packets where the DPORT is the SPORT
from above.
(This also reworks ECN bits to get properly carried over lokinet.)
We don't need a destination/source port for the server-side because
there is only ever one quic server (and we know we're going to it when
the first byte of the header is 1).
Removes the config option for quic exposing ports; a full lokinet will
simply accept anything incoming on quic and tunnel it to the requested
port on the the local endpoint IP (this handler will come in a following
commit).
Replace ConvoTags with full addresses: we need to carry the port, as
well, which the ConvoTag can't give us, so change those to more general
SockAddrs from which we can extract both the ConvoTag *and* the port.
Add a pending connection queue along with new quic-side handlers to call
when a stream becomes available (TunnelManager uses this to wire up
pending incoming conns with quic streams as streams open up).
Completely get rid of tunnel_server/tunnel_client.cpp code; it is now
moved to tunnel.hpp.
Add listen()/forget() methods in TunnelManager for setting up quic
listening sockets (for liblokinet usage).
Add open()/close() methods in TunnelManager for spinning up new quic
clients for outgoing quic connections.
4 years ago
|
|
|
#include <llarp/net/net.hpp>
|
|
|
|
#include <llarp/router/router.hpp>
|
|
|
|
#include <llarp/router/route_poker.hpp>
|
|
|
|
#include <llarp/service/context.hpp>
|
|
|
|
#include <llarp/service/outbound_context.hpp>
|
|
|
|
#include <llarp/service/endpoint_state.hpp>
|
|
|
|
#include <llarp/service/outbound_context.hpp>
|
|
|
|
#include <llarp/service/name.hpp>
|
QUIC lokinet integration refactor
Refactors how quic packets get handled: the actual tunnels now live in
tunnel.hpp's TunnelManager which holds and manages all the quic<->tcp
tunnelling. service::Endpoint now holds a TunnelManager rather than a
quic::Server. We only need one quic server, but we need a separate quic
client instance per outgoing quic tunnel, and TunnelManager handles all
that glue now.
Adds QUIC packet handling to get to the right tunnel code. This
required multiplexing incoming quic packets, as follows:
Adds a very small quic tunnel packet header of 4 bytes:
[1, SPORT, ECN] for client->server packets, where SPORT is our
source "port" (really: just a uint16_t unique quic instance
identifier)
or
[2, DPORT, ECN] for server->client packets where the DPORT is the SPORT
from above.
(This also reworks ECN bits to get properly carried over lokinet.)
We don't need a destination/source port for the server-side because
there is only ever one quic server (and we know we're going to it when
the first byte of the header is 1).
Removes the config option for quic exposing ports; a full lokinet will
simply accept anything incoming on quic and tunnel it to the requested
port on the the local endpoint IP (this handler will come in a following
commit).
Replace ConvoTags with full addresses: we need to carry the port, as
well, which the ConvoTag can't give us, so change those to more general
SockAddrs from which we can extract both the ConvoTag *and* the port.
Add a pending connection queue along with new quic-side handlers to call
when a stream becomes available (TunnelManager uses this to wire up
pending incoming conns with quic streams as streams open up).
Completely get rid of tunnel_server/tunnel_client.cpp code; it is now
moved to tunnel.hpp.
Add listen()/forget() methods in TunnelManager for setting up quic
listening sockets (for liblokinet usage).
Add open()/close() methods in TunnelManager for spinning up new quic
clients for outgoing quic connections.
4 years ago
|
|
|
#include <llarp/service/protocol_type.hpp>
|
|
|
|
#include <llarp/util/meta/memfn.hpp>
|
|
|
|
#include <llarp/nodedb.hpp>
|
QUIC lokinet integration refactor
Refactors how quic packets get handled: the actual tunnels now live in
tunnel.hpp's TunnelManager which holds and manages all the quic<->tcp
tunnelling. service::Endpoint now holds a TunnelManager rather than a
quic::Server. We only need one quic server, but we need a separate quic
client instance per outgoing quic tunnel, and TunnelManager handles all
that glue now.
Adds QUIC packet handling to get to the right tunnel code. This
required multiplexing incoming quic packets, as follows:
Adds a very small quic tunnel packet header of 4 bytes:
[1, SPORT, ECN] for client->server packets, where SPORT is our
source "port" (really: just a uint16_t unique quic instance
identifier)
or
[2, DPORT, ECN] for server->client packets where the DPORT is the SPORT
from above.
(This also reworks ECN bits to get properly carried over lokinet.)
We don't need a destination/source port for the server-side because
there is only ever one quic server (and we know we're going to it when
the first byte of the header is 1).
Removes the config option for quic exposing ports; a full lokinet will
simply accept anything incoming on quic and tunnel it to the requested
port on the the local endpoint IP (this handler will come in a following
commit).
Replace ConvoTags with full addresses: we need to carry the port, as
well, which the ConvoTag can't give us, so change those to more general
SockAddrs from which we can extract both the ConvoTag *and* the port.
Add a pending connection queue along with new quic-side handlers to call
when a stream becomes available (TunnelManager uses this to wire up
pending incoming conns with quic streams as streams open up).
Completely get rid of tunnel_server/tunnel_client.cpp code; it is now
moved to tunnel.hpp.
Add listen()/forget() methods in TunnelManager for setting up quic
listening sockets (for liblokinet usage).
Add open()/close() methods in TunnelManager for spinning up new quic
clients for outgoing quic connections.
4 years ago
|
|
|
#include <llarp/quic/tunnel.hpp>
|
|
|
|
#include <llarp/rpc/endpoint_rpc.hpp>
|
|
|
|
#include <llarp/util/str.hpp>
|
|
|
|
#include <llarp/util/logging/buffer.hpp>
|
|
|
|
#include <llarp/dns/srv_data.hpp>
|
|
|
|
#include <llarp/constants/net.hpp>
|
|
|
|
#include <llarp/constants/platform.hpp>
|
|
|
|
|
|
|
|
#include <oxenc/bt.h>
|
|
|
|
|
|
|
|
namespace llarp::handlers
|
|
|
|
{
|
|
|
|
static auto logcat = log::Cat("tun");
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::MaybeHookDNS(
|
|
|
|
std::shared_ptr<dns::PacketSource_Base> source,
|
|
|
|
const dns::Message& query,
|
|
|
|
const SockAddr& to,
|
|
|
|
const SockAddr& from)
|
|
|
|
{
|
|
|
|
if (not ShouldHookDNSMessage(query))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto job = std::make_shared<dns::QueryJob>(source, query, to, from);
|
|
|
|
if (HandleHookedDNSMessage(query, [job](auto msg) { job->SendReply(msg.ToBuffer()); }))
|
|
|
|
router()->TriggerPump();
|
|
|
|
else
|
|
|
|
job->Cancel();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Intercepts DNS IP packets on platforms where binding to a low port isn't viable.
|
|
|
|
/// (windows/macos/ios/android ... aka everything that is not linux... funny that)
|
|
|
|
class DnsInterceptor : public dns::PacketSource_Base
|
|
|
|
{
|
|
|
|
std::function<void(net::IPPacket)> m_Reply;
|
|
|
|
net::ipaddr_t m_OurIP;
|
|
|
|
llarp::DnsConfig m_Config;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit DnsInterceptor(
|
|
|
|
std::function<void(net::IPPacket)> reply, net::ipaddr_t our_ip, llarp::DnsConfig conf)
|
|
|
|
: m_Reply{std::move(reply)}, m_OurIP{std::move(our_ip)}, m_Config{std::move(conf)}
|
|
|
|
{}
|
|
|
|
|
|
|
|
~DnsInterceptor() override = default;
|
|
|
|
|
|
|
|
void
|
|
|
|
SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const override
|
|
|
|
{
|
|
|
|
auto pkt = net::IPPacket::make_udp(from, to, std::move(buf));
|
|
|
|
|
|
|
|
if (pkt.empty())
|
|
|
|
return;
|
|
|
|
m_Reply(std::move(pkt));
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Stop() override{};
|
|
|
|
|
|
|
|
std::optional<SockAddr>
|
|
|
|
BoundOn() const override
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WouldLoop(const SockAddr& to, const SockAddr& from) const override
|
|
|
|
{
|
|
|
|
if constexpr (platform::is_apple)
|
|
|
|
{
|
|
|
|
// DNS on Apple is a bit weird because in order for the NetworkExtension itself to send
|
|
|
|
// data through the tunnel we have to proxy DNS requests through Apple APIs (and so our
|
|
|
|
// actual upstream DNS won't be set in our resolvers, which is why the vanilla WouldLoop
|
|
|
|
// won't work for us). However when active the mac also only queries the main tunnel IP
|
|
|
|
// for DNS, so we consider anything else to be upstream-bound DNS to let it through the
|
|
|
|
// tunnel.
|
|
|
|
return to.getIP() != m_OurIP;
|
|
|
|
}
|
|
|
|
else if (auto maybe_addr = m_Config.m_QueryBind)
|
|
|
|
{
|
|
|
|
const auto& addr = *maybe_addr;
|
|
|
|
// omit traffic to and from our dns socket
|
|
|
|
return addr == to or addr == from;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class TunDNS : public dns::Server
|
|
|
|
{
|
|
|
|
std::optional<SockAddr> m_QueryBind;
|
|
|
|
net::ipaddr_t m_OurIP;
|
|
|
|
TunEndpoint* const m_Endpoint;
|
|
|
|
|
|
|
|
public:
|
|
|
|
std::shared_ptr<dns::PacketSource_Base> PacketSource;
|
|
|
|
|
|
|
|
virtual ~TunDNS() = default;
|
|
|
|
|
|
|
|
explicit TunDNS(TunEndpoint* ep, const llarp::DnsConfig& conf)
|
|
|
|
: dns::Server{ep->router()->loop(), conf, 0}
|
|
|
|
, m_QueryBind{conf.m_QueryBind}
|
|
|
|
, m_OurIP{ToNet(ep->GetIfAddr())}
|
|
|
|
, m_Endpoint{ep}
|
|
|
|
{}
|
|
|
|
|
|
|
|
std::shared_ptr<dns::PacketSource_Base>
|
|
|
|
MakePacketSourceOn(const SockAddr&, const llarp::DnsConfig& conf) override
|
|
|
|
{
|
|
|
|
auto ptr = std::make_shared<DnsInterceptor>(
|
|
|
|
[ep = m_Endpoint](auto pkt) {
|
|
|
|
ep->HandleWriteIPPacket(pkt.ConstBuffer(), pkt.srcv6(), pkt.dstv6(), 0);
|
|
|
|
},
|
|
|
|
m_OurIP,
|
|
|
|
conf);
|
|
|
|
PacketSource = ptr;
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TunEndpoint::TunEndpoint(Router* r, service::Context* parent) : service::Endpoint{r, parent}
|
|
|
|
{
|
|
|
|
m_PacketRouter = std::make_shared<vpn::PacketRouter>(
|
|
|
|
[this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); });
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TunEndpoint::SetupDNS()
|
|
|
|
{
|
|
|
|
const auto& info = GetVPNInterface()->Info();
|
|
|
|
if (m_DnsConfig.m_raw_dns)
|
|
|
|
{
|
|
|
|
auto dns = std::make_shared<TunDNS>(this, m_DnsConfig);
|
|
|
|
m_DNS = dns;
|
|
|
|
|
|
|
|
m_PacketRouter->AddUDPHandler(huint16_t{53}, [this, dns](net::IPPacket pkt) {
|
|
|
|
auto dns_pkt_src = dns->PacketSource;
|
|
|
|
if (const auto& reply = pkt.reply)
|
|
|
|
dns_pkt_src = std::make_shared<dns::PacketSource_Wrapper>(dns_pkt_src, reply);
|
|
|
|
if (dns->MaybeHandlePacket(
|
|
|
|
std::move(dns_pkt_src), pkt.dst(), pkt.src(), *pkt.L4OwnedBuffer()))
|
|
|
|
return;
|
|
|
|
|
|
|
|
HandleGotUserPacket(std::move(pkt));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_DNS = std::make_shared<dns::Server>(Loop(), m_DnsConfig, info.index);
|
|
|
|
|
|
|
|
m_DNS->AddResolver(weak_from_this());
|
|
|
|
m_DNS->Start();
|
|
|
|
|
|
|
|
if (m_DnsConfig.m_raw_dns)
|
|
|
|
{
|
|
|
|
if (auto vpn = router()->vpn_platform())
|
|
|
|
{
|
|
|
|
// get the first local address we know of
|
|
|
|
std::optional<SockAddr> localaddr;
|
|
|
|
for (auto res : m_DNS->GetAllResolvers())
|
|
|
|
{
|
|
|
|
if (auto ptr = res.lock())
|
|
|
|
{
|
|
|
|
localaddr = ptr->GetLocalAddr();
|
|
|
|
if (localaddr)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (platform::is_windows)
|
|
|
|
{
|
|
|
|
auto dns_io = vpn->create_packet_io(0, localaddr);
|
|
|
|
router()->loop()->add_ticker([r = router(), dns_io, handler = m_PacketRouter]() {
|
|
|
|
net::IPPacket pkt = dns_io->ReadNextPacket();
|
|
|
|
while (not pkt.empty())
|
|
|
|
{
|
|
|
|
handler->HandleIPPacket(std::move(pkt));
|
|
|
|
pkt = dns_io->ReadNextPacket();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
m_RawDNS = dns_io;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_RawDNS)
|
|
|
|
m_RawDNS->Start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
util::StatusObject
|
|
|
|
TunEndpoint::ExtractStatus() const
|
|
|
|
{
|
|
|
|
auto obj = service::Endpoint::ExtractStatus();
|
|
|
|
obj["ifaddr"] = m_OurRange.ToString();
|
|
|
|
obj["ifname"] = m_IfName;
|
|
|
|
|
|
|
|
std::vector<std::string> upstreamRes;
|
|
|
|
for (const auto& ent : m_DnsConfig.m_upstreamDNS)
|
|
|
|
upstreamRes.emplace_back(ent.ToString());
|
|
|
|
obj["ustreamResolvers"] = upstreamRes;
|
|
|
|
|
|
|
|
std::vector<std::string> localRes;
|
|
|
|
for (const auto& ent : m_DnsConfig.m_bind)
|
|
|
|
localRes.emplace_back(ent.ToString());
|
|
|
|
obj["localResolvers"] = localRes;
|
|
|
|
|
|
|
|
// for backwards compat
|
|
|
|
if (not m_DnsConfig.m_bind.empty())
|
|
|
|
obj["localResolver"] = localRes[0];
|
|
|
|
|
|
|
|
util::StatusObject ips{};
|
|
|
|
for (const auto& item : m_IPActivity)
|
|
|
|
{
|
|
|
|
util::StatusObject ipObj{{"lastActive", to_json(item.second)}};
|
|
|
|
std::string remoteStr;
|
|
|
|
AlignedBuffer<32> addr = m_IPToAddr.at(item.first);
|
|
|
|
if (m_SNodes.at(addr))
|
|
|
|
remoteStr = RouterID(addr.as_array()).ToString();
|
|
|
|
else
|
|
|
|
remoteStr = service::Address(addr.as_array()).ToString();
|
|
|
|
ipObj["remote"] = remoteStr;
|
|
|
|
std::string ipaddr = item.first.ToString();
|
|
|
|
ips[ipaddr] = ipObj;
|
|
|
|
}
|
|
|
|
obj["addrs"] = ips;
|
|
|
|
obj["ourIP"] = m_OurIP.ToString();
|
|
|
|
obj["nextIP"] = m_NextIP.ToString();
|
|
|
|
obj["maxIP"] = m_MaxIP.ToString();
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TunEndpoint::Thaw()
|
|
|
|
{
|
|
|
|
if (m_DNS)
|
|
|
|
m_DNS->Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TunEndpoint::ReconfigureDNS(std::vector<SockAddr> servers)
|
|
|
|
{
|
|
|
|
if (m_DNS)
|
|
|
|
{
|
|
|
|
for (auto weak : m_DNS->GetAllResolvers())
|
|
|
|
{
|
|
|
|
if (auto ptr = weak.lock())
|
|
|
|
ptr->ResetResolver(servers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::Configure(const NetworkConfig& conf, const DnsConfig& dnsConf)
|
|
|
|
{
|
|
|
|
if (conf.m_reachable)
|
|
|
|
{
|
|
|
|
_publish_introset = true;
|
|
|
|
LogInfo(Name(), " setting to be reachable by default");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_publish_introset = false;
|
|
|
|
LogInfo(Name(), " setting to be not reachable by default");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (conf.m_AuthType == service::AuthType::eAuthTypeFile)
|
|
|
|
{
|
|
|
|
_auth_policy = service::MakeFileAuthPolicy(router(), conf.m_AuthFiles, conf.m_AuthFileType);
|
|
|
|
}
|
|
|
|
else if (conf.m_AuthType != service::AuthType::eAuthTypeNone)
|
|
|
|
{
|
|
|
|
std::string url, method;
|
|
|
|
if (conf.m_AuthUrl.has_value() and conf.m_AuthMethod.has_value())
|
|
|
|
{
|
|
|
|
url = *conf.m_AuthUrl;
|
|
|
|
method = *conf.m_AuthMethod;
|
|
|
|
}
|
|
|
|
auto auth = std::make_shared<rpc::EndpointAuthRPC>(
|
|
|
|
url,
|
|
|
|
method,
|
|
|
|
conf.m_AuthWhitelist,
|
|
|
|
conf.m_AuthStaticTokens,
|
|
|
|
router()->lmq(),
|
|
|
|
shared_from_this());
|
|
|
|
auth->Start();
|
|
|
|
_auth_policy = std::move(auth);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_DnsConfig = dnsConf;
|
|
|
|
m_TrafficPolicy = conf.m_TrafficPolicy;
|
|
|
|
m_OwnedRanges = conf.m_OwnedRanges;
|
|
|
|
|
|
|
|
m_BaseV6Address = conf.m_baseV6Address;
|
|
|
|
|
|
|
|
if (conf.m_PathAlignmentTimeout)
|
|
|
|
{
|
|
|
|
m_PathAlignmentTimeout = *conf.m_PathAlignmentTimeout;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_PathAlignmentTimeout = service::Endpoint::PathAlignmentTimeout();
|
|
|
|
|
|
|
|
for (const auto& item : conf.m_mapAddrs)
|
|
|
|
{
|
|
|
|
if (not MapAddress(item.second, item.first, false))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_IfName = conf.m_ifname;
|
|
|
|
if (m_IfName.empty())
|
|
|
|
{
|
|
|
|
const auto maybe = router()->net().FindFreeTun();
|
|
|
|
if (not maybe.has_value())
|
|
|
|
throw std::runtime_error("cannot find free interface name");
|
|
|
|
m_IfName = *maybe;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_OurRange = conf.m_ifaddr;
|
|
|
|
if (!m_OurRange.addr.h)
|
|
|
|
{
|
|
|
|
const auto maybe = router()->net().FindFreeRange();
|
|
|
|
if (not maybe.has_value())
|
|
|
|
{
|
|
|
|
throw std::runtime_error("cannot find free address range");
|
|
|
|
}
|
|
|
|
m_OurRange = *maybe;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_OurIP = m_OurRange.addr;
|
|
|
|
m_UseV6 = false;
|
|
|
|
|
|
|
|
m_PersistAddrMapFile = conf.m_AddrMapPersistFile;
|
|
|
|
if (m_PersistAddrMapFile)
|
|
|
|
{
|
|
|
|
const auto& file = *m_PersistAddrMapFile;
|
|
|
|
if (fs::exists(file))
|
|
|
|
{
|
|
|
|
bool shouldLoadFile = true;
|
|
|
|
{
|
|
|
|
constexpr auto LastModifiedWindow = 1min;
|
|
|
|
const auto lastmodified = fs::last_write_time(file);
|
|
|
|
const auto now = decltype(lastmodified)::clock::now();
|
|
|
|
if (now < lastmodified or now - lastmodified > LastModifiedWindow)
|
|
|
|
{
|
|
|
|
shouldLoadFile = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::vector<char> data;
|
|
|
|
if (auto maybe = util::OpenFileStream<fs::ifstream>(file, std::ios_base::binary);
|
|
|
|
maybe and shouldLoadFile)
|
|
|
|
{
|
|
|
|
LogInfo(Name(), " loading address map file from ", file);
|
|
|
|
maybe->seekg(0, std::ios_base::end);
|
|
|
|
const size_t len = maybe->tellg();
|
|
|
|
maybe->seekg(0, std::ios_base::beg);
|
|
|
|
data.resize(len);
|
|
|
|
LogInfo(Name(), " reading ", len, " bytes");
|
|
|
|
maybe->read(data.data(), data.size());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (shouldLoadFile)
|
|
|
|
{
|
|
|
|
LogInfo(Name(), " address map file ", file, " does not exist, so we won't load it");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
LogInfo(Name(), " address map file ", file, " not loaded because it's stale");
|
|
|
|
}
|
|
|
|
if (not data.empty())
|
|
|
|
{
|
|
|
|
std::string_view bdata{data.data(), data.size()};
|
|
|
|
LogDebug(Name(), " parsing address map data: ", bdata);
|
|
|
|
const auto parsed = oxenc::bt_deserialize<oxenc::bt_dict>(bdata);
|
|
|
|
for (const auto& [key, value] : parsed)
|
|
|
|
{
|
|
|
|
huint128_t ip{};
|
|
|
|
if (not ip.FromString(key))
|
|
|
|
{
|
|
|
|
LogWarn(Name(), " malformed IP in addr map data: ", key);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (m_OurIP == ip)
|
|
|
|
continue;
|
|
|
|
if (not m_OurRange.Contains(ip))
|
|
|
|
{
|
|
|
|
LogWarn(Name(), " out of range IP in addr map data: ", ip);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
EndpointBase::AddressVariant_t addr;
|
|
|
|
|
|
|
|
if (const auto* str = std::get_if<std::string>(&value))
|
|
|
|
{
|
|
|
|
if (auto maybe = service::parse_address(*str))
|
|
|
|
{
|
|
|
|
addr = *maybe;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LogWarn(Name(), " invalid address in addr map: ", *str);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LogWarn(Name(), " invalid first entry in addr map, not a string");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (const auto* loki = std::get_if<service::Address>(&addr))
|
|
|
|
{
|
|
|
|
m_IPToAddr.emplace(ip, loki->data());
|
|
|
|
m_AddrToIP.emplace(loki->data(), ip);
|
|
|
|
m_SNodes[*loki] = false;
|
|
|
|
LogInfo(Name(), " remapped ", ip, " to ", *loki);
|
|
|
|
}
|
|
|
|
if (const auto* snode = std::get_if<RouterID>(&addr))
|
|
|
|
{
|
|
|
|
m_IPToAddr.emplace(ip, snode->data());
|
|
|
|
m_AddrToIP.emplace(snode->data(), ip);
|
|
|
|
m_SNodes[*snode] = true;
|
|
|
|
LogInfo(Name(), " remapped ", ip, " to ", *snode);
|
|
|
|
}
|
|
|
|
if (m_NextIP < ip)
|
|
|
|
m_NextIP = ip;
|
|
|
|
// make sure we dont unmap this guy
|
|
|
|
MarkIPActive(ip);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LogInfo(Name(), " skipping loading addr map at ", file, " as it does not currently exist");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (auto* quic = GetQUICTunnel())
|
|
|
|
{
|
|
|
|
quic->listen([this](std::string_view, uint16_t port) {
|
|
|
|
return llarp::SockAddr{net::TruncateV6(GetIfAddr()), huint16_t{port}};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return Endpoint::Configure(conf, dnsConf);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::HasLocalIP(const huint128_t& ip) const
|
|
|
|
{
|
|
|
|
return m_IPToAddr.find(ip) != m_IPToAddr.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TunEndpoint::Pump(llarp_time_t now)
|
|
|
|
{
|
|
|
|
// flush network to user
|
|
|
|
while (not m_NetworkToUserPktQueue.empty())
|
|
|
|
{
|
|
|
|
m_NetIf->WritePacket(m_NetworkToUserPktQueue.top().pkt);
|
|
|
|
m_NetworkToUserPktQueue.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
service::Endpoint::Pump(now);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
is_random_snode(const dns::Message& msg)
|
|
|
|
{
|
|
|
|
return msg.questions[0].IsName("random.snode");
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
is_localhost_loki(const dns::Message& msg)
|
|
|
|
{
|
|
|
|
return msg.questions[0].IsLocalhost();
|
|
|
|
}
|
|
|
|
|
|
|
|
static dns::Message&
|
|
|
|
clear_dns_message(dns::Message& msg)
|
|
|
|
{
|
|
|
|
msg.authorities.resize(0);
|
|
|
|
msg.additional.resize(0);
|
|
|
|
msg.answers.resize(0);
|
|
|
|
msg.hdr_fields &= ~dns::flags_RCODENameError;
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<std::variant<service::Address, RouterID>>
|
|
|
|
TunEndpoint::ObtainAddrForIP(huint128_t ip) const
|
|
|
|
{
|
|
|
|
auto itr = m_IPToAddr.find(ip);
|
|
|
|
if (itr == m_IPToAddr.end())
|
|
|
|
return std::nullopt;
|
|
|
|
if (m_SNodes.at(itr->second))
|
|
|
|
return RouterID{itr->second.as_array()};
|
|
|
|
else
|
|
|
|
return service::Address{itr->second.as_array()};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)> reply)
|
|
|
|
{
|
|
|
|
auto ReplyToSNodeDNSWhenReady = [this, reply](RouterID snode, auto msg, bool isV6) -> bool {
|
|
|
|
return EnsurePathToSNode(
|
|
|
|
snode,
|
|
|
|
[this, snode, msg, reply, isV6](
|
|
|
|
const RouterID&, exit::BaseSession_ptr s, [[maybe_unused]] service::ConvoTag tag) {
|
|
|
|
SendDNSReply(snode, s, msg, reply, isV6);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
auto ReplyToLokiDNSWhenReady = [this, reply, timeout = PathAlignmentTimeout()](
|
|
|
|
service::Address addr, auto msg, bool isV6) -> bool {
|
|
|
|
using service::Address;
|
|
|
|
using service::OutboundContext;
|
|
|
|
if (HasInboundConvo(addr))
|
|
|
|
{
|
|
|
|
// if we have an inbound convo to this address don't mark as outbound so we don't have a
|
|
|
|
// state race this codepath is hit when an application verifies that reverse and forward
|
|
|
|
// dns records match for an inbound session
|
|
|
|
SendDNSReply(addr, this, msg, reply, isV6);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
MarkAddressOutbound(addr);
|
|
|
|
return EnsurePathToService(
|
|
|
|
addr,
|
|
|
|
[this, addr, msg, reply, isV6](const Address&, OutboundContext* ctx) {
|
|
|
|
SendDNSReply(addr, ctx, msg, reply, isV6);
|
|
|
|
},
|
|
|
|
timeout);
|
|
|
|
};
|
|
|
|
|
|
|
|
auto ReplyToDNSWhenReady = [ReplyToLokiDNSWhenReady, ReplyToSNodeDNSWhenReady](
|
|
|
|
std::string name, auto msg, bool isV6) {
|
|
|
|
if (auto saddr = service::Address(); saddr.FromString(name))
|
|
|
|
ReplyToLokiDNSWhenReady(saddr, msg, isV6);
|
|
|
|
|
|
|
|
if (auto rid = RouterID(); rid.FromString(name))
|
|
|
|
ReplyToSNodeDNSWhenReady(rid, msg, isV6);
|
|
|
|
};
|
|
|
|
|
|
|
|
auto ReplyToLokiSRVWhenReady = [this, reply, timeout = PathAlignmentTimeout()](
|
|
|
|
service::Address addr, auto msg) -> bool {
|
|
|
|
using service::Address;
|
|
|
|
using service::OutboundContext;
|
|
|
|
// TODO: how do we handle SRV record lookups for inbound sessions?
|
|
|
|
MarkAddressOutbound(addr);
|
|
|
|
return EnsurePathToService(
|
|
|
|
addr,
|
|
|
|
[msg, addr, reply](const Address&, OutboundContext* ctx) {
|
|
|
|
if (ctx == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const auto& introset = ctx->GetCurrentIntroSet();
|
|
|
|
msg->AddSRVReply(introset.GetMatchingSRVRecords(addr.subdomain));
|
|
|
|
reply(*msg);
|
|
|
|
},
|
|
|
|
timeout);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (msg.answers.size() > 0)
|
|
|
|
{
|
|
|
|
const auto& answer = msg.answers[0];
|
|
|
|
if (answer.HasCNameForTLD(".snode"))
|
|
|
|
{
|
|
|
|
llarp_buffer_t buf(answer.rData);
|
|
|
|
auto qname = dns::DecodeName(&buf, true);
|
|
|
|
if (not qname)
|
|
|
|
return false;
|
|
|
|
RouterID addr;
|
|
|
|
if (not addr.FromString(*qname))
|
|
|
|
return false;
|
|
|
|
auto replyMsg = std::make_shared<dns::Message>(clear_dns_message(msg));
|
|
|
|
return ReplyToSNodeDNSWhenReady(addr, std::move(replyMsg), false);
|
|
|
|
}
|
|
|
|
else if (answer.HasCNameForTLD(".loki"))
|
|
|
|
{
|
|
|
|
llarp_buffer_t buf(answer.rData);
|
|
|
|
auto qname = dns::DecodeName(&buf, true);
|
|
|
|
if (not qname)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
service::Address addr;
|
|
|
|
if (not addr.FromString(*qname))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto replyMsg = std::make_shared<dns::Message>(clear_dns_message(msg));
|
|
|
|
return ReplyToLokiDNSWhenReady(addr, replyMsg, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (msg.questions.size() != 1)
|
|
|
|
{
|
|
|
|
llarp::LogWarn("bad number of dns questions: ", msg.questions.size());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
std::string qname = msg.questions[0].Name();
|
|
|
|
const auto nameparts = split(qname, ".");
|
|
|
|
std::string ons_name;
|
|
|
|
if (nameparts.size() >= 2 and ends_with(qname, ".loki"))
|
|
|
|
{
|
|
|
|
ons_name = nameparts[nameparts.size() - 2];
|
|
|
|
ons_name += ".loki"sv;
|
|
|
|
}
|
|
|
|
if (msg.questions[0].qtype == dns::qTypeTXT)
|
|
|
|
{
|
|
|
|
RouterID snode;
|
|
|
|
if (snode.FromString(qname))
|
|
|
|
{
|
|
|
|
router()->lookup_router(
|
|
|
|
snode, [r = router(), msg = std::move(msg), reply](oxen::quic::message m) mutable {
|
|
|
|
if (m)
|
|
|
|
{
|
|
|
|
std::string payload;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
oxenc::bt_dict_consumer btdc{m.body()};
|
|
|
|
payload = btdc.require<std::string>("RC");
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
log::warning(link_cat, "Failed to parse Find Router response!");
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
r->node_db()->put_rc_if_newer(RouterContact{payload});
|
|
|
|
msg.AddTXTReply(payload);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddNXReply();
|
|
|
|
r->link_manager().handle_find_router_error(std::move(m));
|
|
|
|
}
|
|
|
|
|
|
|
|
reply(msg);
|
|
|
|
});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg.questions[0].IsLocalhost() and msg.questions[0].HasSubdomains())
|
|
|
|
{
|
|
|
|
const auto subdomain = msg.questions[0].Subdomains();
|
|
|
|
if (subdomain == "exit")
|
|
|
|
{
|
|
|
|
if (HasExit())
|
|
|
|
{
|
|
|
|
std::string s;
|
|
|
|
_exit_map.ForEachEntry([&s](const auto& range, const auto& exit) {
|
|
|
|
fmt::format_to(std::back_inserter(s), "{}={}; ", range, exit);
|
|
|
|
});
|
|
|
|
msg.AddTXTReply(std::move(s));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (subdomain == "netid")
|
|
|
|
{
|
|
|
|
msg.AddTXTReply(fmt::format("netid={};", router()->rc().netID));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
|
|
|
|
reply(msg);
|
|
|
|
}
|
|
|
|
else if (msg.questions[0].qtype == dns::qTypeMX)
|
|
|
|
{
|
|
|
|
// mx record
|
|
|
|
service::Address addr;
|
|
|
|
if (addr.FromString(qname, ".loki") || addr.FromString(qname, ".snode")
|
|
|
|
|| is_random_snode(msg) || is_localhost_loki(msg))
|
|
|
|
{
|
|
|
|
msg.AddMXReply(qname, 1);
|
|
|
|
}
|
|
|
|
else if (service::is_valid_name(ons_name))
|
|
|
|
{
|
|
|
|
lookup_name(ons_name, [msg, ons_name, reply](oxen::quic::message m) mutable {
|
|
|
|
if (m)
|
|
|
|
{
|
|
|
|
std::string result;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
oxenc::bt_dict_consumer btdc{m.body()};
|
|
|
|
result = btdc.require<std::string>("NAME");
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
log::warning(log_cat, "Failed to parse find name response!");
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.AddMXReply(result, 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
msg.AddNXReply();
|
|
|
|
|
|
|
|
reply(msg);
|
|
|
|
});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
msg.AddNXReply();
|
|
|
|
reply(msg);
|
|
|
|
}
|
|
|
|
else if (msg.questions[0].qtype == dns::qTypeCNAME)
|
|
|
|
{
|
|
|
|
if (is_random_snode(msg))
|
|
|
|
{
|
|
|
|
RouterID random;
|
|
|
|
if (router()->GetRandomGoodRouter(random))
|
|
|
|
{
|
|
|
|
msg.AddCNAMEReply(random.ToString(), 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
else if (msg.questions[0].IsLocalhost() and msg.questions[0].HasSubdomains())
|
|
|
|
{
|
|
|
|
const auto subdomain = msg.questions[0].Subdomains();
|
|
|
|
if (subdomain == "exit" and HasExit())
|
|
|
|
{
|
|
|
|
_exit_map.ForEachEntry(
|
|
|
|
[&msg](const auto&, const auto& exit) { msg.AddCNAMEReply(exit.ToString(), 1); });
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (is_localhost_loki(msg))
|
|
|
|
{
|
|
|
|
size_t counter = 0;
|
|
|
|
context->ForEachService(
|
|
|
|
[&](const std::string&, const std::shared_ptr<service::Endpoint>& service) -> bool {
|
|
|
|
const service::Address addr = service->GetIdentity().pub.Addr();
|
|
|
|
msg.AddCNAMEReply(addr.ToString(), 1);
|
|
|
|
++counter;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
if (counter == 0)
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
msg.AddNXReply();
|
|
|
|
reply(msg);
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
llarp::service::Address addr;
|
|
|
|
if (isV6 && !SupportsV6())
|
|
|
|
{ // empty reply but not a NXDOMAIN so that client can retry IPv4
|
|
|
|
msg.AddNSReply("localhost.loki.");
|
|
|
|
}
|
|
|
|
// on MacOS this is a typeA query
|
|
|
|
else if (is_random_snode(msg))
|
|
|
|
{
|
|
|
|
RouterID random;
|
|
|
|
if (router()->GetRandomGoodRouter(random))
|
|
|
|
{
|
|
|
|
msg.AddCNAMEReply(random.ToString(), 1);
|
|
|
|
return ReplyToSNodeDNSWhenReady(random, std::make_shared<dns::Message>(msg), isV6);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
else if (is_localhost_loki(msg))
|
|
|
|
{
|
|
|
|
const bool lookingForExit = msg.questions[0].Subdomains() == "exit";
|
|
|
|
huint128_t ip = GetIfAddr();
|
|
|
|
if (ip.h)
|
|
|
|
{
|
|
|
|
if (lookingForExit)
|
|
|
|
{
|
|
|
|
if (HasExit())
|
|
|
|
{
|
|
|
|
_exit_map.ForEachEntry(
|
|
|
|
[&msg](const auto&, const auto& exit) { msg.AddCNAMEReply(exit.ToString()); });
|
|
|
|
msg.AddINReply(ip, isV6);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddCNAMEReply(_identity.pub.Name(), 1);
|
|
|
|
msg.AddINReply(ip, isV6);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddNXReply();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (addr.FromString(qname, ".loki"))
|
|
|
|
{
|
|
|
|
if (isV4 && SupportsV6())
|
|
|
|
{
|
|
|
|
msg.hdr_fields |= dns::flags_QR | dns::flags_AA | dns::flags_RA;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return ReplyToLokiDNSWhenReady(addr, std::make_shared<dns::Message>(msg), isV6);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (addr.FromString(qname, ".snode"))
|
|
|
|
{
|
|
|
|
if (isV4 && SupportsV6())
|
|
|
|
{
|
|
|
|
msg.hdr_fields |= dns::flags_QR | dns::flags_AA | dns::flags_RA;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return ReplyToSNodeDNSWhenReady(
|
|
|
|
addr.as_array(), std::make_shared<dns::Message>(msg), isV6);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (service::is_valid_name(ons_name))
|
|
|
|
{
|
|
|
|
lookup_name(
|
|
|
|
ons_name,
|
|
|
|
[msg = std::make_shared<dns::Message>(msg),
|
|
|
|
name = Name(),
|
|
|
|
ons_name,
|
|
|
|
isV6,
|
|
|
|
reply,
|
|
|
|
ReplyToDNSWhenReady](oxen::quic::message m) mutable {
|
|
|
|
if (m)
|
|
|
|
{
|
|
|
|
std::string name;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
oxenc::bt_dict_consumer btdc{m.body()};
|
|
|
|
name = btdc.require<std::string>("NAME");
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
log::warning(log_cat, "Failed to parse find name response!");
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReplyToDNSWhenReady(name, msg, isV6);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
log::warning(log_cat, "{} (ONS name: {}) not resolved", name, ons_name);
|
|
|
|
msg->AddNXReply();
|
|
|
|
reply(*msg);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
msg.AddNXReply();
|
|
|
|
|
|
|
|
reply(msg);
|
|
|
|
}
|
|
|
|
else if (msg.questions[0].qtype == dns::qTypePTR)
|
|
|
|
{
|
|
|
|
// reverse dns
|
|
|
|
if (auto ip = dns::DecodePTR(msg.questions[0].qname))
|
|
|
|
{
|
|
|
|
if (auto maybe = ObtainAddrForIP(*ip))
|
|
|
|
{
|
|
|
|
var::visit([&msg](auto&& result) { msg.AddAReply(result.ToString()); }, *maybe);
|
|
|
|
reply(msg);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.AddNXReply();
|
|
|
|
reply(msg);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (msg.questions[0].qtype == dns::qTypeSRV)
|
|
|
|
{
|
|
|
|
auto srv_for = msg.questions[0].Subdomains();
|
|
|
|
auto name = msg.questions[0].qname;
|
|
|
|
if (is_localhost_loki(msg))
|
|
|
|
{
|
|
|
|
msg.AddSRVReply(intro_set().GetMatchingSRVRecords(srv_for));
|
|
|
|
reply(msg);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
LookupServiceAsync(
|
|
|
|
name,
|
|
|
|
srv_for,
|
|
|
|
[reply, msg = std::make_shared<dns::Message>(std::move(msg))](auto records) {
|
|
|
|
if (records.empty())
|
|
|
|
{
|
|
|
|
msg->AddNXReply();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg->AddSRVReply(records);
|
|
|
|
}
|
|
|
|
reply(*msg);
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
msg.AddNXReply();
|
|
|
|
reply(msg);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TunEndpoint::ResetInternalState()
|
|
|
|
{
|
|
|
|
service::Endpoint::ResetInternalState();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::SupportsV6() const
|
|
|
|
{
|
|
|
|
return m_UseV6;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: pass in which question it should be addressing
|
|
|
|
bool
|
|
|
|
TunEndpoint::ShouldHookDNSMessage(const dns::Message& msg) const
|
|
|
|
{
|
|
|
|
llarp::service::Address addr;
|
|
|
|
if (msg.questions.size() == 1)
|
|
|
|
{
|
|
|
|
/// hook every .loki
|
|
|
|
if (msg.questions[0].HasTLD(".loki"))
|
|
|
|
return true;
|
|
|
|
/// hook every .snode
|
|
|
|
if (msg.questions[0].HasTLD(".snode"))
|
|
|
|
return true;
|
|
|
|
// hook any ranges we own
|
|
|
|
if (msg.questions[0].qtype == llarp::dns::qTypePTR)
|
|
|
|
{
|
|
|
|
if (auto ip = dns::DecodePTR(msg.questions[0].qname))
|
|
|
|
return m_OurRange.Contains(*ip);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const auto& answer : msg.answers)
|
|
|
|
{
|
|
|
|
if (answer.HasCNameForTLD(".loki"))
|
|
|
|
return true;
|
|
|
|
if (answer.HasCNameForTLD(".snode"))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::MapAddress(const service::Address& addr, huint128_t ip, bool SNode)
|
|
|
|
{
|
|
|
|
auto itr = m_IPToAddr.find(ip);
|
|
|
|
if (itr != m_IPToAddr.end())
|
|
|
|
{
|
|
|
|
llarp::LogWarn(
|
|
|
|
ip, " already mapped to ", service::Address(itr->second.as_array()).ToString());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
llarp::LogInfo(Name() + " map ", addr.ToString(), " to ", ip);
|
|
|
|
|
|
|
|
m_IPToAddr[ip] = addr;
|
|
|
|
m_AddrToIP[addr] = ip;
|
|
|
|
m_SNodes[addr] = SNode;
|
|
|
|
MarkIPActiveForever(ip);
|
|
|
|
MarkAddressOutbound(addr);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string
|
|
|
|
TunEndpoint::GetIfName() const
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
return net::TruncateV6(GetIfAddr()).ToString();
|
|
|
|
#else
|
|
|
|
return m_IfName;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::Start()
|
|
|
|
{
|
|
|
|
if (not Endpoint::Start())
|
|
|
|
return false;
|
|
|
|
return SetupNetworking();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::IsSNode() const
|
|
|
|
{
|
|
|
|
// TODO : implement me
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::SetupTun()
|
|
|
|
{
|
|
|
|
m_NextIP = m_OurIP;
|
|
|
|
m_MaxIP = m_OurRange.HighestAddr();
|
|
|
|
llarp::LogInfo(Name(), " set ", m_IfName, " to have address ", m_OurIP);
|
|
|
|
llarp::LogInfo(Name(), " allocated up to ", m_MaxIP, " on range ", m_OurRange);
|
|
|
|
|
|
|
|
const service::Address ourAddr = _identity.pub.Addr();
|
|
|
|
|
|
|
|
if (not MapAddress(ourAddr, GetIfAddr(), false))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
vpn::InterfaceInfo info;
|
|
|
|
info.addrs.emplace_back(m_OurRange);
|
|
|
|
|
|
|
|
if (m_BaseV6Address)
|
|
|
|
{
|
|
|
|
IPRange v6range = m_OurRange;
|
|
|
|
v6range.addr = (*m_BaseV6Address) | m_OurRange.addr;
|
|
|
|
LogInfo(Name(), " using v6 range: ", v6range);
|
|
|
|
info.addrs.emplace_back(v6range, AF_INET6);
|
|
|
|
}
|
|
|
|
|
|
|
|
info.ifname = m_IfName;
|
|
|
|
|
|
|
|
LogInfo(Name(), " setting up network...");
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
m_NetIf = router()->vpn_platform()->CreateInterface(std::move(info), router());
|
|
|
|
}
|
|
|
|
catch (std::exception& ex)
|
|
|
|
{
|
|
|
|
LogError(Name(), " failed to set up network interface: ", ex.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_IfName = m_NetIf->Info().ifname;
|
|
|
|
LogInfo(Name(), " got network interface ", m_IfName);
|
|
|
|
|
|
|
|
auto handle_packet = [netif = m_NetIf, pkt_router = m_PacketRouter](auto pkt) {
|
|
|
|
pkt.reply = [netif](auto pkt) { netif->WritePacket(std::move(pkt)); };
|
|
|
|
pkt_router->HandleIPPacket(std::move(pkt));
|
|
|
|
};
|
|
|
|
|
|
|
|
if (not router()->loop()->add_network_interface(m_NetIf, std::move(handle_packet)))
|
|
|
|
{
|
|
|
|
LogError(Name(), " failed to add network interface");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_OurIPv6 = llarp::huint128_t{
|
|
|
|
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(m_OurRange.addr).h}};
|
|
|
|
|
|
|
|
if constexpr (not llarp::platform::is_apple)
|
|
|
|
{
|
|
|
|
if (auto maybe = router()->net().GetInterfaceIPv6Address(m_IfName))
|
|
|
|
{
|
|
|
|
m_OurIPv6 = *maybe;
|
|
|
|
LogInfo(Name(), " has ipv6 address ", m_OurIPv6);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LogInfo(Name(), " setting up dns...");
|
|
|
|
SetupDNS();
|
|
|
|
Loop()->call_soon([this]() { router()->route_poker()->set_dns_mode(false); });
|
|
|
|
return HasAddress(ourAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unordered_map<std::string, std::string>
|
|
|
|
TunEndpoint::NotifyParams() const
|
|
|
|
{
|
|
|
|
auto env = Endpoint::NotifyParams();
|
|
|
|
env.emplace("IP_ADDR", m_OurIP.ToString());
|
|
|
|
env.emplace("IF_ADDR", m_OurRange.ToString());
|
|
|
|
env.emplace("IF_NAME", m_IfName);
|
|
|
|
std::string strictConnect;
|
|
|
|
for (const auto& addr : m_StrictConnectAddrs)
|
|
|
|
strictConnect += addr.ToString() + " ";
|
|
|
|
env.emplace("STRICT_CONNECT_ADDRS", strictConnect);
|
|
|
|
return env;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::SetupNetworking()
|
|
|
|
{
|
|
|
|
llarp::LogInfo("Set Up networking for ", Name());
|
|
|
|
return SetupTun();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TunEndpoint::Tick(llarp_time_t now)
|
|
|
|
{
|
|
|
|
Endpoint::Tick(now);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::Stop()
|
|
|
|
{
|
|
|
|
// stop vpn tunnel
|
|
|
|
if (m_NetIf)
|
|
|
|
m_NetIf->Stop();
|
|
|
|
if (m_RawDNS)
|
|
|
|
m_RawDNS->Stop();
|
|
|
|
// save address map if applicable
|
|
|
|
if (m_PersistAddrMapFile and not platform::is_android)
|
|
|
|
{
|
|
|
|
const auto& file = *m_PersistAddrMapFile;
|
|
|
|
LogInfo(Name(), " saving address map to ", file);
|
|
|
|
if (auto maybe = util::OpenFileStream<fs::ofstream>(file, std::ios_base::binary))
|
|
|
|
{
|
|
|
|
std::map<std::string, std::string> addrmap;
|
|
|
|
for (const auto& [ip, addr] : m_IPToAddr)
|
|
|
|
{
|
|
|
|
if (not m_SNodes.at(addr))
|
|
|
|
{
|
|
|
|
const service::Address a{addr.as_array()};
|
|
|
|
if (HasInboundConvo(a))
|
|
|
|
addrmap[ip.ToString()] = a.ToString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto data = oxenc::bt_serialize(addrmap);
|
|
|
|
maybe->write(data.data(), data.size());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (m_DNS)
|
|
|
|
m_DNS->Stop();
|
|
|
|
return llarp::service::Endpoint::Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<service::Address>
|
|
|
|
TunEndpoint::ObtainExitAddressFor(
|
|
|
|
huint128_t ip,
|
|
|
|
std::function<service::Address(std::unordered_set<service::Address>)> exitSelectionStrat)
|
|
|
|
{
|
|
|
|
// is it already mapped? return the mapping
|
|
|
|
if (auto itr = m_ExitIPToExitAddress.find(ip); itr != m_ExitIPToExitAddress.end())
|
|
|
|
return itr->second;
|
|
|
|
|
|
|
|
const auto& net = router()->net();
|
|
|
|
const bool is_bogon = net.IsBogonIP(ip);
|
|
|
|
// build up our candidates to choose
|
|
|
|
|
|
|
|
std::unordered_set<service::Address> candidates;
|
|
|
|
for (const auto& entry : _exit_map.FindAllEntries(ip))
|
|
|
|
{
|
|
|
|
// in the event the exit's range is a bogon range, make sure the ip is located in that range
|
|
|
|
// to allow it
|
|
|
|
if ((is_bogon and net.IsBogonRange(entry.first) and entry.first.Contains(ip))
|
|
|
|
or entry.first.Contains(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)
|
|
|
|
{
|
|
|
|
huint128_t dst, src;
|
|
|
|
if (pkt.IsV4())
|
|
|
|
{
|
|
|
|
dst = pkt.dst4to6();
|
|
|
|
src = pkt.src4to6();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dst = pkt.dstv6();
|
|
|
|
src = pkt.srcv6();
|
|
|
|
}
|
|
|
|
|
|
|
|
if constexpr (llarp::platform::is_apple)
|
|
|
|
{
|
|
|
|
if (dst == m_OurIP)
|
|
|
|
{
|
|
|
|
HandleWriteIPPacket(pkt.ConstBuffer(), src, dst, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_state->is_exit_enabled)
|
|
|
|
{
|
|
|
|
dst = net::ExpandV4(net::TruncateV6(dst));
|
|
|
|
}
|
|
|
|
|
|
|
|
auto itr = m_IPToAddr.find(dst);
|
|
|
|
|
|
|
|
if (itr == m_IPToAddr.end())
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::function<void(void)> extra_cb;
|
|
|
|
|
|
|
|
if (not HasFlowToService(addr))
|
|
|
|
{
|
|
|
|
extra_cb = [poker = router()->route_poker()]() { poker->put_up(); };
|
|
|
|
}
|
|
|
|
|
|
|
|
pkt.ZeroSourceAddress();
|
|
|
|
MarkAddressOutbound(addr);
|
|
|
|
|
|
|
|
EnsurePathToService(
|
|
|
|
addr,
|
|
|
|
[pkt, extra_cb, this](service::Address addr, service::OutboundContext* ctx) {
|
|
|
|
if (ctx)
|
|
|
|
{
|
|
|
|
if (extra_cb)
|
|
|
|
extra_cb();
|
|
|
|
ctx->SendPacketToRemote(pkt.ConstBuffer(), service::ProtocolType::Exit);
|
|
|
|
router()->TriggerPump();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LogWarn("cannot ensure path to exit ", addr, " so we drop some packets");
|
|
|
|
},
|
|
|
|
PathAlignmentTimeout());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::variant<service::Address, RouterID> to;
|
|
|
|
service::ProtocolType type;
|
|
|
|
if (m_SNodes.at(itr->second))
|
|
|
|
{
|
|
|
|
to = RouterID{itr->second.as_array()};
|
|
|
|
type = service::ProtocolType::TrafficV4;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
to = service::Address{itr->second.as_array()};
|
|
|
|
type = _state->is_exit_enabled and src != m_OurIP ? service::ProtocolType::Exit
|
|
|
|
: pkt.ServiceProtocol();
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare packet for insertion into network
|
|
|
|
// this includes clearing IP addresses, recalculating checksums, etc
|
|
|
|
// this does not happen for exits because the point is they don't rewrite addresses
|
|
|
|
if (type != service::ProtocolType::Exit)
|
|
|
|
{
|
|
|
|
if (pkt.IsV4())
|
|
|
|
pkt.UpdateIPv4Address({0}, {0});
|
|
|
|
else
|
|
|
|
pkt.UpdateIPv6Address({0}, {0});
|
|
|
|
}
|
|
|
|
// try sending it on an existing convotag
|
|
|
|
// this succeds for inbound convos, probably.
|
|
|
|
if (auto maybe = GetBestConvoTagFor(to))
|
|
|
|
{
|
|
|
|
if (send_to(*maybe, pkt.to_string()))
|
|
|
|
{
|
|
|
|
MarkIPActive(dst);
|
|
|
|
router()->TriggerPump();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// try establishing a path to this guy
|
|
|
|
// will fail if it's an inbound convo
|
|
|
|
EnsurePathTo(
|
|
|
|
to,
|
|
|
|
[pkt, dst, to, this](auto maybe) mutable {
|
|
|
|
if (not maybe)
|
|
|
|
{
|
|
|
|
var::visit(
|
|
|
|
[this](auto&& addr) {
|
|
|
|
LogWarn(Name(), " failed to ensure path to ", addr, " no convo tag found");
|
|
|
|
},
|
|
|
|
to);
|
|
|
|
}
|
|
|
|
if (send_to(*maybe, pkt.to_string()))
|
|
|
|
{
|
|
|
|
MarkIPActive(dst);
|
|
|
|
router()->TriggerPump();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
var::visit(
|
|
|
|
[this](auto&& addr) {
|
|
|
|
LogWarn(Name(), " failed to send to ", addr, ", SendToOrQueue failed");
|
|
|
|
},
|
|
|
|
to);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
PathAlignmentTimeout());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::ShouldAllowTraffic(const net::IPPacket& pkt) const
|
|
|
|
{
|
|
|
|
if (const auto exitPolicy = GetExitPolicy())
|
|
|
|
{
|
|
|
|
if (not exitPolicy->AllowsTraffic(pkt))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::HandleInboundPacket(
|
|
|
|
const service::ConvoTag tag,
|
|
|
|
const llarp_buffer_t& buf,
|
|
|
|
service::ProtocolType t,
|
|
|
|
uint64_t seqno)
|
|
|
|
{
|
|
|
|
LogTrace("Inbound ", t, " packet (", buf.sz, "B) on convo ", tag);
|
|
|
|
if (t == service::ProtocolType::QUIC)
|
|
|
|
{
|
|
|
|
auto* quic = GetQUICTunnel();
|
|
|
|
if (!quic)
|
QUIC lokinet integration refactor
Refactors how quic packets get handled: the actual tunnels now live in
tunnel.hpp's TunnelManager which holds and manages all the quic<->tcp
tunnelling. service::Endpoint now holds a TunnelManager rather than a
quic::Server. We only need one quic server, but we need a separate quic
client instance per outgoing quic tunnel, and TunnelManager handles all
that glue now.
Adds QUIC packet handling to get to the right tunnel code. This
required multiplexing incoming quic packets, as follows:
Adds a very small quic tunnel packet header of 4 bytes:
[1, SPORT, ECN] for client->server packets, where SPORT is our
source "port" (really: just a uint16_t unique quic instance
identifier)
or
[2, DPORT, ECN] for server->client packets where the DPORT is the SPORT
from above.
(This also reworks ECN bits to get properly carried over lokinet.)
We don't need a destination/source port for the server-side because
there is only ever one quic server (and we know we're going to it when
the first byte of the header is 1).
Removes the config option for quic exposing ports; a full lokinet will
simply accept anything incoming on quic and tunnel it to the requested
port on the the local endpoint IP (this handler will come in a following
commit).
Replace ConvoTags with full addresses: we need to carry the port, as
well, which the ConvoTag can't give us, so change those to more general
SockAddrs from which we can extract both the ConvoTag *and* the port.
Add a pending connection queue along with new quic-side handlers to call
when a stream becomes available (TunnelManager uses this to wire up
pending incoming conns with quic streams as streams open up).
Completely get rid of tunnel_server/tunnel_client.cpp code; it is now
moved to tunnel.hpp.
Add listen()/forget() methods in TunnelManager for setting up quic
listening sockets (for liblokinet usage).
Add open()/close() methods in TunnelManager for spinning up new quic
clients for outgoing quic connections.
4 years ago
|
|
|
{
|
|
|
|
LogWarn("incoming quic packet but this endpoint is not quic capable; dropping");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (buf.sz < 4)
|
|
|
|
{
|
|
|
|
LogWarn("invalid incoming quic packet, dropping");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
LogInfo("tag active T=", tag);
|
|
|
|
quic->receive_packet(tag, buf);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t != service::ProtocolType::TrafficV4 && t != service::ProtocolType::TrafficV6
|
|
|
|
&& t != service::ProtocolType::Exit)
|
|
|
|
return false;
|
|
|
|
std::variant<service::Address, RouterID> addr;
|
|
|
|
if (auto maybe = GetEndpointWithConvoTag(tag))
|
|
|
|
{
|
|
|
|
addr = *maybe;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
huint128_t src, dst;
|
|
|
|
|
|
|
|
net::IPPacket pkt;
|
|
|
|
if (not pkt.Load(buf))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (_state->is_exit_enabled)
|
|
|
|
{
|
|
|
|
// exit side from exit
|
|
|
|
|
|
|
|
// check packet against exit policy and if as needed
|
|
|
|
if (not ShouldAllowTraffic(pkt))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
src = ObtainIPForAddr(addr);
|
|
|
|
if (t == service::ProtocolType::Exit)
|
|
|
|
{
|
|
|
|
if (pkt.IsV4())
|
|
|
|
dst = pkt.dst4to6();
|
|
|
|
else if (pkt.IsV6())
|
|
|
|
{
|
|
|
|
dst = pkt.dstv6();
|
|
|
|
src = net::ExpandV4Lan(net::TruncateV6(src));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// non exit traffic on exit
|
|
|
|
dst = m_OurIP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (t == service::ProtocolType::Exit)
|
|
|
|
{
|
|
|
|
// client side exit traffic from exit
|
|
|
|
if (pkt.IsV4())
|
|
|
|
{
|
|
|
|
dst = m_OurIP;
|
|
|
|
src = pkt.src4to6();
|
|
|
|
}
|
|
|
|
else if (pkt.IsV6())
|
|
|
|
{
|
|
|
|
dst = m_OurIPv6;
|
|
|
|
src = pkt.srcv6();
|
|
|
|
}
|
|
|
|
// find what exit we think this should be for
|
|
|
|
service::Address fromAddr{};
|
|
|
|
if (const auto* ptr = std::get_if<service::Address>(&addr))
|
|
|
|
{
|
|
|
|
fromAddr = *ptr;
|
|
|
|
}
|
|
|
|
else // don't allow snode
|
|
|
|
return false;
|
|
|
|
// make sure the mapping matches
|
|
|
|
if (auto itr = m_ExitIPToExitAddress.find(src); itr != m_ExitIPToExitAddress.end())
|
|
|
|
{
|
|
|
|
if (itr->second != fromAddr)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// snapp traffic
|
|
|
|
src = ObtainIPForAddr(addr);
|
|
|
|
dst = m_OurIP;
|
|
|
|
}
|
|
|
|
HandleWriteIPPacket(buf, src, dst, seqno);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::HandleWriteIPPacket(
|
|
|
|
const llarp_buffer_t& b, huint128_t src, huint128_t dst, uint64_t seqno)
|
|
|
|
{
|
|
|
|
ManagedBuffer buf(b);
|
|
|
|
WritePacket write;
|
|
|
|
write.seqno = seqno;
|
|
|
|
auto& pkt = write.pkt;
|
|
|
|
// load
|
|
|
|
if (!pkt.Load(buf))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (pkt.IsV4())
|
|
|
|
{
|
|
|
|
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(src)), xhtonl(net::TruncateV6(dst)));
|
|
|
|
}
|
|
|
|
else if (pkt.IsV6())
|
|
|
|
{
|
|
|
|
pkt.UpdateIPv6Address(src, dst);
|
|
|
|
}
|
|
|
|
m_NetworkToUserPktQueue.push(std::move(write));
|
|
|
|
// wake up so we ensure that all packets are written to user
|
|
|
|
router()->TriggerPump();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
huint128_t
|
|
|
|
TunEndpoint::GetIfAddr() const
|
|
|
|
{
|
|
|
|
return m_OurIP;
|
|
|
|
}
|
|
|
|
|
|
|
|
huint128_t
|
|
|
|
TunEndpoint::ObtainIPForAddr(std::variant<service::Address, RouterID> addr)
|
|
|
|
{
|
|
|
|
llarp_time_t now = Now();
|
|
|
|
huint128_t nextIP = {0};
|
|
|
|
AlignedBuffer<32> ident{};
|
|
|
|
bool snode = false;
|
|
|
|
|
|
|
|
var::visit([&ident](auto&& val) { ident = val.data(); }, addr);
|
|
|
|
|
|
|
|
if (std::get_if<RouterID>(&addr))
|
|
|
|
{
|
|
|
|
snode = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// previously allocated address
|
|
|
|
auto itr = m_AddrToIP.find(ident);
|
|
|
|
if (itr != m_AddrToIP.end())
|
|
|
|
{
|
|
|
|
// mark ip active
|
|
|
|
MarkIPActive(itr->second);
|
|
|
|
return itr->second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// allocate new address
|
|
|
|
if (m_NextIP < m_MaxIP)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
nextIP = ++m_NextIP;
|
|
|
|
} while (m_IPToAddr.find(nextIP) != m_IPToAddr.end() && m_NextIP < m_MaxIP);
|
|
|
|
if (nextIP < m_MaxIP)
|
|
|
|
{
|
|
|
|
m_AddrToIP[ident] = nextIP;
|
|
|
|
m_IPToAddr[nextIP] = ident;
|
|
|
|
m_SNodes[ident] = snode;
|
|
|
|
var::visit(
|
|
|
|
[&](auto&& remote) { llarp::LogInfo(Name(), " mapped ", remote, " to ", nextIP); },
|
|
|
|
addr);
|
|
|
|
MarkIPActive(nextIP);
|
|
|
|
return nextIP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// we are full
|
|
|
|
// expire least active ip
|
|
|
|
// TODO: prevent DoS
|
|
|
|
std::pair<huint128_t, llarp_time_t> oldest = {huint128_t{0}, 0s};
|
|
|
|
|
|
|
|
// find oldest entry
|
|
|
|
auto itr = m_IPActivity.begin();
|
|
|
|
while (itr != m_IPActivity.end())
|
|
|
|
{
|
|
|
|
if (itr->second <= now)
|
|
|
|
{
|
|
|
|
if ((now - itr->second) > oldest.second)
|
|
|
|
{
|
|
|
|
oldest.first = itr->first;
|
|
|
|
oldest.second = itr->second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
++itr;
|
|
|
|
}
|
|
|
|
// remap address
|
|
|
|
m_IPToAddr[oldest.first] = ident;
|
|
|
|
m_AddrToIP[ident] = oldest.first;
|
|
|
|
m_SNodes[ident] = snode;
|
|
|
|
nextIP = oldest.first;
|
|
|
|
|
|
|
|
// mark ip active
|
|
|
|
m_IPActivity[nextIP] = std::max(m_IPActivity[nextIP], now);
|
|
|
|
|
|
|
|
return nextIP;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
TunEndpoint::HasRemoteForIP(huint128_t ip) const
|
|
|
|
{
|
|
|
|
return m_IPToAddr.find(ip) != m_IPToAddr.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TunEndpoint::MarkIPActive(huint128_t ip)
|
|
|
|
{
|
|
|
|
llarp::LogDebug(Name(), " address ", ip, " is active");
|
|
|
|
m_IPActivity[ip] = std::max(Now(), m_IPActivity[ip]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TunEndpoint::MarkIPActiveForever(huint128_t ip)
|
|
|
|
{
|
|
|
|
m_IPActivity[ip] = std::numeric_limits<llarp_time_t>::max();
|
|
|
|
}
|
|
|
|
|
|
|
|
TunEndpoint::~TunEndpoint() = default;
|
|
|
|
|
|
|
|
} // namespace llarp::handlers
|