#include #include // harmless on other platforms #define __USE_MINGW_ANSI_STDIO 1 #include "tun.hpp" #include #ifndef _WIN32 #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace llarp { namespace handlers { // Intercepts DNS IP packets going to an IP on the tun interface; this is currently used on // Android where binding to a DNS port (i.e. via llarp::dns::Proxy) isn't possible because of OS // restrictions, but a tun interface *is* available. class DnsInterceptor : public dns::PacketHandler { public: TunEndpoint* const m_Endpoint; explicit DnsInterceptor(AbstractRouter* router, TunEndpoint* ep) : dns::PacketHandler{router->loop(), ep}, m_Endpoint{ep} {}; void SendServerMessageBufferTo( const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override { const auto pkt = net::IPPacket::UDP( from.getIPv4(), ToNet(huint16_t{from.getPort()}), to.getIPv4(), ToNet(huint16_t{to.getPort()}), buf); if (pkt.sz == 0) return; m_Endpoint->HandleWriteIPPacket( pkt.ConstBuffer(), net::ExpandV4(from.asIPv4()), net::ExpandV4(to.asIPv4()), 0); } }; TunEndpoint::TunEndpoint(AbstractRouter* r, service::Context* parent) : service::Endpoint(r, parent) , m_UserToNetworkPktQueue("endpoint_sendq", r->loop(), r->loop()) { m_PacketSendWaker = r->loop()->make_waker([this]() { FlushWrite(); }); m_MessageSendWaker = r->loop()->make_waker([this]() { FlushSend(); Pump(Now()); }); m_PacketRouter = std::make_unique( [this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); }); #ifdef ANDROID m_Resolver = std::make_shared(r, this); m_PacketRouter->AddUDPHandler(huint16_t{53}, [&](net::IPPacket pkt) { const size_t ip_header_size = (pkt.Header()->ihl * 4); const uint8_t* ptr = pkt.buf + ip_header_size; const auto dst = ToNet(pkt.dstv4()); const auto src = ToNet(pkt.srcv4()); const SockAddr laddr{src, nuint16_t{*reinterpret_cast(ptr)}}; const SockAddr raddr{dst, nuint16_t{*reinterpret_cast(ptr + 2)}}; OwnedBuffer buf{pkt.sz - (8 + ip_header_size)}; std::copy_n(ptr + 8, buf.sz, buf.buf.get()); if (m_Resolver->ShouldHandlePacket(laddr, raddr, buf)) m_Resolver->HandlePacket(laddr, raddr, buf); else HandleGotUserPacket(std::move(pkt)); }); #else m_Resolver = std::make_shared(r->loop(), this); #endif } util::StatusObject TunEndpoint::ExtractStatus() const { auto obj = service::Endpoint::ExtractStatus(); obj["ifaddr"] = m_OurRange.ToString(); obj["ifname"] = m_IfName; std::vector resolvers; for (const auto& addr : m_UpstreamResolvers) resolvers.emplace_back(addr.toString()); obj["ustreamResolvers"] = resolvers; obj["localResolver"] = m_LocalResolverAddr.toString(); 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_Resolver) m_Resolver->Restart(); } bool TunEndpoint::Configure(const NetworkConfig& conf, const DnsConfig& dnsConf) { if (conf.m_reachable) { m_PublishIntroSet = true; LogInfo(Name(), " setting to be reachable by default"); } else { m_PublishIntroSet = false; LogInfo(Name(), " setting to be not reachable by default"); } 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( url, method, conf.m_AuthWhitelist, Router()->lmq(), shared_from_this()); auth->Start(); m_AuthPolicy = std::move(auth); } m_TrafficPolicy = conf.m_TrafficPolicy; m_OwnedRanges = conf.m_OwnedRanges; m_LocalResolverAddr = dnsConf.m_bind; m_UpstreamResolvers = dnsConf.m_upstreamDNS; 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 = llarp::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 = llarp::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; 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::Flush() { FlushSend(); Pump(Now()); FlushWrite(); } void TunEndpoint::FlushWrite() { // flush network to user while (not m_NetworkToUserPktQueue.empty()) { m_NetIf->WritePacket(m_NetworkToUserPktQueue.top().pkt); m_NetworkToUserPktQueue.pop(); } } 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> 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 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; 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]( auto addr, auto msg, bool isV6) { if (auto ptr = std::get_if(&addr)) { ReplyToSNodeDNSWhenReady(*ptr, msg, isV6); return; } if (auto ptr = std::get_if(&addr)) { ReplyToLokiDNSWhenReady(*ptr, msg, isV6); return; } }; auto ReplyToLokiSRVWhenReady = [this, reply, timeout = PathAlignmentTimeout()]( service::Address addr, auto msg) -> bool { using service::Address; using service::OutboundContext; 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")) { dns::Name_t qname; llarp_buffer_t buf(answer.rData); if (not dns::DecodeName(&buf, qname, true)) return false; RouterID addr; if (not addr.FromString(qname)) return false; auto replyMsg = std::make_shared(clear_dns_message(msg)); return ReplyToSNodeDNSWhenReady(addr, std::move(replyMsg), false); } else if (answer.HasCNameForTLD(".loki")) { dns::Name_t qname; llarp_buffer_t buf(answer.rData); if (not dns::DecodeName(&buf, qname, true)) return false; service::Address addr; if (not addr.FromString(qname)) return false; auto replyMsg = std::make_shared(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 lnsName; if (nameparts.size() >= 2 and ends_with(qname, ".loki")) { lnsName = nameparts[nameparts.size() - 2]; lnsName += ".loki"sv; } if (msg.questions[0].qtype == dns::qTypeTXT) { RouterID snode; if (snode.FromString(qname)) { m_router->LookupRouter(snode, [reply, msg = std::move(msg)](const auto& found) mutable { if (found.empty()) { msg.AddNXReply(); } else { std::stringstream ss; for (const auto& rc : found) rc.ToTXTRecord(ss); msg.AddTXTReply(ss.str()); } reply(msg); }); return true; } else if (msg.questions[0].IsLocalhost() and msg.questions[0].HasSubdomains()) { const auto subdomain = msg.questions[0].Subdomains(); if (subdomain == "exit") { if (HasExit()) { std::stringstream ss; m_ExitMap.ForEachEntry([&ss](const auto& range, const auto& exit) { ss << range.ToString() << "=" << exit.ToString() << "; "; }); msg.AddTXTReply(ss.str()); } else { msg.AddNXReply(); } } else if (subdomain == "netid") { std::stringstream ss; ss << "netid=" << m_router->rc().netID.ToString() << ";"; msg.AddTXTReply(ss.str()); } 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::NameIsValid(lnsName)) { LookupNameAsync(lnsName, [msg, lnsName, reply](auto maybe) mutable { if (maybe.has_value()) { var::visit([&](auto&& value) { msg.AddMXReply(value.ToString(), 1); }, *maybe); } 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()) { m_ExitMap.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) -> 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(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()) { m_ExitMap.ForEachEntry( [&msg](const auto&, const auto& exit) { msg.AddCNAMEReply(exit.ToString()); }); msg.AddINReply(ip, isV6); } else { msg.AddNXReply(); } } else { msg.AddCNAMEReply(m_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(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(msg), isV6); } } else if (service::NameIsValid(lnsName)) { LookupNameAsync( lnsName, [msg = std::make_shared(msg), name = Name(), lnsName, isV6, reply, ReplyToDNSWhenReady](auto maybe) { if (not maybe.has_value()) { LogWarn(name, " lns name ", lnsName, " not resolved"); msg->AddNXReply(); reply(*msg); return; } ReplyToDNSWhenReady(*maybe, msg, isV6); }); return true; } else msg.AddNXReply(); reply(msg); } else if (msg.questions[0].qtype == dns::qTypePTR) { // reverse dns huint128_t ip = {0}; if (!dns::DecodePTR(msg.questions[0].qname, ip)) { msg.AddNXReply(); reply(msg); return true; } 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) { llarp::service::Address addr; if (is_localhost_loki(msg)) { msg.AddSRVReply(introSet().GetMatchingSRVRecords(msg.questions[0].Subdomains())); reply(msg); return true; } else if (addr.FromString(qname, ".loki")) { llarp::LogDebug("SRV request for: ", qname); return ReplyToLokiSRVWhenReady(addr, std::make_shared(msg)); } } 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) { huint128_t ip = {0}; if (!dns::DecodePTR(msg.questions[0].qname, ip)) return false; return m_OurRange.Contains(ip); } } 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 (!Endpoint::Start()) { llarp::LogWarn("Couldn't start endpoint"); 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 = m_Identity.pub.Addr(); if (not MapAddress(ourAddr, GetIfAddr(), false)) { return false; } vpn::InterfaceInfo info; info.addrs.emplace(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(v6range, AF_INET6); } info.ifname = m_IfName; info.dnsaddr.FromString(m_LocalResolverAddr.toHost()); LogInfo(Name(), " setting up network..."); try { m_NetIf = Router()->GetVPNPlatform()->ObtainInterface(std::move(info)); } catch (std::exception& ex) { LogError(Name(), " failed to set up network interface: ", ex.what()); } if (not m_NetIf) { LogError(Name(), " failed to obtain network interface"); return false; } m_IfName = m_NetIf->IfName(); LogInfo(Name(), " got network interface ", m_IfName); if (not Router()->loop()->add_network_interface(m_NetIf, [this](net::IPPacket pkt) { m_PacketRouter->HandleIPPacket(std::move(pkt)); })) { LogError(Name(), " failed to add network interface"); return false; } const auto maybe = GetInterfaceIPv6Address(m_IfName); if (maybe.has_value()) { m_OurIPv6 = *maybe; LogInfo(Name(), " has ipv6 address ", m_OurIPv6); } Router()->loop()->add_ticker([this] { Flush(); }); // Attempt to register DNS on the interface systemd_resolved_set_dns( m_IfName, m_LocalResolverAddr.createSockAddr(), false /* just .loki/.snode DNS initially */); if (m_OnUp) { m_OnUp->NotifyAsync(NotifyParams()); } return HasAddress(ourAddr); } std::unordered_map 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()); if (!SetupTun()) { llarp::LogError(Name(), " failed to set up network interface"); return false; } if (!m_Resolver->Start(m_LocalResolverAddr.createSockAddr(), m_UpstreamResolvers)) { llarp::LogError(Name(), " failed to start DNS server"); return false; } return true; } void TunEndpoint::Tick(llarp_time_t now) { Endpoint::Tick(now); } bool TunEndpoint::Stop() { if (m_Resolver) m_Resolver->Stop(); return llarp::service::Endpoint::Stop(); } void TunEndpoint::FlushSend() { m_UserToNetworkPktQueue.Process([&](net::IPPacket& pkt) { huint128_t dst, src; if (pkt.IsV4()) { dst = pkt.dst4to6(); src = pkt.src4to6(); } else { 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)); } 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()) { // 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 (range.BogonRange() and range.Contains(dst)) { // we permit this because it matches our rules and we allow bogons addr = exitAddr; } else if (not IsBogon(dst)) { // allow because the destination is not a bogon and the mapped range is not a bogon 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( addr, [pkt](service::Address addr, service::OutboundContext* ctx) { if (ctx) { ctx->SendPacketToRemote(pkt.ConstBuffer(), service::ProtocolType::Exit); return; } LogWarn("cannot ensure path to exit ", addr, " so we drop some packets"); }, PathAlignmentTimeout()); return; } std::variant 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 = m_state->m_ExitEnabled 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 (SendToOrQueue(to, pkt.ConstBuffer(), type)) { MarkIPActive(dst); return; } // try establishing a path to this guy // will fail if it's an inbound convo EnsurePathTo( to, [pkt, type, dst, to, this](auto maybe) { if (not maybe) { var::visit( [&](auto&& addr) { LogWarn(Name(), " failed to ensure path to ", addr, " no convo tag found"); }, to); } if (SendToOrQueue(*maybe, pkt.ConstBuffer(), type)) { MarkIPActive(dst); } else { var::visit( [&](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) { 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 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 (m_state->m_ExitEnabled) { // 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(&addr)) { fromAddr = *ptr; } else // don't allow snode return false; const auto mapped = m_ExitMap.FindAllEntries(src); bool allow = false; for (const auto& [range, exitAddr] : mapped) { if ((range.BogonRange() and range.Contains(src)) or not IsBogon(src)) { // allow if this address matches the endpoint we think it should be allow = exitAddr == fromAddr; break; } } if (not allow) { var::visit( [&](auto&& address) { LogWarn(Name(), " does not allow ", src, " from ", address); }, addr); 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 packet flushing event so we ensure that all packets are written to user m_PacketSendWaker->Trigger(); return true; } huint128_t TunEndpoint::GetIfAddr() const { return m_OurIP; } huint128_t TunEndpoint::ObtainIPForAddr(std::variant 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(&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 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::max(); } void TunEndpoint::HandleGotUserPacket(net::IPPacket pkt) { m_UserToNetworkPktQueue.Emplace(std::move(pkt)); m_MessageSendWaker->Trigger(); } TunEndpoint::~TunEndpoint() = default; } // namespace handlers } // namespace llarp