1
1
Fork 0
mirror of https://github.com/oxen-io/lokinet synced 2023-12-14 06:53:00 +01:00

temp: everything else

This commit is contained in:
jeff 2023-03-24 00:59:52 -04:00 committed by Jeff Becker
parent 170ecd7add
commit e3e9b16ddf
No known key found for this signature in database
GPG key ID: 025C02EE3A092F2D
138 changed files with 5685 additions and 3264 deletions

View file

@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.13...3.24) # 3.13 is buster's version
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Has to be set before `project()`, and ignored on non-macos:
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)")
@ -185,6 +186,7 @@ if(NOT TARGET sodium)
endif()
set(warning_flags -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Werror=vla)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND warning_flags -Wno-unknown-warning-option)
endif()

View file

@ -25,7 +25,6 @@ cmake \
-S "$root" -B "$build" \
-G 'Unix Makefiles' \
-DCMAKE_EXE_LINKER_FLAGS=-fstack-protector \
-DCMAKE_CXX_FLAGS=-fdiagnostics-color=always \
-DCMAKE_TOOLCHAIN_FILE="$root/contrib/cross/mingw64.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_STATIC_DEPS=ON \

View file

@ -1,3 +1,4 @@
#include <chrono>
#include <llarp/config/config.hpp> // for ensure_config
#include <llarp/constants/version.hpp>
#include <llarp.hpp>
@ -74,7 +75,7 @@ namespace
{
llarp::log::info(logcat, "Handling signal {}", sig);
if (ctx)
ctx->loop->call([sig] { ctx->HandleSignal(sig); });
ctx->HandleSignal(sig);
else
std::cerr << "Received signal " << sig << ", but have no context yet. Ignoring!" << std::endl;
}
@ -504,7 +505,7 @@ namespace
do
{
// do periodic non lokinet related tasks here
if (ctx and ctx->IsUp() and not ctx->LooksAlive())
if (ctx and (ctx->IsUp() or ctx->IsStopping()) and not ctx->LooksAlive())
{
auto deadlock_cat = llarp::log::Cat("deadlock");
for (const auto& wtf :
@ -534,7 +535,7 @@ namespace
llarp::sys::service_manager->failed();
std::abort();
}
} while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready);
} while (ftr.wait_for(std::chrono::milliseconds(100)) != std::future_status::ready);
main_thread.join();
@ -674,4 +675,4 @@ main(int argc, char* argv[])
return 1;
}
#endif
}
}

View file

@ -13,7 +13,6 @@ add_library(lokinet-util
util/bencode.cpp
util/buffer.cpp
util/file.cpp
util/json.cpp
util/logging/buffer.cpp
util/easter_eggs.cpp
util/mem.cpp
@ -76,6 +75,14 @@ if(APPLE)
target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp)
endif()
# set to ON when we want to flip over to the new platform and flow layer.
set(use_new_platform_layer OFF)
# set to ON when we want to flip over to the refactor routing layer (called the route layer).
set(use_new_route_layer OFF)
set(use_new_onion_layer OFF)
# lokinet-dns is the dns parsing and hooking library that we use to
# parse modify and reconstitute dns wire proto, dns queries and RR
# should have no concept of dns caching, this is left as an implementation
@ -87,7 +94,6 @@ add_library(lokinet-dns
dns/platform.cpp
dns/question.cpp
dns/rr.cpp
dns/serialize.cpp
dns/server.cpp
dns/srv_data.cpp)
@ -175,7 +181,18 @@ add_library(lokinet-dht
# flows between lokinet snapp endpoints be they .loki or .snode
add_library(lokinet-layer-flow
STATIC
layers/flow/stub.cpp # todo: remove me
layers/flow/flow_addr.cpp
layers/flow/flow_auth.cpp
layers/flow/flow_data_kind.cpp
layers/flow/flow_establish.cpp
layers/flow/flow_identity.cpp
layers/flow/flow_info.cpp
layers/flow/flow_layer.cpp
layers/flow/flow_state.cpp
layers/flow/flow_stats.cpp
layers/flow/flow_tag.cpp
layers/flow/name_cache.cpp
layers/flow/name_resolver.cpp
)
@ -183,6 +200,13 @@ add_library(lokinet-layer-flow
# with onion paths. onion paths anonymize routing layer pdu.
add_library(lokinet-layer-onion
STATIC
layers/onion/onion_layer.cpp
layers/onion/onion_stats.cpp
)
add_library(lokinet-deprecated-layer-onion
STATIC
# old compilation units for onion layer
path/ihophandler.cpp
path/path_context.cpp
path/path.cpp
@ -194,6 +218,7 @@ add_library(lokinet-layer-onion
messages/relay_status.cpp
)
# lokinet-layer-wire is a layer 1 analog which splits up
# layer 2 frames into layer 1 symbols which in the case of iwp are encrypted udp/ip packets
add_library(lokinet-layer-wire
@ -234,6 +259,7 @@ add_library(lokinet-plainquic
add_library(lokinet-context
STATIC
context.cpp
layers/layers.cpp
link/link_manager.cpp
router/outbound_message_handler.cpp
router/outbound_session_maker.cpp
@ -263,8 +289,14 @@ add_library(lokinet-peerstats
# lokinet-layer-routing holds logic related to the routing layer
# routing layer is anonymized over the onion layer
add_library(lokinet-layer-routing
add_library(lokinet-layer-route
STATIC
layers/route/route_layer.cpp
)
add_library(lokinet-deprecated-layer-route
STATIC
# old compilation units for route layer
routing/dht_message.cpp
routing/message_parser.cpp
routing/path_confirm_message.cpp
@ -274,22 +306,22 @@ add_library(lokinet-layer-routing
)
# kitchen sink to be removed after refactor
add_library(lokinet-service-deprecated-kitchensink
add_library(lokinet-deprecated-layer-kitchensink
STATIC
endpoint_base.cpp
handlers/exit.cpp
handlers/tun.cpp
exit/context.cpp
exit/endpoint.cpp
exit/exit_messages.cpp
exit/policy.cpp
exit/session.cpp
handlers/exit.cpp
handlers/tun.cpp
service/context.cpp
service/name.cpp
service/address.cpp
service/async_key_exchange.cpp
service/auth.cpp
service/convotag.cpp
service/context.cpp
service/endpoint_state.cpp
service/endpoint_util.cpp
service/endpoint.cpp
@ -311,7 +343,13 @@ add_library(lokinet-service-deprecated-kitchensink
add_library(lokinet-layer-platform
STATIC
layers/platform/stub.cpp # todo: remove me
layers/platform/addr_mapper.cpp
layers/platform/dns_bridge.cpp
layers/platform/ethertype.cpp
layers/platform/os_traffic.cpp
layers/platform/platform_addr.cpp
layers/platform/platform_layer.cpp
layers/platform/platform_stats.cpp
)
@ -371,7 +409,7 @@ if(TARGET lokinet-dns-systemd)
)
endif()
lokinet_link_lib(lokinet-platform lokinet-util)
lokinet_link_lib(lokinet-platform lokinet-util lokinet-config)
lokinet_link_lib(lokinet-config
lokinet-util
@ -447,42 +485,54 @@ function(link_lokinet_layers)
endif()
endfunction()
link_lokinet_layers(
lokinet-layer-platform
lokinet-layer-flow
lokinet-layer-routing
lokinet-layer-onion
lokinet-layer-link
lokinet-layer-wire
)
# set me to OFF to disable old codepath
set(use_old_impl ON)
if(use_old_impl)
# flow layer deprecated-kitchensink (remove me after refactor)
lokinet_link_lib(lokinet-service-deprecated-kitchensink
lokinet-dns
lokinet-nodedb
lokinet-context
lokinet-plainquic
lokinet-layer-routing
lokinet-layer-onion
lokinet-dht
lokinet-platform
lokinet-rpc
)
target_link_libraries(lokinet-layers INTERFACE lokinet-service-deprecated-kitchensink)
endif()
add_library(lokinet-deprecated INTERFACE)
# todo: add branching case for only new platform layer
target_link_libraries(lokinet-layers INTERFACE
lokinet-layer-platform
lokinet-layer-flow
lokinet-layer-routing
lokinet-layer-route
lokinet-layer-onion
lokinet-layer-link
lokinet-layer-wire)
lokinet_link_lib(lokinet-deprecated-layer-route
lokinet-nodedb
lokinet-context
lokinet-platform)
lokinet_link_lib(lokinet-deprecated-layer-onion
lokinet-deprecated-layer-route
lokinet-nodedb
lokinet-context
lokinet-platform)
lokinet_link_lib(lokinet-deprecated-layer-kitchensink
lokinet-deprecated-layer-route
lokinet-deprecated-layer-onion
lokinet-dns
lokinet-nodedb
lokinet-context
lokinet-plainquic
lokinet-dht
lokinet-platform
lokinet-rpc)
target_link_libraries(lokinet-deprecated INTERFACE lokinet-deprecated-layer-onion lokinet-deprecated-layer-route lokinet-deprecated-layer-kitchensink)
target_compile_definitions(lokinet-base INTERFACE -DUSE_DEPRECATED_LAYERS)
link_lokinet_layers(
lokinet-layer-platform
lokinet-layer-flow
lokinet-layer-route
lokinet-layer-onion
lokinet-layer-link
lokinet-layer-wire
)
target_link_libraries(lokinet-layers INTERFACE lokinet-deprecated)
# per component external deps
@ -521,6 +571,7 @@ target_link_libraries(lokinet-plainquic PUBLIC
if(WITH_EMBEDDED_LOKINET)
include(GNUInstallDirs)
add_library(lokinet-shared SHARED lokinet_shared.cpp)
target_include_directories(lokinet-shared PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(lokinet-shared PUBLIC lokinet-amalgum)
if(WIN32)
set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "")

View file

@ -6,22 +6,96 @@
namespace llarp::apple
{
struct Context;
/// make layers with platform specific quarks applied.
std::unique_ptr<const layers::Layers>
make_apple_layers(Context& ctx, Config conf);
/// router with apple quarks applied.
struct AppleRouter : public llarp::Router
{
AppleRouter(
const std::shared_ptr<EventLoop>& loop, std::shared_ptr<VPNPlatform> plat, Context& ctx)
: Router{loop, std::move(plat)}, _apple_ctx{ctx}
{}
~AppleRouter() override = default;
Context& _apple_ctx;
std::unique_ptr<const layers::Layers>
create_layers() override
{
return make_apple_layers(_apple_ctx, *m_Config);
}
};
struct Context : public llarp::Context
{
std::shared_ptr<vpn::Platform>
makeVPNPlatform() override
{
return std::make_shared<VPNPlatform>(
*this, m_PacketWriter, m_OnReadable, route_callbacks, callback_context);
return std::make_shared<VPNPlatform>(*this, write_packet, route_callbacks, callback_context);
}
std::shared_ptr<AbstractRouter>
makeRouter(const std::shared_ptr<EventLoop>& loop) override
{
auto ptr = std::make_shared<AppleRouter>(loop, makeVPNPlatform(), *this);
return std::static_pointer_cast<AbstractRouter>(ptr);
}
// Callbacks that must be set for packet handling *before* calling Setup/Configure/Run; the main
// point of these is to get passed through to VPNInterface, which will be called during setup,
// after construction.
VPNInterface::packet_write_callback m_PacketWriter;
VPNInterface::on_readable_callback m_OnReadable;
packet_write_callback write_packet;
on_readable_callback on_readable;
llarp_route_callbacks route_callbacks{};
void* callback_context = nullptr;
};
/// applies os traffic io quarks for apple platform
class AppleOSTraffic_IO : public layers::platform::OSTraffic_IO_Base
{
public:
using layers::platform::OSTraffic_IO_Base::OSTraffic_IO_Base;
std::vector<layers::platform::OSTraffic>
read_platform_traffic() override
{
auto vec = layers::platform::OSTraffic_IO_Base::read_platform_traffic();
// we collected all reads from the past read cycle. queue reads again.
if (auto ptr = std::dynamic_pointer_cast<llarp::apple::AppleVPNInterface>(_netif))
ptr->on_readable();
return vec;
}
};
/// applies quarks for apple for the platform layer.
class ApplePlatformLayer : public layers::platform::PlatformLayer
{
protected:
std::unique_ptr<layers::platform::OSTraffic_IO_Base>
make_io() const override
{
return std::unique_ptr<layers::platform::OSTraffic_IO_Base>{
new AppleOSTraffic_IO{_router.loop()}};
}
public:
using layers::platform::PlatformLayer::PlatformLayer;
};
std::unique_ptr<const layers::Layers>
make_apple_layers(AbstractRouter& router, Config conf)
{
auto ptr = layers::make_layers(router, conf);
ptr->platform.reset(new ApplePlatformLayer{router, *ptr->flow, conf.network});
return ptr->freeze();
}
} // namespace llarp::apple

View file

@ -21,8 +21,8 @@ namespace
std::thread runner;
packet_writer_callback packet_writer;
start_reading_callback start_reading;
std::weak_ptr<llarp::apple::VPNInterface> iface;
llarp::apple::on_readable_callback on_readable;
std::weak_ptr<llarp::apple::AppleVPNInterface> iface;
};
} // namespace
@ -103,10 +103,8 @@ llarp_apple_init(llarp_apple_config* appleconf)
auto inst = std::make_unique<instance_data>();
inst->context.Configure(std::move(config));
inst->context.route_callbacks = appleconf->route_callbacks;
inst->packet_writer = appleconf->packet_writer;
inst->start_reading = appleconf->start_reading;
return inst.release();
}
catch (const std::exception& e)
@ -123,12 +121,12 @@ llarp_apple_start(void* lokinet, void* callback_context)
inst->context.callback_context = callback_context;
inst->context.m_PacketWriter = [inst, callback_context](int af_family, void* data, size_t size) {
inst->context.write_packet = [inst, callback_context](int af_family, void* data, size_t size) {
inst->packet_writer(af_family, data, size, callback_context);
return true;
};
inst->context.m_OnReadable = [inst, callback_context](llarp::apple::VPNInterface& iface) {
inst->context.on_readable = [inst, callback_context](llarp::apple::AppleVPNInterface& iface) {
inst->iface = iface.weak_from_this();
inst->start_reading(callback_context);
};
@ -158,7 +156,6 @@ llarp_apple_start(void* lokinet, void* callback_context)
llarp::LogError("Failed to initialize lokinet: ", e.what());
return -1;
}
return 0;
}
@ -177,20 +174,22 @@ llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t
auto& inst = *static_cast<instance_data*>(lokinet);
auto iface = inst.iface.lock();
if (!iface)
if (not iface)
return -1;
int count = 0;
for (size_t i = 0; i < size; i++)
{
llarp_buffer_t buf{static_cast<const uint8_t*>(packets[i].bytes), packets[i].size};
if (iface->OfferReadPacket(buf))
count++;
else
llarp::LogError("invalid IP packet: ", llarp::buffer_printer(buf));
}
llarp::net::IPPacket pkt{
llarp::byte_view_t{reinterpret_cast<const byte_t*>(packets[i].bytes), packets[i].size}};
iface->MaybeWakeUpperLayers();
if (pkt.empty())
continue;
iface->offer_read(std::move(pkt));
count++;
}
// idempotently wake up the part of lokinet that cares.
inst.context.router->get_layers()->platform->wakeup->Trigger();
return count;
}

View file

@ -18,23 +18,22 @@ namespace llarp::apple
return;
}
std::shared_ptr<llarp::handlers::TunEndpoint> tun;
router->hiddenServiceContext().ForEachService([&tun](const auto& /*name*/, const auto ep) {
tun = std::dynamic_pointer_cast<llarp::handlers::TunEndpoint>(ep);
return !tun;
});
if (!tun)
auto dns = router->get_dns();
if (not dns)
{
LogError("Cannot reconfigure to use DNS trampoline: no tun endpoint found (!?)");
LogError("Cannot reconfigure to use DNS trampoline: no dns on router");
return;
}
if (enable)
tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, {dns_trampoline_port}}});
else
tun->ReconfigureDNS(router->GetConfig()->dns.m_upstreamDNS);
const std::vector<SockAddr> upstream_addrs{
enable ? std::vector<SockAddr>{SockAddr{127, 0, 0, 1, {dns_trampoline_port}}}
: router->GetConfig()->dns.m_upstreamDNS};
for (auto weak : dns->GetAllResolvers())
{
if (auto ptr = weak.lock())
ptr->ResetResolver(upstream_addrs);
}
trampoline_active = enable;
}

View file

@ -1,59 +1,33 @@
#include "vpn_interface.hpp"
#include "context.hpp"
#include "llarp/net/ip_packet.hpp"
#include "llarp/vpn/platform.hpp"
#include <cstddef>
#include <llarp/router/abstractrouter.hpp>
namespace llarp::apple
{
VPNInterface::VPNInterface(
Context& ctx,
packet_write_callback packet_writer,
on_readable_callback on_readable,
AbstractRouter* router)
: vpn::NetworkInterface{{}}
, m_PacketWriter{std::move(packet_writer)}
, m_OnReadable{std::move(on_readable)}
, _router{router}
AppleVPNInterface::AppleVPNInterface(
Context& ctx, layers::platform::PlatformLayer& plat, packet_write_callback packet_writer)
: vpn::QueuedNetworkInterface{vpn::InterfaceInfo{}, plat.wakeup_send, plat.wakeup_recv}
, _apple_ctx{ctx}
, _write_packet{std::move(packet_writer)}
{
ctx.loop->call_soon([this] { m_OnReadable(*this); });
}
bool
VPNInterface::OfferReadPacket(const llarp_buffer_t& buf)
{
llarp::net::IPPacket pkt;
if (!pkt.Load(buf))
return false;
m_ReadQueue.tryPushBack(std::move(pkt));
return true;
// make the reads happen.
ctx.loop->call_soon([this]() { on_readable(); });
}
void
VPNInterface::MaybeWakeUpperLayers() const
AppleVPNInterface::on_readable()
{
_router->TriggerPump();
}
int
VPNInterface::PollFD() const
{
return -1;
}
net::IPPacket
VPNInterface::ReadNextPacket()
{
net::IPPacket pkt{};
if (not m_ReadQueue.empty())
pkt = m_ReadQueue.popFront();
return pkt;
_apple_ctx.on_readable(*this);
}
bool
VPNInterface::WritePacket(net::IPPacket pkt)
AppleVPNInterface::WritePacket(net::IPPacket pkt)
{
int af_family = pkt.IsV6() ? AF_INET6 : AF_INET;
return m_PacketWriter(af_family, pkt.data(), pkt.size());
return _write_packet(pkt.family(), pkt.data(), pkt.size());
}
} // namespace llarp::apple

View file

@ -3,54 +3,38 @@
#include <llarp.hpp>
#include <llarp/vpn/platform.hpp>
#include <llarp/util/thread/queue.hpp>
#include <memory>
#include <llarp/layers/platform/platform_layer.hpp>
namespace llarp::apple
{
struct Context;
class VPNInterface final : public vpn::NetworkInterface,
public std::enable_shared_from_this<VPNInterface>
using packet_write_callback = std::function<bool(int af_family, void* data, int size)>;
class AppleVPNInterface final : public vpn::QueuedNetworkInterface,
public std::enable_shared_from_this<AppleVPNInterface>
{
public:
using packet_write_callback = std::function<bool(int af_family, void* data, int size)>;
using on_readable_callback = std::function<void(VPNInterface&)>;
using on_readable_callback = std::function<void(AppleVPNInterface&)>;
explicit VPNInterface(
explicit AppleVPNInterface(
Context& ctx,
layers::platform::PlatformLayer& plat,
packet_write_callback packet_writer,
on_readable_callback on_readable,
AbstractRouter* router);
// Method to call when a packet has arrived to deliver the packet to lokinet
bool
OfferReadPacket(const llarp_buffer_t& buf);
int
PollFD() const override;
net::IPPacket
ReadNextPacket() override;
on_readable_callback on_readable);
bool
WritePacket(net::IPPacket pkt) override;
/// continue reading packets.
void
MaybeWakeUpperLayers() const override;
on_readable();
private:
Context& _apple_ctx;
// Function for us to call when we have a packet to emit. Should return true if the packet was
// handed off to the OS successfully.
packet_write_callback m_PacketWriter;
// Called when we are ready to start reading packets
on_readable_callback m_OnReadable;
static inline constexpr auto PacketQueueSize = 1024;
thread::Queue<net::IPPacket> m_ReadQueue{PacketQueueSize};
AbstractRouter* const _router;
packet_write_callback _write_packet;
};
using on_readable_callback = AppleVPNInterface::on_readable_callback;
} // namespace llarp::apple

View file

@ -1,23 +1,47 @@
#include "vpn_platform.hpp"
#include "context.hpp"
#include "llarp/layers/platform/platform_layer.hpp"
#include "llarp/net/ip_packet.hpp"
namespace llarp::apple
{
class VPNInterface : public vpn::QueuedNetworkInterface
{
packet_write_callback _write_packet;
public:
VPNInterface(
Context& ctx,
const layers::platform::PlatformLayer& plat,
packet_write_callback write_packet)
: vpn::QueuedNetworkInterface{vpn::InterfaceInfo{}, plat.wakeup_send, plat.wakeup_recv}
, _write_packet{std::move(write_packet)}
{
_recv_wakeup->Trigger();
}
bool
WritePacket(net::IPPacket pkt) override
{
return _write_packet(pkt.family(), pkt.data(), static_cast<int>(pkt.size()));
}
};
VPNPlatform::VPNPlatform(
Context& ctx,
VPNInterface::packet_write_callback packet_writer,
VPNInterface::on_readable_callback on_readable,
packet_write_callback packet_writer,
llarp_route_callbacks route_callbacks,
void* callback_context)
: m_Context{ctx}
, m_RouteManager{ctx, std::move(route_callbacks), callback_context}
, m_PacketWriter{std::move(packet_writer)}
, m_OnReadable{std::move(on_readable)}
{}
std::shared_ptr<vpn::NetworkInterface>
VPNPlatform::ObtainInterface(vpn::InterfaceInfo, AbstractRouter* router)
{
return std::make_shared<VPNInterface>(m_Context, m_PacketWriter, m_OnReadable, router);
const auto& plat = *router->get_layers()->platform;
return std::make_shared<VPNInterface>(m_Context, plat, m_PacketWriter);
}
} // namespace llarp::apple

View file

@ -1,6 +1,5 @@
#pragma once
#include <llarp/vpn/platform.hpp>
#include "vpn_interface.hpp"
#include "route_manager.hpp"
@ -11,8 +10,7 @@ namespace llarp::apple
public:
explicit VPNPlatform(
Context& ctx,
VPNInterface::packet_write_callback packet_writer,
VPNInterface::on_readable_callback on_readable,
packet_write_callback packet_writer,
llarp_route_callbacks route_callbacks,
void* callback_context);
@ -28,8 +26,7 @@ namespace llarp::apple
private:
Context& m_Context;
apple::RouteManager m_RouteManager;
VPNInterface::packet_write_callback m_PacketWriter;
VPNInterface::on_readable_callback m_OnReadable;
packet_write_callback m_PacketWriter;
};
} // namespace llarp::apple

View file

@ -1,6 +1,7 @@
#include "config.hpp"
#include "definition.hpp"
#include "ini.hpp"
#include "llarp/net/ip_range.hpp"
#include <llarp/constants/files.hpp>
#include <llarp/constants/platform.hpp>
@ -611,7 +612,7 @@ namespace llarp
"Interface name for lokinet traffic. If unset lokinet will look for a free name",
"matching 'lokinetN', starting at N=0 (e.g. lokinet0, lokinet1, ...).",
},
AssignmentAcceptor(m_ifname));
AssignmentAcceptor(_ifname));
conf.defineOption<std::string>(
"network",
@ -622,10 +623,12 @@ namespace llarp
"lokinet will attempt to find an unused private range.",
},
[this](std::string arg) {
if (not m_ifaddr.FromString(arg))
IPRange range;
if (not range.FromString(arg))
{
throw std::invalid_argument{fmt::format("[network]:ifaddr invalid value: '{}'", arg)};
}
_ifaddr = range;
});
conf.defineOption<std::string>(
@ -735,13 +738,7 @@ namespace llarp
"https://docs.oxen.io/products-built-on-oxen/lokinet/snapps/hosting-snapps",
"and general description of DNS SRV record configuration.",
},
[this](std::string arg) {
llarp::dns::SRVData newSRV;
if (not newSRV.fromString(arg))
throw std::invalid_argument{fmt::format("Invalid SRV Record string: {}", arg)};
m_SRVRecords.push_back(std::move(newSRV));
});
[this](std::string_view arg) { m_SRVRecords.emplace_back(arg); });
conf.defineOption<int>(
"network",
@ -779,6 +776,26 @@ namespace llarp
conf.defineOption<std::string>("network", "enabled", Deprecated);
}
IPRange
NetworkConfig::ifaddr(const net::Platform& net_plat) const
{
if (_ifaddr)
return *_ifaddr;
if (auto maybe_range = net_plat.FindFreeRange())
return *maybe_range;
throw std::runtime_error{"cannot infer sensible address for local lokinet ip range"};
}
std::string
NetworkConfig::ifname(const net::Platform& net_plat) const
{
if (_ifname)
return *_ifname;
if (auto maybe_ifname = net_plat.FindFreeTun())
return *maybe_ifname;
throw std::runtime_error{"cannot infer sensible interface name for lokinet"};
}
void
DnsConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params)
{

View file

@ -1,6 +1,8 @@
#pragma once
#include "ini.hpp"
#include "definition.hpp"
#include "llarp/net/ip_range.hpp"
#include "llarp/net/net.hpp"
#include <chrono>
@ -109,8 +111,8 @@ namespace llarp
std::optional<bool> m_enableProfiling;
bool m_saveProfiles;
std::set<RouterID> m_strictConnect;
std::string m_ifname;
IPRange m_ifaddr;
std::optional<std::string> _ifname;
std::optional<IPRange> _ifaddr;
std::optional<fs::path> m_keyfile;
std::string m_endpointType;
@ -151,6 +153,16 @@ namespace llarp
void
defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params);
/// obtain interface name. uses configured value or runtime inferred sensible default if one is
/// not provided by config. default is inferred with net_plat.
std::string
ifname(const net::Platform& net_plat) const;
/// obtain ip range for network interface. uses configured value or runtime inferred sensible
/// default if one is one not provided by config. default is inferred with net_plat.
IPRange
ifaddr(const net::Platform& net_plat) const;
};
struct DnsConfig

View file

@ -97,4 +97,12 @@ namespace llarp::platform
/// does this platform support native ipv6 ?
// TODO: make windows support ipv6
inline constexpr bool supports_ipv6 = not is_windows;
/// this platform uses reactor style io.
/// see https://wikipedia.org/wiki/Reactor_pattern?lang=en
inline constexpr bool has_reactor_io = is_linux and not is_android;
/// this platform users proactor style io.
/// see https://wikipedia.org/wiki/Proactor_pattern?lang=en
inline constexpr bool has_proactor_io = not has_reactor_io;
} // namespace llarp::platform

View file

@ -127,22 +127,17 @@ namespace llarp
loop->run();
if (closeWaiter)
{
closeWaiter->set_value();
}
Close();
return 0;
}
void
Context::CloseAsync()
{
/// already closing
if (IsStopping())
return;
loop->call([this]() { HandleSignal(SIGTERM); });
closeWaiter = std::make_unique<std::promise<void>>();
if (not closeWaiter)
closeWaiter = std::make_unique<std::promise<void>>();
HandleSignal(SIGTERM);
}
bool
@ -155,10 +150,8 @@ namespace llarp
Context::Wait()
{
if (closeWaiter)
{
closeWaiter->get_future().wait();
closeWaiter.reset();
}
closeWaiter.reset();
}
void
@ -175,7 +168,7 @@ namespace llarp
if (router and not router->IsServiceNode())
{
LogInfo("SIGUSR1: resetting network state");
router->Thaw();
CallSafe([r = router]() { r->Thaw(); });
}
}
if (sig == SIGHUP)
@ -196,26 +189,10 @@ namespace llarp
{
llarp::log::debug(logcat, "Handling SIGINT");
/// async stop router on sigint
router->Stop();
CallSafe([r = router]() { r->Stop(); });
}
}
void
Context::Close()
{
llarp::LogDebug("free config");
config.reset();
llarp::LogDebug("free nodedb");
nodedb.reset();
llarp::LogDebug("free router");
router.reset();
llarp::LogDebug("free loop");
loop.reset();
}
Context::Context()
{
// service_manager is a global and context isnt

View file

@ -326,6 +326,7 @@ namespace llarp
const RouterID K(askpeer.as_array());
pendingExploreLookups().NewTX(
peer, whoasked, K, new ExploreNetworkJob(askpeer.as_array(), this));
router->TriggerPump();
}
void
@ -497,7 +498,12 @@ namespace llarp
if (not reply.M.empty())
{
auto path = router->pathContext().GetByUpstream(router->pubkey(), id);
return path && path->SendRoutingMessage(reply, router);
if (not path)
return false;
if (not path->SendRoutingMessage(reply, router))
return false;
router->pathContext().PumpUpstream();
return true;
}
return true;
}
@ -517,6 +523,7 @@ namespace llarp
asker,
asker,
new LocalServiceAddressLookup(path, txid, relayOrder, addr, this, askpeer));
router->TriggerPump();
}
void
@ -531,6 +538,7 @@ namespace llarp
const TXOwner peer(tellpeer, ++ids);
_pendingIntrosetLookups.NewTX(
peer, asker, asker, new PublishServiceJob(asker, introset, this, relayOrder));
router->TriggerPump();
}
void
@ -548,6 +556,7 @@ namespace llarp
asker,
peer,
new LocalPublishServiceJob(peer, from, txid, introset, this, relayOrder));
router->TriggerPump();
}
void
@ -563,6 +572,7 @@ namespace llarp
const TXOwner peer(askpeer, ++ids);
_pendingIntrosetLookups.NewTX(
peer, asker, asker, new ServiceAddressLookup(asker, addr, this, relayOrder, handler));
router->TriggerPump();
}
void
@ -577,6 +587,7 @@ namespace llarp
const TXOwner peer(askpeer, ++ids);
_pendingIntrosetLookups.NewTX(
peer, asker, asker, new ServiceAddressLookup(asker, addr, this, 0, handler), 1s);
router->TriggerPump();
}
bool
@ -634,6 +645,7 @@ namespace llarp
const TXOwner whoasked(OurKey(), txid);
_pendingRouterLookups.NewTX(
peer, whoasked, target, new LocalRouterLookup(path, txid, target, this));
router->TriggerPump();
}
void
@ -648,6 +660,7 @@ namespace llarp
const TXOwner peer(askpeer, ++ids);
_pendingRouterLookups.NewTX(
peer, asker, target, new RecursiveRouterLookup(asker, target, this, handler));
router->TriggerPump();
}
llarp_time_t

30
llarp/dns/bits.hpp Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <cstdint>
namespace llarp::dns::bits
{
/// query reply bitmask
constexpr uint16_t query_reply = 1 << 15;
/// authoritative response bitmask
constexpr uint16_t authoritative = 1 << 10;
/// recursion desirsed bitmask
constexpr uint16_t recursion_desired = 1 << 8;
// recursion allowed bitmask
constexpr uint16_t recursion_allowed = 1 << 7;
/// rcode for nx reply
constexpr uint16_t rcode_name_error = 3;
/// rcdode for srv fail
constexpr uint16_t rcode_servfail = 2;
/// recode for no error
constexpr uint16_t rcode_no_error = 0;
constexpr uint16_t qclass_in = 1;
inline constexpr uint16_t
make_rcode(uint16_t rcode)
{
return (rcode | query_reply | authoritative | recursion_allowed) & (~recursion_desired);
}
} // namespace llarp::dns::bits

View file

@ -1,30 +0,0 @@
#pragma once
#include <cstdint>
namespace llarp
{
namespace dns
{
constexpr uint16_t qTypeSRV = 33;
constexpr uint16_t qTypeAAAA = 28;
constexpr uint16_t qTypeTXT = 16;
constexpr uint16_t qTypeMX = 15;
constexpr uint16_t qTypePTR = 12;
constexpr uint16_t qTypeCNAME = 5;
constexpr uint16_t qTypeNS = 2;
constexpr uint16_t qTypeA = 1;
constexpr uint16_t qClassIN = 1;
constexpr uint16_t flags_QR = (1 << 15);
constexpr uint16_t flags_AA = (1 << 10);
constexpr uint16_t flags_TC = (1 << 9);
constexpr uint16_t flags_RD = (1 << 8);
constexpr uint16_t flags_RA = (1 << 7);
constexpr uint16_t flags_RCODENameError = (3);
constexpr uint16_t flags_RCODEServFail = (2);
constexpr uint16_t flags_RCODENoError = (0);
} // namespace dns
} // namespace llarp

View file

@ -1,434 +1,169 @@
#include "message.hpp"
#include <oxenc/endian.h>
#include "dns.hpp"
#include "bits.hpp"
#include "llarp/util/str.hpp"
#include "srv_data.hpp"
#include <cstdint>
#include <future>
#include <llarp/util/buffer.hpp>
#include <llarp/util/logging.hpp>
#include <llarp/net/ip.hpp>
#include <fmt/core.h>
#include <oxenc/endian.h>
#include <array>
#include <numeric>
#include <stdexcept>
#include <utility>
#include <vector>
namespace llarp
namespace llarp::dns
{
namespace dns
static auto logcat = log::Cat("dns");
MessageHeader::MessageHeader(byte_view_t& raw)
{
static auto logcat = log::Cat("dns");
bool
MessageHeader::Encode(llarp_buffer_t* buf) const
if (raw.size() < MessageHeader::Size)
throw std::invalid_argument{
"dns message buffer too small: {} < {}"_format(raw.size(), MessageHeader::Size)};
for (auto* i : {&id, &fields, &qd_count, &an_count, &ns_count, &ar_count})
{
if (!buf->put_uint16(id))
return false;
if (!buf->put_uint16(fields))
return false;
if (!buf->put_uint16(qd_count))
return false;
if (!buf->put_uint16(an_count))
return false;
if (!buf->put_uint16(ns_count))
return false;
return buf->put_uint16(ar_count);
*i = oxenc::load_big_to_host<uint16_t>(raw.data());
raw = raw.substr(2);
}
}
bool
MessageHeader::Decode(llarp_buffer_t* buf)
util::StatusObject
MessageHeader::to_json() const
{
return util::StatusObject{
{"id", id},
{"fields", fields},
{"qd", qd_count},
{"an", an_count},
{"ns", ns_count},
{"ar", ar_count}};
}
MessageHeader&
MessageHeader::rcode(uint16_t code)
{
id = bits::make_rcode(code);
return *this;
}
bstring_t
MessageHeader::encode_dns() const
{
bstring_t raw;
raw.resize(MessageHeader::Size);
auto ptr = reinterpret_cast<uint16_t*>(raw.data());
for (uint16_t i : {id, fields, qd_count, an_count, ns_count, ar_count})
{
if (!buf->read_uint16(id))
return false;
if (!buf->read_uint16(fields))
return false;
if (!buf->read_uint16(qd_count))
return false;
if (!buf->read_uint16(an_count))
return false;
if (!buf->read_uint16(ns_count))
return false;
if (!buf->read_uint16(ar_count))
return false;
return true;
oxenc::write_host_as_big(i, ptr);
ptr++;
}
return raw;
}
util::StatusObject
MessageHeader::ToJSON() const
namespace
{
/// decode N dns data types and shrinks raw as it reads.
/// returns a vector of size N or throws.
template <typename T>
[[nodiscard]] auto
decode_dns(byte_view_t& raw, size_t n)
{
return util::StatusObject{};
}
Message::Message(Message&& other)
: hdr_id(std::move(other.hdr_id))
, hdr_fields(std::move(other.hdr_fields))
, questions(std::move(other.questions))
, answers(std::move(other.answers))
, authorities(std::move(other.authorities))
, additional(std::move(other.additional))
{}
Message::Message(const Message& other)
: hdr_id(other.hdr_id)
, hdr_fields(other.hdr_fields)
, questions(other.questions)
, answers(other.answers)
, authorities(other.authorities)
, additional(other.additional)
{}
Message::Message(const MessageHeader& hdr) : hdr_id(hdr.id), hdr_fields(hdr.fields)
{
questions.resize(size_t(hdr.qd_count));
answers.resize(size_t(hdr.an_count));
authorities.resize(size_t(hdr.ns_count));
additional.resize(size_t(hdr.ar_count));
}
Message::Message(const Question& question) : hdr_id{0}, hdr_fields{}
{
questions.emplace_back(question);
}
bool
Message::Encode(llarp_buffer_t* buf) const
{
MessageHeader hdr;
hdr.id = hdr_id;
hdr.fields = hdr_fields;
hdr.qd_count = questions.size();
hdr.an_count = answers.size();
hdr.ns_count = 0;
hdr.ar_count = 0;
if (!hdr.Encode(buf))
return false;
for (const auto& question : questions)
if (!question.Encode(buf))
return false;
for (const auto& answer : answers)
if (!answer.Encode(buf))
return false;
return true;
}
bool
Message::Decode(llarp_buffer_t* buf)
{
for (auto& qd : questions)
std::vector<T> ents;
while (n > 0)
{
if (!qd.Decode(buf))
{
log::error(logcat, "failed to decode question");
return false;
}
log::debug(logcat, "question: {}", qd);
ents.emplace_back(raw);
--n;
}
for (auto& an : answers)
{
if (not an.Decode(buf))
{
log::debug(logcat, "failed to decode answer");
return false;
}
}
return true;
return ents;
}
} // namespace
util::StatusObject
Message::ToJSON() const
Message::Message(byte_view_t& raw) : hdr{raw}
{
questions = decode_dns<Question>(raw, hdr.qd_count);
answers = decode_dns<ResourceRecord>(raw, hdr.an_count);
authorities = decode_dns<ResourceRecord>(raw, hdr.ns_count);
additional = decode_dns<ResourceRecord>(raw, hdr.ar_count);
}
Message&
Message::nx()
{
hdr.rcode(bits::rcode_name_error);
return *this;
}
Message&
Message::servfail()
{
hdr.rcode(bits::rcode_servfail);
return *this;
}
bstring_t
Message::encode_dns() const
{
// strip off authorities and additional because we encode them wrong or something so here we
// reconstruct the header we write so it is correct.
MessageHeader _hdr{hdr};
_hdr.qd_count = questions.size();
_hdr.an_count = answers.size();
_hdr.ns_count = 0;
_hdr.ar_count = 0;
_hdr.fields = bits::make_rcode(answers.empty() ? bits::rcode_name_error : bits::rcode_no_error);
auto ret = _hdr.encode_dns();
for (const auto& question : questions)
ret += question.encode_dns();
for (const auto& answer : answers)
ret += answer.encode_dns();
return ret;
}
util::StatusObject
Message::to_json() const
{
std::vector<util::StatusObject> ques;
std::vector<util::StatusObject> ans;
for (const auto& q : questions)
{
std::vector<util::StatusObject> ques;
std::vector<util::StatusObject> ans;
for (const auto& q : questions)
{
ques.push_back(q.ToJSON());
}
for (const auto& a : answers)
{
ans.push_back(a.ToJSON());
}
return util::StatusObject{{"questions", ques}, {"answers", ans}};
ques.push_back(q.to_json());
}
OwnedBuffer
Message::ToBuffer() const
for (const auto& a : answers)
{
std::array<byte_t, 1500> tmp;
llarp_buffer_t buf{tmp};
if (not Encode(&buf))
throw std::runtime_error("cannot encode dns message");
return OwnedBuffer::copy_used(buf);
ans.push_back(a.to_json());
}
return util::StatusObject{{"questions", ques}, {"answers", ans}};
}
void
Message::AddServFail(RR_TTL_t)
{
if (questions.size())
{
hdr_fields |= flags_RCODEServFail;
// authorative response with recursion available
hdr_fields |= flags_QR | flags_AA | flags_RA;
// don't allow recursion on this request
hdr_fields &= ~flags_RD;
}
}
std::string
MessageHeader::ToString() const
{
return fmt::format(
"[dns::MessageHeader id={:x} flags={:x} questions={:x} answers={:x} ns={:x} ar={:x}]"_format(
id, fields, qd_count, an_count, ns_count, ar_count));
}
static constexpr uint16_t
reply_flags(uint16_t setbits)
{
return setbits | flags_QR | flags_AA | flags_RA;
}
std::string
Message::ToString() const
{
return fmt::format(
"[dns::Message hdr={} questions={{{}}} answers={{{}}} authorities={{{}}} "
"additional={{{}}}]",
hdr,
fmt::format("{}", fmt::join(questions, ",")),
fmt::format("{}", fmt::join(answers, ",")),
fmt::format("{}", fmt::join(authorities, ",")),
fmt::format("{}", fmt::join(additional, ",")));
}
void
Message::AddINReply(llarp::huint128_t ip, bool isV6, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
ResourceRecord rec;
rec.rr_name = questions[0].qname;
rec.rr_class = qClassIN;
rec.ttl = ttl;
if (isV6)
{
rec.rr_type = qTypeAAAA;
ip.ToV6(rec.rData);
}
else
{
const auto addr = net::TruncateV6(ip);
rec.rr_type = qTypeA;
rec.rData.resize(4);
oxenc::write_host_as_big(addr.h, rec.rData.data());
}
answers.emplace_back(std::move(rec));
}
}
void
Message::AddAReply(std::string name, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = question.qtype;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void
Message::AddNSReply(std::string name, RR_TTL_t ttl)
{
if (not questions.empty())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeNS;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void
Message::AddCNAMEReply(std::string name, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeCNAME;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void
Message::AddMXReply(std::string name, uint16_t priority, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeMX;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
buf.put_uint16(priority);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void
Message::AddSRVReply(std::vector<SRVData> records, RR_TTL_t ttl)
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
for (const auto& srv : records)
{
if (not srv.IsValid())
{
AddNXReply();
return;
}
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeSRV;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
buf.put_uint16(srv.priority);
buf.put_uint16(srv.weight);
buf.put_uint16(srv.port);
std::string target;
if (srv.target == "")
{
// get location of second dot (after service.proto) in qname
size_t pos = question.qname.find(".");
pos = question.qname.find(".", pos + 1);
target = question.qname.substr(pos + 1);
}
else
{
target = srv.target;
}
if (not EncodeNameTo(&buf, target))
{
AddNXReply();
return;
}
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
void
Message::AddTXTReply(std::string str, RR_TTL_t ttl)
{
auto& rec = answers.emplace_back();
rec.rr_name = questions[0].qname;
rec.rr_class = qClassIN;
rec.rr_type = qTypeTXT;
rec.ttl = ttl;
std::array<byte_t, 1024> tmp{};
llarp_buffer_t buf(tmp);
while (not str.empty())
{
const auto left = std::min(str.size(), size_t{256});
const auto sub = str.substr(0, left);
uint8_t byte = left;
*buf.cur = byte;
buf.cur++;
if (not buf.write(sub.begin(), sub.end()))
throw std::length_error("text record too big");
str = str.substr(left);
}
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
std::copy_n(buf.base, buf.sz, rec.rData.data());
}
void
Message::AddNXReply(RR_TTL_t)
{
if (questions.size())
{
answers.clear();
authorities.clear();
additional.clear();
// authorative response with recursion available
hdr_fields = reply_flags(hdr_fields);
// don't allow recursion on this request
hdr_fields &= ~flags_RD;
hdr_fields |= flags_RCODENameError;
}
}
std::string
Message::ToString() const
{
return fmt::format(
"[DNSMessage id={:x} fields={:x} questions={{{}}} answers={{{}}} authorities={{{}}} "
"additional={{{}}}]",
hdr_id,
hdr_fields,
fmt::format("{}", fmt::join(questions, ",")),
fmt::format("{}", fmt::join(answers, ",")),
fmt::format("{}", fmt::join(authorities, ",")),
fmt::format("{}", fmt::join(additional, ",")));
}
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf)
{
MessageHeader hdr{};
if (not hdr.Decode(&buf))
return std::nullopt;
Message msg{hdr};
if (not msg.Decode(&buf))
return std::nullopt;
return msg;
}
} // namespace dns
} // namespace llarp
} // namespace llarp::dns

View file

@ -1,113 +1,181 @@
#pragma once
#include "serialize.hpp"
#include <cstdint>
#include <llarp/util/buffer.hpp>
#include <llarp/util/str.hpp>
#include "rr.hpp"
#include "question.hpp"
#include "srv_data.hpp"
#include "bits.hpp"
namespace llarp::dns
{
struct MessageHeader
{
static constexpr size_t Size = 12;
MessageHeader() = default;
MessageHeader(const MessageHeader&) = default;
MessageHeader(MessageHeader&&) = default;
MessageHeader&
operator=(const MessageHeader&) = default;
MessageHeader&
operator=(MessageHeader&&) = default;
/// construct from binary view.
/// raw is modified to contain any remaining data.
explicit MessageHeader(byte_view_t& raw);
uint16_t id;
uint16_t fields;
uint16_t qd_count;
uint16_t an_count;
uint16_t ns_count;
uint16_t ar_count;
bstring_t
encode_dns() const;
util::StatusObject
to_json() const;
/// set rcode.
MessageHeader&
rcode(uint16_t i);
std::string
ToString() const;
bool
operator==(const MessageHeader& other) const
{
return id == other.id && fields == other.fields && qd_count == other.qd_count
&& an_count == other.an_count && ns_count == other.ns_count && ar_count == other.ar_count;
}
};
struct Message
{
/// constructs a dns message from a raw dns wire protocol message.
/// raw is modified to contain any remaining data.
explicit Message(byte_view_t& raw);
Message() = default;
Message(const Message&) = default;
Message(Message&&) = default;
Message&
operator=(const Message&) = default;
Message&
operator=(Message&&) = default;
/// mark as not found.
Message&
nx();
/// mark as servfail.
Message&
servfail();
/// encodes this dns message to dns wire protocol.
bstring_t
encode_dns() const;
util::StatusObject
to_json() const;
// Wrapper around Encode that encodes into a new buffer and returns it
[[nodiscard]] inline OwnedBuffer
ToBuffer() const
{
const auto data = encode_dns();
return OwnedBuffer{data.data(), data.size()};
}
std::string
ToString() const;
MessageHeader hdr;
std::vector<Question> questions;
std::vector<ResourceRecord> answers;
std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additional;
template <typename... Args_t>
void
add_reply(RRType rr_type, Args_t&&... args)
{
auto qname = questions[0].qname();
answers.emplace_back(qname, rr_type, args...);
}
inline void
AddNSReply(std::string_view txt, uint32_t ttl = 1) [[deprecated("use rr_ns")]]
{
// todo
}
inline void
AddTXTReply(std::string_view txt, uint32_t ttl = 1) [[deprecated("use rr_txt")]]
{
// todo
}
inline void
AddMXReply(std::string_view name, uint32_t ttl = 1) [[deprecated("use rr_mx")]]
{
// todo
}
inline void
AddCNAMEReply(std::string_view name, uint32_t ttl = 1) [[deprecated("use rr_cname")]]
{
add_reply(RRType::CNAME, RData{split_dns_name(name)}, ttl);
}
inline void
AddPTRReply(std::string_view name, uint32_t ttl = 1) [[deprecated("use rr_ptr")]]
{
add_reply(RRType::PTR, RData{split_dns_name(name)}, ttl);
}
inline void
AddAReply(net::ipv4addr_t ip, uint32_t ttl = 1) [[deprecated("use rr_cname")]]
{
add_reply(RRType::A, RData{ip}, ttl);
}
inline void
AddSRVReply(const std::vector<dns::SRVData>& recs, uint32_t ttl = 1)
[[deprecated("use rr_srv")]]
{
for (const auto& rec : recs)
add_reply(RRType::SRV, RData{rec.encode_dns(questions[0].qname())}, ttl);
}
inline void
rr_add_ipaddr(net::ipaddr_t ip, uint32_t ttl = 1)
{
add_reply(
std::holds_alternative<net::ipv4addr_t>(ip) ? RRType::A : RRType::AAAA,
var::visit([](auto&& ip) { return RData{ip}; }, ip),
ttl);
}
inline void
AddINReply(std::string_view addr, uint32_t ttl = 1) [[deprecated("use rr_in")]]
{}
};
} // namespace llarp::dns
namespace llarp
{
namespace dns
{
struct SRVData;
using MsgID_t = uint16_t;
using Fields_t = uint16_t;
using Count_t = uint16_t;
struct MessageHeader : public Serialize
{
static constexpr size_t Size = 12;
MessageHeader() = default;
MsgID_t id;
Fields_t fields;
Count_t qd_count;
Count_t an_count;
Count_t ns_count;
Count_t ar_count;
bool
Encode(llarp_buffer_t* buf) const override;
bool
Decode(llarp_buffer_t* buf) override;
util::StatusObject
ToJSON() const override;
bool
operator==(const MessageHeader& other) const
{
return id == other.id && fields == other.fields && qd_count == other.qd_count
&& an_count == other.an_count && ns_count == other.ns_count
&& ar_count == other.ar_count;
}
};
struct Message : public Serialize
{
explicit Message(const MessageHeader& hdr);
explicit Message(const Question& question);
Message(Message&& other);
Message(const Message& other);
util::StatusObject
ToJSON() const override;
void
AddNXReply(RR_TTL_t ttl = 1);
void
AddServFail(RR_TTL_t ttl = 30);
void
AddMXReply(std::string name, uint16_t priority, RR_TTL_t ttl = 1);
void
AddCNAMEReply(std::string name, RR_TTL_t ttl = 1);
void
AddINReply(llarp::huint128_t addr, bool isV6, RR_TTL_t ttl = 1);
void
AddAReply(std::string name, RR_TTL_t ttl = 1);
void
AddSRVReply(std::vector<SRVData> records, RR_TTL_t ttl = 1);
void
AddNSReply(std::string name, RR_TTL_t ttl = 1);
void
AddTXTReply(std::string value, RR_TTL_t ttl = 1);
bool
Encode(llarp_buffer_t* buf) const override;
bool
Decode(llarp_buffer_t* buf) override;
// Wrapper around Encode that encodes into a new buffer and returns it
[[nodiscard]] OwnedBuffer
ToBuffer() const;
std::string
ToString() const;
MsgID_t hdr_id;
Fields_t hdr_fields;
std::vector<Question> questions;
std::vector<ResourceRecord> answers;
std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additional;
};
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf);
} // namespace dns
template <>
constexpr inline bool IsToStringFormattable<llarp::dns::Message> = true;
template <>
constexpr inline bool IsToStringFormattable<llarp::dns::MessageHeader> = true;
} // namespace llarp

View file

@ -1,139 +1,173 @@
#include "name.hpp"
#include <cstdint>
#include <limits>
#include <llarp/net/net.hpp>
#include <llarp/net/ip.hpp>
#include <llarp/util/str.hpp>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include "llarp/util/types.hpp"
#include <fmt/core.h>
#include <oxenc/hex.h>
namespace llarp
namespace llarp::dns
{
namespace dns
namespace
{
std::optional<std::string>
DecodeName(llarp_buffer_t* buf, bool trimTrailingDot)
template <typename Label_t>
std::vector<Label_t>
decode(byte_view_t& bstr)
{
if (buf->size_left() < 1)
return std::nullopt;
auto result = std::make_optional<std::string>();
auto& name = *result;
size_t l;
std::vector<Label_t> labels;
size_t label_len{};
do
{
l = *buf->cur;
buf->cur++;
if (l)
{
if (buf->size_left() < l)
return std::nullopt;
if (bstr.empty())
throw std::invalid_argument{"unexpected end of data"};
label_len = bstr[0];
auto label_size = label_len + 1;
if (bstr.size() < label_size)
throw std::invalid_argument{
"dns label size mismatch: {} < {}"_format(bstr.size(), label_size)};
name.append((const char*)buf->cur, l);
name += '.';
}
buf->cur = buf->cur + l;
} while (l);
/// trim off last dot
if (trimTrailingDot)
name.pop_back();
return result;
if (label_len)
labels.emplace_back(
reinterpret_cast<const typename Label_t::value_type*>(&bstr[1]), label_len);
bstr = bstr.substr(label_size);
} while (label_len > 0);
// remove trailing empty label.
if (labels.back().empty())
labels.pop_back();
return labels;
}
bool
EncodeNameTo(llarp_buffer_t* buf, std::string_view name)
template <typename Ret_t, typename Val_t>
Ret_t
encode(std::vector<Val_t> labels)
{
if (name.size() && name.back() == '.')
name.remove_suffix(1);
for (auto part : llarp::split(name, "."))
using char_type_t = typename Ret_t::value_type;
Ret_t str;
for (const auto& label : labels)
{
size_t l = part.length();
if (l > 63)
return false;
*(buf->cur) = l;
buf->cur++;
if (buf->size_left() < l)
return false;
if (l)
{
std::memcpy(buf->cur, part.data(), l);
buf->cur += l;
}
else
if (label.empty())
break;
auto label_size = label.size();
if (label_size > max_dns_label_size)
throw std::invalid_argument{
"dns label too big: {} > {}"_format(label_size, max_dns_label_size)};
str += static_cast<char_type_t>(label_size);
str += std::basic_string_view<char_type_t>{
reinterpret_cast<const char_type_t*>(label.data()), label_size};
}
*buf->cur = 0;
buf->cur++;
return true;
str += char_type_t{};
return str;
}
} // namespace
std::optional<huint128_t>
DecodePTR(std::string_view name)
std::vector<std::string>
decode_dns_labels(byte_view_t& bstr)
{
return decode<std::string>(bstr);
}
std::vector<std::string_view>
decode_dns_label_views(byte_view_t& bstr)
{
return decode<std::string_view>(bstr);
}
bstring_t
encode_dns_labels(std::vector<std::string_view> labels)
{
return encode<bstring_t>(labels);
}
bstring_t
encode_dns_labels(std::vector<std::string> labels)
{
return encode<bstring_t>(labels);
}
bstring_t
encode_dns_name(std::string_view name)
{
return encode_dns_labels(split_dns_name(name));
}
std::optional<huint128_t>
DecodePTR(std::string_view name)
{
bool isV6 = false;
auto pos = name.find(".in-addr.arpa");
if (pos == std::string::npos)
{
bool isV6 = false;
auto pos = name.find(".in-addr.arpa");
if (pos == std::string::npos)
{
pos = name.find(".ip6.arpa");
isV6 = true;
}
if (pos == std::string::npos)
return std::nullopt;
name = name.substr(0, pos + 1);
const auto numdots = std::count(name.begin(), name.end(), '.');
if (numdots == 4 && !isV6)
{
std::array<uint8_t, 4> q;
for (int i = 3; i >= 0; i--)
{
pos = name.find('.');
if (!llarp::parse_int(name.substr(0, pos), q[i]))
return std::nullopt;
name.remove_prefix(pos + 1);
}
return net::ExpandV4(llarp::ipaddr_ipv4_bits(q[0], q[1], q[2], q[3]));
}
if (numdots == 32 && name.size() == 64 && isV6)
{
// We're going to convert from nybbles a.b.c.d.e.f.0.1.2.3.[...] into hex string
// "badcfe1032...", then decode the hex string to bytes.
std::array<char, 32> in;
auto in_pos = in.data();
for (size_t i = 0; i < 64; i += 4)
{
if (not(oxenc::is_hex_digit(name[i]) and name[i + 1] == '.'
and oxenc::is_hex_digit(name[i + 2]) and name[i + 3] == '.'))
return std::nullopt;
// Flip the nybbles because the smallest one is first
*in_pos++ = name[i + 2];
*in_pos++ = name[i];
}
assert(in_pos == in.data() + in.size());
huint128_t ip;
static_assert(in.size() == 2 * sizeof(ip.h));
// our string right now is the little endian representation, so load it as such on little
// endian, or in reverse on big endian.
if constexpr (oxenc::little_endian)
oxenc::from_hex(in.begin(), in.end(), reinterpret_cast<uint8_t*>(&ip.h));
else
oxenc::from_hex(in.rbegin(), in.rend(), reinterpret_cast<uint8_t*>(&ip.h));
return ip;
}
pos = name.find(".ip6.arpa");
isV6 = true;
}
if (pos == std::string::npos)
return std::nullopt;
}
bool
NameIsReserved(std::string_view name)
name = name.substr(0, pos + 1);
const auto numdots = std::count(name.begin(), name.end(), '.');
if (numdots == 4 && !isV6)
{
const std::vector<std::string_view> reserved_names = {
".snode.loki"sv, ".loki.loki"sv, ".snode.loki."sv, ".loki.loki."sv};
for (const auto& reserved : reserved_names)
std::array<uint8_t, 4> q;
for (int i = 3; i >= 0; i--)
{
if (ends_with(name, reserved)) // subdomain foo.loki.loki
return true;
if (name == reserved.substr(1)) // loki.loki itself
return true;
pos = name.find('.');
if (!llarp::parse_int(name.substr(0, pos), q[i]))
return std::nullopt;
name.remove_prefix(pos + 1);
}
return false;
return net::ExpandV4(llarp::ipaddr_ipv4_bits(q[0], q[1], q[2], q[3]));
}
} // namespace dns
} // namespace llarp
if (numdots == 32 && name.size() == 64 && isV6)
{
// We're going to convert from nybbles a.b.c.d.e.f.0.1.2.3.[...] into hex string
// "badcfe1032...", then decode the hex string to bytes.
std::array<char, 32> in;
auto in_pos = in.data();
for (size_t i = 0; i < 64; i += 4)
{
if (not(oxenc::is_hex_digit(name[i]) and name[i + 1] == '.'
and oxenc::is_hex_digit(name[i + 2]) and name[i + 3] == '.'))
return std::nullopt;
// Flip the nybbles because the smallest one is first
*in_pos++ = name[i + 2];
*in_pos++ = name[i];
}
assert(in_pos == in.data() + in.size());
huint128_t ip;
static_assert(in.size() == 2 * sizeof(ip.h));
// our string right now is the little endian representation, so load it as such on little
// endian, or in reverse on big endian.
if constexpr (oxenc::little_endian)
oxenc::from_hex(in.begin(), in.end(), reinterpret_cast<uint8_t*>(&ip.h));
else
oxenc::from_hex(in.rbegin(), in.rend(), reinterpret_cast<uint8_t*>(&ip.h));
return ip;
}
return std::nullopt;
}
bool
NameIsReserved(std::string_view name)
{
constexpr std::array reserved_names = {
".snode.snode"sv, ".snode.loki"sv, ".loki.snode"sv, ".loki.loki"sv};
for (const auto& reserved : reserved_names)
{
if (ends_with(name, reserved)) // subdomain foo.loki.loki
return true;
if (name == reserved.substr(1)) // loki.loki itself
return true;
}
return false;
}
} // namespace llarp::dns

View file

@ -1,28 +1,66 @@
#pragma once
#include <algorithm>
#include <llarp/net/net_int.hpp>
#include <llarp/util/buffer.hpp>
#include <llarp/util/str.hpp>
#include <string>
#include <optional>
#include <string_view>
#include <vector>
#include <array>
namespace llarp
namespace llarp::dns
{
namespace dns
/// the max size of one dns label in bytes.
constexpr auto max_dns_label_size = 63;
/// return true if this tld is a lokinet tld.
inline bool
is_lokinet_tld(std::string_view str)
{
/// decode name from buffer; return nullopt on failure
std::optional<std::string>
DecodeName(llarp_buffer_t* buf, bool trimTrailingDot = false);
constexpr std::array lokinet_tlds = {"loki"sv, "snode"sv};
for (const auto& tld : lokinet_tlds)
{
if (string_iequal(tld, str))
return true;
}
return false;
}
/// encode name to buffer
bool
EncodeNameTo(llarp_buffer_t* buf, std::string_view name);
std::optional<huint128_t>
DecodePTR(std::string_view name);
std::optional<huint128_t>
DecodePTR(std::string_view name);
bool
NameIsReserved(std::string_view name);
bool
NameIsReserved(std::string_view name);
/// encode a series of dns labels into dns wire protocol.
bstring_t
encode_dns_labels(std::vector<std::string_view> labels);
} // namespace dns
} // namespace llarp
/// encode a series of dns labels into dns wire protocol.
bstring_t
encode_dns_labels(std::vector<std::string> labels);
/// encode a dns name in dotted form and encode it to dns wire protocol.
bstring_t
encode_dns_name(std::string_view name);
/// decode dns lables from dns wire protocol
std::vector<std::string>
decode_dns_labels(byte_view_t& buf);
/// decode dns lables from dns wire protocol, no copy version.
std::vector<std::string_view>
decode_dns_label_views(byte_view_t& buf);
/// named helper functiopn to split a dns name into labels.
template <typename T>
auto
split_dns_name(const T& name)
{
return split(name, ".");
}
} // namespace llarp::dns

View file

@ -1,129 +1,159 @@
#include "question.hpp"
#include "bits.hpp"
#include "llarp/dns/name.hpp"
#include <llarp/util/underlying.hpp>
#include <oxenc/endian.h>
#include <llarp/util/logging.hpp>
#include <llarp/util/str.hpp>
#include "dns.hpp"
#include <stdexcept>
#include <string_view>
namespace llarp
#include <oxen/log/format.hpp>
#include <type_traits>
#include <vector>
namespace llarp::dns
{
namespace dns
static auto logcat = log::Cat("dns");
Question::Question(std::vector<std::string_view> labels, RRType type)
: qtype{to_underlying(type)}, qclass{bits::qclass_in}
{
static auto logcat = log::Cat("dns");
Question::Question(Question&& other)
: qname(std::move(other.qname))
, qtype(std::move(other.qtype))
, qclass(std::move(other.qclass))
{}
Question::Question(const Question& other)
: qname(other.qname), qtype(other.qtype), qclass(other.qclass)
{}
Question::Question(std::string name, QType_t type)
: qname{std::move(name)}, qtype{type}, qclass{qClassIN}
if (labels.empty())
throw std::invalid_argument{"dns labels are empty"};
bool prev_was_empty{labels.front().empty()};
for (const auto& label : labels)
{
if (qname.empty())
throw std::invalid_argument{"qname cannot be empty"};
bool _empty = label.empty();
if (_empty and prev_was_empty)
throw std::invalid_argument{"more than one empty label given in a row"};
prev_was_empty = _empty;
if (label.size() > max_dns_label_size)
throw std::invalid_argument{
fmt::format("dns label too big: {} > {}", label.size(), max_dns_label_size)};
_qname_labels.emplace_back(label);
}
if (_qname_labels.back().empty())
_qname_labels.pop_back();
}
bstring_t
Question::encode_dns() const
{
bstring_t ret(size_t{4}, uint8_t{});
oxenc::write_host_as_big<uint16_t>(qtype, &ret[0]);
oxenc::write_host_as_big<uint16_t>(bits::qclass_in, &ret[2]);
return encode_dns_labels(_qname_labels) + ret;
}
Question::Question(byte_view_t& str)
{
_qname_labels = decode_dns_labels(str);
if (str.size() < 4)
throw std::invalid_argument{"data too small"};
qtype = oxenc::load_big_to_host<uint16_t>(&str[0]);
qclass = oxenc::load_big_to_host<uint16_t>(&str[2]);
str = str.substr(4);
}
bool
Question::valid() const
{
return qclass == bits::qclass_in;
}
util::StatusObject
Question::to_json() const
{
return util::StatusObject{{"qname", qname()}, {"qtype", qtype}, {"qclass", qclass}};
}
bool
Question::IsName(const std::string& other) const
{
auto name = qname();
return other == name or (other + ".") == name;
}
bool
Question::IsLocalhost() const
{
auto sz = _qname_labels.size();
return sz >= 2 and is_lokinet_tld(_qname_labels.back())
and _qname_labels[sz - 2] == "localhost";
}
bool
Question::HasSubdomains() const
{
return _qname_labels.size() > 2;
}
std::string
Question::Subdomains() const
{
if (not HasSubdomains())
return "";
auto it = _qname_labels.rbegin();
++it;
++it;
std::string ret{*it};
++it;
for (auto itr = it; itr != _qname_labels.rend(); ++itr)
{
ret = fmt::format("{}.{}", *itr, ret);
}
bool
Question::Encode(llarp_buffer_t* buf) const
{
if (!EncodeNameTo(buf, qname))
return false;
if (!buf->put_uint16(qtype))
return false;
return buf->put_uint16(qclass);
}
return ret;
}
bool
Question::Decode(llarp_buffer_t* buf)
{
if (auto name = DecodeName(buf))
qname = *std::move(name);
else
{
log::error(logcat, "failed to decode name");
return false;
}
if (!buf->read_uint16(qtype))
{
log::error(logcat, "failed to decode type");
return false;
}
if (!buf->read_uint16(qclass))
{
log::error(logcat, "failed to decode class");
return false;
}
return true;
}
std::string
Question::qname() const
{
return fmt::format("{}", fmt::join(_qname_labels, "."));
}
util::StatusObject
Question::ToJSON() const
{
return util::StatusObject{{"qname", qname}, {"qtype", qtype}, {"qclass", qclass}};
}
std::string_view
Question::tld() const
{
if (_qname_labels.size() < 2)
return "";
return _qname_labels[_qname_labels.size() - 1];
}
bool
Question::IsName(const std::string& other) const
{
// does other have a . at the end?
if (other.find_last_of('.') == (other.size() - 1))
return other == qname;
// no, add it and retry
return IsName(other + ".");
}
std::string
Question::Domain() const
{
auto itr = _qname_labels.rbegin();
if (_qname_labels.size() > 1)
++itr;
return *itr;
}
bool
Question::IsLocalhost() const
{
return (qname == "localhost.loki." or llarp::ends_with(qname, ".localhost.loki."));
}
std::string
Question::ToString() const
{
return fmt::format("[DNSQuestion qname={} qtype={:x} qclass={:x}]", qname(), qtype, qclass);
}
bool
Question::HasSubdomains() const
{
const auto parts = split(qname, ".", true);
return parts.size() >= 3;
}
std::string
Question::Subdomains() const
{
if (qname.size() < 2)
return "";
size_t pos;
pos = qname.rfind('.', qname.size() - 2);
if (pos == std::string::npos or pos == 0)
return "";
pos = qname.rfind('.', pos - 1);
if (pos == std::string::npos or pos == 0)
return "";
return qname.substr(0, pos);
}
std::string
Question::Name() const
{
return qname.substr(0, qname.find_last_of('.'));
}
bool
Question::HasTLD(const std::string& tld) const
{
return qname.find(tld) != std::string::npos
&& qname.rfind(tld) == (qname.size() - tld.size()) - 1;
}
std::string
Question::ToString() const
{
return fmt::format("[DNSQuestion qname={} qtype={:x} qclass={:x}]", qname, qtype, qclass);
}
} // namespace dns
} // namespace llarp
std::vector<std::string_view>
Question::view_dns_labels() const
{
std::vector<std::string_view> view;
for (const auto& label : _qname_labels)
view.emplace_back(label);
return view;
}
} // namespace llarp::dns

View file

@ -1,72 +1,99 @@
#pragma once
#include "serialize.hpp"
#include "name.hpp"
#include "rr.hpp"
#include <cstdint>
#include <llarp/net/net_int.hpp>
#include <string_view>
#include <vector>
#include <llarp/util/status.hpp>
#include <llarp/util/str.hpp>
namespace llarp::dns
{
struct Question
{
Question() = default;
Question(const Question&) = default;
Question(Question&&) = default;
Question&
operator=(const Question&) = default;
Question&
operator=(Question&&) = default;
explicit Question(byte_view_t& raw);
Question(std::string name, RRType type) : Question{split_dns_name(name), type}
{}
Question(std::vector<std::string_view> labels, RRType type);
/// encode to dns wire proto.
bstring_t
encode_dns() const;
std::string
ToString() const;
uint16_t qtype;
uint16_t qclass;
/// construct the full domain name including trailing dot.
std::string
qname() const;
/// determine if we match a name
bool
IsName(const std::string& other) const;
/// is the name [something.]localhost.loki. ?
bool
IsLocalhost() const;
/// return true if we have subdomains in ths question
bool
HasSubdomains() const;
/// get subdomain(s), if any, from qname
std::string
Subdomains() const;
/// get the dns label before the gtld, including the gtld and no trailing full stop.
std::string
Domain() const;
/// get the tld for the question's qname.
std::string_view
tld() const;
inline bool
HasTLD(std::string_view _tld) const [[deprecated]]
{
return tld() == _tld;
}
util::StatusObject
to_json() const;
/// return true of this question has sane data.
bool
valid() const;
/// get a view over all the dns labels for this question.
std::vector<std::string_view>
view_dns_labels() const;
private:
std::vector<std::string> _qname_labels;
};
} // namespace llarp::dns
namespace llarp
{
namespace dns
{
using QType_t = uint16_t;
using QClass_t = uint16_t;
struct Question : public Serialize
{
Question() = default;
explicit Question(std::string name, QType_t type);
Question(Question&& other);
Question(const Question& other);
bool
Encode(llarp_buffer_t* buf) const override;
bool
Decode(llarp_buffer_t* buf) override;
std::string
ToString() const;
bool
operator==(const Question& other) const
{
return qname == other.qname && qtype == other.qtype && qclass == other.qclass;
}
std::string qname;
QType_t qtype;
QClass_t qclass;
/// determine if we match a name
bool
IsName(const std::string& other) const;
/// is the name [something.]localhost.loki. ?
bool
IsLocalhost() const;
/// return true if we have subdomains in ths question
bool
HasSubdomains() const;
/// get subdomain(s), if any, from qname
std::string
Subdomains() const;
/// return qname with no trailing .
std::string
Name() const;
/// determine if we are using this TLD
bool
HasTLD(const std::string& tld) const;
util::StatusObject
ToJSON() const override;
};
} // namespace dns
} // namespace llarp
template <>
constexpr inline bool llarp::IsToStringFormattable<llarp::dns::Question> = true;
template <>
constexpr inline bool IsToStringFormattable<dns::Question> = true;
}

View file

@ -1,125 +1,252 @@
#include "rr.hpp"
#include "dns.hpp"
#include "bits.hpp"
#include "llarp/dns/name.hpp"
#include "llarp/dns/question.hpp"
#include "llarp/util/time.hpp"
#include <cstddef>
#include <cstdint>
#include <llarp/util/formattable.hpp>
#include <llarp/util/mem.hpp>
#include <llarp/util/str.hpp>
#include <llarp/util/logging.hpp>
#include <llarp/util/underlying.hpp>
namespace llarp
#include <nlohmann/json_fwd.hpp>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_set>
#include <vector>
#include <algorithm>
#include <oxenc/endian.h>
#include <oxenc/hex.h>
namespace llarp::dns
{
namespace dns
static auto logcat = log::Cat("dns");
namespace
{
static auto logcat = log::Cat("dns");
ResourceRecord::ResourceRecord(const ResourceRecord& other)
: rr_name(other.rr_name)
, rr_type(other.rr_type)
, rr_class(other.rr_class)
, ttl(other.ttl)
, rData(other.rData)
{}
ResourceRecord::ResourceRecord(ResourceRecord&& other)
: rr_name(std::move(other.rr_name))
, rr_type(std::move(other.rr_type))
, rr_class(std::move(other.rr_class))
, ttl(std::move(other.ttl))
, rData(std::move(other.rData))
{}
ResourceRecord::ResourceRecord(std::string name, RRType_t type, RR_RData_t data)
: rr_name{std::move(name)}
, rr_type{type}
, rr_class{qClassIN}
, ttl{1}
, rData{std::move(data)}
{}
bool
ResourceRecord::Encode(llarp_buffer_t* buf) const
template <typename T>
[[nodiscard]] auto
encode_rdata(const T& rdata)
{
if (not EncodeNameTo(buf, rr_name))
return false;
if (!buf->put_uint16(rr_type))
{
return false;
}
if (!buf->put_uint16(rr_class))
{
return false;
}
if (!buf->put_uint32(ttl))
{
return false;
}
if (!EncodeRData(buf, rData))
{
return false;
}
return true;
const std::size_t sz = rdata.size();
if (sz > 65536)
throw std::invalid_argument{"rdata too big: {} > {}"_format(sz, 65536)};
uint16_t len{static_cast<uint16_t>(sz)};
bstring_t vec;
vec.resize(sz + 2);
auto* ptr = vec.data();
oxenc::write_host_as_big(len, ptr);
ptr += 2;
std::copy(rdata.begin(), rdata.end(), ptr);
return vec;
}
bool
ResourceRecord::Decode(llarp_buffer_t* buf)
[[nodiscard]] byte_view_t
decode_rdata(byte_view_t& raw)
{
uint16_t discard;
if (!buf->read_uint16(discard))
return false;
if (!buf->read_uint16(rr_type))
{
log::debug(logcat, "failed to decode rr type");
return false;
}
if (!buf->read_uint16(rr_class))
{
log::debug(logcat, "failed to decode rr class");
return false;
}
if (!buf->read_uint32(ttl))
{
log::debug(logcat, "failed to decode ttl");
return false;
}
if (!DecodeRData(buf, rData))
{
log::debug(logcat, "failed to decode rr rdata {}", *this);
return false;
}
return true;
if (raw.size() < 2)
throw std::invalid_argument{
("resource record data too small to be a resource record: {} < 2"_format(raw.size()))};
size_t len = oxenc::load_big_to_host<uint16_t>(raw.data());
raw = raw.substr(2);
if (raw.size() < len)
throw std::invalid_argument{
"resource record data buffer too small: {} < {}"_format(raw.size(), len)};
byte_view_t ret{raw.data(), len};
raw = raw.substr(len);
return ret;
}
util::StatusObject
ResourceRecord::ToJSON() const
[[nodiscard]] byte_view_t
decode_rdata(bstring_t&& raw)
{
return util::StatusObject{
{"name", rr_name},
{"type", rr_type},
{"class", rr_class},
{"ttl", ttl},
{"rdata", std::string{reinterpret_cast<const char*>(rData.data()), rData.size()}}};
byte_view_t view{raw};
return decode_rdata(view);
}
} // namespace
std::string
ResourceRecord::ToString() const
ResourceRecord::ResourceRecord(std::string name, RRType type, RData data, uint32_t ttl)
: rr_name_labels{}, rr_type{type}, rr_ttl{ttl}, rr_data{std::move(data)}
{
auto parts = split(name, ".");
rr_name_labels.insert(rr_name_labels.cbegin(), parts.begin(), parts.end());
}
bstring_t
ResourceRecord::encode_dns() const
{
bstring_t ret;
ret.resize(8);
auto ptr = ret.data();
oxenc::write_host_as_big(static_cast<std::underlying_type_t<decltype(rr_type)>>(rr_type), ptr);
ptr += 2;
oxenc::write_host_as_big(bits::qclass_in, ptr);
ptr += 2;
oxenc::write_host_as_big(rr_ttl, ptr);
ptr += 4;
ret += rr_data.data();
return ret;
}
std::optional<RRType>
get_rr_type(uint16_t i)
{
const std::unordered_set<std::underlying_type_t<RRType>> mapping = {
to_underlying(RRType::SRV),
to_underlying(RRType::A),
to_underlying(RRType::AAAA),
to_underlying(RRType::CNAME),
to_underlying(RRType::MX),
to_underlying(RRType::NS),
to_underlying(RRType::PTR),
to_underlying(RRType::TXT)};
if (mapping.count(i))
return RRType{i};
return std::nullopt;
}
std::string
ToString(RRType t)
{
std::string ret;
switch (t)
{
return fmt::format(
"[RR name={} type={} class={} ttl={} rdata-size={}]",
rr_name,
rr_type,
rr_class,
ttl,
rData.size());
case RRType::SRV:
ret = "SRV";
break;
case RRType::A:
ret = "A";
break;
case RRType::AAAA:
ret = "AAAA";
break;
case RRType::CNAME:
ret = "CNAME";
break;
case RRType::MX:
ret = "MX";
break;
case RRType::NS:
ret = "NS";
break;
case RRType::PTR:
ret = "PTR";
break;
case RRType::TXT:
ret = "TXT";
break;
}
return ret;
}
bool
ResourceRecord::HasCNameForTLD(const std::string& tld) const
{
if (rr_type != qTypeCNAME)
return false;
llarp_buffer_t buf(rData);
if (auto name = DecodeName(&buf))
return name->rfind(tld) == name->size() - tld.size() - 1;
ResourceRecord::ResourceRecord(bstring_t&& raw)
{
if (raw.size() < 12)
throw std::invalid_argument{"resource record too small: {} < {}"_format(raw.size(), 12)};
auto t = oxenc::load_big_to_host<uint16_t>(raw.data());
auto maybe_type = get_rr_type(t);
if (not maybe_type)
throw std::invalid_argument{"unknown RR type: {:x}"_format(t)};
rr_type = *maybe_type;
raw = raw.substr(2);
if (auto qc = oxenc::load_big_to_host<uint16_t>(raw.data()); qc != bits::qclass_in)
throw std::invalid_argument{
"resource record does not have qclass IN, has {:x} instead"_format(qc)};
raw = raw.substr(2);
rr_ttl = oxenc::load_big_to_host<uint32_t>(raw.data());
raw = raw.substr(4);
rr_data = RData{std::move(raw)};
}
util::StatusObject
ResourceRecord::to_json() const
{
return util::StatusObject{
{"name", "{{}}"_format(fmt::join(rr_name_labels, "."))},
{"type", rr_type},
{"ttl", rr_ttl},
{"rr_data", rr_data.to_json()}};
}
std::string
ResourceRecord::ToString() const
{
return fmt::format(
"[RR name={{}} type={} ttl={} rr_data={}]",
fmt::join(rr_name_labels, "."),
rr_type,
rr_ttl,
rr_data);
}
bool
ResourceRecord::HasCNameForTLD(const std::string& tld) const
{
if (rr_type != RRType::CNAME)
return false;
}
if (auto labels = rr_data.view_dns_labels(); labels.size() > 1)
return labels[labels.size() - 1] == tld;
return false;
}
} // namespace dns
} // namespace llarp
RData::RData(std::vector<std::string_view> dns_labels)
: _raw{encode_rdata(encode_dns_labels(dns_labels))}
{}
RData::RData(net::ipv4addr_t ip) : _raw{}
{
_raw.resize(4);
std::memcpy(_raw.data(), &ip.n, 4);
}
RData::RData(net::ipv6addr_t ip) : _raw{}
{
_raw.resize(16);
std::memcpy(_raw.data(), &ip.n, 16);
}
RData::RData(bstring_t&& raw) : _raw{raw}
{}
RData::RData(uint16_t priority, std::vector<std::string_view> dns_labels)
{
_raw.resize(2);
oxenc::write_host_as_big(priority, _raw.data());
_raw += encode_dns_labels(dns_labels);
}
nlohmann::json
RData::to_json() const
{
return {{"rdata_hex", oxenc::to_hex(_raw.begin(), _raw.end())}};
}
std::vector<std::string_view>
RData::view_dns_labels() const
{
byte_view_t view{_raw};
return decode_dns_label_views(view);
}
std::string
RData::ToString() const
{
return fmt::format("[RData hex={}]", oxenc::to_hex(_raw.begin(), _raw.end()));
}
ResourceRecord::ResourceRecord(byte_view_t& raw) : ResourceRecord{bstring_t{decode_rdata(raw)}}
{}
} // namespace llarp::dns

View file

@ -1,52 +1,139 @@
#pragma once
#include "name.hpp"
#include "serialize.hpp"
#include <llarp/net/net_int.hpp>
#include <llarp/util/status.hpp>
#include <llarp/util/underlying.hpp>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
namespace llarp
namespace llarp::dns
{
namespace dns
/// resource record types enum.
enum class RRType : uint16_t
{
using RRClass_t = uint16_t;
using RRType_t = uint16_t;
using RR_RData_t = std::vector<byte_t>;
using RR_TTL_t = uint32_t;
SRV = 33,
AAAA = 28,
TXT = 16,
MX = 15,
PTR = 12,
CNAME = 5,
NS = 2,
A = 1
};
struct ResourceRecord : public Serialize
std::optional<RRType>
get_rr_type(uint16_t i);
std::string
ToString(RRType t);
class RData
{
bstring_t _raw;
public:
RData() = default;
RData(const RData&) = default;
RData(RData&&) = default;
RData&
operator=(const RData&) = default;
RData&
operator=(RData&&) = default;
/// construct from view, modifies view to contain remaining unread data.
explicit RData(byte_view_t& view);
/// construct rdata holding dns labels.
explicit RData(std::vector<std::string_view> dns_labels);
/// construct holding ipv4 address
explicit RData(net::ipv4addr_t ip);
/// construct holding ipv6 address
explicit RData(net::ipv6addr_t ip);
/// mx record constructor.
explicit RData(uint16_t priority, std::vector<std::string_view> dns_labels);
/// construct from raw buffer.
explicit RData(bstring_t&& raw);
[[nodiscard]] constexpr const auto&
data() const
{
ResourceRecord() = default;
ResourceRecord(const ResourceRecord& other);
ResourceRecord(ResourceRecord&& other);
return _raw;
}
explicit ResourceRecord(std::string name, RRType_t type, RR_RData_t rdata);
std::vector<std::string_view>
view_dns_labels() const;
bool
Encode(llarp_buffer_t* buf) const override;
inline std::string
as_dns_name() const
{
std::string name;
bool
Decode(llarp_buffer_t* buf) override;
for (auto part : view_dns_labels())
name += fmt::format("{}.", part);
util::StatusObject
ToJSON() const override;
return name.substr(0, name.size() - 2);
}
std::string
ToString() const;
util::StatusObject
to_json() const;
bool
HasCNameForTLD(const std::string& tld) const;
std::string
ToString() const;
};
std::string rr_name;
RRType_t rr_type;
RRClass_t rr_class;
RR_TTL_t ttl;
RR_RData_t rData;
};
} // namespace dns
} // namespace llarp
class ResourceRecord
{
explicit ResourceRecord(bstring_t&&);
public:
ResourceRecord() = default;
ResourceRecord(std::string name, RRType type, RData rdata, uint32_t ttl);
/// construct from raw bytes, modifies raw.
explicit ResourceRecord(byte_view_t& raw);
bstring_t
encode_dns() const;
util::StatusObject
to_json() const;
std::string
ToString() const;
bool
HasCNameForTLD(const std::string& tld) const;
std::vector<std::string> rr_name_labels;
RRType rr_type;
uint32_t rr_ttl;
RData rr_data;
};
[[deprecated]] constexpr auto qTypePTR = to_underlying(RRType::PTR);
[[deprecated]] constexpr auto qTypeA = to_underlying(RRType::A);
[[deprecated]] constexpr auto qTypeAAAA = to_underlying(RRType::AAAA);
[[deprecated]] constexpr auto qTypeSRV = to_underlying(RRType::SRV);
[[deprecated]] constexpr auto qTypeCNAME = to_underlying(RRType::CNAME);
[[deprecated]] constexpr auto qTypeMX = to_underlying(RRType::MX);
[[deprecated]] constexpr auto qTypeNS = to_underlying(RRType::NS);
} // namespace llarp::dns
template <>
constexpr inline bool llarp::IsToStringFormattable<llarp::dns::ResourceRecord> = true;
template <>
constexpr inline bool llarp::IsToStringFormattable<llarp::dns::RData> = true;
template <>
constexpr inline bool llarp::IsToStringFormattable<llarp::dns::RRType> = true;

View file

@ -1,44 +1,37 @@
#include "serialize.hpp"
#include <oxenc/endian.h>
#include <cstdint>
#include <llarp/net/net_int.hpp>
#include <optional>
#include <stdexcept>
#include <vector>
#include "llarp/dns/rr.hpp"
#include "llarp/util/str.hpp"
namespace llarp
{
namespace dns
dns::RR_RData_t
encode_rdata(bstring_view rdata)
{
Serialize::~Serialize() = default;
if (rdata.size() > 65536)
throw std::invalid_argument{"rdata too big: {} > {}"_format(rdata.size(), 65536)};
uint16_t len{static_cast<uint16_t>(rdata.size())};
dns::RR_RData_t vec(rdata.size() + 2);
oxenc::write_host_as_big(len, vec.data());
std::copy_n(rdata.data(), rdata.size(), vec.data() + 2);
return vec;
}
bool
EncodeRData(llarp_buffer_t* buf, const std::vector<byte_t>& v)
{
if (v.size() > 65536)
return false;
uint16_t len = v.size();
if (!buf->put_uint16(len))
return false;
if (buf->size_left() < len)
return false;
memcpy(buf->cur, v.data(), len);
buf->cur += len;
return true;
}
bool
DecodeRData(llarp_buffer_t* buf, std::vector<byte_t>& v)
{
uint16_t len;
if (!buf->read_uint16(len))
return false;
size_t left = buf->size_left();
if (left < len)
return false;
v.resize(size_t(len));
if (len)
{
memcpy(v.data(), buf->cur, len);
buf->cur += len;
}
return true;
}
} // namespace dns
std::optional<bstring_view>
maybe_decode_rdata(const RR_RData_t& vec)
{
if (vec.size() < 2)
return std::nullopt;
auto len = oxenc::load_big_to_host<uint16_t>(vec.data());
if (vec.size() - 2 < len)
return std::nullopt;
return bstring_view{vec.data() + 2, size_t{len}};
}
} // namespace dns
} // namespace llarp

View file

@ -3,34 +3,5 @@
#include <llarp/util/buffer.hpp>
#include <llarp/util/status.hpp>
#include <vector>
namespace llarp
{
namespace dns
{
/// base type for serializable dns entities
struct Serialize
{
virtual ~Serialize() = 0;
/// encode entity to buffer
virtual bool
Encode(llarp_buffer_t* buf) const = 0;
/// decode entity from buffer
virtual bool
Decode(llarp_buffer_t* buf) = 0;
/// convert this whatever into json
virtual util::StatusObject
ToJSON() const = 0;
};
bool
EncodeRData(llarp_buffer_t* buf, const std::vector<byte_t>& rdata);
bool
DecodeRData(llarp_buffer_t* buf, std::vector<byte_t>& rdata);
} // namespace dns
} // namespace llarp
#include "llarp/util/str.hpp"
#include "llarp/util/types.hpp"

View file

@ -1,18 +1,28 @@
#include "server.hpp"
#include <algorithm>
#include <exception>
#include <llarp/constants/platform.hpp>
#include <llarp/constants/apple.hpp>
#include "dns.hpp"
#include <iterator>
#include <llarp/crypto/crypto.hpp>
#include <array>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <llarp/ev/udp_handle.hpp>
#include <optional>
#include <memory>
#include <unbound.h>
#include <uvw.hpp>
#include <vector>
#include "llarp/dns/bits.hpp"
#include "llarp/dns/message.hpp"
#include "llarp/dns/name.hpp"
#include "llarp/net/sock_addr.hpp"
#include "llarp/util/buffer.hpp"
#include "llarp/util/str.hpp"
#include "oxen/log.hpp"
#include "sd_platform.hpp"
#include "nm_platform.hpp"
@ -25,36 +35,47 @@ namespace llarp::dns
QueryJob_Base::Cancel()
{
Message reply{m_Query};
reply.AddServFail();
reply.hdr.rcode(bits::rcode_servfail);
SendReply(reply.ToBuffer());
}
QueryJob::QueryJob(
std::shared_ptr<PacketSource_Base> source, Message query, SockAddr to_, SockAddr from_)
: QueryJob_Base{query}
, src{std::move(source)}
, resolver{std::move(to_)}
, asker{std::move(from_)}
{}
void
QueryJob::SendReply(OwnedBuffer buf)
{
src->SendTo(asker, resolver, std::move(buf));
}
/// sucks up udp packets from a bound socket and feeds it to a server
class UDPReader : public PacketSource_Base, public std::enable_shared_from_this<UDPReader>
{
Server& m_DNS;
std::shared_ptr<llarp::UDPHandle> m_udp;
SockAddr m_LocalAddr;
public:
explicit UDPReader(Server& dns, const EventLoop_ptr& loop, llarp::SockAddr bindaddr)
: m_DNS{dns}
{
m_udp = loop->make_udp([&](auto&, SockAddr src, llarp::OwnedBuffer buf) {
if (src == m_LocalAddr)
return;
if (not m_DNS.MaybeHandlePacket(shared_from_this(), m_LocalAddr, src, std::move(buf)))
{
log::warning(logcat, "did not handle dns packet from {} to {}", src, m_LocalAddr);
}
});
m_udp->listen(bindaddr);
if (auto maybe_addr = BoundOn())
{
m_LocalAddr = *maybe_addr;
}
else
throw std::runtime_error{"cannot find which address our dns socket is bound on"};
m_udp = loop->make_udp(
[&](auto&, SockAddr src, llarp::OwnedBuffer buf) {
auto laddr = m_udp->LocalAddr();
if (src == laddr)
return;
if (not m_DNS.MaybeHandlePacket(shared_from_this(), laddr, src, std::move(buf)))
{
log::warning(logcat, "did not handle dns packet from {} to {}", src, laddr);
}
},
bindaddr);
}
std::optional<SockAddr>
@ -66,7 +87,7 @@ namespace llarp::dns
bool
WouldLoop(const SockAddr& to, const SockAddr&) const override
{
return to != m_LocalAddr;
return to != m_udp->LocalAddr();
}
void
@ -153,8 +174,9 @@ namespace llarp::dns
auto* query = static_cast<Query*>(data);
if (err)
{
// some kind of error from upstream
// we cant do anything about this kind of failure so cancel query.
log::warning(logcat, "Upstream DNS failure: {}", ub_strerror(err));
// todo: handle exceptions.
query->Cancel();
return;
}
@ -162,16 +184,20 @@ namespace llarp::dns
log::trace(logcat, "queueing dns response from libunbound to userland");
// rewrite response
OwnedBuffer pkt{(const byte_t*)result->answer_packet, (size_t)result->answer_len};
llarp_buffer_t buf{pkt};
MessageHeader hdr;
hdr.Decode(&buf);
hdr.id = query->Underlying().hdr_id;
buf.cur = buf.base;
hdr.Encode(&buf);
// send reply
query->SendReply(std::move(pkt));
try
{
OwnedBuffer pkt{(const byte_t*)result->answer_packet, (size_t)result->answer_len};
auto view = pkt.view();
MessageHeader hdr{view};
hdr.id = query->Underlying().hdr.id;
auto hdr_buff = hdr.encode_dns();
std::copy_n(hdr_buff.data(), hdr_buff.size(), pkt.buf.get());
query->SendReply(std::move(pkt));
}
catch (std::exception& ex)
{
log::error(logcat, "failed to rewrite response: {}", ex.what());
}
}
void
@ -488,7 +514,7 @@ namespace llarp::dns
for (const auto& q : query.questions)
{
// dont process .loki or .snode
if (q.HasTLD(".loki") or q.HasTLD(".snode"))
if (is_lokinet_tld(q.tld()))
{
log::warning(
logcat,
@ -530,7 +556,7 @@ namespace llarp::dns
const auto& q = query.questions[0];
if (auto err = ub_resolve_async(
m_ctx,
q.Name().c_str(),
q.qname().c_str(),
q.qtype,
q.qclass,
tmp.get(),
@ -577,11 +603,11 @@ namespace llarp::dns
}
} // namespace libunbound
Server::Server(EventLoop_ptr loop, llarp::DnsConfig conf, unsigned int netif)
: m_Loop{std::move(loop)}
Server::Server(EventLoop_ptr loop, llarp::DnsConfig conf)
: m_netif_index{}
, m_Loop{std::move(loop)}
, m_Config{std::move(conf)}
, m_Platform{CreatePlatform()}
, m_NetIfIndex{std::move(netif)}
{}
std::vector<std::weak_ptr<Resolver_Base>>
@ -591,8 +617,10 @@ namespace llarp::dns
}
void
Server::Start()
Server::Start(unsigned int index)
{
m_netif_index = index;
log::debug(logcat, "starting dns server for if_index={}", m_netif_index);
// set up udp sockets
for (const auto& addr : m_Config.m_bind)
{
@ -602,7 +630,12 @@ namespace llarp::dns
// add default resolver as needed
if (auto ptr = MakeDefaultResolver())
{
log::debug(logcat, "adding default resolver: '{}'", ptr->ResolverName());
AddResolver(ptr);
}
else
log::debug(logcat, "no default resolver made");
}
std::shared_ptr<I_Platform>
@ -620,6 +653,7 @@ namespace llarp::dns
std::shared_ptr<PacketSource_Base>
Server::MakePacketSourceOn(const llarp::SockAddr& addr, const llarp::DnsConfig&)
{
log::debug(logcat, "make udp dns socket on {}", addr);
return std::make_shared<UDPReader>(*this, m_Loop, addr);
}
@ -685,6 +719,12 @@ namespace llarp::dns
void
Server::AddPacketSource(std::shared_ptr<PacketSource_Base> pkt)
{
std::optional<std::string> maybe_addr;
if (auto maybe_sockaddr = pkt->BoundOn())
maybe_addr = fmt::format("bound to {}", maybe_sockaddr->ToString());
log::debug(logcat, "adding owned packet source {}", maybe_addr.value_or("not bound to socket"));
AddPacketSource(std::weak_ptr<PacketSource_Base>{pkt});
m_OwnedPacketSources.push_back(std::move(pkt));
}
@ -713,7 +753,7 @@ namespace llarp::dns
Server::SetDNSMode(bool all_queries)
{
if (auto maybe_addr = FirstBoundPacketSourceAddr())
m_Platform->set_resolver(m_NetIfIndex, *maybe_addr, all_queries);
m_Platform->set_resolver(m_netif_index, *maybe_addr, all_queries);
}
bool
@ -730,45 +770,50 @@ namespace llarp::dns
return false;
}
auto maybe = MaybeParseDNSMessage(buf);
if (not maybe)
try
{
log::warning(logcat, "invalid dns message format from {} to dns listener on {}", from, to);
return false;
}
auto view = buf.view();
Message msg{view};
auto& msg = *maybe;
// we don't provide a DoH resolver because it requires verified TLS
// TLS needs X509/ASN.1-DER and opting into the Root CA Cabal
// thankfully mozilla added a backdoor that allows ISPs to turn it off
// so we disable DoH for firefox using mozilla's ISP backdoor
// see: https://github.com/oxen-io/lokinet/issues/832
for (const auto& q : msg.questions)
{
// is this firefox looking for their backdoor record?
if (q.IsName("use-application-dns.net"))
// we don't provide a DoH resolver because it requires verified TLS
// TLS needs X509/ASN.1-DER and opting into the Root CA Cabal
// thankfully mozilla added a backdoor that allows ISPs to turn it off
// so we disable DoH for firefox using mozilla's ISP backdoor
// see: https://github.com/oxen-io/lokinet/issues/832
for (const auto& q : msg.questions)
{
// yea it is, let's turn off DoH because god is dead.
msg.AddNXReply();
// press F to pay respects and send it back where it came from
ptr->SendTo(from, to, msg.ToBuffer());
return true;
}
}
for (const auto& resolver : m_Resolvers)
{
if (auto res_ptr = resolver.lock())
{
log::trace(
logcat, "check resolver {} for dns from {} to {}", res_ptr->ResolverName(), from, to);
if (res_ptr->MaybeHookDNS(ptr, msg, to, from))
// is this firefox looking for their backdoor record?
if (q.IsName("use-application-dns.net"))
{
log::trace(
logcat, "resolver {} handling dns from {} to {}", res_ptr->ResolverName(), from, to);
// yea it is, let's turn off DoH because god is dead.
// press F to pay respects and send it back where it came from
ptr->SendTo(from, to, msg.nx().ToBuffer());
return true;
}
}
for (const auto& resolver : m_Resolvers)
{
if (auto res_ptr = resolver.lock())
{
log::trace(
logcat, "check resolver {} for dns from {} to {}", res_ptr->ResolverName(), from, to);
if (res_ptr->MaybeHookDNS(ptr, msg, to, from))
{
log::trace(
logcat,
"resolver {} handling dns from {} to {}",
res_ptr->ResolverName(),
from,
to);
return true;
}
}
}
}
catch (std::exception& ex)
{
log::error(logcat, "failed to handle dns packet: {}", ex.what());
}
return false;
}

View file

@ -1,5 +1,6 @@
#pragma once
#include "llarp/net/sock_addr.hpp"
#include "message.hpp"
#include "platform.hpp"
#include <llarp/config/config.hpp>
@ -124,19 +125,11 @@ namespace llarp::dns
const SockAddr asker;
public:
explicit QueryJob(
std::shared_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to_,
const SockAddr& from_)
: QueryJob_Base{query}, src{source}, resolver{to_}, asker{from_}
{}
QueryJob(
std::shared_ptr<PacketSource_Base> source, Message query, SockAddr to_, SockAddr from_);
void
SendReply(llarp::OwnedBuffer replyBuf) override
{
src->SendTo(asker, resolver, std::move(replyBuf));
}
SendReply(llarp::OwnedBuffer replyBuf) override;
};
/// handler of dns query hooking
@ -214,10 +207,12 @@ namespace llarp::dns
virtual std::shared_ptr<I_Platform>
CreatePlatform() const;
unsigned int m_netif_index;
public:
virtual ~Server() = default;
explicit Server(EventLoop_ptr loop, llarp::DnsConfig conf, unsigned int netif_index);
Server(EventLoop_ptr loop, llarp::DnsConfig conf);
/// returns all sockaddr we have from all of our PacketSources
std::vector<SockAddr>
@ -239,9 +234,10 @@ namespace llarp::dns
virtual std::shared_ptr<PacketSource_Base>
MakePacketSourceOn(const SockAddr& bindaddr, const llarp::DnsConfig& conf);
/// sets up all internal binds and such and begins operation
/// sets up all internal binds and such and begins operation.
/// we pass in the network interface index of the tun interface we are associated with here.
virtual void
Start();
Start(unsigned int index);
/// stops all operation
virtual void
@ -278,7 +274,6 @@ namespace llarp::dns
std::shared_ptr<I_Platform> m_Platform;
private:
const unsigned int m_NetIfIndex;
std::set<std::shared_ptr<Resolver_Base>, ComparePtr<std::shared_ptr<Resolver_Base>>>
m_OwnedResolvers;
std::set<std::weak_ptr<Resolver_Base>, CompareWeakPtr<Resolver_Base>> m_Resolvers;

View file

@ -1,11 +1,20 @@
#include "srv_data.hpp"
#include <cstdint>
#include <llarp/util/str.hpp>
#include <llarp/util/logging.hpp>
#include <limits>
#include <stdexcept>
#include <string_view>
#include <tuple>
#include <vector>
#include <oxenc/bt_serialize.h>
#include <oxenc/endian.h>
#include "llarp/dns/name.hpp"
#include "llarp/dns/question.hpp"
#include "llarp/util/bencode.h"
#include "llarp/util/buffer.hpp"
#include "llarp/util/types.hpp"
namespace llarp::dns
@ -13,105 +22,104 @@ namespace llarp::dns
static auto logcat = log::Cat("dns");
bool
SRVData::IsValid() const
SRVData::valid() const
{
// if target is of first two forms outlined above
if (target == "." or target.size() == 0)
{
if (empty_target() or target_full_stop())
return true;
}
// check target size is not absurd
if (target.size() > TARGET_MAX_SIZE)
{
log::warning(logcat, "SRVData target larger than max size ({})", TARGET_MAX_SIZE);
return false;
}
// does target end in .loki?
size_t pos = target.find(".loki");
if (pos != std::string::npos && pos == (target.size() - 5))
{
return true;
}
for (auto suffix : {".loki"sv, ".loki."sv, ".snode"sv, ".snode."sv})
if (ends_with(target, suffix))
return true;
// does target end in .snode?
pos = target.find(".snode");
if (pos != std::string::npos && pos == (target.size() - 6))
{
return true;
}
// if we're here, target is invalid
log::warning(logcat, "SRVData invalid");
return false;
}
SRVTuple
SRVData::toTuple() const
SRVData::SRVData(SRVTuple tuple_) : _tuple{std::move(tuple_)}
{}
SRVData::SRVData(std::string_view str) : SRVData{}
{
return std::make_tuple(service_proto, priority, weight, port, target);
from_string(std::move(str));
}
SRVData
SRVData::fromTuple(SRVTuple tuple)
void
SRVData::from_string(std::string_view str)
{
SRVData s;
std::tie(s.service_proto, s.priority, s.weight, s.port, s.target) = std::move(tuple);
return s;
}
bool
SRVData::fromString(std::string_view srvString)
{
log::debug(logcat, "SRVData::fromString(\"{}\")", srvString);
log::debug(logcat, "SRVData::fromString(\"{}\")", str);
// split on spaces, discard trailing empty strings
auto splits = split(srvString, " ", false);
auto splits = split(str, " ", false);
if (splits.size() != 5 && splits.size() != 4)
{
log::warning(logcat, "SRV record should have either 4 or 5 space-separated parts");
return false;
}
throw std::invalid_argument{"SRV record should have either 4 or 5 space-separated parts"};
service_proto = splits[0];
if (not parse_int(splits[1], priority))
{
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (priority)", splits[1]);
return false;
}
throw std::invalid_argument{
"SRV record failed to parse \"{}\" as uint16_t (priority)"_format(splits[1])};
if (not parse_int(splits[2], weight))
{
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (weight)", splits[2]);
return false;
}
throw std::invalid_argument{
"SRV record failed to parse \"{}\" as uint16_t (weight)"_format(splits[2])};
if (not parse_int(splits[3], port))
{
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (port)", splits[3]);
return false;
}
throw std::invalid_argument{
"SRV record failed to parse \"{}\" as uint16_t (port)"_format(splits[3])};
if (splits.size() == 5)
target = splits[4];
else
target = "";
target = "@";
return IsValid();
if (not valid())
throw std::invalid_argument{"srv record is invalid"};
}
bool
SRVData::BEncode(llarp_buffer_t* buf) const
{
const std::string data = oxenc::bt_serialize(toTuple());
auto data = bt_serizalize();
return buf->write(data.begin(), data.end());
}
bstring_t
SRVData::bt_serizalize() const
{
auto data = oxenc::bt_serialize(tuple);
return bstring_t{reinterpret_cast<const byte_t*>(data.data()), data.size()};
}
void
SRVData::bt_deserialize(byte_view_t& raw)
{
oxenc::bt_list_consumer bt{
std::string_view{reinterpret_cast<const char*>(raw.data()), raw.size()}};
bt.consume_list(_tuple);
auto rem = bt.current_buffer();
if (not valid())
throw std::invalid_argument{"invalid srv data"};
raw = raw.substr(raw.size() - rem.size());
}
SRVData&
SRVData::operator=(SRVData&& other)
{
_tuple = std::move(other._tuple);
return *this;
}
SRVData&
SRVData::operator=(const SRVData& other)
{
_tuple = other.tuple;
return *this;
}
bool
SRVData::BDecode(llarp_buffer_t* buf)
{
@ -119,14 +127,11 @@ namespace llarp::dns
if (not bencode_discard(buf))
return false;
byte_t* end = buf->cur;
std::string_view srvString{
reinterpret_cast<char*>(begin), static_cast<std::size_t>(end - begin)};
try
{
SRVTuple tuple{};
oxenc::bt_deserialize(srvString, tuple);
*this = fromTuple(std::move(tuple));
return IsValid();
byte_view_t b{begin, static_cast<size_t>(end - begin)};
bt_deserialize(b);
return valid();
}
catch (const oxenc::bt_deserialize_invalid&)
{
@ -144,4 +149,39 @@ namespace llarp::dns
{"port", port},
{"target", target}};
}
bstring_t
SRVData::encode_dns(std::string rootname) const
{
bstring_t buf;
buf.resize(6);
auto ptr = buf.data();
oxenc::write_host_as_big(priority, ptr);
ptr += 2;
oxenc::write_host_as_big(weight, ptr);
ptr += 2;
oxenc::write_host_as_big(port, ptr);
ptr += 2;
buf += encode_dns_labels(target_dns_labels(std::move(rootname)));
return buf;
}
std::vector<std::string_view>
SRVData::target_dns_labels(std::string_view rootname) const
{
return split(empty_target() ? rootname : target, ".");
}
bool
SRVData::empty_target() const
{
return target.empty() or target == "@";
}
bool
SRVData::target_full_stop() const
{
return target == ".";
}
} // namespace llarp::dns

View file

@ -1,10 +1,13 @@
#pragma once
#include "llarp/util/str.hpp"
#include "name.hpp"
#include "serialize.hpp"
#include <tuple>
#include <string_view>
#include <type_traits>
#include <utility>
#include "llarp/util/status.hpp"
@ -12,63 +15,10 @@ namespace llarp::dns
{
using SRVTuple = std::tuple<std::string, uint16_t, uint16_t, uint16_t, std::string>;
struct SRVData
class SRVData
{
static constexpr size_t TARGET_MAX_SIZE = 200;
std::string service_proto; // service and protocol may as well be together
uint16_t priority;
uint16_t weight;
uint16_t port;
// target string for the SRV record to point to
// options:
// empty - refer to query name
// dot - authoritative "no such service available"
// any other .loki or .snode - target is that .loki or .snode
std::string target;
// do some basic validation on the target string
// note: this is not a conclusive, regex solution,
// but rather some sanity/safety checks
bool
IsValid() const;
SRVTuple
toTuple() const;
auto
toTupleRef() const
{
return std::tie(service_proto, priority, weight, port, target);
}
/// so we can put SRVData in a std::set
bool
operator<(const SRVData& other) const
{
return toTupleRef() < other.toTupleRef();
}
bool
operator==(const SRVData& other) const
{
return toTupleRef() == other.toTupleRef();
}
bool
BEncode(llarp_buffer_t*) const;
bool
BDecode(llarp_buffer_t*);
util::StatusObject
ExtractStatus() const;
static SRVData
fromTuple(SRVTuple tuple);
/* bind-like formatted string for SRV records in config file
*
* format:
@ -76,17 +26,99 @@ namespace llarp::dns
*
* exactly one space character between parts.
*
* target can be empty, in which case the space after port should
* be omitted. if this is the case, the target is
* interpreted as the .loki or .snode of the current context.
*
* if target is not empty, it must be either
* - simply a full stop (dot/period) OR
* - a name within the .loki or .snode subdomains. a target
* specified in this manner must not end with a full stop.
* - a full .loki or .snode address ending with a full stop.
* - the literal character '@' in which the primary flow layer address
* be it .loki or .snode is used.
*
* if empty target is provided the trailing space after port MUST be omitted.
* when the target is empty it is treated like '@'.
*/
void
from_string(std::string_view srvString);
SRVTuple _tuple;
public:
explicit SRVData(SRVTuple tup);
explicit SRVData(std::string_view str);
SRVData() = default;
SRVData(const SRVData&) = default;
SRVData(SRVData&&) = default;
SRVData&
operator=(SRVData&& other);
SRVData&
operator=(const SRVData& other);
std::string& service_proto{
std::get<0>(_tuple)}; // service and protocol may as well be together
uint16_t& priority{std::get<1>(_tuple)};
uint16_t& weight{std::get<2>(_tuple)};
uint16_t& port{std::get<3>(_tuple)};
// target string for the SRV record to point to
// options:
// empty - dns query's qname
// @ - dns query's qname
// dot - authoritative "no such service available"
// any other .loki or .snode - target is that .loki or .snode
std::string& target{std::get<4>(_tuple)};
const SRVTuple& tuple{_tuple};
// do some basic validation on the target string
// note: this is not a conclusive, regex solution,
// but rather some sanity/safety checks
bool
fromString(std::string_view srvString);
valid() const;
/// return if the target refers to the query's qname.
bool
empty_target() const;
/// returns true if the target is authoritative "no such service"
bool
target_full_stop() const;
/// so we can put SRVData in a std::set
constexpr bool
operator<(const SRVData& other) const
{
return tuple < other.tuple;
}
constexpr bool
operator==(const SRVData& other) const
{
return tuple == other.tuple;
}
bool
BEncode(llarp_buffer_t*) const;
bstring_t
bt_serizalize() const;
void
bt_deserialize(byte_view_t& raw);
bool
BDecode(llarp_buffer_t*);
util::StatusObject
ExtractStatus() const;
std::vector<std::string_view>
target_dns_labels(std::string_view rootname) const;
bstring_t
encode_dns(std::string rootname) const;
};
} // namespace llarp::dns

View file

@ -1,20 +1,24 @@
#pragma once
#include "llarp/service/address.hpp"
#include "llarp/service/convotag.hpp"
#include "llarp/service/protocol_type.hpp"
#include "router_id.hpp"
#include "llarp/ev/ev.hpp"
#include "llarp/dns/srv_data.hpp"
#include "router_contact.hpp"
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <optional>
#include <type_traits>
#include <unordered_set>
#include <set>
#include "oxenc/variant.h"
#include <oxenc/variant.h>
#include <llarp/config/config.hpp>
#include <llarp/service/address.hpp>
#include <llarp/service/convotag.hpp>
#include <llarp/service/protocol_type.hpp>
#include <llarp/ev/ev.hpp>
#include <vector>
namespace llarp
{
@ -28,7 +32,7 @@ namespace llarp
class Server;
}
class EndpointBase
class [[deprecated]] EndpointBase
{
std::unordered_set<dns::SRVData> m_SRVRecords;
@ -119,6 +123,8 @@ namespace llarp
virtual std::optional<service::ConvoTag>
GetBestConvoTagFor(AddressVariant_t addr) const = 0;
/// MUST call MarkAddressOutbound first if we are initiating the flow to the remote.
// TODO: this requirement is stupid.
virtual bool
EnsurePathTo(
AddressVariant_t addr,
@ -129,12 +135,20 @@ namespace llarp
LookupNameAsync(
std::string name, std::function<void(std::optional<AddressVariant_t>)> resultHandler) = 0;
virtual bool
LookupRC(RouterID rid, RouterLookupHandler handler) = 0;
virtual const EventLoop_ptr&
Loop() = 0;
virtual bool
SendToOrQueue(
service::ConvoTag tag, const llarp_buffer_t& payload, service::ProtocolType t) = 0;
SendToOrQueue(service::ConvoTag tag, std::vector<byte_t> data, service::ProtocolType t) = 0;
bool
SendToOrQueue(service::ConvoTag tag, const llarp_buffer_t& payload, service::ProtocolType t)
{
return SendToOrQueue(std::move(tag), payload.copy(), t);
}
/// lookup srv records async
virtual void
@ -143,8 +157,32 @@ namespace llarp
std::string service,
std::function<void(std::vector<dns::SRVData>)> resultHandler) = 0;
/// will mark a remote as something we want to initiate a flow to.
/// making this explicit prevents us from doing weird things when we are the recipient.
virtual void
MarkAddressOutbound(AddressVariant_t remote) = 0;
virtual bool
Configure(const NetworkConfig&, const DnsConfig&) = 0;
virtual std::string_view
endpoint_name() const = 0;
/// called to do any deferred startup items after configure.
virtual void
start_endpoint(){};
/// initiate shutdown of all periodic operations.
virtual void
stop_endpoint(){};
/// called in a periodic event loop timer.
virtual void
periodic_tick(){};
/// called in an idempotent event loop handler after reading io each tick.
virtual void
event_loop_pump(){};
};
} // namespace llarp

View file

@ -1,5 +1,6 @@
#pragma once
#include <llarp/net/sock_addr.hpp>
#include <llarp/util/buffer.hpp>
#include <llarp/util/time.hpp>
#include <llarp/util/thread/threading.hpp>
@ -9,6 +10,7 @@
#include <deque>
#include <list>
#include <future>
#include <optional>
#include <utility>
namespace uvw
@ -18,7 +20,6 @@ namespace uvw
namespace llarp
{
struct SockAddr;
struct UDPHandle;
namespace vpn
@ -44,9 +45,9 @@ namespace llarp
/// callback to fire again if already triggered depending on the underlying implementation).
virtual ~EventLoopWakeup() = default;
/// trigger this task to run on the next event loop iteration; does nothing if already
/// triggered.
virtual void
/// trigger this task to run on the next event loop iteration idempotently.
/// returns true if we already triggered it before on this event loop cycle.
virtual bool
Trigger() = 0;
};
@ -193,7 +194,7 @@ namespace llarp
// Constructs a UDP socket that can be used for sending and/or receiving
virtual std::shared_ptr<UDPHandle>
make_udp(UDPReceiveFunc on_recv) = 0;
make_udp(UDPReceiveFunc on_recv, const std::optional<SockAddr>& laddr = std::nullopt) = 0;
/// Make a thread-safe event loop waker (an "async" in libuv terminology) on this event loop;
/// you can call `->Trigger()` on the returned shared pointer to fire the callback at the next

View file

@ -1,4 +1,5 @@
#include "libuv.hpp"
#include <cstdint>
#include <memory>
#include <thread>
#include <type_traits>
@ -9,6 +10,13 @@
#include <llarp/vpn/platform.hpp>
#include <uvw.hpp>
#include <llarp/constants/platform.hpp>
#include "llarp/ev/ev.hpp"
#include "llarp/net/net_int.hpp"
#include "llarp/net/sock_addr.hpp"
#include "llarp/util/buffer.hpp"
#include "uvw/udp.h"
#include "uvw/util.h"
namespace llarp::uv
{
@ -21,18 +29,24 @@ namespace llarp::uv
class UVWakeup final : public EventLoopWakeup
{
std::shared_ptr<uvw::AsyncHandle> async;
std::atomic_flag _triggered = ATOMIC_FLAG_INIT;
public:
UVWakeup(uvw::Loop& loop, std::function<void()> callback)
: async{loop.resource<uvw::AsyncHandle>()}
{
async->on<uvw::AsyncEvent>([f = std::move(callback)](auto&, auto&) { f(); });
async->on<uvw::AsyncEvent>([this, f = std::move(callback)](auto&, auto&) {
_triggered.clear();
f();
});
}
void
bool
Trigger() override
{
bool ret = _triggered.test_and_set();
async->send();
return ret;
}
~UVWakeup() override
@ -64,19 +78,23 @@ namespace llarp::uv
struct UDPHandle final : llarp::UDPHandle
{
UDPHandle(uvw::Loop& loop, ReceiveFunc rf);
bool
listen(const SockAddr& addr) override;
UDPHandle(uvw::Loop& loop, ReceiveFunc rf, std::optional<SockAddr> laddr);
bool
send(const SockAddr& dest, const llarp_buffer_t& buf) override;
std::optional<SockAddr>
SockAddr
LocalAddr() const override
{
auto addr = handle->sock<uvw::IPv4>();
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
if (not maybe_laddr)
{
auto addr = handle->sock<uvw::IPv4>();
if (addr.ip.empty())
addr = handle->sock<uvw::IPv6>();
maybe_laddr = SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
}
return *maybe_laddr;
}
std::optional<int>
@ -96,9 +114,7 @@ namespace llarp::uv
private:
std::shared_ptr<uvw::UDPHandle> handle;
void
reset_handle(uvw::Loop& loop);
mutable std::optional<SockAddr> maybe_laddr;
};
void
@ -138,6 +154,7 @@ namespace llarp::uv
m_nextID.store(0);
if (!(m_WakeUp = m_Impl->resource<uvw::AsyncHandle>()))
throw std::runtime_error{"Failed to create libuv async"};
m_WakeUp->on<uvw::AsyncEvent>([this](const auto&, auto&) { tick_event_loop(); });
}
@ -165,10 +182,10 @@ namespace llarp::uv
}
std::shared_ptr<llarp::UDPHandle>
Loop::make_udp(UDPReceiveFunc on_recv)
Loop::make_udp(UDPReceiveFunc on_recv, const std::optional<SockAddr>& laddr)
{
return std::static_pointer_cast<llarp::UDPHandle>(
std::make_shared<llarp::uv::UDPHandle>(*m_Impl, std::move(on_recv)));
std::make_shared<llarp::uv::UDPHandle>(*m_Impl, std::move(on_recv), laddr));
}
static void
@ -241,41 +258,36 @@ namespace llarp::uv
std::shared_ptr<llarp::vpn::NetworkInterface> netif,
std::function<void(llarp::net::IPPacket)> handler)
{
#ifdef __linux__
using event_t = uvw::PollEvent;
auto handle = m_Impl->resource<uvw::PollHandle>(netif->PollFD());
#else
// we use a uv_prepare_t because it fires before blocking for new io events unconditionally
// we want to match what linux does, using a uv_check_t does not suffice as the order of
// operations is not what we need.
using event_t = uvw::PrepareEvent;
auto handle = m_Impl->resource<uvw::PrepareHandle>();
#endif
if (!handle)
return false;
handle->on<event_t>([netif = std::move(netif), handler = std::move(handler)](
const event_t&, [[maybe_unused]] auto& handle) {
auto reader = [netif, handler = std::move(handler), this](const auto&, auto&) {
for (auto pkt = netif->ReadNextPacket(); true; pkt = netif->ReadNextPacket())
{
if (pkt.empty())
return;
if (handler)
handler(std::move(pkt));
// on windows/apple, vpn packet io does not happen as an io action that wakes up the event
// loop thus, we must manually wake up the event loop when we get a packet on our interface.
// on linux/android this is a nop
netif->MaybeWakeUpperLayers();
// on protactor style platforms we want to emulate the reactor style pattern so we wakeup
// the event loop when we get packets.
if constexpr (platform::has_proactor_io)
wakeup();
}
});
#ifdef __linux__
handle->start(uvw::PollHandle::Event::READABLE);
#else
handle->start();
#endif
};
if constexpr (llarp::platform::has_reactor_io)
{
auto handle = m_Impl->resource<uvw::PollHandle>(netif->PollFD());
if (not handle)
return false;
handle->on<uvw::PollEvent>(std::move(reader));
handle->start(uvw::PollHandle::Event::READABLE);
}
else
{
auto handle = m_Impl->resource<uvw::PrepareHandle>();
if (not handle)
return false;
handle->on<uvw::PrepareEvent>(std::move(reader));
handle->start();
}
return true;
}
@ -294,44 +306,30 @@ namespace llarp::uv
FlushLogic();
}
m_LogicCalls.pushBack(f);
m_WakeUp->send();
}
// Sets `handle` to a new uvw UDP handle, first initiating a close and then disowning the handle
// if already set, allocating the resource, and setting the receive event on it.
void
UDPHandle::reset_handle(uvw::Loop& loop)
llarp::uv::UDPHandle::UDPHandle(uvw::Loop& loop, ReceiveFunc rf, std::optional<SockAddr> laddr)
: llarp::UDPHandle{std::move(rf)}, handle{loop.resource<uvw::UDPHandle>()}
{
if (handle)
handle->close();
handle = loop.resource<uvw::UDPHandle>();
handle->on<uvw::UDPDataEvent>([this](auto& event, auto& /*handle*/) {
std::optional<std::string> maybe_addr_str;
if (laddr)
maybe_addr_str = laddr->ToString();
auto err = handle->on<uvw::ErrorEvent>(
[addr_str = maybe_addr_str.value_or("all interfaces")](auto& event, auto&) {
throw llarp::util::bind_socket_error{
fmt::format("failed to bind udp socket on {}: {}", addr_str, event.what())};
});
if (laddr)
handle->bind(*static_cast<const sockaddr*>(*laddr));
handle->on<uvw::UDPDataEvent>([this](auto& ev, auto&) {
on_recv(
*this,
SockAddr{event.sender.ip, huint16_t{static_cast<uint16_t>(event.sender.port)}},
OwnedBuffer{std::move(event.data), event.length});
SockAddr{ev.sender.ip, huint16_t{static_cast<uint16_t>(ev.sender.port)}},
OwnedBuffer{std::move(ev.data), ev.length});
});
}
llarp::uv::UDPHandle::UDPHandle(uvw::Loop& loop, ReceiveFunc rf) : llarp::UDPHandle{std::move(rf)}
{
reset_handle(loop);
}
bool
UDPHandle::listen(const SockAddr& addr)
{
if (handle->active())
reset_handle(handle->loop());
auto err = handle->on<uvw::ErrorEvent>([addr](auto& event, auto&) {
throw llarp::util::bind_socket_error{
fmt::format("failed to bind udp socket on {}: {}", addr, event.what())};
});
handle->bind(*static_cast<const sockaddr*>(addr));
handle->recv();
handle->erase(err);
return true;
}
bool

View file

@ -1,5 +1,6 @@
#pragma once
#include "ev.hpp"
#include "llarp/net/sock_addr.hpp"
#include "udp_handle.hpp"
#include <llarp/util/thread/queue.hpp>
#include <llarp/util/meta/memfn.hpp>
@ -11,6 +12,8 @@
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <vector>
namespace llarp::uv
@ -64,7 +67,7 @@ namespace llarp::uv
make_repeater() override;
virtual std::shared_ptr<llarp::UDPHandle>
make_udp(UDPReceiveFunc on_recv) override;
make_udp(UDPReceiveFunc on_recv, const std::optional<SockAddr>& laddr = std::nullopt) override;
void
FlushLogic();

View file

@ -1,5 +1,6 @@
#include "ev.hpp"
#include "../util/buffer.hpp"
#include "llarp/net/sock_addr.hpp"
namespace llarp
{
@ -8,12 +9,6 @@ namespace llarp
{
using ReceiveFunc = EventLoop::UDPReceiveFunc;
// Starts listening for incoming UDP packets on the given address. Returns true on success,
// false if the address could not be bound. If you send without calling this first then the
// socket will bind to a random high port on 0.0.0.0 (the "all addresses" address).
virtual bool
listen(const SockAddr& addr) = 0;
// Sends a packet to the given recipient, immediately. Returns true if the send succeeded,
// false it could not be performed (either because of error, or because it would have blocked).
// If listen hasn't been called then a random IP/port will be used.
@ -34,14 +29,14 @@ namespace llarp
}
/// returns the local address we are bound on
virtual std::optional<SockAddr>
virtual SockAddr
LocalAddr() const = 0;
// Base class destructor
virtual ~UDPHandle() = default;
protected:
explicit UDPHandle(ReceiveFunc on_recv) : on_recv{std::move(on_recv)}
UDPHandle(ReceiveFunc on_recv) : on_recv{std::move(on_recv)}
{
// It makes no sense at all to use this with a null receive function:
assert(this->on_recv);

View file

@ -1,241 +1,233 @@
#include "endpoint.hpp"
#include <llarp/handlers/exit.hpp>
#include <llarp/path/path_context.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/quic/tunnel.hpp>
#include <llarp/router/abstractrouter.hpp>
namespace llarp
namespace llarp::exit
{
namespace exit
Endpoint::Endpoint(
AbstractRouter& router,
const llarp::PubKey& remoteIdent,
const llarp::path::HopHandler_ptr& beginPath,
bool rewriteIP,
huint128_t ip)
: createdAt{router.Now()}
, _router{router}
, m_remoteSignKey{remoteIdent}
, m_CurrentPath{beginPath}
, m_IP{ip}
, m_LastActive{router.Now()}
, m_RewriteSource{rewriteIP}
{}
void
Endpoint::Close()
{}
util::StatusObject
Endpoint::ExtractStatus() const
{
Endpoint::Endpoint(
const llarp::PubKey& remoteIdent,
const llarp::path::HopHandler_ptr& beginPath,
bool rewriteIP,
huint128_t ip,
llarp::handlers::ExitEndpoint* parent)
: createdAt{parent->Now()}
, m_Parent{parent}
, m_remoteSignKey{remoteIdent}
, m_CurrentPath{beginPath}
, m_IP{ip}
, m_RewriteSource{rewriteIP}
{
m_LastActive = parent->Now();
}
auto now = _router.Now();
util::StatusObject obj{
{"identity", m_remoteSignKey.ToString()},
{"ip", m_IP.ToString()},
{"txRate", m_TxRate},
{"rxRate", m_RxRate},
{"createdAt", to_json(createdAt)},
{"exiting", !m_RewriteSource},
{"looksDead", LooksDead(now)},
{"expiresSoon", ExpiresSoon(now)},
{"expired", IsExpired(now)}};
return obj;
}
Endpoint::~Endpoint()
{
if (m_CurrentPath)
m_Parent->DelEndpointInfo(m_CurrentPath->RXID());
}
bool
Endpoint::UpdateLocalPath(const llarp::PathID_t& nextPath)
{
// const auto & route = _router->get_layer()->route;
// TODO: tell route layer "we are over here now" given here is new pathid
void
Endpoint::Close()
{
m_Parent->RemoveExit(this);
}
const RouterID us{_router.pubkey()};
m_CurrentPath = _router.pathContext().GetByUpstream(us, nextPath);
return true;
}
util::StatusObject
Endpoint::ExtractStatus() const
{
auto now = m_Parent->Now();
util::StatusObject obj{
{"identity", m_remoteSignKey.ToString()},
{"ip", m_IP.ToString()},
{"txRate", m_TxRate},
{"rxRate", m_RxRate},
{"createdAt", to_json(createdAt)},
{"exiting", !m_RewriteSource},
{"looksDead", LooksDead(now)},
{"expiresSoon", ExpiresSoon(now)},
{"expired", IsExpired(now)}};
return obj;
}
void
Endpoint::Tick(llarp_time_t now)
{
(void)now;
m_RxRate = 0;
m_TxRate = 0;
}
bool
Endpoint::UpdateLocalPath(const llarp::PathID_t& nextPath)
bool
Endpoint::IsExpired(llarp_time_t now) const
{
auto path = GetCurrentPath();
if (path)
{
if (!m_Parent->UpdateEndpointPath(m_remoteSignKey, nextPath))
return path->Expired(now);
}
// if we don't have an underlying path we are considered expired
return true;
}
bool
Endpoint::ExpiresSoon(llarp_time_t now, llarp_time_t dlt) const
{
if (m_CurrentPath)
return m_CurrentPath->ExpiresSoon(now, dlt);
return true;
}
bool
Endpoint::LooksDead(llarp_time_t now, llarp_time_t timeout) const
{
if (ExpiresSoon(now, timeout))
return true;
auto path = GetCurrentPath();
if (not path)
return true;
auto lastPing = path->LastRemoteActivityAt();
if (lastPing == 0s || (now > lastPing && now - lastPing > timeout))
return now > m_LastActive && now - m_LastActive > timeout;
else if (lastPing > 0s) // NOLINT
return now > lastPing && now - lastPing > timeout;
return lastPing > 0s;
}
/*
bool
Endpoint::QueueOutboundTraffic(
PathID_t path, std::vector<byte_t> buf, uint64_t counter, service::ProtocolType t)
{
const service::ConvoTag tag{path.as_array()};
if (t == service::ProtocolType::QUIC)
{
auto quic = m_Parent->GetQUICTunnel();
if (not quic)
return false;
const RouterID us{m_Parent->GetRouter()->pubkey()};
m_CurrentPath = m_Parent->GetRouter()->pathContext().GetByUpstream(us, nextPath);
m_TxRate += buf.size();
quic->receive_packet(tag, std::move(buf));
m_LastActive = _router.Now();
return true;
}
// queue overflow
if (m_UpstreamQueue.size() > MaxUpstreamQueueSize)
return false;
void
Endpoint::Tick(llarp_time_t now)
llarp::net::IPPacket pkt{std::move(buf)};
if (pkt.empty())
return false;
if (pkt.IsV6() && m_Parent->SupportsV6())
{
(void)now;
m_RxRate = 0;
m_TxRate = 0;
huint128_t dst;
if (m_RewriteSource)
dst = m_Parent->GetIfAddr();
else
dst = pkt.dstv6();
pkt.UpdateIPv6Address(m_IP, dst);
}
bool
Endpoint::IsExpired(llarp_time_t now) const
else if (pkt.IsV4() && !m_Parent->SupportsV6())
{
auto path = GetCurrentPath();
if (path)
{
return path->Expired(now);
}
// if we don't have an underlying path we are considered expired
return true;
huint32_t dst;
if (m_RewriteSource)
dst = net::TruncateV6(m_Parent->GetIfAddr());
else
dst = pkt.dstv4();
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(m_IP)), xhtonl(dst));
}
bool
Endpoint::ExpiresSoon(llarp_time_t now, llarp_time_t dlt) const
else
{
if (m_CurrentPath)
return m_CurrentPath->ExpiresSoon(now, dlt);
return true;
return false;
}
m_TxRate += pkt.size();
m_UpstreamQueue.emplace(std::move(pkt), counter);
m_LastActive = m_Parent->Now();
return true;
}
bool
Endpoint::LooksDead(llarp_time_t now, llarp_time_t timeout) const
bool
Endpoint::QueueInboundTraffic(std::vector<byte_t> buf, service::ProtocolType type)
{
if (type != service::ProtocolType::QUIC)
{
if (ExpiresSoon(now, timeout))
return true;
auto path = GetCurrentPath();
if (not path)
return true;
auto lastPing = path->LastRemoteActivityAt();
if (lastPing == 0s || (now > lastPing && now - lastPing > timeout))
return now > m_LastActive && now - m_LastActive > timeout;
else if (lastPing > 0s) // NOLINT
return now > lastPing && now - lastPing > timeout;
return lastPing > 0s;
}
bool
Endpoint::QueueOutboundTraffic(
PathID_t path, std::vector<byte_t> buf, uint64_t counter, service::ProtocolType t)
{
const service::ConvoTag tag{path.as_array()};
if (t == service::ProtocolType::QUIC)
{
auto quic = m_Parent->GetQUICTunnel();
if (not quic)
return false;
m_TxRate += buf.size();
quic->receive_packet(tag, std::move(buf));
m_LastActive = m_Parent->Now();
return true;
}
// queue overflow
if (m_UpstreamQueue.size() > MaxUpstreamQueueSize)
return false;
llarp::net::IPPacket pkt{std::move(buf)};
if (pkt.empty())
return false;
if (pkt.IsV6() && m_Parent->SupportsV6())
{
huint128_t dst;
if (m_RewriteSource)
dst = m_Parent->GetIfAddr();
else
dst = pkt.dstv6();
pkt.UpdateIPv6Address(m_IP, dst);
}
else if (pkt.IsV4() && !m_Parent->SupportsV6())
{
huint32_t dst;
if (m_RewriteSource)
dst = net::TruncateV6(m_Parent->GetIfAddr());
else
dst = pkt.dstv4();
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(m_IP)), xhtonl(dst));
}
huint128_t src;
if (m_RewriteSource)
src = m_Parent->GetIfAddr();
else
{
return false;
}
m_TxRate += pkt.size();
m_UpstreamQueue.emplace(std::move(pkt), counter);
m_LastActive = m_Parent->Now();
return true;
src = pkt.srcv6();
if (pkt.IsV6())
pkt.UpdateIPv6Address(src, m_IP);
else
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(src)), xhtonl(net::TruncateV6(m_IP)));
buf = pkt.steal();
}
bool
Endpoint::QueueInboundTraffic(std::vector<byte_t> buf, service::ProtocolType type)
const uint8_t queue_idx = buf.size() / llarp::routing::ExitPadSize;
if (m_DownstreamQueues.find(queue_idx) == m_DownstreamQueues.end())
m_DownstreamQueues.emplace(queue_idx, InboundTrafficQueue_t{});
auto& queue = m_DownstreamQueues.at(queue_idx);
if (queue.size() == 0)
{
if (type != service::ProtocolType::QUIC)
{
llarp::net::IPPacket pkt{std::move(buf)};
if (pkt.empty())
return false;
huint128_t src;
if (m_RewriteSource)
src = m_Parent->GetIfAddr();
else
src = pkt.srcv6();
if (pkt.IsV6())
pkt.UpdateIPv6Address(src, m_IP);
else
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(src)), xhtonl(net::TruncateV6(m_IP)));
buf = pkt.steal();
}
const uint8_t queue_idx = buf.size() / llarp::routing::ExitPadSize;
if (m_DownstreamQueues.find(queue_idx) == m_DownstreamQueues.end())
m_DownstreamQueues.emplace(queue_idx, InboundTrafficQueue_t{});
auto& queue = m_DownstreamQueues.at(queue_idx);
if (queue.size() == 0)
{
queue.emplace_back();
queue.back().protocol = type;
return queue.back().PutBuffer(std::move(buf), m_Counter++);
}
auto& msg = queue.back();
if (msg.Size() + buf.size() > llarp::routing::ExitPadSize)
{
queue.emplace_back();
queue.back().protocol = type;
return queue.back().PutBuffer(std::move(buf), m_Counter++);
}
msg.protocol = type;
return msg.PutBuffer(std::move(buf), m_Counter++);
queue.emplace_back();
queue.back().protocol = type;
return queue.back().PutBuffer(std::move(buf), m_Counter++);
}
bool
Endpoint::Flush()
auto& msg = queue.back();
if (msg.Size() + buf.size() > llarp::routing::ExitPadSize)
{
// flush upstream queue
while (m_UpstreamQueue.size())
queue.emplace_back();
queue.back().protocol = type;
return queue.back().PutBuffer(std::move(buf), m_Counter++);
}
msg.protocol = type;
return msg.PutBuffer(std::move(buf), m_Counter++);
}
bool
Endpoint::Flush()
{
// flush upstream queue
while (m_UpstreamQueue.size())
{
m_Parent->QueueOutboundTraffic(
const_cast<net::IPPacket&>(m_UpstreamQueue.top().pkt).steal());
m_UpstreamQueue.pop();
}
// flush downstream queue
auto path = GetCurrentPath();
bool sent = path != nullptr;
if (path)
{
for (auto& item : m_DownstreamQueues)
{
m_Parent->QueueOutboundTraffic(
const_cast<net::IPPacket&>(m_UpstreamQueue.top().pkt).steal());
m_UpstreamQueue.pop();
}
// flush downstream queue
auto path = GetCurrentPath();
bool sent = path != nullptr;
if (path)
{
for (auto& item : m_DownstreamQueues)
auto& queue = item.second;
while (queue.size())
{
auto& queue = item.second;
while (queue.size())
auto& msg = queue.front();
msg.S = path->NextSeqNo();
if (path->SendRoutingMessage(msg, m_Parent->GetRouter()))
{
auto& msg = queue.front();
msg.S = path->NextSeqNo();
if (path->SendRoutingMessage(msg, m_Parent->GetRouter()))
{
m_RxRate += msg.Size();
sent = true;
}
queue.pop_front();
m_RxRate += msg.Size();
sent = true;
}
queue.pop_front();
}
}
for (auto& item : m_DownstreamQueues)
item.second.clear();
return sent;
}
} // namespace exit
} // namespace llarp
for (auto& item : m_DownstreamQueues)
item.second.clear();
return sent;
}
*/
} // namespace llarp::exit

View file

@ -11,133 +11,128 @@
namespace llarp
{
namespace handlers
{
// forward declare
struct ExitEndpoint;
} // namespace handlers
struct AbstractRouter;
}
namespace exit
namespace llarp::exit
{
/// persistant exit state for 1 identity on the exit node
struct Endpoint
{
/// persistant exit state for 1 identity on the exit node
struct Endpoint
static constexpr size_t MaxUpstreamQueueSize = 256;
Endpoint(
AbstractRouter& router,
const llarp::PubKey& remoteIdent,
const llarp::path::HopHandler_ptr& path,
bool rewriteIP,
huint128_t ip);
/// close ourselves
void
Close();
/// implement istateful
util::StatusObject
ExtractStatus() const;
/// return true if we are expired right now
bool
IsExpired(llarp_time_t now) const;
bool
ExpiresSoon(llarp_time_t now, llarp_time_t dlt = 5s) const;
/// return true if this endpoint looks dead right now
bool
LooksDead(llarp_time_t now, llarp_time_t timeout = 10s) const;
/// tick ourself, reset tx/rx rates
void
Tick(llarp_time_t now);
/// queue traffic from service node / internet to be transmitted
bool
QueueInboundTraffic(std::vector<byte_t> data, service::ProtocolType t);
/// flush inbound and outbound traffic queues
bool
Flush();
/// queue outbound traffic on tun
/// does ip rewrite here
// bool
// QueueOutboundTraffic(
// PathID_t txid, std::vector<byte_t> data, uint64_t counter, service::ProtocolType t);
/// update local path id and cascade information to parent
/// return true if success
bool
UpdateLocalPath(const llarp::PathID_t& nextPath);
llarp::path::HopHandler_ptr
GetCurrentPath() const
{
static constexpr size_t MaxUpstreamQueueSize = 256;
return m_CurrentPath;
}
explicit Endpoint(
const llarp::PubKey& remoteIdent,
const llarp::path::HopHandler_ptr& path,
bool rewriteIP,
huint128_t ip,
llarp::handlers::ExitEndpoint* parent);
const llarp::PubKey&
PubKey() const
{
return m_remoteSignKey;
}
~Endpoint();
uint64_t
TxRate() const
{
return m_TxRate;
}
/// close ourselves
void
Close();
uint64_t
RxRate() const
{
return m_RxRate;
}
/// implement istateful
util::StatusObject
ExtractStatus() const;
huint128_t
LocalIP() const
{
return m_IP;
}
/// return true if we are expired right now
bool
IsExpired(llarp_time_t now) const;
const llarp_time_t createdAt;
private:
AbstractRouter& _router;
llarp::PubKey m_remoteSignKey;
llarp::path::HopHandler_ptr m_CurrentPath;
llarp::huint128_t m_IP;
uint64_t m_TxRate, m_RxRate;
llarp_time_t m_LastActive;
bool m_RewriteSource;
using InboundTrafficQueue_t = std::deque<llarp::routing::TransferTrafficMessage>;
using TieredQueue = std::map<uint8_t, InboundTrafficQueue_t>;
// maps number of fragments the message will fit in to the queue for it
TieredQueue m_DownstreamQueues;
struct UpstreamBuffer
{
UpstreamBuffer(llarp::net::IPPacket p, uint64_t c) : pkt{std::move(p)}, counter(c)
{}
llarp::net::IPPacket pkt;
uint64_t counter;
bool
ExpiresSoon(llarp_time_t now, llarp_time_t dlt = 5s) const;
/// return true if this endpoint looks dead right now
bool
LooksDead(llarp_time_t now, llarp_time_t timeout = 10s) const;
/// tick ourself, reset tx/rx rates
void
Tick(llarp_time_t now);
/// queue traffic from service node / internet to be transmitted
bool
QueueInboundTraffic(std::vector<byte_t> data, service::ProtocolType t);
/// flush inbound and outbound traffic queues
bool
Flush();
/// queue outbound traffic
/// does ip rewrite here
bool
QueueOutboundTraffic(
PathID_t txid, std::vector<byte_t> data, uint64_t counter, service::ProtocolType t);
/// update local path id and cascade information to parent
/// return true if success
bool
UpdateLocalPath(const llarp::PathID_t& nextPath);
llarp::path::HopHandler_ptr
GetCurrentPath() const
operator<(const UpstreamBuffer& other) const
{
return m_CurrentPath;
return counter < other.counter;
}
const llarp::PubKey&
PubKey() const
{
return m_remoteSignKey;
}
uint64_t
TxRate() const
{
return m_TxRate;
}
uint64_t
RxRate() const
{
return m_RxRate;
}
huint128_t
LocalIP() const
{
return m_IP;
}
const llarp_time_t createdAt;
private:
llarp::handlers::ExitEndpoint* m_Parent;
llarp::PubKey m_remoteSignKey;
llarp::path::HopHandler_ptr m_CurrentPath;
llarp::huint128_t m_IP;
uint64_t m_TxRate, m_RxRate;
llarp_time_t m_LastActive;
bool m_RewriteSource;
using InboundTrafficQueue_t = std::deque<llarp::routing::TransferTrafficMessage>;
using TieredQueue = std::map<uint8_t, InboundTrafficQueue_t>;
// maps number of fragments the message will fit in to the queue for it
TieredQueue m_DownstreamQueues;
struct UpstreamBuffer
{
UpstreamBuffer(llarp::net::IPPacket p, uint64_t c) : pkt{std::move(p)}, counter(c)
{}
llarp::net::IPPacket pkt;
uint64_t counter;
bool
operator<(const UpstreamBuffer& other) const
{
return counter < other.counter;
}
};
using UpstreamQueue_t = std::priority_queue<UpstreamBuffer>;
UpstreamQueue_t m_UpstreamQueue;
uint64_t m_Counter;
};
} // namespace exit
} // namespace llarp
using UpstreamQueue_t = std::priority_queue<UpstreamBuffer>;
UpstreamQueue_t m_UpstreamQueue;
uint64_t m_Counter;
};
} // namespace llarp::exit

View file

@ -27,8 +27,7 @@ namespace llarp
{
std::array<byte_t, 1024> tmp;
llarp_buffer_t buf(tmp);
ObtainExitMessage copy;
copy = *this;
ObtainExitMessage copy{*this};
copy.Z.Zero();
if (!copy.BEncode(&buf))
{
@ -140,8 +139,7 @@ namespace llarp
{
std::array<byte_t, 512> tmp;
llarp_buffer_t buf(tmp);
GrantExitMessage copy;
copy = *this;
GrantExitMessage copy{*this};
copy.Z.Zero();
if (!copy.BEncode(&buf))
return false;
@ -231,8 +229,8 @@ namespace llarp
{
std::array<byte_t, 512> tmp;
llarp_buffer_t buf(tmp);
RejectExitMessage copy;
copy = *this;
RejectExitMessage copy{*this};
copy.Z.Zero();
if (!copy.BEncode(&buf))
return false;
@ -289,8 +287,8 @@ namespace llarp
{
std::array<byte_t, 512> tmp;
llarp_buffer_t buf(tmp);
UpdateExitMessage copy;
copy = *this;
UpdateExitMessage copy{*this};
copy.Z.Zero();
if (!copy.BEncode(&buf))
return false;
@ -389,8 +387,8 @@ namespace llarp
{
std::array<byte_t, 512> tmp;
llarp_buffer_t buf(tmp);
CloseExitMessage copy;
copy = *this;
CloseExitMessage copy{*this};
copy.Z.Zero();
if (!copy.BEncode(&buf))
return false;

View file

@ -1,9 +1,11 @@
#pragma once
#include <cstdint>
#include <llarp/crypto/types.hpp>
#include "policy.hpp"
#include <llarp/routing/message.hpp>
#include <ratio>
#include <vector>
namespace llarp
@ -12,15 +14,23 @@ namespace llarp
{
struct ObtainExitMessage final : public IMessage
{
std::vector<llarp::exit::Policy> B;
uint64_t E{0};
llarp::PubKey I;
uint64_t T{0};
std::vector<llarp::exit::Policy> W;
uint64_t X{0};
llarp::Signature Z;
std::vector<llarp::exit::Policy> blacklist;
uint64_t wants_exit{0};
llarp::PubKey source_identity;
uint64_t txid{0};
std::vector<llarp::exit::Policy> whitelist;
uint64_t request_expires_at{0};
llarp::Signature sig;
ObtainExitMessage() : IMessage()
decltype(blacklist)& B{blacklist};
decltype(wants_exit)& E{wants_exit};
decltype(source_identity)& I{source_identity};
decltype(txid)& T{txid};
decltype(whitelist)& W{whitelist};
decltype(request_expires_at)& X{request_expires_at};
decltype(sig)& Z{sig};
ObtainExitMessage() : IMessage{}
{}
~ObtainExitMessage() override = default;
@ -28,13 +38,14 @@ namespace llarp
void
Clear() override
{
B.clear();
E = 0;
I.Zero();
T = 0;
W.clear();
X = 0;
Z.Zero();
IMessage::Clear();
blacklist.clear();
wants_exit = 0;
source_identity.Zero();
txid = 0;
whitelist.clear();
request_expires_at = 0;
sig.Zero();
}
/// populates I and signs
@ -52,15 +63,22 @@ namespace llarp
bool
HandleMessage(IMessageHandler* h, AbstractRouter* r) const override;
std::string
ToString() const;
};
struct GrantExitMessage final : public IMessage
{
using Nonce_t = llarp::AlignedBuffer<16>;
uint64_t T;
Nonce_t Y;
llarp::Signature Z;
uint64_t txid;
Nonce_t nounce;
llarp::Signature sig;
decltype(txid)& T{txid};
decltype(nounce)& Y{nounce};
decltype(sig)& Z{sig};
bool
BEncode(llarp_buffer_t* buf) const override;
@ -80,7 +98,8 @@ namespace llarp
void
Clear() override
{
T = 0;
IMessage::Clear();
txid = 0;
Y.Zero();
Z.Zero();
}
@ -89,20 +108,27 @@ namespace llarp
struct RejectExitMessage final : public IMessage
{
using Nonce_t = llarp::AlignedBuffer<16>;
uint64_t B;
std::vector<llarp::exit::Policy> R;
uint64_t T;
Nonce_t Y;
llarp::Signature Z;
uint64_t backoff;
std::vector<llarp::exit::Policy> rejected_policies;
uint64_t txid;
Nonce_t nonce;
llarp::Signature sig;
decltype(backoff)& B{backoff};
decltype(rejected_policies)& R{rejected_policies};
decltype(txid)& T{txid};
decltype(nonce)& Y{nonce};
decltype(sig)& Z{sig};
void
Clear() override
{
B = 0;
R.clear();
T = 0;
Y.Zero();
Z.Zero();
IMessage::Clear();
backoff = 0;
rejected_policies.clear();
txid = 0;
nonce.Zero();
sig.Zero();
}
bool
@ -124,9 +150,13 @@ namespace llarp
struct UpdateExitVerifyMessage final : public IMessage
{
using Nonce_t = llarp::AlignedBuffer<16>;
uint64_t T;
Nonce_t Y;
llarp::Signature Z;
uint64_t txid;
Nonce_t nounce;
llarp::Signature sig;
decltype(txid)& T{txid};
decltype(nounce)& Y{nounce};
decltype(sig)& Z{sig};
~UpdateExitVerifyMessage() override = default;
@ -151,10 +181,15 @@ namespace llarp
struct UpdateExitMessage final : public IMessage
{
using Nonce_t = llarp::AlignedBuffer<16>;
llarp::PathID_t P;
uint64_t T;
Nonce_t Y;
llarp::Signature Z;
llarp::PathID_t path_id;
uint64_t txid;
Nonce_t nounce;
llarp::Signature sig;
decltype(path_id)& P{path_id};
decltype(txid)& T{txid};
decltype(nounce)& Y{nounce};
decltype(sig)& Z{sig};
bool
Sign(const llarp::SecretKey& sk);
@ -174,6 +209,7 @@ namespace llarp
void
Clear() override
{
IMessage::Clear();
P.Zero();
T = 0;
Y.Zero();
@ -185,8 +221,10 @@ namespace llarp
{
using Nonce_t = llarp::AlignedBuffer<16>;
Nonce_t Y;
llarp::Signature Z;
Nonce_t nounce;
llarp::Signature sig;
decltype(nounce)& Y{nounce};
decltype(sig)& Z{sig};
bool
BEncode(llarp_buffer_t* buf) const override;
@ -206,6 +244,7 @@ namespace llarp
void
Clear() override
{
IMessage::Clear();
Y.Zero();
Z.Zero();
}

View file

@ -114,7 +114,7 @@ namespace llarp
bool
IsReady() const;
const llarp::RouterID
llarp::RouterID
Endpoint() const
{
return m_ExitRouter;
@ -218,8 +218,7 @@ namespace llarp
PopulateRequest(llarp::routing::ObtainExitMessage& msg) const override
{
// TODO: set expiration time
msg.X = 0;
msg.E = 1;
msg.wants_exit = 1;
}
};
@ -247,8 +246,7 @@ namespace llarp
PopulateRequest(llarp::routing::ObtainExitMessage& msg) const override
{
// TODO: set expiration time
msg.X = 0;
msg.E = 0;
msg.wants_exit = 0;
}
};

View file

@ -1,17 +1,18 @@
#include "exit.hpp"
#include <llarp/dns/dns.hpp>
#include <llarp/dns/server.hpp>
#include <llarp/net/net.hpp>
#include <llarp/path/path_context.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/util/str.hpp>
#include <llarp/util/bits.hpp>
#include <llarp/util/underlying.hpp>
#include <llarp/quic/tunnel.hpp>
#include <llarp/router/i_rc_lookup_handler.hpp>
#include <cassert>
#include <llarp/service/protocol_type.hpp>
#include <llarp/vpn/platform.hpp>
namespace llarp
{
@ -97,7 +98,7 @@ namespace llarp
bool
ExitEndpoint::SendToOrQueue(
service::ConvoTag tag, const llarp_buffer_t& payload, service::ProtocolType type)
service::ConvoTag tag, std::vector<byte_t> payload, service::ProtocolType type)
{
if (auto maybeAddr = GetEndpointWithConvoTag(tag))
{
@ -109,7 +110,7 @@ namespace llarp
{
if (not itr->second->LooksDead(Now()))
{
if (itr->second->QueueInboundTraffic(payload.copy(), type))
if (itr->second->QueueInboundTraffic(std::move(payload), type))
return true;
}
}
@ -117,10 +118,10 @@ namespace llarp
if (not m_Router->PathToRouterAllowed(*rid))
return false;
ObtainSNodeSession(*rid, [pkt = payload.copy(), type](auto session) mutable {
ObtainSNodeSession(*rid, [payload, type](auto session) mutable {
if (session and session->IsReady())
{
session->SendPacketToRemote(std::move(pkt), type);
session->SendPacketToRemote(std::move(payload), type);
}
});
}
@ -189,19 +190,25 @@ namespace llarp
{
if (msg.questions.size() == 0)
return false;
const auto& question = msg.questions[0];
if (not(question.tld() == "snode" or question.tld() == "loki"))
return false;
const auto& qtype = question.qtype;
// always hook ptr for ranges we own
if (msg.questions[0].qtype == dns::qTypePTR)
if (qtype == to_underlying(dns::RRType::PTR))
{
if (auto ip = dns::DecodePTR(msg.questions[0].qname))
if (auto ip = dns::DecodePTR(msg.questions[0].qname()))
return m_OurRange.Contains(*ip);
return false;
}
if (msg.questions[0].qtype == dns::qTypeA || msg.questions[0].qtype == dns::qTypeCNAME
|| msg.questions[0].qtype == dns::qTypeAAAA)
if (qtype == to_underlying(dns::RRType::A) or qtype == to_underlying(dns::RRType::AAAA)
or qtype == to_underlying(dns::RRType::CNAME))
{
if (msg.questions[0].IsName("localhost.loki"))
if (question.tld() == "snode")
return true;
if (msg.questions[0].HasTLD(".snode"))
if (question.tld() == "loki")
return true;
}
return false;
@ -226,94 +233,109 @@ namespace llarp
bool
ExitEndpoint::HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)> reply)
{
if (msg.questions[0].qtype == dns::qTypePTR)
auto name = msg.questions[0].qname();
if (msg.questions[0].qtype == to_underlying(dns::RRType::PTR))
{
auto ip = dns::DecodePTR(msg.questions[0].qname);
auto ip = dns::DecodePTR(name);
if (not ip)
return false;
if (ip == m_IfAddr)
{
RouterID us = GetRouter()->pubkey();
msg.AddAReply(us.ToString(), 300);
msg.answers.emplace_back(
name, dns::RRType::PTR, dns::RData{dns::split_dns_name(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());
msg.answers.emplace_back(
name,
dns::RRType::PTR,
dns::RData{dns::split_dns_name(itr->second.ToString())},
300);
}
else
msg.AddNXReply();
msg.nx();
}
}
else if (msg.questions[0].qtype == dns::qTypeCNAME)
else if (msg.questions[0].qtype == to_underlying(dns::RRType::CNAME))
{
if (msg.questions[0].IsName("random.snode"))
{
RouterID random;
if (GetRouter()->GetRandomGoodRouter(random))
msg.AddCNAMEReply(random.ToString(), 1);
msg.answers.emplace_back(
name, dns::RRType::CNAME, dns::RData{dns::split_dns_name(random.ToString())}, 1);
else
msg.AddNXReply();
msg.nx();
}
else if (msg.questions[0].IsName("localhost.loki"))
{
RouterID us = m_Router->pubkey();
msg.AddAReply(us.ToString(), 1);
msg.answers.emplace_back(
name, dns::RRType::PTR, dns::RData{dns::split_dns_name(us.ToString())}, 1);
}
else
msg.AddNXReply();
msg.nx();
}
else if (msg.questions[0].qtype == dns::qTypeA || msg.questions[0].qtype == dns::qTypeAAAA)
else if (msg.questions[0].qtype == to_underlying(dns::RRType::A))
{
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);
msg.answers.emplace_back(
name, dns::RRType::CNAME, dns::RData{dns::split_dns_name(random.ToString())}, 1);
auto rdata = var::visit(
[](auto&& ip) { return dns::RData{ip}; },
net::maybe_truncate_ip(ToNet(ObtainServiceNodeIP(random))));
msg.answers.emplace_back(name, dns::RRType::A, std::move(rdata), 1);
}
else
msg.AddNXReply();
msg.nx();
reply(msg);
return true;
}
if (msg.questions[0].IsName("localhost.loki"))
{
msg.AddINReply(GetIfAddr(), isV6);
auto rdata = var::visit(
[](auto&& ip) { return dns::RData{ip}; }, net::maybe_truncate_ip(ToNet(GetIfAddr())));
msg.answers.emplace_back(name, dns::RRType::A, std::move(rdata), 1);
reply(msg);
return true;
}
// forward dns for snode
RouterID r;
if (r.FromString(msg.questions[0].Name()))
auto name = msg.questions[0].qname();
if (ends_with(name, "."))
name = name.substr(0, name.size() - 2);
if (r.FromString(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())
if (m_SNodeKeys.find(pubKey) == m_SNodeKeys.end())
{
// we do not have it mapped, async obtain it
ObtainSNodeSession(
r,
[&, msg = std::make_shared<dns::Message>(msg), reply](
[this, pubKey, msg = std::make_shared<dns::Message>(msg), reply](
std::shared_ptr<exit::BaseSession> session) {
auto name = msg->questions[0].qname();
if (session && session->IsReady())
{
msg->AddINReply(m_KeyToIP[pubKey], isV6);
auto rdata = var::visit(
[](auto&& ip) { return dns::RData{ip}; },
net::maybe_truncate_ip(ToNet(m_KeyToIP[pubKey])));
msg->answers.emplace_back(name, dns::RRType::A, std::move(rdata), 1);
}
else
{
msg->AddNXReply();
msg->nx();
}
reply(*msg);
});
@ -325,15 +347,17 @@ namespace llarp
auto itr = m_KeyToIP.find(pubKey);
if (itr != m_KeyToIP.end())
{
ip = itr->second;
msg.AddINReply(ip, isV6);
auto rdata = var::visit(
[](auto&& ip) { return dns::RData{ip}; },
net::maybe_truncate_ip(ToNet(itr->second)));
msg.answers.emplace_back(name, dns::RRType::A, std::move(rdata), 1);
}
else // fallback case that should never happen (probably)
msg.AddNXReply();
msg.nx();
}
}
else
msg.AddNXReply();
msg.nx();
}
reply(msg);
return true;
@ -478,9 +502,8 @@ namespace llarp
GetRouter()->loop()->add_ticker([this] { Flush(); });
#ifndef _WIN32
m_Resolver = std::make_shared<dns::Server>(
m_Router->loop(), m_DNSConf, if_nametoindex(m_ifname.c_str()));
m_Resolver->Start();
m_Resolver = std::make_shared<dns::Server>(m_Router->loop(), m_DNSConf);
m_Resolver->Start(if_nametoindex(m_ifname.c_str()));
#endif
}
@ -701,7 +724,7 @@ namespace llarp
return true;
}
void
bool
ExitEndpoint::Configure(const NetworkConfig& networkConfig, const DnsConfig& dnsConfig)
{
/*
@ -728,14 +751,7 @@ namespace llarp
m_ShouldInitTun = false;
}
m_OurRange = networkConfig.m_ifaddr;
if (!m_OurRange.addr.h)
{
const auto maybe = m_Router->Net().FindFreeRange();
if (not maybe.has_value())
throw std::runtime_error("cannot find free interface range");
m_OurRange = *maybe;
}
m_OurRange = networkConfig.ifaddr(m_Router->Net());
const auto host_str = m_OurRange.BaseAddressString();
// string, or just a plain char array?
m_IfAddr = m_OurRange.addr;
@ -743,14 +759,8 @@ namespace llarp
m_HigestAddr = m_OurRange.HighestAddr();
m_UseV6 = not m_OurRange.IsV4();
m_ifname = networkConfig.m_ifname;
if (m_ifname.empty())
{
const auto maybe = m_Router->Net().FindFreeTun();
if (not maybe.has_value())
throw std::runtime_error("cannot find free interface name");
m_ifname = *maybe;
}
m_ifname = networkConfig.ifname(m_Router->Net());
LogInfo(Name(), " set ifname to ", m_ifname);
if (auto* quic = GetQUICTunnel())
{
@ -758,6 +768,7 @@ namespace llarp
return llarp::SockAddr{ifaddr, huint16_t{port}};
});
}
return true;
}
huint128_t
@ -809,7 +820,7 @@ namespace llarp
m_SNodeKeys.emplace(pk.as_array());
}
m_ActiveExits.emplace(
pk, std::make_unique<exit::Endpoint>(pk, handler, !wantInternet, ip, this));
pk, std::make_unique<exit::Endpoint>(*m_Router, pk, handler, !wantInternet, ip));
m_Paths[path] = pk;

View file

@ -1,7 +1,7 @@
#pragma once
#include <llarp/exit/endpoint.hpp>
#include "tun.hpp"
#include <llarp/exit/session.hpp>
#include <llarp/dns/server.hpp>
#include <unordered_map>
@ -64,13 +64,22 @@ namespace llarp
bool
SendToOrQueue(
service::ConvoTag tag, const llarp_buffer_t& payload, service::ProtocolType t) override;
service::ConvoTag tag, std::vector<byte_t> payload, service::ProtocolType t) override;
std::string_view
endpoint_name() const override
{
return Name();
}
void
Tick(llarp_time_t now);
void
Configure(const NetworkConfig& networkConfig, const DnsConfig& dnsConfig);
bool
Configure(const NetworkConfig& networkConfig, const DnsConfig& dnsConfig) override;
bool
LookupRC(RouterID remote, RouterLookupHandler handler) override;
std::string
Name() const;

View file

@ -9,11 +9,11 @@
namespace llarp::handlers
{
struct NullEndpoint final : public llarp::service::Endpoint,
public std::enable_shared_from_this<NullEndpoint>
struct NullEndpoint final : public llarp::service::Endpoint
{
NullEndpoint(AbstractRouter* r, llarp::service::Context* parent)
: llarp::service::Endpoint{r, parent}
NullEndpoint(AbstractRouter& r)
: llarp::service::Endpoint{r}
, m_PacketRouter{new vpn::EgresPacketRouter{[](auto from, auto pkt) {
var::visit(
[&pkt](auto&& from) {
@ -22,17 +22,14 @@ namespace llarp::handlers
from);
}}}
{
r->loop()->add_ticker([this] { Pump(Now()); });
r.loop()->add_ticker([this] { Pump(Now()); });
}
virtual bool
bool
HandleInboundPacket(
const service::ConvoTag tag,
const llarp_buffer_t& buf,
service::ProtocolType t,
uint64_t) override
const service::ConvoTag tag, std::vector<byte_t> buf, service::ProtocolType t, uint64_t)
{
LogTrace("Inbound ", t, " packet (", buf.sz, "B) on convo ", tag);
LogTrace("Inbound ", t, " packet (", buf.size(), "B) on convo ", tag);
if (t == service::ProtocolType::Control)
{
return true;
@ -41,8 +38,8 @@ namespace llarp::handlers
{
if (auto from = GetEndpointWithConvoTag(tag))
{
net::IPPacket pkt{};
if (not pkt.Load(buf))
net::IPPacket pkt{std::move(buf)};
if (pkt.empty())
{
LogWarn("invalid ip packet from remote T=", tag);
return false;
@ -65,35 +62,23 @@ namespace llarp::handlers
LogWarn("incoming quic packet but this endpoint is not quic capable; dropping");
return false;
}
if (buf.sz < 4)
if (buf.size() < 4)
{
LogWarn("invalid incoming quic packet, dropping");
return false;
}
quic->receive_packet(tag, buf);
quic->receive_packet(tag, std::move(buf));
return true;
}
std::string
GetIfName() const override
GetIfName() const
{
return "";
}
path::PathSet_ptr
GetSelf() override
{
return shared_from_this();
}
std::weak_ptr<path::PathSet>
GetWeak() override
{
return weak_from_this();
}
bool
SupportsV6() const override
SupportsV6() const
{
return false;
}
@ -101,18 +86,6 @@ namespace llarp::handlers
void
SendPacketToRemote(const llarp_buffer_t&, service::ProtocolType) override{};
huint128_t
ObtainIPForAddr(std::variant<service::Address, RouterID>) override
{
return {0};
}
std::optional<std::variant<service::Address, RouterID>>
ObtainAddrForIP(huint128_t) const override
{
return std::nullopt;
}
vpn::EgresPacketRouter*
EgresPacketRouter() override
{

View file

@ -8,7 +8,8 @@
#include <netdb.h>
#endif
#include <llarp/dns/dns.hpp>
#include <llarp/dns/server.hpp>
#include <llarp/dns/bits.hpp>
#include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp>
#include <llarp/router/abstractrouter.hpp>
@ -25,6 +26,7 @@
#include <llarp/rpc/endpoint_rpc.hpp>
#include <llarp/util/str.hpp>
#include <llarp/util/logging/buffer.hpp>
#include <llarp/util/underlying.hpp>
#include <llarp/dns/srv_data.hpp>
#include <llarp/constants/net.hpp>
#include <llarp/constants/platform.hpp>
@ -117,17 +119,17 @@ namespace llarp
{
std::optional<SockAddr> m_QueryBind;
net::ipaddr_t m_OurIP;
TunEndpoint* const m_Endpoint;
TunEndpoint& 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}
TunDNS(TunEndpoint& ep, const llarp::DnsConfig& conf)
: dns::Server{ep.Router()->loop(), conf}
, m_QueryBind{conf.m_QueryBind}
, m_OurIP{ToNet(ep->GetIfAddr())}
, m_OurIP{ToNet(ep.GetIfAddr())}
, m_Endpoint{ep}
{}
@ -135,8 +137,8 @@ namespace llarp
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);
[this](auto pkt) {
m_Endpoint.HandleWriteIPPacket(pkt.ConstBuffer(), pkt.srcv6(), pkt.dstv6(), 0);
},
m_OurIP,
conf);
@ -145,8 +147,7 @@ namespace llarp
}
};
TunEndpoint::TunEndpoint(AbstractRouter* r, service::Context* parent)
: service::Endpoint{r, parent}
TunEndpoint::TunEndpoint(AbstractRouter& r) : service::Endpoint{r}
{
m_PacketRouter = std::make_shared<vpn::PacketRouter>(
[this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); });
@ -158,7 +159,7 @@ namespace llarp
const auto& info = GetVPNInterface()->Info();
if (m_DnsConfig.m_raw_dns)
{
auto dns = std::make_shared<TunDNS>(this, m_DnsConfig);
auto dns = std::make_shared<TunDNS>(*this, m_DnsConfig);
m_DNS = dns;
m_PacketRouter->AddUDPHandler(huint16_t{53}, [this, dns](net::IPPacket pkt) {
@ -173,10 +174,10 @@ namespace llarp
});
}
else
m_DNS = std::make_shared<dns::Server>(Loop(), m_DnsConfig, info.index);
m_DNS = std::make_shared<dns::Server>(Loop(), m_DnsConfig);
m_DNS->AddResolver(weak_from_this());
m_DNS->Start();
m_DNS->AddResolver(std::enable_shared_from_this<TunEndpoint>::weak_from_this());
m_DNS->Start(info.index);
if (m_DnsConfig.m_raw_dns)
{
@ -307,7 +308,7 @@ namespace llarp
conf.m_AuthWhitelist,
conf.m_AuthStaticTokens,
Router()->lmq(),
shared_from_this());
std::enable_shared_from_this<TunEndpoint>::shared_from_this());
auth->Start();
m_AuthPolicy = std::move(auth);
}
@ -331,25 +332,8 @@ namespace llarp
return false;
}
m_IfName = conf.m_ifname;
if (m_IfName.empty())
{
const auto maybe = m_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 = m_router->Net().FindFreeRange();
if (not maybe.has_value())
{
throw std::runtime_error("cannot find free address range");
}
m_OurRange = *maybe;
}
m_IfName = conf.ifname(m_router->Net());
m_OurRange = conf.ifaddr(m_router->Net());
m_OurIP = m_OurRange.addr;
m_UseV6 = false;
@ -504,7 +488,7 @@ namespace llarp
msg.authorities.resize(0);
msg.additional.resize(0);
msg.answers.resize(0);
msg.hdr_fields &= ~dns::flags_RCODENameError;
msg.hdr.rcode(~(dns::bits::rcode_name_error | dns::bits::rcode_servfail));
return msg;
}
@ -590,25 +574,19 @@ namespace llarp
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;
auto name = answer.rr_data.as_dns_name();
RouterID addr;
if (not addr.FromString(*qname))
if (not addr.FromString(name))
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;
auto name = answer.rr_data.as_dns_name();
service::Address addr;
if (not addr.FromString(*qname))
if (not addr.FromString(name))
return false;
auto replyMsg = std::make_shared<dns::Message>(clear_dns_message(msg));
@ -620,30 +598,28 @@ namespace llarp
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)
auto qname = msg.questions[0].qname();
qname = qname.substr(0, qname.size() - 2);
std::string lnsName = qname;
if (msg.questions[0].qtype == to_underlying(dns::RRType::TXT))
{
RouterID snode;
if (snode.FromString(qname))
{
m_router->LookupRouter(snode, [reply, msg = std::move(msg)](const auto& found) mutable {
m_router->LookupRouter(snode, [reply, _msg = std::move(msg), qname](const auto& found) {
dns::Message msg{_msg};
if (found.empty())
{
msg.AddNXReply();
msg.nx();
}
else
{
std::string recs;
for (const auto& rc : found)
recs += rc.ToTXTRecord();
msg.AddTXTReply(std::move(recs));
msg.AddTXTReply(std::string_view{recs}, 1);
}
reply(msg);
});
@ -660,30 +636,32 @@ namespace llarp
m_ExitMap.ForEachEntry([&s](const auto& range, const auto& exit) {
fmt::format_to(std::back_inserter(s), "{}={}; ", range, exit);
});
msg.AddTXTReply(std::move(s));
msg.AddTXTReply(std::string_view{s}, 1);
}
else
{
msg.AddNXReply();
msg.nx();
}
}
else if (subdomain == "netid")
{
msg.AddTXTReply(fmt::format("netid={};", m_router->rc().netID));
std::string s = fmt::format("netid={};", m_router->rc().netID);
msg.AddTXTReply(std::string_view{s}, 1);
}
else
{
msg.AddNXReply();
msg.nx();
}
}
else
{
msg.AddNXReply();
msg.nx();
}
reply(msg);
}
else if (msg.questions[0].qtype == dns::qTypeMX)
else if (msg.questions[0].qtype == to_underlying(dns::RRType::MX))
{
// mx record
service::Address addr;
@ -695,23 +673,23 @@ namespace llarp
else if (service::NameIsValid(lnsName))
{
LookupNameAsync(lnsName, [msg, lnsName, reply](auto maybe) mutable {
if (maybe.has_value())
if (maybe)
{
var::visit([&](auto&& value) { msg.AddMXReply(value.ToString(), 1); }, *maybe);
var::visit([&msg](auto&& value) { msg.AddMXReply(value.ToString(), 1); }, *maybe);
}
else
{
msg.AddNXReply();
msg.nx();
}
reply(msg);
});
return true;
}
else
msg.AddNXReply();
msg.nx();
reply(msg);
}
else if (msg.questions[0].qtype == dns::qTypeCNAME)
else if (msg.questions[0].qtype == to_underlying(dns::RRType::CNAME))
{
if (is_random_snode(msg))
{
@ -721,7 +699,7 @@ namespace llarp
msg.AddCNAMEReply(random.ToString(), 1);
}
else
msg.AddNXReply();
msg.nx();
}
else if (msg.questions[0].IsLocalhost() and msg.questions[0].HasSubdomains())
{
@ -733,34 +711,28 @@ namespace llarp
}
else
{
msg.AddNXReply();
msg.nx();
}
}
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();
const service::Address addr = GetIdentity().pub.Addr();
msg.AddCNAMEReply(addr.ToString(), 1);
}
else
msg.AddNXReply();
msg.nx();
reply(msg);
}
else if (msg.questions[0].qtype == dns::qTypeA || msg.questions[0].qtype == dns::qTypeAAAA)
else if (
msg.questions[0].qtype == to_underlying(dns::RRType::A)
or msg.questions[0].qtype == to_underlying(dns::RRType::AAAA))
{
const bool isV6 = msg.questions[0].qtype == dns::qTypeAAAA;
const bool isV4 = msg.questions[0].qtype == dns::qTypeA;
const bool isV6 = msg.questions[0].qtype == to_underlying(dns::RRType::AAAA);
const bool isV4 = msg.questions[0].qtype == to_underlying(dns::RRType::A);
llarp::service::Address addr;
if (isV6 && !SupportsV6())
{ // empty reply but not a NXDOMAIN so that client can retry IPv4
msg.AddNSReply("localhost.loki.");
msg.AddNSReply("localhost.loki.", 1);
}
// on MacOS this is a typeA query
else if (is_random_snode(msg))
@ -772,7 +744,7 @@ namespace llarp
return ReplyToSNodeDNSWhenReady(random, std::make_shared<dns::Message>(msg), isV6);
}
else
msg.AddNXReply();
msg.nx();
}
else if (is_localhost_loki(msg))
{
@ -784,48 +756,35 @@ namespace llarp
{
if (HasExit())
{
m_ExitMap.ForEachEntry(
[&msg](const auto&, const auto& exit) { msg.AddCNAMEReply(exit.ToString()); });
msg.AddINReply(ip, isV6);
m_ExitMap.ForEachEntry([&msg](const auto&, const auto& exit) {
msg.AddCNAMEReply(exit.ToString(), 1);
});
msg.rr_add_ipaddr(net::maybe_truncate_ip(ToNet(ip)));
}
else
{
msg.AddNXReply();
msg.nx();
}
}
else
{
msg.AddCNAMEReply(m_Identity.pub.Name(), 1);
msg.AddINReply(ip, isV6);
msg.rr_add_ipaddr(net::maybe_truncate_ip(ToNet(ip)));
}
}
else
{
msg.AddNXReply();
msg.nx();
}
}
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);
}
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);
}
return ReplyToSNodeDNSWhenReady(
addr.as_array(), std::make_shared<dns::Message>(msg), isV6);
}
else if (service::NameIsValid(lnsName))
{
@ -837,11 +796,10 @@ namespace llarp
isV6,
reply,
ReplyToDNSWhenReady](auto maybe) {
if (not maybe.has_value())
if (not maybe)
{
LogWarn(name, " lns name ", lnsName, " not resolved");
msg->AddNXReply();
reply(*msg);
reply(msg->nx());
return;
}
ReplyToDNSWhenReady(*maybe, msg, isV6);
@ -849,31 +807,30 @@ namespace llarp
return true;
}
else
msg.AddNXReply();
msg.nx();
reply(msg);
}
else if (msg.questions[0].qtype == dns::qTypePTR)
else if (msg.questions[0].qtype == to_underlying(dns::RRType::PTR))
{
// reverse dns
if (auto ip = dns::DecodePTR(msg.questions[0].qname))
if (auto ip = dns::DecodePTR(msg.questions[0].qname()))
{
if (auto maybe = ObtainAddrForIP(*ip))
{
var::visit([&msg](auto&& result) { msg.AddAReply(result.ToString()); }, *maybe);
msg.AddINReply(var::visit([](auto&& addr) { return addr.ToString(); }, *maybe));
reply(msg);
return true;
}
}
msg.AddNXReply();
reply(msg);
reply(msg.nx());
return true;
}
else if (msg.questions[0].qtype == dns::qTypeSRV)
else if (msg.questions[0].qtype == to_underlying(dns::RRType::SRV))
{
auto srv_for = msg.questions[0].Subdomains();
auto name = msg.questions[0].qname;
auto name = msg.questions[0].qname();
if (is_localhost_loki(msg))
{
msg.AddSRVReply(introSet().GetMatchingSRVRecords(srv_for));
@ -886,20 +843,19 @@ namespace llarp
[reply, msg = std::make_shared<dns::Message>(std::move(msg))](auto records) {
if (records.empty())
{
msg->AddNXReply();
reply(msg->nx());
}
else
{
msg->AddSRVReply(records);
msg->AddSRVReply(records, 1);
reply(*msg);
}
reply(*msg);
});
return true;
}
else
{
msg.AddNXReply();
reply(msg);
reply(msg.nx());
}
return true;
}
@ -932,7 +888,7 @@ namespace llarp
// hook any ranges we own
if (msg.questions[0].qtype == llarp::dns::qTypePTR)
{
if (auto ip = dns::DecodePTR(msg.questions[0].qname))
if (auto ip = dns::DecodePTR(msg.questions[0].qname()))
return m_OurRange.Contains(*ip);
return false;
}
@ -1257,7 +1213,7 @@ namespace llarp
// this succeds for inbound convos, probably.
if (auto maybe = GetBestConvoTagFor(to))
{
if (SendToOrQueue(*maybe, pkt.ConstBuffer(), type))
if (SendToOrQueue(*maybe, pkt.copy(), type))
{
MarkIPActive(dst);
Router()->TriggerPump();
@ -1277,7 +1233,7 @@ namespace llarp
},
to);
}
if (SendToOrQueue(*maybe, pkt.ConstBuffer(), type))
if (SendToOrQueue(*maybe, pkt.copy(), type))
{
MarkIPActive(dst);
Router()->TriggerPump();

View file

@ -22,9 +22,9 @@ namespace llarp
{
struct TunEndpoint : public service::Endpoint,
public dns::Resolver_Base,
public std::enable_shared_from_this<TunEndpoint>
private std::enable_shared_from_this<TunEndpoint>
{
TunEndpoint(AbstractRouter* r, llarp::service::Context* parent);
explicit TunEndpoint(AbstractRouter& r);
~TunEndpoint() override;
vpn::NetworkInterface*
@ -52,18 +52,6 @@ namespace llarp
const SockAddr& to,
const SockAddr& from) override;
path::PathSet_ptr
GetSelf() override
{
return shared_from_this();
}
std::weak_ptr<path::PathSet>
GetWeak() override
{
return weak_from_this();
}
void
Thaw() override;
@ -72,13 +60,13 @@ namespace llarp
ReconfigureDNS(std::vector<SockAddr> servers);
bool
Configure(const NetworkConfig& conf, const DnsConfig& dnsConf) override;
Configure(const NetworkConfig& conf, const DnsConfig& dnsConf);
void
SendPacketToRemote(const llarp_buffer_t&, service::ProtocolType) override{};
std::string
GetIfName() const override;
GetIfName() const;
void
Tick(llarp_time_t now) override;
@ -90,7 +78,7 @@ namespace llarp
NotifyParams() const override;
bool
SupportsV6() const override;
SupportsV6() const;
bool
ShouldHookDNSMessage(const dns::Message& msg) const;
@ -131,13 +119,12 @@ namespace llarp
bool
SetupNetworking() override;
/// overrides Endpoint
bool
HandleInboundPacket(
const service::ConvoTag tag,
const llarp_buffer_t& pkt,
service::ProtocolType t,
uint64_t seqno) override;
uint64_t seqno);
/// handle inbound traffic
bool
@ -188,7 +175,7 @@ namespace llarp
/// get a key for ip address
std::optional<std::variant<service::Address, RouterID>>
ObtainAddrForIP(huint128_t ip) const override;
ObtainAddrForIP(huint128_t ip) const;
bool
HasAddress(const AlignedBuffer<32>& addr) const
@ -198,7 +185,7 @@ namespace llarp
/// get ip address for key unconditionally
huint128_t
ObtainIPForAddr(std::variant<service::Address, RouterID> addr) override;
ObtainIPForAddr(std::variant<service::Address, RouterID> addr);
void
ResetInternalState() override;
@ -272,12 +259,11 @@ namespace llarp
{
if (ctx)
{
huint128_t ip = ObtainIPForAddr(addr);
query->answers.clear();
query->AddINReply(ip, sendIPv6);
huint128_t host_ip = ObtainIPForAddr(addr);
query->rr_add_ipaddr(net::maybe_truncate_ip(ToNet(host_ip)), 1);
}
else
query->AddNXReply();
query->nx();
reply(*query);
}

View file

@ -0,0 +1,60 @@
#include "flow_addr.hpp"
#include <oxenc/base32z.h>
#include <stdexcept>
#include <llarp/util/str.hpp>
namespace llarp::layers::flow
{
FlowAddr::Kind
FlowAddr::get_kind(std::string_view addr_str)
{
if (ends_with(addr_str, ".loki"))
return Kind::snapp;
if (ends_with(addr_str, ".snode"))
return Kind::snode;
throw std::invalid_argument{"bad flow address: '{}'"_format(addr_str)};
}
namespace
{
llarp::AlignedBuffer<32>::Data
decode_addr(std::string_view str)
{
llarp::AlignedBuffer<32>::Data data{};
if (auto sz = oxenc::to_base32z_size(str.size()); sz != data.size())
throw std::invalid_argument{"data decode size {} != {}"_format(sz, data.size())};
oxenc::from_base32z(str.begin(), str.end(), data.begin());
return data;
}
} // namespace
FlowAddr::FlowAddr(std::string str)
: llarp::AlignedBuffer<32>{decode_addr(split(str, ".", true)[0])}, _kind{get_kind(str)}
{}
FlowAddr::FlowAddr(EndpointBase::AddressVariant_t addr)
: FlowAddr{var::visit([](auto&& a) { return a.ToString(); }, addr)}
{}
std::string
FlowAddr::ToString() const
{
return oxenc::to_base32z(begin(), end())
+ (kind() == Kind::snapp ? ".loki"
: kind() == Kind::snode ? ".snode"
: "");
}
bool
FlowAddr::operator==(const FlowAddr& other) const
{
using parent_t = AlignedBuffer<SIZE>;
return kind() == other.kind() and parent_t::operator==(static_cast<const parent_t&>(other));
}
FlowAddr::Kind
FlowAddr::kind() const
{
return _kind;
}
} // namespace llarp::layers::flow

View file

@ -1,6 +1,5 @@
#pragma once
#include <fmt/core.h>
#include <llarp/util/aligned.hpp>
#include <string>
#include <string_view>

View file

@ -0,0 +1,25 @@
#include "flow_auth.hpp"
namespace llarp::layers::flow
{
std::string_view
ToString(FlowAuthPhase phase)
{
switch (phase)
{
case FlowAuthPhase::auth_more:
return "auth_more";
case FlowAuthPhase::auth_nack:
return "auth_nack";
case FlowAuthPhase::auth_req_not_sent:
return "auth_req_not_sent";
case FlowAuthPhase::auth_req_sent:
return "auth_req_sent";
case FlowAuthPhase::auth_ok:
return "auth_ok";
default:
return "unknown";
}
}
} // namespace llarp::layers::flow

View file

@ -0,0 +1,24 @@
#include "flow_data_kind.hpp"
namespace llarp::layers::flow
{
std::string_view
ToString(FlowDataKind kind)
{
switch (kind)
{
case FlowDataKind::auth:
return "auth";
case FlowDataKind::direct_ip_unicast:
return "direct_ip_unicast";
case FlowDataKind::exit_ip_unicast:
return "exit_ip_unicast";
case FlowDataKind::stream_unicast:
return "stream_unicast";
default:
return "unknown";
}
}
} // namespace llarp::layers::flow

View file

@ -0,0 +1,43 @@
#include "flow_establish.hpp"
#include <functional>
#include <optional>
namespace llarp::layers::flow
{
FlowEstablish::FlowEstablish(
std::function<void(std::optional<FlowInfo>, std::string)> completiton_handler,
std::function<void(FlowAuthPhase, std::string)> phase_handler)
: _completion_handler{std::move(completiton_handler)}
, _phase_handler{std::move(phase_handler)}
{}
void
FlowEstablish::fail(std::string name)
{
_result = std::nullopt;
enter_phase(FlowAuthPhase::auth_nack, std::move(name));
}
void
FlowEstablish::ready(FlowInfo flow_info)
{
_result = flow_info;
enter_phase(FlowAuthPhase::auth_ok, "");
_result = std::nullopt;
}
void
FlowEstablish::enter_phase(FlowAuthPhase phase, std::string info)
{
bool completed = phase == FlowAuthPhase::auth_ok or phase == FlowAuthPhase::auth_nack;
if (completed)
{
if (_completion_handler)
_completion_handler(_result, std::move(info));
_completion_handler = nullptr;
}
if (_phase_handler)
_phase_handler(phase, info);
}
} // namespace llarp::layers::flow

View file

@ -0,0 +1,106 @@
#include "flow_identity.hpp"
#include <oxenc/bt_serialize.h>
#include <chrono>
#include <llarp/crypto/crypto.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <memory>
#include <stdexcept>
#include <variant>
namespace llarp::layers::flow
{
FlowIdentityPrivateKeys
FlowIdentityPrivateKeys::keygen()
{
FlowIdentityPrivateKeys keys{};
auto crypto = CryptoManager::instance();
crypto->identity_keygen(keys.identity);
crypto->encryption_keygen(keys.encryption);
crypto->pqe_keygen(keys.sntrup);
if (not crypto->derive_subkey_private(keys.derivedKey, keys.identity, 1))
throw std::runtime_error("failed to derive subkey");
return keys;
}
const FlowAddr&
FlowIdentityPrivateKeys::public_addr() const
{
if (_root_pubkey.IsZero())
_root_pubkey = FlowAddr{service::Address{identity.toPublic()}};
return _root_pubkey;
}
const dht::Key_t&
FlowIdentityPrivateKeys::keyspace_location() const
{
if (_derived_pubkey.IsZero())
derivedKey.toPublic(reinterpret_cast<PubKey&>(_derived_pubkey));
return _derived_pubkey;
}
FlowIdentity::FlowIdentity(
FlowLayer& parent,
FlowAddr remote_addr,
FlowTag flow_tag_,
const FlowIdentityPrivateKeys& privkeys)
: _parent{parent}
, _local_privkeys{privkeys}
, _state{}
, flow_info{privkeys.public_addr(), std::move(remote_addr), std::move(flow_tag_)}
{}
bool
FlowIdentity::operator==(const FlowIdentity& other) const
{
return flow_info == other.flow_info;
}
namespace
{
EndpointBase::AddressVariant_t
to_addr_variant(const FlowAddr& addr)
{
switch (addr.kind())
{
case FlowAddr::Kind::snode:
return RouterID{addr.as_array()};
case FlowAddr::Kind::snapp:
return service::Address{addr.as_array()};
default:
throw std::invalid_argument{
"flow addr cannot be converted to EndpointBase::AddressVariant_t"};
}
}
struct deprecated_path_ensure_handler
{
FlowEstablish handshaker;
FlowInfo flow_info;
void
operator()(std::optional<service::ConvoTag> maybe_tag)
{
if (not maybe_tag)
{
handshaker.fail("cannot establish flow");
return;
}
handshaker.ready(flow_info);
}
};
} // namespace
void
FlowIdentity::async_ensure_flow(FlowEstablish handshaker)
{
handshaker.fail("not wired up");
}
void
FlowIdentity::send_to_remote(std::vector<byte_t>, FlowDataKind)
{}
} // namespace llarp::layers::flow

View file

@ -0,0 +1,16 @@
#include "flow_info.hpp"
namespace llarp::layers::flow
{
bool
FlowInfo::operator==(const FlowInfo& other) const
{
return src == other.src and dst == other.dst and tag == other.tag;
}
std::string
FlowInfo::ToString() const
{
return fmt::format("[flow src={} dst={} tag={} mtu={}]", src, dst, tag, int{mtu});
}
} // namespace llarp::layers::flow

View file

@ -15,13 +15,6 @@ namespace llarp::layers::flow
/// source and destination addresses we associate with this flow.
FlowAddr src, dst;
/// flow address of the pivot we use to get from src to dst.
/// for .loki traffic this is a .snode address, which we aligh our paths to.
/// for .snode traffic this is a .snode address, which we align our paths to.
/// for .exit traffic this is a .loki address, which we will fetch the exit descriptor it is
/// associated with.
FlowAddr pivot;
/// cleartext identifier used on outer framing of the traffic. lets us tell what is from who.
FlowTag tag;

View file

@ -0,0 +1,213 @@
#include "flow_layer.hpp"
#include <oxenc/bt_producer.h>
#include <oxenc/bt_serialize.h>
#include <filesystem>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/service/endpoint.hpp>
#include <llarp/path/path_context.hpp>
#include <vector>
namespace llarp::layers::flow
{
static auto logcat = log::Cat("flow-layer");
void
FlowLayer::maybe_store_or_load_privkeys()
{
if (auto maybe_keyfile = _conf.m_keyfile)
{
// put down existing private keys if it's not on disk
const auto& keyfile = *maybe_keyfile;
if (not fs::exists(keyfile))
{
if (auto err = util::EnsurePrivateFile(keyfile))
throw std::runtime_error{"failed to create file '{}': {}"_format(keyfile, err.message())};
std::string_view keys{
reinterpret_cast<const char*>(_privkeys.identity.data()), _privkeys.identity.size()};
util::dump_file(keyfile, oxenc::bt_serialize(keys));
return;
}
// otherwise we load the identity keys from disk
// todo: migrate old format?
auto data = oxenc::bt_deserialize<std::string>(util::slurp_file(keyfile));
if (data.size() != _privkeys.identity.size())
throw std::runtime_error{
"privkey has bad size: {} != {}"_format(data.size(), _privkeys.identity.size())};
std::copy(data.begin(), data.end(), _privkeys.identity.begin());
}
}
/// handler of feeding flow traffic from flow layer to platform layer.
struct platform_layer_waker
{
FlowLayer& flow_layer;
void
operator()()
{
// propagate flow traffic up to platform layer.
if (auto traffic = flow_layer.poll_flow_traffic(); not traffic.empty())
flow_layer.router.get_layers()->platform->flow_traffic(std::move(traffic));
// continue feeding any downstream traffic into the flow layer.
flow_layer.router.pathContext().PumpDownstream();
}
};
/// handler of sending flow layer stuff out to the void.
/// called after we queued sending all the things in the flow layer in the current event loop
/// cycle.
struct flow_layer_waker
{
path::PathContext& paths;
void
operator()()
{
// take the things we need to send out on paths and send them out.
paths.PumpUpstream();
}
};
FlowLayer::FlowLayer(AbstractRouter& r, NetworkConfig conf)
: _conf{std::move(conf)}
, _privkeys{FlowIdentityPrivateKeys::keygen()}
, _name_cache{}
, _deprecated_endpoint{std::make_shared<service::Endpoint>(r)}
, _wakeup_send{r.loop()->make_waker(flow_layer_waker{r.pathContext()})}
, _wakeup_recv{r.loop()->make_waker(platform_layer_waker{*this})}
, name_resolver{_name_cache, *this}
, router{r}
{}
const FlowAddr&
FlowLayer::local_addr(const std::optional<FlowTag>& maybe_tag) const
{
if (maybe_tag)
{
const auto& flow_tag = *maybe_tag;
for (const auto& local_flow : _local_flows)
{
if (local_flow->flow_info.tag == flow_tag)
return local_flow->flow_info.src;
}
throw std::invalid_argument{"no such local address for flow tag: {}"_format(flow_tag)};
}
return _privkeys.public_addr();
}
void
FlowLayer::start()
{
log::info(logcat, "flow layer starting");
const auto& _dns_conf = router.GetConfig()->dns;
if (not _deprecated_endpoint->Configure(_conf, _dns_conf))
throw std::runtime_error{"deprecated endpoint failed to configure"};
if (not _deprecated_endpoint->Start())
throw std::runtime_error{"deprecated endpoint did not start"};
router.loop()->call_every(
100ms, _deprecated_endpoint, [ep = _deprecated_endpoint]() { ep->Tick(ep->Now()); });
log::info(logcat, "flow layer up");
}
void
FlowLayer::stop()
{
log::info(logcat, "flow layer stopping");
if (not _deprecated_endpoint->Stop())
throw std::runtime_error{"deprecated endpoint did not stop"};
}
void
FlowLayer::offer_flow_traffic(FlowTraffic&& traff)
{
_recv.push_back(traff);
_wakeup_recv->Trigger();
}
bool
FlowLayer::has_flow(const FlowInfo& flow_info) const
{
for (const auto& _flow : _local_flows)
{
if (_flow->flow_info == flow_info)
return true;
}
return false;
}
void
FlowLayer::remove_flow(const FlowInfo& flow_info)
{
for (auto itr = _local_flows.begin(); itr != _local_flows.end();)
{
if ((*itr)->flow_info == flow_info)
itr = _local_flows.erase(itr);
else
++itr;
}
}
std::vector<FlowTraffic>
FlowLayer::poll_flow_traffic()
{
std::vector<FlowTraffic> ret{_recv.capacity()};
std::swap(ret, _recv);
return ret;
}
FlowStats
FlowLayer::current_stats() const
{
// todo: implement
return FlowStats{};
}
FlowTag
FlowLayer::unique_flow_tag() const
{
FlowTag flow_tag{};
do
{
flow_tag.Randomize();
} while (has_flow_tag(flow_tag));
return flow_tag;
}
bool
FlowLayer::has_flow_tag(const FlowTag& flow_tag) const
{
for (const auto& local_flow : _local_flows)
{
if (local_flow->flow_info.tag == flow_tag)
return true;
}
return false;
}
std::shared_ptr<service::Endpoint>
FlowLayer::local_deprecated_loki_endpoint() const
{
return _deprecated_endpoint;
}
std::shared_ptr<FlowIdentity>
FlowLayer::flow_to(const FlowAddr& addr)
{
for (auto& local_flow : _local_flows)
{
if (local_flow->flow_info.dst == addr)
return local_flow;
}
return _local_flows.emplace_back(
std::make_shared<FlowIdentity>(*this, addr, unique_flow_tag(), _privkeys));
}
} // namespace llarp::layers::flow

View file

@ -0,0 +1,12 @@
#include "flow_state.hpp"
#include <llarp/service/outbound_context.hpp>
#include <memory>
namespace llarp::layers::flow
{
struct FlowState_Pimpl
{
std::shared_ptr<service::OutboundContext> obctx;
};
} // namespace llarp::layers::flow

View file

@ -0,0 +1,37 @@
#include "flow_stats.hpp"
namespace llarp::layers::flow
{
bool
FlowStats::has_good_flow_to(const FlowAddr& remote) const
{
for (const auto& [flow_info, stats] : all_flows)
{
if (flow_info.dst == remote and stats.state == layers::flow::FlowInfoState::good)
return true;
}
return false;
}
std::unordered_set<FlowInfo>
FlowStats::good_flows() const
{
// todo: implement me
return {};
}
std::vector<FlowAddr>
FlowStats::local_addrs() const
{
// todo: implement me
return {};
}
std::unordered_map<FlowAddr, std::string>
FlowStats::auth_map() const
{
// todo: implement me
return {};
}
} // namespace llarp::layers::flow

View file

@ -0,0 +1,20 @@
#include "flow_tag.hpp"
namespace llarp::layers::flow
{
FlowTag
FlowTag::random()
{
FlowTag tag;
tag.Randomize();
return tag;
}
std::string
FlowTag::ToString() const
{
return fmt::format("[flowtag '{}']", ToHex());
}
} // namespace llarp::layers::flow

View file

@ -0,0 +1,23 @@
#include "name_cache.hpp"
namespace llarp::layers::flow
{
NameCache::NameCache(std::chrono::seconds ttl) : _names{ttl}
{}
std::optional<FlowAddr>
NameCache::get(std::string name)
{
auto now = uptime();
_names.Decay(now);
return _names.Get(std::move(name));
}
void
NameCache::put(std::string name, FlowAddr addr)
{
auto now = uptime();
_names.Decay(now);
_names.Put(name, addr, now);
}
} // namespace llarp::layers::flow

View file

@ -0,0 +1,31 @@
#include "name_resolver.hpp"
#include "flow_layer.hpp"
#include <llarp/service/endpoint.hpp>
#include <functional>
#include <memory>
#include <optional>
#include <utility>
namespace llarp::layers::flow
{
NameResolver::NameResolver(NameCache& name_cache, FlowLayer& parent)
: _name_cache{name_cache}, _parent{parent}
{}
void
NameResolver::resolve_flow_addr_async(
std::string name, std::function<void(std::optional<FlowAddr>)> result_handler)
{
_parent.local_deprecated_loki_endpoint()->LookupNameAsync(
name, [result_handler = std::move(result_handler)](auto maybe_addr) {
if (not maybe_addr)
{
result_handler(std::nullopt);
return;
}
result_handler(FlowAddr{*maybe_addr});
});
}
} // namespace llarp::layers::flow

46
llarp/layers/layers.cpp Normal file
View file

@ -0,0 +1,46 @@
#include "layers.hpp"
#include <memory>
#include <llarp/config/config.hpp>
namespace llarp::layers
{
std::unique_ptr<Layers>
make_layers(AbstractRouter& router, const Config& conf)
{
auto layers = std::make_unique<Layers>();
// TODO: placeholders
layers->onion = std::make_unique<onion::OnionLayer>(router);
layers->route = std::make_unique<route::RouteLayer>(router);
layers->flow = std::make_unique<flow::FlowLayer>(router, conf.network);
layers->platform =
std::make_unique<platform::PlatformLayer>(router, *layers->flow, conf.network);
// TODO: add each layer as they are wired up
return layers;
}
std::unique_ptr<const Layers>
Layers::freeze()
{
return std::unique_ptr<const Layers>(
new const Layers{std::move(platform), std::move(flow), std::move(route), std::move(onion)});
}
void
Layers::start_all() const
{
platform->start();
flow->start();
onion->start();
}
void
Layers::stop_all() const
{
platform->stop();
flow->stop();
onion->stop();
}
} // namespace llarp::layers

View file

@ -0,0 +1,25 @@
#include "onion_layer.hpp"
#include <llarp/router/abstractrouter.hpp>
#include <llarp/ev/ev.hpp>
namespace llarp::layers::onion
{
OnionStats
OnionLayer::current_stats() const
{
// todo: implement
return OnionStats{};
}
void
OnionLayer::start()
{}
void
OnionLayer::stop()
{}
OnionLayer::OnionLayer(AbstractRouter& router) : _router{router}
{}
} // namespace llarp::layers::onion

View file

@ -0,0 +1,19 @@
#include "onion_stats.hpp"
#include <unordered_set>
namespace llarp::layers::onion
{
bool
OnionStats::ready() const
{
// todo: implement me
return false;
}
std::unordered_set<OnionPathInfo>
OnionStats::built_paths() const
{
// todo: implement me
return {};
}
} // namespace llarp::layers::onion

View file

@ -0,0 +1,260 @@
#include "addr_mapper.hpp"
#include <fmt/core.h>
#include <algorithm>
#include <chrono>
#include <nlohmann/json_fwd.hpp>
#include <optional>
#include <stdexcept>
#include <vector>
#include <llarp/layers/flow/flow_layer.hpp>
#include "llarp/layers/flow/flow_addr.hpp"
#include "llarp/layers/flow/flow_info.hpp"
#include "llarp/layers/platform/platform_addr.hpp"
#include "llarp/layers/platform/platform_layer.hpp"
#include "llarp/net/ip_range.hpp"
#include "llarp/net/net_int.hpp"
namespace llarp::layers::platform
{
AddressMapping&
AddressMappingEntry::access()
{
_last_used_at = decltype(_last_used_at)::clock::now();
return _entry;
}
const AddressMapping&
AddressMappingEntry::view() const
{
return _entry;
}
bool
AddressMappingEntry::has_addr(const PlatformAddr& src, const PlatformAddr& dst) const
{
return _entry.src == src and _entry.dst == dst;
}
std::optional<AddressMapping>
AddrMapper::get_addr_for_flow(const flow::FlowInfo& flow_info)
{
auto itr =
find_if([&flow_info](const auto& mapping) { return mapping.flow_info == flow_info; });
if (itr == std::end(_addrs))
return std::nullopt;
return itr->access();
}
bool
AddressMapping::owns_range(const IPRange& range) const
{
return std::find(owned_ranges.begin(), owned_ranges.end(), range) != std::end(owned_ranges);
}
std::string
AddressMapping::ToString() const
{
std::optional<std::string> flow_info_str;
if (flow_info)
flow_info_str = flow_info->ToString();
return fmt::format(
"[AddressMapping flow={} src={} dst={} owned_ranges=({})]",
flow_info_str.value_or("none"),
src,
dst,
fmt::join(owned_ranges, ", "));
}
AddrMapper::AddrMapper(const IPRange& range) : _our_range{range}
{}
AddressMapping&
AddrMapper::allocate_mapping(std::optional<PlatformAddr> src)
{
auto dst = next_addr();
auto& mapping = _addrs.emplace_back().access();
mapping.dst = dst;
mapping.src = src.value_or(PlatformAddr{_our_range.addr});
return mapping;
}
PlatformAddr
AddrMapper::next_addr()
{
const PlatformAddr highest{_our_range.HighestAddr()};
const PlatformAddr lowest{huint128_t{1} + _our_range.addr};
// first try using highest mapped value.
PlatformAddr next{lowest};
for (const auto& addr : _addrs)
next = std::max(next, addr.view().dst);
// we already allocated the highest one before.
if (next == highest)
{
// try finding the lowest one.
for (const auto& addr : _addrs)
next = std::min(next, addr.view().dst);
// we are full.
if (next == lowest)
throw std::runtime_error{"addr mapper full"};
// use 1 below the lowest one we have allocated now.
next = PlatformAddr{net::ToHost(next.ip) - huint128_t{1}};
}
return next;
}
bool
AddrMapper::is_full() const
{
const PlatformAddr highest{_our_range.HighestAddr()};
const PlatformAddr lowest{huint128_t{1} + _our_range.addr};
PlatformAddr lo{highest}, hi{lowest};
for (const auto& addr : _addrs)
{
lo = std::min(addr.view().dst, lo);
hi = std::max(addr.view().dst, hi);
}
return highest == hi and lowest == lo;
}
std::optional<AddressMapping>
AddrMapper::mapping_for(const PlatformAddr& src, const PlatformAddr& dst)
{
for (auto& ent : _addrs)
{
if (ent.has_addr(src, dst))
return ent.access();
}
return std::nullopt;
}
std::vector<AddressMapping>
AddrMapper::all_exits() const
{
std::vector<AddressMapping> exits;
view_all_entries([&exits](const auto& ent) {
if (not ent.owned_ranges.empty())
exits.emplace_back(ent);
return true;
});
return exits;
}
net::IPRangeMap<flow::FlowAddr>
AddrMapper::exit_map() const
{
net::IPRangeMap<flow::FlowAddr> map{};
view_all_entries([&map](const auto& ent) {
for (const auto& range : ent.owned_ranges)
{
if (ent.flow_info)
map.Insert(range, ent.flow_info->dst);
}
return true;
});
return map;
}
void
AddrMapper::remove_lru()
{
std::chrono::steady_clock::time_point lru_time{std::chrono::steady_clock::time_point::max()};
auto lru_itr = _addrs.end();
for (auto itr = _addrs.begin(); itr != _addrs.end(); ++itr)
{
if (lru_time >= itr->last_used_at())
continue;
lru_itr = itr;
lru_time = lru_itr->last_used_at();
}
if (lru_itr != _addrs.end())
_addrs.erase(lru_itr);
}
void
AddrMapper::put(AddressMapping&& ent)
{
auto itr = find_if([&ent](const auto& other) {
if (ent.flow_info and other.flow_info)
return *ent.flow_info == *other.flow_info;
return ent.dst == other.dst and ent.src == other.src;
});
// construct it if we dont have it.
if (itr == std::end(_addrs))
{
if (is_full())
remove_lru();
itr = _addrs.emplace(std::end(_addrs));
}
// access the entry for assignment to mark as just used now.
itr->access() = ent;
}
void
AddrMapper::prune(flow::FlowLayer& flow_layer)
{
// todo: put constexpr elsewhere.
constexpr auto max_idle_threshold = 15min;
remove_if_entry([&flow_layer, max_idle_threshold](const auto& ent) {
// remove stale entries or ones that we dont have a flow on.
const auto& maybe_flow_info = ent.view().flow_info;
if ((not maybe_flow_info) or not flow_layer.has_flow(*maybe_flow_info))
return ent.idle_for() > max_idle_threshold;
// remove stale entries we have a flow for and remove the flow for them too.
if (ent.idle_for() > max_idle_threshold)
flow_layer.remove_flow(*maybe_flow_info);
return not flow_layer.has_flow(*maybe_flow_info);
});
}
std::vector<AddressMapping>
AddrMapper::mappings_to(const flow::FlowAddr& remote) const
{
std::vector<AddressMapping> mappings;
view_all_entries([&mappings, remote](const auto& entry) {
if (not entry.flow_info)
return true;
if (entry.flow_info->dst == remote)
mappings.emplace_back(entry);
return true;
});
return mappings;
}
} // namespace llarp::layers::platform
namespace llarp
{
nlohmann::json
to_json(const layers::platform::AddressMapping& mapping)
{
auto ranges = nlohmann::json::array();
for (const auto& range : mapping.owned_ranges)
ranges.push_back(range.ToString());
auto object = nlohmann::json::object(
{{"ranges", ranges},
{"src_ip",
var::visit([](auto&& addr) { return addr.ToString(); }, mapping.src.as_ipaddr())},
{"dst_ip",
var::visit([](auto&& addr) { return addr.ToString(); }, mapping.dst.as_ipaddr())}});
if (mapping.flow_info)
{
object["remote_addr"] = mapping.flow_info->dst.ToString();
object["local_addr"] = mapping.flow_info->src.ToString();
}
return object;
}
} // namespace llarp

View file

@ -1,15 +1,11 @@
#pragma once
#include "llarp/layers/flow/flow_addr.hpp"
#include "platform_addr.hpp"
#include <chrono>
#include <llarp/layers/flow/flow_info.hpp>
#include <nlohmann/json_fwd.hpp>
#include <chrono>
#include <string>
#include <optional>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>

View file

@ -0,0 +1,182 @@
#include "dns_bridge.hpp"
#include "platform_layer.hpp"
#include "addr_mapper.hpp"
#include <cstdint>
#include <llarp/dns/question.hpp>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <llarp/layers/flow/flow_layer.hpp>
#include <llarp/dns/name.hpp>
#include <llarp/util/logging.hpp>
namespace llarp::layers::platform
{
static auto logcat = log::Cat("dns");
DNSZone::DNSZone() : _flow_addr{}, _addr_mapper{nullptr}
{}
DNSZone::DNSZone(AddrMapper& addr_mapper, flow::FlowAddr addr)
: _flow_addr{std::move(addr)}, _addr_mapper{&addr_mapper}
{}
void
DNSZone::add_srv_record(dns::SRVTuple tup)
{
_srv.emplace_back(std::move(tup));
}
std::vector<dns::RData>
DNSZone::cname_records() const
{
std::vector<dns::RData> datas;
for (const auto& name : _ons_names)
datas.emplace_back(dns::encode_dns_name(name));
return datas;
}
std::vector<dns::RData>
DNSZone::srv_records() const
{
std::vector<dns::RData> datas;
for (const auto& srv : _srv)
datas.emplace_back(srv.encode_dns(zone_name()));
return datas;
}
std::string
DNSZone::zone_name() const
{
return _flow_addr.ToString();
}
std::vector<dns::ResourceRecord>
DNSZone::synth_rr_for(dns::RRType rr_type) const
{
std::vector<dns::RData> recs;
for (const auto& mapping : addr_mapper().mappings_to(_flow_addr))
{
if (auto maybe_v4 = mapping.dst.as_ipv4addr(); maybe_v4 and rr_type == dns::RRType::A)
recs.emplace_back(*maybe_v4);
else if (rr_type == dns::RRType::AAAA)
recs.emplace_back(mapping.dst.ip);
else if (rr_type == dns::RRType::SRV and not _srv.empty())
{
for (auto srv : srv_records())
recs.insert(recs.end(), std::move(srv));
}
else if (rr_type == dns::RRType::CNAME and not _ons_names.empty())
{
for (auto cname : cname_records())
recs.insert(recs.end(), std::move(cname));
}
else if (rr_type == dns::RRType::MX)
recs.emplace_back(uint16_t{10}, dns::split_dns_name(zone_name()));
}
std::vector<dns::ResourceRecord> ret;
for (auto& rdata : recs)
ret.emplace_back(zone_name(), rr_type, std::move(rdata), ttl(rr_type));
return ret;
}
uint32_t
DNSZone::ttl(dns::RRType) const
{
// todo: this is a placeholder value.
return 1;
}
DNSQueryHandler::DNSQueryHandler(PlatformLayer& plat) : _plat{plat}
{}
void
DNSQueryHandler::async_obtain_dns_zone(
const dns::Question& question, std::function<void(std::optional<DNSZone>)> result_handler)
{
if (question.IsLocalhost())
{
result_handler(_plat.local_dns_zone());
return;
}
// todo: get auth code from config.
std::string auth = "";
_plat.map_remote(
question.Domain(),
auth,
{},
std::nullopt,
[result_handler = std::move(result_handler), this](auto maybe_flow_info, auto) {
if (not maybe_flow_info)
{
result_handler(std::nullopt);
return;
}
const auto& addr = maybe_flow_info->dst;
auto [itr, inserted] = _zones.try_emplace(addr, _plat.addr_mapper, addr);
if (inserted)
log::debug(logcat, "allocated new address for {}", addr);
result_handler(itr->second);
});
}
int
DNSQueryHandler::Rank() const
{
return 1;
}
std::string_view
DNSQueryHandler::ResolverName() const
{
return "lokinet";
}
bool
DNSQueryHandler::MaybeHookDNS(
std::shared_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from)
{
if (query.questions.empty())
return false;
// make sure this query is meant for us.
const auto& question = query.questions.at(0);
if (not dns::is_lokinet_tld(question.tld()))
return false;
// make sure we understand the rr type requested.
auto maybe_qtype = dns::get_rr_type(question.qtype);
if (not maybe_qtype)
{
dns::Message reply{query};
// no such rr type know by us. we dont serv fail so queries dont leak.
source->SendTo(from, to, reply.nx().ToBuffer());
return true;
}
async_obtain_dns_zone(
question,
[source, qtype = *maybe_qtype, query = dns::Message{query}, from, to](auto maybe_zone) {
dns::Message msg{query};
if (not maybe_zone)
{
source->SendTo(from, to, msg.nx().ToBuffer());
return;
}
for (auto found_rr : maybe_zone->synth_rr_for(qtype))
msg.answers.emplace_back(std::move(found_rr));
source->SendTo(from, to, msg.ToBuffer());
});
return true;
}
} // namespace llarp::layers::platform

View file

@ -8,11 +8,8 @@
#include <unordered_map>
#include <vector>
#include <llarp/layers/flow/flow_addr.hpp>
#include "llarp/dns/question.hpp"
#include "llarp/dns/rr.hpp"
#include "llarp/layers/flow/flow_layer.hpp"
#include "llarp/util/decaying_hashtable.hpp"
#include "llarp/util/str.hpp"
#include <llarp/dns/question.hpp>
#include <llarp/dns/rr.hpp>
namespace llarp::layers::flow
{
@ -34,7 +31,7 @@ namespace llarp::layers::platform
/// any known cached ons names for this zone.
std::vector<std::string> _ons_names;
std::vector<dns::SRVData> _srv;
AddrMapper& _addr_mapper;
AddrMapper* const _addr_mapper;
/// synth srv records rdata.
std::vector<dns::RData>
@ -51,8 +48,15 @@ namespace llarp::layers::platform
std::string
zone_name() const;
constexpr AddrMapper&
addr_mapper() const
{
return *_addr_mapper;
}
public:
DNSZone(AddrMapper&, flow::FlowAddr addr);
DNSZone();
DNSZone(AddrMapper& mapper, flow::FlowAddr addr);
/// add a new srv recrod to this dns zone.
void
@ -68,7 +72,8 @@ namespace llarp::layers::platform
{
PlatformLayer& _plat;
std::unordered_map<flow::FlowAddr, std::unique_ptr<DNSZone>> _zones;
/// all remote zones.
std::unordered_map<flow::FlowAddr, DNSZone> _zones;
/// async resolve dns zone given a dns question.
void

View file

@ -0,0 +1,15 @@
#include "ethertype.hpp"
#include "llarp/util/time.hpp"
namespace llarp::layers::platform
{
std::string
ToString(EtherType_t kind)
{
const std::unordered_map<EtherType_t, std::string> loopkup{
{EtherType_t::ip_unicast, "ip_unicast"},
{EtherType_t::plainquic, "plainquic"},
{EtherType_t::proto_auth, "auth"}};
return loopkup.at(kind);
}
} // namespace llarp::layers::platform

View file

@ -1,6 +1,7 @@
#pragma once
#include "llarp/util/formattable.hpp"
#include <llarp/util/formattable.hpp>
namespace llarp::layers::platform
{
/// the kind of traffic we are tunneling

View file

@ -0,0 +1,25 @@
#include "name_cache.hpp"
#include "llarp/layers/flow/flow_addr.hpp"
#include "llarp/util/time.hpp"
namespace llarp::layers::platform
{
NameCache::NameCache(std::chrono::seconds ttl) : _names{ttl}
{}
std::optional<flow::FlowAddr>
NameCache::get(std::string name)
{
auto now = uptime();
_names.Decay(now);
return _names.Get(std::move(name));
}
void
NameCache::put(std::string name, flow::FlowAddr addr)
{
auto now = uptime();
_names.Decay(now);
_names.Put(name, addr, now);
}
} // namespace llarp::layers::platform

View file

@ -0,0 +1,50 @@
#include "name_resolver.hpp"
#include <functional>
#include <memory>
#include "name_cache.hpp"
#include "deprecated_pimpl.hpp"
#include <llarp/layers/flow/deprecated_flow_layer_pimpl.hpp>
namespace llarp::layers::platform
{
NameResolver::NameResolver(NameCache& name_cache, flow::FlowLayer& flow_layer)
: _name_cache{name_cache}, _flow_layer{flow_layer}
{}
namespace
{
/// converts an optional address variant to an optional flow addr in a callback.
struct name_resolver_lookup_handler
{
std::function<void(std::optional<flow::FlowAddr>)> result_handler;
void
operator()(std::optional<EndpointBase::AddressVariant_t> result) const
{
if (result)
result_handler(flow::FlowAddr{*result});
else
result_handler(std::nullopt);
}
};
} // namespace
void
NameResolver::resolve_flow_addr_async(
std::string name, std::function<void(std::optional<flow::FlowAddr>)> result_handler)
{
// todo: use flow layer
if (const auto& endpoint_ptr = _flow_layer.pimpl->endpoint)
{
endpoint_ptr->LookupNameAsync(
std::move(name), name_resolver_lookup_handler{std::move(result_handler)});
}
else
result_handler(std::nullopt);
}
} // namespace llarp::layers::platform

View file

@ -0,0 +1,23 @@
#include "os_traffic.hpp"
#include <cstdint>
#include <vector>
namespace llarp::layers::platform
{
OSTraffic::OSTraffic(
EtherType_t ethertype, PlatformAddr src_, PlatformAddr dst_, std::vector<uint8_t> data_)
: kind{ethertype}, src{std::move(src_)}, dst{std::move(dst_)}, data{std::move(data_)}
{}
void
OSTraffic::recompute_checksums()
{
// todo: implement me
}
std::string
OSTraffic::ToString() const
{
return fmt::format("[OSTraffic kind={} src={} dst={} {} bytes]", kind, src, dst, data.size());
}
} // namespace llarp::layers::platform

View file

@ -0,0 +1,61 @@
#include "platform_addr.hpp"
#include <optional>
#include <type_traits>
#include "llarp/layers/platform/platform_layer.hpp"
#include "llarp/net/ip.hpp"
#include "llarp/net/ip_range.hpp"
#include "llarp/net/net_int.hpp"
namespace llarp::layers::platform
{
PlatformAddr::PlatformAddr(huint128_t addr) : PlatformAddr{ToNet(addr)}
{}
PlatformAddr::PlatformAddr(net::ipaddr_t addr) : ip{net::maybe_expand_ip(addr)}
{}
PlatformAddr::PlatformAddr(const std::string& str) : PlatformAddr{net::ipaddr_from_string(str)}
{}
net::ipaddr_t
PlatformAddr::as_ipaddr() const
{
return net::maybe_truncate_ip(ip);
}
std::optional<net::ipv4addr_t>
PlatformAddr::as_ipv4addr() const
{
auto ip = as_ipaddr();
if (auto* ptr = std::get_if<net::ipv4addr_t>(&ip))
return *ptr;
return std::nullopt;
}
bool
PlatformAddr::operator==(const PlatformAddr& other) const
{
return ip == other.ip;
}
bool
PlatformAddr::operator!=(const PlatformAddr& other) const
{
return not(*this == other);
}
bool
PlatformAddr::operator<(const PlatformAddr& other) const
{
return ToHost(ip) < ToHost(other.ip);
}
std::string
PlatformAddr::ToString() const
{
return fmt::format(
"[PlatformAddr '{}' flowlabel={:x}']",
var::visit([](auto&& addr) { return addr.ToString(); }, as_ipaddr()),
ToHost(flowlabel).h);
}
} // namespace llarp::layers::platform

View file

@ -0,0 +1,487 @@
#include "platform_layer.hpp"
#include "dns_bridge.hpp"
#include "llarp/layers/flow/flow_addr.hpp"
#include "llarp/layers/platform/addr_mapper.hpp"
#include "llarp/layers/platform/platform_addr.hpp"
#include "llarp/net/ip_packet.hpp"
#include "llarp/net/ip_range.hpp"
#include "oxen/log.hpp"
#include <llarp/config/config.hpp>
#include <llarp/ev/ev.hpp>
#include <llarp/layers/flow/flow_layer.hpp>
#include <llarp/dns/server.hpp>
#include <llarp/constants/platform.hpp>
#include <llarp/link/i_link_manager.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/vpn/platform.hpp>
#include <llarp/router/route_poker.hpp>
#include <memory>
#include <nlohmann/detail/string_concat.hpp>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <unordered_map>
#include <vector>
namespace llarp::layers::platform
{
static auto logcat = log::Cat("platform-layer");
void
OSTraffic_IO_Base::got_ip_packet(net::IPPacket pkt)
{
if (not _running.load())
return;
auto& os_pkt = _recv_queue.emplace_back();
os_pkt.kind = EtherType_t::ip_unicast;
os_pkt.src.ip = net::ipv6addr_t::from_host(pkt.srcv6());
os_pkt.dst.ip = net::ipv6addr_t::from_host(pkt.dstv6());
os_pkt.data = pkt.steal();
log::trace(logcat, "got_ip_packet: {}", os_pkt);
}
OSTraffic_IO_Base::OSTraffic_IO_Base(const EventLoop_ptr& loop) : _running{false}, _loop{loop}
{
_running.store(true);
}
std::vector<OSTraffic>
OSTraffic_IO_Base::read_platform_traffic()
{
std::vector<OSTraffic> ret = std::move(_recv_queue);
_recv_queue = decltype(_recv_queue){};
_recv_queue.reserve(ret.capacity());
log::trace(logcat, "read_platform_traffic: {} packets", ret.size());
return ret;
}
void
OSTraffic_IO_Base::write_platform_traffic(std::vector<OSTraffic>&& traff)
{
if (not _running.load())
return;
if (not _netif)
return;
log::trace(logcat, "write_platform_traffic: {} packets", traff.size());
for (auto& os_pkt : traff)
{
// todo: figure out plainquic.
if (os_pkt.kind != EtherType_t::ip_unicast)
continue;
net::IPPacket pkt{std::move(os_pkt.data)};
// drop any malformed packets.
if (pkt.empty())
continue;
_netif->WritePacket(std::move(pkt));
}
}
void
OSTraffic_IO_Base::attach(
std::shared_ptr<vpn::NetworkInterface> netif, std::shared_ptr<EventLoopWakeup> wakeup_recv)
{
log::debug(logcat, "attach OSTraffic_IO_Base to {}", netif->Info().ifname);
_netif = std::move(netif);
_our_addr = PlatformAddr{_netif->Info().addrs[0].range.addr};
// set up event loop reader.
// this idempotently wakes up those who want ip packets from us.
auto loop = _loop.lock();
if (not loop)
throw std::runtime_error{"event loop gone"};
loop->add_network_interface(_netif, [this, wakeup = std::move(wakeup_recv)](auto pkt) {
if (not _running)
return;
got_ip_packet(std::move(pkt));
wakeup->Trigger();
});
_netif->Start();
log::debug(logcat, "network interface '{}' started up", _netif->Info().ifname);
}
void
OSTraffic_IO_Base::detach()
{
if (not _running.exchange(false))
return;
log::trace(logcat, "detach from event loop");
if (_netif)
_netif->Stop();
_netif = nullptr;
}
void
PlatformLayer::on_io()
{
log::trace(logcat, "on_io()");
// read all packets from last io cycle.
auto pkts = _io->read_platform_traffic();
// if we read nothing we are done.
if (pkts.empty())
return;
os_traffic(std::move(pkts));
}
static std::vector<byte_t>
strip_ip_packet(std::vector<byte_t> data, bool strip_src, bool strip_dst)
{
net::IPPacket pkt{std::move(data)};
// malformed?
if (pkt.empty())
return std::vector<byte_t>{};
if (strip_dst and strip_src)
{
pkt.ZeroAddresses();
}
else if (strip_src)
{
pkt.ZeroSourceAddress();
}
return pkt.steal();
}
static EtherType_t
to_ether_type(flow::FlowDataKind kind)
{
const std::unordered_map<flow::FlowDataKind, EtherType_t> kind_to_ethertype = {
{flow::FlowDataKind::auth, EtherType_t::proto_auth},
{flow::FlowDataKind::stream_unicast, EtherType_t::plainquic},
{flow::FlowDataKind::direct_ip_unicast, EtherType_t::ip_unicast},
{flow::FlowDataKind::exit_ip_unicast, EtherType_t::ip_unicast},
};
return kind_to_ethertype.at(kind);
}
void
PlatformLayer::flow_traffic(std::vector<flow::FlowTraffic>&& traff)
{
std::vector<OSTraffic> os_pkts;
// for each flow layer traffic ...
for (auto& pkt : traff)
{
// figure out what it is tracked on.
auto maybe_addr = addr_mapper.get_addr_for_flow(pkt.flow_info);
if (not maybe_addr)
{
// this is traffic for something we are not tracking.
log::warning(
logcat,
"unexpected flow traffic on {}: {} bytes of {}",
pkt.flow_info,
pkt.datum.size(),
pkt.kind);
continue;
}
// translate flow traffic to platform traffic.
log::trace(logcat, "got {} bytes of {} on {}", pkt.datum.size(), pkt.kind, *maybe_addr);
os_pkts.emplace_back(OSTraffic{
to_ether_type(pkt.kind), maybe_addr->dst, maybe_addr->src, std::move(pkt.datum)});
}
// write platform traffic.
if (not os_pkts.empty())
_io->write_platform_traffic(std::move(os_pkts));
}
void
PlatformLayer::os_traffic(std::vector<OSTraffic>&& traff)
{
std::vector<OSTraffic> icmp_reject;
// for each ip packet...
for (auto& os_pkt : traff)
{
if (os_pkt.kind != EtherType_t::ip_unicast)
continue;
if (os_pkt.data.empty())
{
// there is nothing here.. ?
log::warning(logcat, "got empty packet? src={} dst={}", os_pkt.src, os_pkt.dst);
continue;
}
// find an existing flow.
auto maybe_mapping = addr_mapper.mapping_for(os_pkt.src, os_pkt.dst);
if ((not maybe_mapping) or (not maybe_mapping->flow_info))
{
// we have no flows to that place so we will send an icmp reject back from our address to
// whoever sent it.
log::trace(logcat, "no mapping for src={} dst={}", os_pkt.src, os_pkt.dst);
// dest host unreachable
auto icmp = net::IPPacket::make_icmp(
std::move(os_pkt.data), _io->our_platform_addr().as_ipaddr(), 3, 1);
icmp_reject.emplace_back(
os_pkt.kind, _io->our_platform_addr(), os_pkt.src, std::move(icmp));
continue;
}
const auto& flow_info = *maybe_mapping->flow_info;
if (os_pkt.data.size() > flow_info.mtu)
{
log::trace(logcat, "packet too big: {} > {}", os_pkt.data.size(), flow_info.mtu);
// traffic too big. write icmp reject for fragmentation.
OSTraffic reply;
reply.kind = os_pkt.kind;
reply.src = _io->our_platform_addr();
reply.dst = os_pkt.src;
reply.data = net::IPPacket::make_icmp(
std::move(os_pkt.data), reply.src.as_ipaddr(), 3, 4); // ip fragmentation.
icmp_reject.push_back(std::move(reply));
}
flow::FlowDataKind data_kind = flow::FlowDataKind::direct_ip_unicast;
bool is_exit = not maybe_mapping->owned_ranges.empty();
if (is_exit)
data_kind = flow::FlowDataKind::exit_ip_unicast;
// get a flow layer source to send with, ...
auto local_source = _flow_layer.flow_to(flow_info.dst);
// ... ensure it is sanitized and well formed ...
auto pkt = strip_ip_packet(std::move(os_pkt.data), true, not is_exit);
if (pkt.empty())
{
log::debug(logcat, "did not strip ip packet on {}", local_source->flow_info);
continue;
}
log::trace(logcat, "send {} bytes to {}", pkt.size(), local_source->flow_info);
// ... and send the data along.
local_source->send_to_remote(std::move(pkt), data_kind);
}
// write any reject packets we need to.
if (not icmp_reject.empty())
_io->write_platform_traffic(std::move(icmp_reject));
}
/// this calls the io handlers after an io cycle consumes events.
struct platform_io_wakeup_handler
{
PlatformLayer& driver;
void
operator()()
{
driver.on_io();
}
};
PlatformLayer::PlatformLayer(
AbstractRouter& router, flow::FlowLayer& flow_layer, const NetworkConfig& netconf)
: _router{router}
, _flow_layer{flow_layer}
, _os_recv{router.loop()->make_waker(platform_io_wakeup_handler{*this})}
, _netconf{netconf}
, _dns_query_handler{std::make_shared<DNSQueryHandler>(*this)}
, addr_mapper{_netconf.ifaddr(router.Net())}
{}
void
PlatformLayer::start()
{
_io = make_io();
// attach dns resolver.
if (const auto& dns = _router.get_dns())
dns->AddResolver(std::weak_ptr<dns::Resolver_Base>{_dns_query_handler});
auto* vpn = _router.GetVPNPlatform();
if (not vpn)
return;
// create vpn interface and attach to platform layer.
const auto& _net = _router.Net();
auto netif = vpn->CreateInterface(vpn::InterfaceInfo{_netconf, _net}, &_router);
if (not netif)
throw std::runtime_error{"did not make vpn interface"};
_netif = netif;
_io->attach(netif, _os_recv);
// attach vpn interface to dns and start it.
if (const auto& dns = _router.get_dns())
dns->Start(netif->Info().index);
}
void
PlatformLayer::stop()
{
log::info(logcat, "stop");
// stop all io operations on our end and detach from event loop.
_io->detach();
}
/// handles when we have established a flow with a remote.
struct got_flow_handler
{
AddrMapper& mapper;
AddressMapping entry;
std::string name;
std::function<void(std::optional<flow::FlowInfo>, std::string)> result_handler;
void
fail(std::string error_reason) const
{
result_handler(std::nullopt, "could not obtain flow to '{}': {}"_format(name, error_reason));
}
void
success(const flow::FlowInfo& flow_info)
{
entry.flow_info = flow_info;
mapper.put(std::move(entry));
result_handler(flow_info, "");
}
void
operator()(std::optional<flow::FlowInfo> maybe_flow_info, std::string error)
{
if (maybe_flow_info)
success(*maybe_flow_info);
else
fail(std::move(error));
}
};
/// called to handle when name resolution completes with either failure or success.
struct resolved_name_handler
{
flow::FlowLayer& flow_layer;
std::string name;
flow::FlowEstablish flow_establisher;
bool hydrate_cache{false};
void
operator()(std::optional<flow::FlowAddr> maybe_addr)
{
if (not maybe_addr)
{
flow_establisher.fail("failed to resolve name");
return;
}
// get a flow tag for this remote.
flow_layer.flow_to(*maybe_addr)->async_ensure_flow(std::move(flow_establisher));
}
};
void
PlatformLayer::map_remote(
std::string name,
std::string auth,
std::vector<IPRange> dst_ranges,
std::optional<PlatformAddr> src,
std::function<void(std::optional<flow::FlowInfo>, std::string)> result_handler)
{
if (addr_mapper.is_full())
{
result_handler(std::nullopt, "address map full");
return;
}
// reserve a mapping.
AddressMapping mapping = addr_mapper.allocate_mapping(src.value_or(_io->our_platform_addr()));
mapping.owned_ranges = std::move(dst_ranges);
// call this when we got a flow to the guy
// it will wire up the mapping.
got_flow_handler got_flow{addr_mapper, std::move(mapping), name, std::move(result_handler)};
// do any extra handshaking with a flow handshaker, then call the got flow handler
// todo: this really belongs in flow layer.
flow::FlowEstablish flow_establisher{std::move(got_flow), nullptr};
flow_establisher.authcode = std::move(auth);
resolved_name_handler resolved_name{_flow_layer, name, std::move(flow_establisher)};
// fire off the lookup.
_flow_layer.name_resolver.resolve_flow_addr_async(std::move(name), std::move(resolved_name));
}
std::shared_ptr<vpn::NetworkInterface>
PlatformLayer::vpn_interface() const
{
return _netif.lock();
}
vpn::IRouteManager*
PlatformLayer::route_manager() const
{
if (auto* vpn_plat = _router.GetVPNPlatform())
return &vpn_plat->RouteManager();
return nullptr;
}
std::unique_ptr<OSTraffic_IO_Base>
PlatformLayer::make_io() const
{
return std::make_unique<OSTraffic_IO_Base>(_router.loop());
}
/// handles when we map an exit to an ip range after we have gotten a flow with it.
struct map_exit_handler
{
AbstractRouter& router;
std::function<void(bool, std::string)> result_handler;
void
operator()(std::optional<flow::FlowInfo> found, std::string msg) const
{
if (found)
{
// found the result, put firewall up.
if (const auto& poker = router.routePoker())
poker->Up();
result_handler(true, std::move(msg));
return;
}
result_handler(false, std::move(msg));
}
};
void
PlatformLayer::map_exit(
std::string name,
std::string auth,
std::vector<IPRange> ranges,
std::function<void(bool, std::string)> result_handler)
{
map_remote(
std::move(name),
std::move(auth),
std::move(ranges),
_io->our_platform_addr(),
map_exit_handler{_router, std::move(result_handler)});
}
void
PlatformLayer::unmap_all_exits_on_range(IPRange range)
{
addr_mapper.remove_if_mapping(
[range](const auto& mapping) -> bool { return mapping.owns_range(range); });
}
void
PlatformLayer::unmap_exit(flow::FlowAddr addr, std::optional<IPRange> range)
{
addr_mapper.remove_if_mapping([addr, range](const auto& mapping) -> bool {
return mapping.flow_info and mapping.flow_info->dst == addr
and (range ? mapping.owns_range(*range) : true);
});
}
PlatformStats
PlatformLayer::current_stats() const
{
return PlatformStats{};
}
DNSZone&
PlatformLayer::local_dns_zone()
{
if (not _local_dns_zone)
_local_dns_zone = std::make_shared<DNSZone>(addr_mapper, _flow_layer.local_addr());
return *_local_dns_zone;
}
} // namespace llarp::layers::platform

View file

@ -0,0 +1,4 @@
#include "platform_stats.hpp"
namespace llarp::layers::platform
{}

View file

@ -0,0 +1,63 @@
#include "route_layer.hpp"
namespace llarp::layers::route
{
RouteLayer::RouteLayer(AbstractRouter&)
{}
Destination*
RouteLayer::create_destination(const path::TransitHopInfo&, PubKey, RouterID)
{
// todo: implement me.
return nullptr;
}
bool
RouteLayer::remove_destination_on(const path::TransitHopInfo&)
{
// todo: implement me.
return false;
}
size_t
RouteLayer::remove_destinations_from(const PubKey&)
{
// todo: implement me.
return 0;
}
size_t
RouteLayer::remove_destinations_to(const PubKey&, const RouterID&)
{
// todo: implement me.
return 0;
}
Destination*
RouteLayer::destination_to(const PathID_t&)
{
// todo: implement me.
return nullptr;
}
Destination*
RouteLayer::destination_from(const PathID_t&)
{
// todo: implement me.
return nullptr;
}
std::vector<Destination*>
RouteLayer::all_destinations_from(const PubKey&)
{
// todo: implement me.
return {};
}
Destination*
RouteLayer::destination_on(const path::TransitHopInfo&) const
{
// todo: implement me.
return nullptr;
}
} // namespace llarp::layers::route

View file

@ -142,13 +142,8 @@ namespace llarp
pkt.resize(buf.sz);
std::copy_n(buf.base, buf.sz, pkt.data());
RecvFrom(from, std::move(pkt));
});
if (m_udp->listen(m_ourAddr))
return;
throw std::runtime_error{
fmt::format("failed to listen {} udp socket on {}", Name(), m_ourAddr)};
},
m_ourAddr);
}
void

View file

@ -12,6 +12,10 @@
#include <llarp/util/logging/buffer.hpp>
#include <llarp/util/logging/callback_sink.hpp>
#include <llarp/vpn/platform.hpp>
#include <llarp/net/net_int.hpp>
#include <oxenc/base32z.h>
#include <mutex>
@ -80,9 +84,9 @@ namespace
struct UDPHandler
{
using AddressVariant_t = llarp::vpn::AddressVariant_t;
using AddressVariant_t = llarp::EndpointBase::AddressVariant_t;
int m_SocketID;
llarp::nuint16_t m_LocalPort;
llarp::net::port_t m_LocalPort;
lokinet_udp_flow_filter m_Filter;
lokinet_udp_flow_recv_func m_Recv;
lokinet_udp_flow_timeout_func m_Timeout;
@ -303,11 +307,12 @@ struct lokinet_context
}
[[nodiscard]] auto
endpoint(std::string name = "default") const
endpoint() const
{
return impl->router->hiddenServiceContext().GetEndpointByName(name);
return impl->router->get_layers()->flow->local_deprecated_loki_endpoint();
}
/// maps socket id to a bool which says if it is for a socket where we are the recipiant or not.
std::unordered_map<int, bool> streams;
std::unordered_map<int, std::shared_ptr<UDPHandler>> udp_sockets;

View file

@ -4,6 +4,9 @@
#include <llarp/util/buffer.hpp>
#include <llarp/util/mem.hpp>
#include <llarp/util/str.hpp>
#include <optional>
#include <stdexcept>
#include <vector>
#ifndef _WIN32
#include <netinet/in.h>
#endif
@ -561,6 +564,9 @@ namespace llarp::net
std::optional<IPPacket>
IPPacket::MakeICMPUnreachable() const
{
if (empty())
return std::nullopt;
if (IsV4())
{
constexpr auto icmp_Header_size = 8;
@ -603,7 +609,7 @@ namespace llarp::net
*checksum = ipchksum(icmp_begin, icmp_size);
return pkt;
}
return std::nullopt;
return IPPacket{};
}
std::optional<std::pair<const char*, size_t>>
@ -700,4 +706,14 @@ namespace llarp::net
// TODO: ipv6
return net::IPPacket{size_t{}};
}
std::vector<byte_t>
IPPacket::make_icmp(std::vector<byte_t>&& data, net::ipaddr_t our_ip, int type, int code)
{
(void)our_ip, (void)type, (void)code;
IPPacket pkt{std::vector<byte_t>{data}};
if (auto maybe = pkt.MakeICMPUnreachable())
return maybe->steal();
return std::vector<byte_t>{};
}
} // namespace llarp::net

View file

@ -8,6 +8,7 @@
#include <memory>
#include <llarp/service/protocol_type.hpp>
#include <utility>
#include <vector>
namespace llarp::net
{
@ -189,6 +190,24 @@ namespace llarp::net
return buf;
}
/// make a copy of the underlying vector
inline std::vector<byte_t>
copy() const
{
return _buf;
}
/// return address family this ip packet belongs to.
inline auto
family() const
{
if (IsV4())
return AF_INET;
if (IsV6())
return AF_INET6;
return AF_UNSPEC;
}
inline byte_t*
data()
{
@ -372,6 +391,14 @@ namespace llarp::net
MakeICMPUnreachable() const;
std::function<void(net::IPPacket)> reply;
/// recompute all ip packet checksums so that they match match the data.
void
recompute_checksums();
/// make an icmp packet from an existing ip packet, replacing the source address with our ip.
static std::vector<byte_t>
make_icmp(std::vector<byte_t>&& pkt, net::ipaddr_t our_ip, int type, int code);
};
/// generate ip checksum

View file

@ -1,6 +1,7 @@
#pragma once
#include "ip.hpp"
#include "llarp/net/net_int.hpp"
#include "net_bits.hpp"
#include <llarp/util/bits.hpp>
#include <llarp/util/buffer.hpp>
@ -10,6 +11,7 @@
#include <optional>
#include <stdexcept>
#include <string>
#include <variant>
namespace llarp
{
@ -44,11 +46,9 @@ namespace llarp
}
static inline IPRange
FromIPv4(net::ipv4addr_t addr, net::ipv4addr_t netmask)
FromIPv4(net::ipv4addr_t addr, byte_t mask)
{
return IPRange{
net::ExpandV4(llarp::net::ToHost(addr)),
netmask_ipv6_bits(bits::count_bits(netmask) + 96)};
return IPRange{net::ExpandV4(llarp::net::ToHost(addr)), netmask_ipv6_bits(96 + mask)};
}
/// return true if this iprange is in the IPv4 mapping range for containing ipv4 addresses
@ -80,12 +80,19 @@ namespace llarp
/// return true if our range and other intersect
constexpr bool
operator*(const IPRange& other) const
intersects_with(const IPRange& other) const
{
return Contains(other) or other.Contains(*this);
}
/// return true if the other range is inside our range
/// calls intersects_with()
constexpr bool
operator*(const IPRange& other) const
{
return intersects_with(other);
}
/// return true if the other range is inside ( subset of ) our range
constexpr bool
Contains(const IPRange& other) const
{
@ -111,7 +118,18 @@ namespace llarp
inline bool
Contains(const net::ipaddr_t& ip) const
{
return var::visit([this](auto&& ip) { return Contains(llarp::net::ToHost(ip)); }, ip);
Addr_t _ip{};
if (auto* ptr = std::get_if<net::ipv4addr_t>(&ip))
{
if (not IsV4())
return false;
_ip = net::ExpandV4(ToHost(*ptr));
}
if (auto* ptr = std::get_if<net::ipv6addr_t>(&ip))
{
_ip = ToHost(*ptr);
}
return (addr & netmask_bits) == (_ip & netmask_bits);
}
/// get the highest address on this range

View file

@ -1,34 +1,37 @@
#pragma once
#include <cstddef>
#include "net_int.hpp"
namespace llarp
{
/// get a netmask with the higest numset bits set
constexpr huint128_t
_netmask_ipv6_bits(uint32_t numset)
{
return (128 - numset) ? (huint128_t{1} << numset) | _netmask_ipv6_bits(numset + 1)
: huint128_t{0};
}
constexpr huint128_t
netmask_ipv6_bits(uint32_t numset)
{
return _netmask_ipv6_bits(128 - numset);
}
/// get a netmask with the higest numset bits set
constexpr uint32_t
_netmask_ipv4_bits(uint32_t numset)
{
return (32 - numset) ? (1 << numset) | _netmask_ipv4_bits(numset + 1) : 0;
}
/// get a netmask given some /N range
constexpr huint32_t
netmask_ipv4_bits(uint32_t num)
template <typename UInt_t>
constexpr huint_t<UInt_t>
netmask_bits(const int numset)
{
return huint32_t{_netmask_ipv4_bits(32 - num)};
constexpr int max_bits = sizeof(UInt_t) * 8;
if (numset > max_bits)
throw std::invalid_argument{
fmt::format("invalid netmask size requested: {} > {}", numset, max_bits)};
huint_t<UInt_t> bits{};
for (int idx = numset; idx > 0; --idx)
{
bits.h |= (UInt_t{1} << (max_bits - idx));
}
return bits;
}
constexpr huint128_t
netmask_ipv6_bits(int numset)
{
return netmask_bits<uint128_t>(numset);
}
constexpr huint32_t
netmask_ipv4_bits(int numset)
{
return netmask_bits<uint32_t>(numset);
}
constexpr huint32_t
@ -40,14 +43,13 @@ namespace llarp
// IPv4 mapped address live at ::ffff:0:0/96
constexpr std::array<uint8_t, 12> ipv4_map_prefix{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
namespace net
constexpr auto
ipaddr_netmask_bits(int cidr, int af)
{
inline auto
ipaddr_netmask_bits(uint32_t bits, int af)
{
if (af == AF_INET6)
return netmask_ipv6_bits(bits);
return ExpandV4(netmask_ipv4_bits(bits));
};
} // namespace net
if (af == AF_INET)
return net::ExpandV4(netmask_bits<uint32_t>(cidr));
else
return netmask_bits<uint128_t>(cidr);
}
} // namespace llarp

View file

@ -1,14 +1,21 @@
#include "net_int.hpp"
#include "ip.hpp"
#include "llarp/net/ip_range.hpp"
#include <cstdint>
#include <stdexcept>
#include <string>
#include <variant>
#include <oxenc/endian.h>
#include <oxen/log/format.hpp>
using namespace oxen::log::literals;
namespace llarp
{
namespace net
{
huint16_t
ToHost(port_t x)
{
@ -44,6 +51,34 @@ namespace llarp
{
return ipv6addr_t{hton128(x.h)};
}
ipv6addr_t
maybe_expand_ip(ipaddr_t ip)
{
if (auto* ptr = std::get_if<ipv6addr_t>(&ip))
return *ptr;
return ToNet(ExpandV4(ToHost(std::get<ipv4addr_t>(ip))));
}
ipaddr_t
maybe_truncate_ip(ipv6addr_t ip)
{
if (IPRange::V4MappedRange().Contains(ip))
return ToNet(TruncateV6(ToHost(ip)));
return ip;
}
ipaddr_t
ipaddr_from_string(const std::string& str)
{
ipv6addr_t v6{};
if (v6.FromString(str))
return v6;
ipv4addr_t v4{};
if (v4.FromString(str))
return v4;
throw std::invalid_argument{"'{}' is not an valid ip address"_format(str)};
}
} // namespace net
template <>

View file

@ -105,6 +105,12 @@ namespace llarp
return h == x.h;
}
constexpr bool
operator<=(huint_t x) const
{
return h <= x.h;
}
using V6Container = std::vector<uint8_t>;
[[deprecated]] void
ToV6(V6Container& c);
@ -241,6 +247,18 @@ namespace llarp
ipv4addr_t ToNet(huint32_t);
ipv6addr_t ToNet(huint128_t);
/// construct an ip variant from a string.
ipaddr_t
ipaddr_from_string(const std::string& str);
/// takes in an ipaddr_t and if it's a ipv4 address it converts it to a mapped ipv6 address.
ipv6addr_t
maybe_expand_ip(ipaddr_t ip);
/// takes in an ipv6 address and truncates it to v4 if it is in the ipv4 mapped range.
ipaddr_t
maybe_truncate_ip(ipv6addr_t ip);
} // namespace net
template <>

View file

@ -1,3 +1,4 @@
#include "llarp/util/bits.hpp"
#include "net.hpp"
#include "net_if.hpp"
#include <stdexcept>
@ -86,7 +87,7 @@ namespace llarp::net
{
ipv4addr_t addr{reinterpret_cast<sockaddr_in*>(i->ifa_addr)->sin_addr.s_addr};
ipv4addr_t mask{reinterpret_cast<sockaddr_in*>(i->ifa_netmask)->sin_addr.s_addr};
currentRanges.emplace_back(IPRange::FromIPv4(addr, mask));
currentRanges.emplace_back(IPRange::FromIPv4(addr, bits::count_bits(mask)));
}
});

View file

@ -85,13 +85,19 @@ namespace llarp
RouterID
Path::Endpoint() const
{
return hops[hops.size() - 1].rc.pubkey;
return EndpointHop().rc.pubkey;
}
PubKey
const PubKey&
Path::EndpointPubKey() const
{
return hops[hops.size() - 1].rc.pubkey;
return EndpointHop().rc.pubkey;
}
const PathHopConfig&
Path::EndpointHop() const
{
return hops[hops.size() - 1];
}
PathID_t

View file

@ -353,9 +353,12 @@ namespace llarp
RouterID
Endpoint() const;
PubKey
const PubKey&
EndpointPubKey() const;
const PathHopConfig&
EndpointHop() const;
bool
IsEndpoint(const RouterID& router, const PathID_t& path) const;

View file

@ -1,14 +1,23 @@
#include "path_context.hpp"
#include <functional>
#include <llarp/messages/relay_commit.hpp>
#include "llarp/path/pathset.hpp"
#include "llarp/util/compare_ptr.hpp"
#include "llarp/util/thread/annotations.hpp"
#include "oxen/log.hpp"
#include "path.hpp"
#include <llarp/router/abstractrouter.hpp>
#include <llarp/router/i_outbound_message_handler.hpp>
#include <type_traits>
#include <unordered_set>
namespace llarp
{
namespace path
{
static auto logcat = log::Cat("path-context");
static constexpr auto DefaultPathBuildLimit = 500ms;
PathContext::PathContext(AbstractRouter* router)
@ -415,5 +424,12 @@ namespace llarp
void
PathContext::RemovePathSet(PathSet_ptr)
{}
void
PathContext::periodic_tick()
{
const auto now = m_Router->Now();
ExpirePaths(now);
}
} // namespace path
} // namespace llarp

View file

@ -33,8 +33,7 @@ namespace llarp
struct PathContext
{
explicit PathContext(AbstractRouter* router);
/// called from router tick function
/// apply path expiration.
void
ExpirePaths(llarp_time_t now);
@ -177,6 +176,10 @@ namespace llarp
uint64_t
CurrentOwnedPaths(path::PathStatus status = path::PathStatus::ePathEstablished);
/// handle a periodic tick on all pathsets.
void
periodic_tick();
private:
AbstractRouter* m_Router;
SyncTransitMap_t m_TransitPaths;

View file

@ -1,4 +1,5 @@
#include "pathbuilder.hpp"
#include "oxen/log.hpp"
#include "path_context.hpp"
#include <llarp/crypto/crypto.hpp>
@ -239,13 +240,13 @@ namespace llarp
std::optional<RouterContact> found = std::nullopt;
m_router->ForEachPeer(
[&](const ILinkSession* s, bool isOutbound) {
if (s && s->IsEstablished() && isOutbound && not found.has_value())
if (s && s->IsEstablished() && isOutbound && not found)
{
const RouterContact rc = s->GetRemoteRC();
#ifndef TESTNET
if (m_router->IsBootstrapNode(rc.pubkey))
if (not m_router->rcLookupHandler().PathIsAllowed(rc.pubkey))
return;
#endif
if (exclude.count(rc.pubkey))
return;
@ -349,9 +350,19 @@ namespace llarp
std::vector<RouterContact> hops;
{
const auto maybe = SelectFirstHop(exclude);
if (not maybe.has_value())
if (not maybe)
{
log::warning(log_path, "{} has no first hop candidate", Name());
if (m_router->NumberOfConnectedRouters())
log::warning(
log_path,
"cannot build path for '{}' becuase we do not have enough edge connections to the "
"network.",
Name());
else
log::warning(
log_path,
"cannot build path for '{}' because we are not collected to the network",
Name());
return std::nullopt;
}
hops.emplace_back(*maybe);

View file

@ -1,18 +1,19 @@
#include "path.hpp"
#include "transit_hop.hpp"
#include "path_context.hpp"
#include <llarp/dht/context.hpp>
#include <llarp/exit/context.hpp>
#include <llarp/exit/exit_messages.hpp>
#include <llarp/link/i_link_manager.hpp>
#include <llarp/messages/discard.hpp>
#include <llarp/messages/relay_commit.hpp>
#include <llarp/messages/relay_status.hpp>
#include "path_context.hpp"
#include "transit_hop.hpp"
#include <llarp/routing/transfer_traffic_message.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/routing/path_latency_message.hpp>
#include <llarp/routing/path_transfer_message.hpp>
#include <llarp/routing/handler.hpp>
#include <llarp/util/buffer.hpp>
#include <oxenc/endian.h>
@ -21,6 +22,9 @@ namespace llarp
{
namespace path
{
static auto logcat = log::Cat("transit-hop");
std::string
TransitHopInfo::ToString() const
{
@ -284,52 +288,65 @@ namespace llarp
TransitHop::HandleObtainExitMessage(
const llarp::routing::ObtainExitMessage& msg, AbstractRouter* r)
{
if (msg.Verify() && r->exitContext().ObtainNewExit(msg.I, info.rxID, msg.E != 0))
if (not msg.Verify())
{
llarp::routing::GrantExitMessage grant;
grant.S = NextSeqNo();
grant.T = msg.T;
if (!grant.Sign(r->identity()))
// invalid sig, tell them their request was discarded.
return SendRoutingMessage(routing::DataDiscardMessage{info.rxID, msg.S}, r);
}
// this message is always asking for our local snode.
// future protocol versions are expected to permit non local routing destinations.
RouterID to{r->pubkey()};
auto destination = r->get_layers()->route->create_destination(info, msg.source_identity, to);
if (not destination)
{
// we did not give the requester an allocation.
// send them a reject.
llarp::routing::RejectExitMessage reject;
reject.S = NextSeqNo();
reject.txid = msg.txid;
// todo: exponential backoff
reject.backoff = 2000;
if (not reject.Sign(r->identity()))
{
llarp::LogError("Failed to sign grant exit message");
log::info(
logcat,
"snode destionation for {} to {} was rejected by us"_format(msg.source_identity, to));
return false;
}
return SendRoutingMessage(grant, r);
}
// TODO: exponential backoff
// TODO: rejected policies
llarp::routing::RejectExitMessage reject;
reject.S = NextSeqNo();
reject.T = msg.T;
if (!reject.Sign(r->identity()))
llarp::routing::GrantExitMessage grant;
grant.S = NextSeqNo();
grant.txid = msg.txid;
if (not grant.Sign(r->identity()))
{
llarp::LogError("Failed to sign reject exit message");
return false;
// failed to sign, tell them it was discarded after removing routing destination.
r->get_layers()->route->remove_destination_on(info);
return SendRoutingMessage(routing::DataDiscardMessage{info.rxID, msg.S}, r);
}
return SendRoutingMessage(reject, r);
return SendRoutingMessage(grant, r);
}
bool
TransitHop::HandleCloseExitMessage(
const llarp::routing::CloseExitMessage& msg, AbstractRouter* r)
{
const llarp::routing::DataDiscardMessage discard(info.rxID, msg.S);
auto ep = r->exitContext().FindEndpointForPath(info.rxID);
if (ep && msg.Verify(ep->PubKey()))
{
llarp::routing::CloseExitMessage reply;
reply.Y = msg.Y;
reply.S = NextSeqNo();
if (reply.Sign(r->identity()))
{
if (SendRoutingMessage(reply, r))
{
ep->Close();
return true;
}
}
}
return SendRoutingMessage(discard, r);
const llarp::routing::DataDiscardMessage discard{info.rxID, msg.S};
auto maybe_dest = r->get_layers()->route->destination_on(info);
if (not maybe_dest)
return SendRoutingMessage(discard, r);
if (not msg.Verify(maybe_dest->src))
return SendRoutingMessage(discard, r);
llarp::routing::CloseExitMessage reply;
reply.Y = msg.Y;
reply.S = NextSeqNo();
r->get_layers()->route->remove_destination_on(info);
reply.Sign(r->identity());
return SendRoutingMessage(reply, r);
}
bool
@ -346,23 +363,26 @@ namespace llarp
TransitHop::HandleUpdateExitMessage(
const llarp::routing::UpdateExitMessage& msg, AbstractRouter* r)
{
auto ep = r->exitContext().FindEndpointForPath(msg.P);
if (ep)
{
if (!msg.Verify(ep->PubKey()))
return false;
auto maybe_previous_dest = r->get_layers()->route->destination_from(msg.path_id);
// make sure we have the last destination.
if (not maybe_previous_dest)
return SendRoutingMessage(routing::DataDiscardMessage{info.rxID, msg.S}, r);
// verify that the update message was signed by the previous mapping's identity.
if (not msg.Verify(maybe_previous_dest->src))
return SendRoutingMessage(routing::DataDiscardMessage{info.rxID, msg.S}, r);
if (ep->UpdateLocalPath(info.rxID))
{
llarp::routing::UpdateExitVerifyMessage reply;
reply.T = msg.T;
reply.S = NextSeqNo();
return SendRoutingMessage(reply, r);
}
}
// on fail tell message was discarded
llarp::routing::DataDiscardMessage discard(info.rxID, msg.S);
return SendRoutingMessage(discard, r);
auto next_dest = r->get_layers()->route->create_destination(
info, maybe_previous_dest->src, maybe_previous_dest->dst);
// allocate new routing destination.
if (not next_dest)
return SendRoutingMessage(routing::DataDiscardMessage{info.rxID, msg.S}, r);
// remove old routing destination.
// r->get_layers()->route->remove_destination_on(maybe_previous_dest->onion_info);
llarp::routing::UpdateExitVerifyMessage reply;
reply.T = msg.T;
reply.S = NextSeqNo();
return SendRoutingMessage(reply, r);
}
bool
@ -389,27 +409,23 @@ namespace llarp
TransitHop::HandleTransferTrafficMessage(
const llarp::routing::TransferTrafficMessage& msg, AbstractRouter* r)
{
auto endpoint = r->exitContext().FindEndpointForPath(info.rxID);
if (endpoint)
{
bool sent = true;
for (const auto& pkt : msg.X)
{
// check short packet buffer
if (pkt.size() <= 8)
continue;
auto counter = oxenc::load_big_to_host<uint64_t>(pkt.data());
llarp_buffer_t buf{pkt.data() + 8, pkt.size() - 8};
sent =
endpoint->QueueOutboundTraffic(info.rxID, buf.copy(), counter, msg.protocol) and sent;
}
return sent;
}
auto maybe_dest = r->get_layers()->route->destination_on(info);
// no destination on this path.
if (not maybe_dest)
return SendRoutingMessage(llarp::routing::DataDiscardMessage{info.rxID, msg.S}, r);
// destination not applicable for this use case.
if (not maybe_dest->flow_info)
return SendRoutingMessage(llarp::routing::DataDiscardMessage{info.rxID, msg.S}, r);
llarp::LogError("No exit endpoint on ", info);
// discarded
llarp::routing::DataDiscardMessage discard(info.rxID, msg.S);
return SendRoutingMessage(discard, r);
const auto& flow_info = *maybe_dest->flow_info;
// propagate all traffic up to flow layer.
for (auto& traffic : msg.to_flow_traffic())
{
traffic.flow_info = flow_info;
r->get_layers()->flow->offer_flow_traffic(std::move(traffic));
}
return true;
}
bool

View file

@ -1,21 +1,24 @@
#pragma once
#include <optional>
#include <vector>
#include <functional>
#include <memory>
#include "i_outbound_message_handler.hpp"
#include <llarp/layers/layers.hpp>
#include <llarp/config/config.hpp>
#include <llarp/config/key_manager.hpp>
#include <memory>
#include <llarp/util/types.hpp>
#include <llarp/util/status.hpp>
#include "i_outbound_message_handler.hpp"
#include <vector>
#include <llarp/ev/ev.hpp>
#include <functional>
#include <llarp/router_contact.hpp>
#include <llarp/tooling/router_event.hpp>
#include <llarp/peerstats/peer_db.hpp>
#include <llarp/consensus/reachability_testing.hpp>
#include <optional>
#ifdef LOKINET_HIVE
#include <llarp/tooling/router_event.hpp>
#endif
@ -45,19 +48,27 @@ namespace llarp
struct I_RCLookupHandler;
struct RoutePoker;
class EndpointBase;
namespace dns
{
class I_SystemSettings;
}
class Server;
} // namespace dns
namespace net
{
class Platform;
}
namespace exit
namespace layers
{
struct Context;
struct Layers;
}
namespace layers::platform
{
class PlatformDriver;
}
namespace rpc
@ -70,16 +81,16 @@ namespace llarp
struct PathContext;
}
namespace quic
{
class TunnelManager;
}
namespace routing
{
struct IMessageHandler;
}
namespace service
{
struct Context;
}
namespace thread
{
class ThreadPool;
@ -100,6 +111,19 @@ namespace llarp
virtual ~AbstractRouter() = default;
/// overrideable creation of protocol layers
virtual std::unique_ptr<const layers::Layers>
create_layers() = 0;
virtual const std::unique_ptr<const layers::Layers>&
get_layers() const = 0;
virtual const std::shared_ptr<dns::Server>&
get_dns() const = 0;
virtual const std::shared_ptr<quic::TunnelManager>&
quic_tunnel() const = 0;
virtual bool
HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) = 0;
@ -136,9 +160,6 @@ namespace llarp
virtual void
ModifyOurRC(std::function<std::optional<RouterContact>(RouterContact)> modify) = 0;
virtual exit::Context&
exitContext() = 0;
virtual const std::shared_ptr<KeyManager>&
keyManager() const = 0;
@ -161,16 +182,7 @@ namespace llarp
virtual void QueueDiskIO(std::function<void(void)>) = 0;
virtual std::shared_ptr<Config>
GetConfig() const
{
return nullptr;
}
virtual service::Context&
hiddenServiceContext() = 0;
virtual const service::Context&
hiddenServiceContext() const = 0;
GetConfig() const = 0;
virtual IOutboundMessageHandler&
outboundMessageHandler() = 0;

View file

@ -35,7 +35,7 @@ namespace llarp
QueueMessage(const RouterID& remote, const ILinkMessage& msg, SendStatusHandler callback) = 0;
virtual void
Pump() = 0;
IdempotentPump() = 0;
virtual void
RemovePath(const PathID_t& pathid) = 0;

View file

@ -8,6 +8,7 @@
#include <algorithm>
#include <cstdlib>
#include <type_traits>
namespace llarp
{
@ -79,19 +80,25 @@ namespace llarp
}
void
OutboundMessageHandler::Pump()
OutboundMessageHandler::Pump(bool also_pump_router)
{
m_Killer.TryAccess([this]() {
m_Killer.TryAccess([this, also_pump_router]() {
recentlyRemovedPaths.Decay();
ProcessOutboundQueue();
// TODO: this probably shouldn't be pumping, as it defeats the purpose
// of having a limit on sends per tick, but chaning it is potentially bad
// and requires testing so it should be changed later.
if (/*bool more = */ SendRoundRobin())
if (bool more = SendRoundRobin(); more and also_pump_router)
_router->TriggerPump();
});
}
void
OutboundMessageHandler::IdempotentPump()
{
_wakeup->Trigger();
}
void
OutboundMessageHandler::RemovePath(const PathID_t& pathid)
{
@ -131,6 +138,7 @@ namespace llarp
OutboundMessageHandler::Init(AbstractRouter* router)
{
_router = router;
_wakeup = router->loop()->make_waker([this]() { Pump(false); });
outboundMessageQueues.emplace(zeroID, MessageQueue());
}

View file

@ -10,6 +10,7 @@
#include <llarp/router_id.hpp>
#include <list>
#include <memory>
#include <unordered_map>
#include <utility>
@ -57,9 +58,15 @@ namespace llarp
*
* Sends all routing messages that have been queued, indicated by pathid 0 when queued.
* Sends messages from path queues until all are empty or a set cap has been reached.
*
* if also_pump_router is true, this call will call Router::TriggerPump() if we queued things.
*/
void
Pump() override;
Pump(bool also_pump_router = true);
/// idempotently call Pump()
void
IdempotentPump() override;
/* Called from outside this class to inform it that a path has died / expired
* and its queue should be discarded.
@ -202,6 +209,8 @@ namespace llarp
static const PathID_t zeroID;
MessageQueueStats m_queueStats;
std::shared_ptr<EventLoopWakeup> _wakeup;
};
} // namespace llarp

View file

@ -4,7 +4,6 @@
#include <llarp/link/i_link_manager.hpp>
#include <llarp/link/server.hpp>
#include <llarp/crypto/crypto.hpp>
#include <llarp/service/context.hpp>
#include <llarp/router_contact.hpp>
#include <llarp/util/types.hpp>
#include <llarp/util/thread/threading.hpp>
@ -39,6 +38,9 @@ namespace llarp
beigelist.insert(new_beige.begin(), new_beige.end());
}
RCLookupHandler::RCLookupHandler(AbstractRouter& router) : _router{router}
{}
void
RCLookupHandler::SetRouterWhitelist(
const std::vector<RouterID>& whitelist,
@ -69,9 +71,9 @@ namespace llarp
RouterContact remoteRC;
if (not forceLookup)
{
if (const auto maybe = _nodedb->Get(router); maybe.has_value())
if (auto maybe = _router.nodedb()->Get(router))
{
remoteRC = *maybe;
remoteRC = std::move(*maybe);
if (callback)
{
callback(router, &remoteRC, RCRequestResult::Success);
@ -97,24 +99,14 @@ namespace llarp
if (shouldDoLookup)
{
auto fn = [this, router](const auto& res) { HandleDHTLookupResult(router, res); };
auto lookup = [this](RouterID target, RouterLookupHandler handler) -> bool {
if (_router.IsServiceNode())
return _router.dht()->impl->LookupRouter(target, std::move(handler));
// todo: client lookup via path.
return false;
};
// if we are a client try using the hidden service endpoints
if (!isServiceNode)
{
bool sent = false;
LogInfo("Lookup ", router, " anonymously");
_hiddenServiceContext->ForEachService(
[&](const std::string&, const std::shared_ptr<service::Endpoint>& ep) -> bool {
const bool success = ep->LookupRouterAnon(router, fn);
sent = sent || success;
return !success;
});
if (sent)
return;
LogWarn("cannot lookup ", router, " anonymously");
}
if (!_dht->impl->LookupRouter(router, fn))
if (not lookup(router, std::move(fn)))
{
FinalizeRequest(router, nullptr, RCRequestResult::RouterNotFound);
}
@ -160,11 +152,11 @@ namespace llarp
bool
RCLookupHandler::PathIsAllowed(const RouterID& remote) const
{
if (_strictConnectPubkeys.size() && _strictConnectPubkeys.count(remote) == 0
&& !RemoteInBootstrap(remote))
{
if (_strictConnectPubkeys.count(remote) == 1 and not isServiceNode)
return true;
// clients shall not build paths over bootstrap node.
if (RemoteInBootstrap(remote) and not isServiceNode)
return false;
}
if (not useWhitelist)
return true;
@ -177,11 +169,10 @@ namespace llarp
bool
RCLookupHandler::SessionIsAllowed(const RouterID& remote) const
{
if (_strictConnectPubkeys.size() && _strictConnectPubkeys.count(remote) == 0
&& !RemoteInBootstrap(remote))
{
return false;
}
if (_strictConnectPubkeys.count(remote) == 1 and not isServiceNode)
return true;
if (RemoteInBootstrap(remote))
return true;
if (not useWhitelist)
return true;
@ -196,11 +187,11 @@ namespace llarp
{
if (not SessionIsAllowed(rc.pubkey))
{
_dht->impl->DelRCNodeAsync(dht::Key_t{rc.pubkey});
_router.dht()->impl->DelRCNodeAsync(dht::Key_t{rc.pubkey});
return false;
}
if (not rc.Verify(_dht->impl->Now()))
if (not rc.Verify(_router.dht()->impl->Now()))
{
LogWarn("RC for ", RouterID(rc.pubkey), " is invalid");
return false;
@ -210,8 +201,8 @@ namespace llarp
if (rc.IsPublicRouter())
{
LogDebug("Adding or updating RC for ", RouterID(rc.pubkey), " to nodedb and dht.");
_loop->call([rc, n = _nodedb] { n->PutIfNewer(rc); });
_dht->impl->PutRCNodeAsync(rc);
_router.loop()->call([rc, n = _router.nodedb()] { n->PutIfNewer(rc); });
_router.dht()->impl->PutRCNodeAsync(rc);
}
return true;
@ -248,13 +239,12 @@ namespace llarp
if (!SessionIsAllowed(newrc.pubkey))
return false;
auto func = [this, newrc] { CheckRC(newrc); };
_work(func);
_router.QueueWork([this, newrc] { CheckRC(newrc); });
// update dht if required
if (_dht->impl->Nodes()->HasNode(dht::Key_t{newrc.pubkey}))
if (_router.dht()->impl->Nodes()->HasNode(dht::Key_t{newrc.pubkey}))
{
_dht->impl->Nodes()->PutNode(newrc);
_router.dht()->impl->Nodes()->PutNode(newrc);
}
// TODO: check for other places that need updating the RC
@ -267,8 +257,8 @@ namespace llarp
// try looking up stale routers
std::unordered_set<RouterID> routersToLookUp;
_nodedb->VisitInsertedBefore(
[&](const RouterContact& rc) {
_router.nodedb()->VisitInsertedBefore(
[this, &routersToLookUp](const RouterContact& rc) {
if (HavePendingLookup(rc.pubkey))
return;
routersToLookUp.insert(rc.pubkey);
@ -280,13 +270,14 @@ namespace llarp
GetRC(router, nullptr, true);
}
_nodedb->RemoveStaleRCs(_bootstrapRouterIDList, now - RouterContact::StaleInsertionAge);
_router.nodedb()->RemoveStaleRCs(
_bootstrapRouterIDList, now - RouterContact::StaleInsertionAge);
}
void
RCLookupHandler::ExploreNetwork()
{
const size_t known = _nodedb->NumLoaded();
const size_t known = _router.nodedb()->NumLoaded();
if (_bootstrapRCList.empty() && known == 0)
{
LogError("we have no bootstrap nodes specified");
@ -296,7 +287,7 @@ namespace llarp
for (const auto& rc : _bootstrapRCList)
{
LogInfo("Doing explore via bootstrap node: ", RouterID(rc.pubkey));
_dht->impl->ExploreNetworkVia(dht::Key_t{rc.pubkey});
_router.dht()->impl->ExploreNetworkVia(dht::Key_t{rc.pubkey});
}
}
@ -315,7 +306,7 @@ namespace llarp
util::Lock l(_mutex);
for (const auto& r : whitelistRouters)
{
if (now > _routerLookupTimes[r] + RerequestInterval and not _nodedb->Has(r))
if (now > _routerLookupTimes[r] + RerequestInterval and not _router.nodedb()->Has(r))
{
lookupRouters.emplace_back(r);
}
@ -333,43 +324,32 @@ namespace llarp
return;
}
// service nodes gossip, not explore
if (_dht->impl->GetRouter()->IsServiceNode())
if (_router.dht()->impl->GetRouter()->IsServiceNode())
return;
// explore via every connected peer
_linkManager->ForEachPeer([&](ILinkSession* s) {
_router.linkManager().ForEachPeer([this](ILinkSession* s) {
if (!s->IsEstablished())
return;
const RouterContact rc = s->GetRemoteRC();
if (rc.IsPublicRouter() && (_bootstrapRCList.find(rc) == _bootstrapRCList.end()))
{
LogDebug("Doing explore via public node: ", RouterID(rc.pubkey));
_dht->impl->ExploreNetworkVia(dht::Key_t{rc.pubkey});
_router.dht()->impl->ExploreNetworkVia(dht::Key_t{rc.pubkey});
}
});
}
void
RCLookupHandler::Init(
llarp_dht_context* dht,
std::shared_ptr<NodeDB> nodedb,
EventLoop_ptr loop,
WorkerFunc_t dowork,
ILinkManager* linkManager,
service::Context* hiddenServiceContext,
const std::unordered_set<RouterID>& strictConnectPubkeys,
const std::set<RouterContact>& bootstrapRCList,
bool useWhitelist_arg,
bool isServiceNode_arg)
{
_dht = dht;
_nodedb = std::move(nodedb);
_loop = std::move(loop);
_work = std::move(dowork);
_hiddenServiceContext = hiddenServiceContext;
_strictConnectPubkeys = strictConnectPubkeys;
_bootstrapRCList = bootstrapRCList;
_linkManager = linkManager;
useWhitelist = useWhitelist_arg;
isServiceNode = isServiceNode_arg;

View file

@ -17,12 +17,7 @@ namespace llarp
class NodeDB;
class EventLoop;
namespace service
{
struct Context;
} // namespace service
struct AbstractRouter;
struct ILinkManager;
struct RCLookupHandler final : public I_RCLookupHandler
@ -32,6 +27,8 @@ namespace llarp
using WorkerFunc_t = std::function<void(Work_t)>;
using CallbacksQueue = std::list<RCRequestCallback>;
RCLookupHandler(AbstractRouter&);
~RCLookupHandler() override = default;
void
@ -94,12 +91,6 @@ namespace llarp
void
Init(
llarp_dht_context* dht,
std::shared_ptr<NodeDB> nodedb,
std::shared_ptr<EventLoop> loop,
WorkerFunc_t dowork,
ILinkManager* linkManager,
service::Context* hiddenServiceContext,
const std::unordered_set<RouterID>& strictConnectPubkeys,
const std::set<RouterContact>& bootstrapRCList,
bool useWhitelist_arg,
@ -128,12 +119,7 @@ namespace llarp
mutable util::Mutex _mutex; // protects pendingCallbacks, whitelistRouters
llarp_dht_context* _dht = nullptr;
std::shared_ptr<NodeDB> _nodedb;
std::shared_ptr<EventLoop> _loop;
WorkerFunc_t _work = nullptr;
service::Context* _hiddenServiceContext = nullptr;
ILinkManager* _linkManager = nullptr;
AbstractRouter& _router;
/// explicit whitelist of routers we will connect to directly (not for
/// service nodes)

View file

@ -1,8 +1,9 @@
#include "route_poker.hpp"
#include <llarp/router/abstractrouter.hpp>
#include <llarp/net/sock_addr.hpp>
#include <llarp/service/context.hpp>
#include <llarp/dns/platform.hpp>
#include <llarp/dns/server.hpp>
#include <llarp/vpn/platform.hpp>
#include <unordered_set>
namespace llarp
@ -129,26 +130,22 @@ namespace llarp
void
RoutePoker::Update()
{
if (not m_Router)
throw std::runtime_error{"Attempting to use RoutePoker before calling Init"};
// ensure we have an endpoint
auto ep = m_Router->hiddenServiceContext().GetDefault();
if (ep == nullptr)
return;
// ensure we have a vpn platform
auto* platform = m_Router->GetVPNPlatform();
if (platform == nullptr)
return;
// ensure we have a vpn interface
auto* vpn = ep->GetVPNInterface();
if (vpn == nullptr)
// we dont need to do anything we are not enabled on runtime by config.
if (not IsEnabled())
return;
auto& route = platform->RouteManager();
auto* route_man = m_Router->get_layers()->platform->route_manager();
auto vpn = m_Router->get_layers()->platform->vpn_interface();
if (not(vpn and route_man))
{
// no vpn interface with route manager so we cannot poke routes yet.
return;
}
// get current gateways, assume sorted by lowest metric first
auto gateways = route.GetGatewaysNotOnInterface(*vpn);
auto gateways = route_man->GetGatewaysNotOnInterface(*vpn);
std::optional<net::ipv4addr_t> next_gw;
for (auto& gateway : gateways)
{
@ -188,10 +185,10 @@ namespace llarp
void
RoutePoker::SetDNSMode(bool exit_mode_on) const
{
auto ep = m_Router->hiddenServiceContext().GetDefault();
if (not ep)
if (m_Router->IsServiceNode())
return;
if (auto dns_server = ep->DNS())
if (const auto& dns_server = m_Router->get_dns())
dns_server->SetDNSMode(exit_mode_on);
}
@ -210,24 +207,22 @@ namespace llarp
{
log::warning(logcat, "RokerPoker came up, but we don't know of a gateway!");
}
else
else if (auto vpn = m_Router->get_layers()->platform->vpn_interface())
{
log::info(logcat, "RoutePoker coming up; poking routes");
if (auto* route_man = m_Router->get_layers()->platform->route_manager())
{
log::info(logcat, "RoutePoker coming up; poking routes");
// black hole all routes if enabled
if (m_Router->GetConfig()->network.m_BlackholeRoutes)
route_man->AddBlackhole();
vpn::IRouteManager& route = m_Router->GetVPNPlatform()->RouteManager();
// black hole all routes if enabled
if (m_Router->GetConfig()->network.m_BlackholeRoutes)
route.AddBlackhole();
// explicit route pokes for first hops
m_Router->ForEachPeer(
[this](auto session, auto) { AddRoute(session->GetRemoteEndpoint().getIPv4()); },
false);
// add default route
const auto ep = m_Router->hiddenServiceContext().GetDefault();
if (auto* vpn = ep->GetVPNInterface())
route.AddDefaultRouteViaInterface(*vpn);
// explicit route pokes for first hops
m_Router->ForEachPeer(
[this](auto session, auto) { AddRoute(session->GetRemoteEndpoint().getIPv4()); },
false);
// add default route
route_man->AddDefaultRouteViaInterface(*vpn);
}
log::info(logcat, "route poker up");
}
}
@ -243,16 +238,14 @@ namespace llarp
[this](auto session, auto) { DelRoute(session->GetRemoteEndpoint().getIPv4()); }, false);
// remove default route
if (IsEnabled() and m_up)
if (const auto& vpn = m_Router->get_layers()->platform->vpn_interface();
vpn and IsEnabled() and m_up)
{
vpn::IRouteManager& route = m_Router->GetVPNPlatform()->RouteManager();
const auto ep = m_Router->hiddenServiceContext().GetDefault();
if (auto* vpn = ep->GetVPNInterface())
route.DelDefaultRouteViaInterface(*vpn);
// delete route blackhole
route.DelBlackhole();
if (auto route_man = m_Router->get_layers()->platform->route_manager())
{
route_man->DelBlackhole();
route_man->DelDefaultRouteViaInterface(*vpn);
}
log::info(logcat, "route poker down");
}
if (m_up)

Some files were not shown because too many files have changed in this diff Show more