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

just the headers for the internals refactor.

llarp/layers/platform/*.hpp

was the "front end" of the previous kitchen sink.

this component bridges the userland platforms, be the full VPN mode,
embedded mode or any other kind of future mode of operation we may
support. this component is meant to slowly consilidate the location of
platform specific quarks, like ones for proactor vs reactor event
loops. the vpn platform code will exist independantly from this layer
but this will be where any kind of idempotent queue flushing when we
get something from or destinted the "platform" (e.g. the vpn
interface, or some kind of internal for embedded lokinet).

llarp/layers/flow/*.hpp

was the "back end" of the previous kitchen sink.

we provide all headers for the logic of .loki and .snode traffic
codepaths for snodes and clients. these headers support .loki and
.snode in a unified api, any additional kinds of remote endpoint types
like a .exit whatever else would be easy to add with this.

defines hooks for any authentication between us and remote flows,
regardless of if we are initiator or recipient.

defines ons name to flow address lookup interface, as well as ons name
caching interface. both of which adds a slot to define future
configurable knobs how strict of a consensus we want for a ons lookup
and result caching lifetime and lookup result
invalidation (e.g. allows us to purge things when we add consensus
state hints into lokinet clients).

llarp/layers/route/*.hpp

the routing layer which used to blur into the "back end" old kitchen
sink (now called the flow layer). currently mostly baren and exists as
a placeholder stub until later.

llarp/layers/onion/*.hpp

stub for onion layer, basically for llarp/path/* as a mechanism for
removing llarp::path::PathSet type forever. this has yet to happen.

llarp/context.hpp

this was include/llarp.hpp, but now it is here and no longer a public
header. we really dont have a nice way to expose a C++ api just a C
api. going forward a public C++ api does not seem to be a worthwhile
efffort for abi stability reasons and how crap our current headers are.

will remove include/llarp.hpp later as so much includes it.

(maybe C++ modules will help later... maybe...)
This commit is contained in:
jeff 2023-03-23 22:57:51 -04:00
parent 7d2ad5621b
commit 10eddfb177
30 changed files with 1781 additions and 0 deletions

115
llarp/context.hpp Normal file
View file

@ -0,0 +1,115 @@
#ifndef LLARP_HPP
#define LLARP_HPP
#include <future>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace llarp
{
namespace vpn
{
class Platform;
}
class EventLoop;
struct Config;
struct RouterContact;
struct Config;
struct Crypto;
struct CryptoManager;
struct AbstractRouter;
class NodeDB;
namespace thread
{
class ThreadPool;
}
struct RuntimeOptions
{
bool showBanner = true;
bool debug = false;
bool isSNode = false;
};
struct Context
{
std::shared_ptr<Crypto> crypto = nullptr;
std::shared_ptr<CryptoManager> cryptoManager = nullptr;
std::shared_ptr<AbstractRouter> router = nullptr;
std::shared_ptr<EventLoop> loop = nullptr;
std::shared_ptr<NodeDB> nodedb = nullptr;
Context();
virtual ~Context() = default;
void
Setup(const RuntimeOptions& opts);
int
Run(const RuntimeOptions& opts);
void
HandleSignal(int sig);
/// Configure given the specified config.
void
Configure(std::shared_ptr<Config> conf);
/// handle SIGHUP
void
Reload();
bool
IsUp() const;
bool
LooksAlive() const;
bool
IsStopping() const;
/// close async
void
CloseAsync();
/// wait until closed and done
void
Wait();
/// call a function in logic thread
/// return true if queued for calling
/// return false if not queued for calling
bool
CallSafe(std::function<void(void)> f);
/// Creates a router. Can be overridden to allow a different class of router
/// to be created instead. Defaults to llarp::Router.
virtual std::shared_ptr<AbstractRouter>
makeRouter(const std::shared_ptr<EventLoop>& loop);
/// create the nodedb given our current configs
virtual std::shared_ptr<NodeDB>
makeNodeDB();
/// create the vpn platform for use in creating network interfaces
virtual std::shared_ptr<llarp::vpn::Platform>
makeVPNPlatform();
int androidFD = -1;
protected:
std::shared_ptr<Config> config = nullptr;
private:
void
SigINT();
std::unique_ptr<std::promise<void>> closeWaiter;
};
} // namespace llarp
#endif

View file

@ -0,0 +1,67 @@
#pragma once
#include <fmt/core.h>
#include <llarp/util/aligned.hpp>
#include <string>
#include <string_view>
#include <llarp/endpoint_base.hpp>
#include <type_traits>
namespace llarp::layers::flow
{
/// a long form .snode or .loki address
class FlowAddr : public AlignedBuffer<32>
{
public:
enum class Kind : uint8_t
{
snapp,
snode,
};
FlowAddr() = default;
/// construct from string
explicit FlowAddr(std::string str);
explicit FlowAddr(EndpointBase::AddressVariant_t arg);
std::string
ToString() const;
bool
operator==(const FlowAddr& other) const;
Kind
kind() const;
private:
static Kind
get_kind(std::string_view str);
Kind _kind;
};
} // namespace llarp::layers::flow
namespace llarp
{
template <>
inline constexpr bool IsToStringFormattable<llarp::layers::flow::FlowAddr> = true;
}
namespace std
{
template <>
struct hash<llarp::layers::flow::FlowAddr>
{
size_t
operator()(const llarp::layers::flow::FlowAddr& addr) const
{
return std::hash<llarp::AlignedBuffer<32>>{}(addr)
^ (std::hash<uint8_t>{}(static_cast<uint8_t>(addr.kind())) << 3);
}
};
} // namespace std

View file

@ -0,0 +1,37 @@
#pragma once
#include <functional>
#include <string>
#include <optional>
#include <llarp/util/formattable.hpp>
#include <string_view>
namespace llarp::layers::flow
{
/// which phase in authentication a flow is in while establishing.
enum class FlowAuthPhase
{
/// we have not sent the auth to the remote yet.
auth_req_not_sent,
/// we sent our auth to the remote.
auth_req_sent,
/// the remote explicitly rejected our auth.
auth_nack,
/// the remote asked for auth.
auth_more,
/// the remote accepted our auth.
auth_ok,
};
std::string_view
ToString(FlowAuthPhase phase);
} // namespace llarp::layers::flow
namespace llarp
{
template <>
inline constexpr bool IsToStringFormattable<layers::flow::FlowAuthPhase> = true;
} // namespace llarp

View file

@ -0,0 +1,12 @@
#pragma once
#include <cstdint>
namespace llarp::layers::flow
{
/// the mtu of plaintext data we transport via the flow layer.
static inline constexpr uint16_t default_flow_mtu = 1500;
/// maximum mtu we can carry of the flow layer
// TODO: verify
static inline constexpr uint16_t max_flow_mtu = 4096;
} // namespace llarp::layers::flow

View file

@ -0,0 +1,28 @@
#pragma once
#include <llarp/util/formattable.hpp>
namespace llarp::layers::flow
{
enum class FlowDataKind
{
unknown,
/// unicast ip traffic that does NOT exit lokinet.
direct_ip_unicast,
/// unicast ip traffic that does exit lokinet.
exit_ip_unicast,
/// auth data.
auth,
/// unicast stream data.
stream_unicast,
};
std::string_view
ToString(FlowDataKind kind);
} // namespace llarp::layers::flow
namespace llarp
{
template <>
inline constexpr bool IsToStringFormattable<llarp::layers::flow::FlowDataKind> = true;
}

View file

@ -0,0 +1,44 @@
#pragma once
#include <functional>
#include <string>
#include <optional>
#include "flow_info.hpp"
#include "flow_auth.hpp"
namespace llarp::layers::flow
{
/// handles informing an observer at each step of obtaining a flow on the flow layer.
class FlowEstablish
{
std::function<void(std::optional<FlowInfo>, std::string)> _completion_handler;
std::function<void(FlowAuthPhase, std::string)> _phase_handler;
std::optional<FlowInfo> _result;
public:
FlowEstablish(
std::function<void(std::optional<FlowInfo>, std::string)> completion_handler,
std::function<void(FlowAuthPhase, std::string)> phase_handler);
/// an optional static auth code
std::string authcode;
/// how long we wait for establishment timeout
std::chrono::milliseconds timeout{5s};
/// enter phase of flow establishment.
void
enter_phase(FlowAuthPhase phase, std::string info);
/// explicitly fail establishment with an error message.
void
fail(std::string msg);
/// explicitly inform that we are done with the establishment and we are ready to send.
/// calls _completion_handler.
void
ready(FlowInfo flow_info);
};
} // namespace llarp::layers::flow

View file

@ -0,0 +1,100 @@
#pragma once
#include <oxenc/bt_producer.h>
#include <oxenc/bt_serialize.h>
#include <llarp/crypto/types.hpp>
#include <llarp/dht/key.hpp>
#include <memory>
#include <string_view>
#include "flow_addr.hpp"
#include "flow_establish.hpp"
#include "flow_info.hpp"
#include "flow_state.hpp"
#include "flow_tag.hpp"
#include "flow_traffic.hpp"
namespace llarp::layers::flow
{
class FlowLayer;
/// private keys used at the flow layer that are persistable.
/// this includes any kinds of ephemeral private keys.
/// ephemeral keys do not need to be persisted.
/// shared secrets not included.
struct FlowIdentityPrivateKeys
{
PQKeyPair sntrup;
SecretKey identity;
SecretKey encryption;
PrivateKey derivedKey;
/// generate a new private key bundle for the flow layer.
static FlowIdentityPrivateKeys
keygen();
/// get the public .loki or .snode address
const FlowAddr&
public_addr() const;
/// get the blinded dht keyspace location
const dht::Key_t&
keyspace_location() const;
private:
mutable FlowAddr _root_pubkey;
mutable dht::Key_t _derived_pubkey;
};
/// the local end of a one to one flow to a remote given a flow isolation metric (flow_tag)
/// flows do not change their source/destination address or their flow tag/convo tag.
/// note: historically path handovers were allowed, but going forward this is discontinued.
class FlowIdentity
{
FlowLayer& _parent;
const FlowIdentityPrivateKeys& _local_privkeys;
/// internal state holder.
/// effectively a pimpl to abstract away .loki vs .snode codepaths.
std::unique_ptr<FlowState_Base> _state;
public:
/// holds the local/remote flow layer address and any flow isolation metric
const FlowInfo flow_info;
FlowIdentity(const FlowIdentity&) = delete;
FlowIdentity(FlowIdentity&&) = delete;
FlowIdentity(
FlowLayer& parent,
FlowAddr remote_addr,
FlowTag flow_tag,
const FlowIdentityPrivateKeys& local_keys);
/// ensure we have a flow to the remote endpoint.
/// pass in a handshaker to do any additional steps after establishment.
void
async_ensure_flow(FlowEstablish handshaker);
/// send flow traffic to the remote.
void
send_to_remote(std::vector<byte_t> dataum, FlowDataKind kind);
bool
operator==(const FlowIdentity& other) const;
};
} // namespace llarp::layers::flow
namespace std
{
template <>
struct hash<llarp::layers::flow::FlowIdentity>
{
size_t
operator()(const llarp::layers::flow::FlowIdentity& ident) const
{
return std::hash<llarp::layers::flow::FlowInfo>{}(ident.flow_info);
}
};
} // namespace std

View file

@ -0,0 +1,59 @@
#pragma once
#include "flow_addr.hpp"
#include "flow_tag.hpp"
#include "flow_constants.hpp"
namespace llarp::layers::flow
{
/// in lokinet our onion routed flows are comprised of a source and destination flow layer
/// address, a flow tag (an identifier to mark a distinct flow), and a pivot that each is using in
/// common which acts as our analog to an ipv6 flow label, see rfc6437.
struct FlowInfo
{
/// 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;
/// mtu between src and dst.
uint16_t mtu{default_flow_mtu};
std::string
ToString() const;
bool
operator==(const FlowInfo& other) const;
};
} // namespace llarp::layers::flow
namespace llarp
{
template <>
inline constexpr bool IsToStringFormattable<llarp::layers::flow::FlowInfo> = true;
}
namespace std
{
template <>
struct hash<llarp::layers::flow::FlowInfo>
{
size_t
operator()(const llarp::layers::flow::FlowInfo& info) const
{
return std::hash<llarp::layers::flow::FlowAddr>{}(info.src)
^ (std::hash<llarp::layers::flow::FlowAddr>{}(info.dst) << 3)
^ (std::hash<llarp::layers::flow::FlowTag>{}(info.tag) << 5);
}
};
} // namespace std

View file

@ -0,0 +1,113 @@
#pragma once
#include "flow_addr.hpp"
#include "flow_identity.hpp"
#include "flow_info.hpp"
#include "flow_tag.hpp"
#include "flow_stats.hpp"
#include "flow_traffic.hpp"
#include "name_cache.hpp"
#include "name_resolver.hpp"
#include <llarp/config/config.hpp>
#include <memory>
#include <vector>
namespace llarp
{
struct AbstractRouter;
}
namespace llarp::service
{
struct Endpoint;
}
namespace llarp::layers::flow
{
/// flow layer's context that holds all the things in the flow layer
class FlowLayer
{
const NetworkConfig _conf;
std::vector<std::shared_ptr<FlowIdentity>> _local_flows;
FlowIdentityPrivateKeys _privkeys;
NameCache _name_cache;
std::shared_ptr<service::Endpoint> _deprecated_endpoint;
/// flow layer traffic we got from the void.
std::vector<FlowTraffic> _recv;
/// wake this up to send things out the lower layers.
std::shared_ptr<EventLoopWakeup> _wakeup_send;
/// wakes up the upper layers to tell it we recieved flow layer traffic.
std::shared_ptr<EventLoopWakeup> _wakeup_recv;
/// ensure privkeys are generated and persisted if requested by configuration.
/// throws on any kind of failure. TODO: document what is thrown when.
void
maybe_store_or_load_privkeys();
/// generate a flow tag we dont current have tracked.
FlowTag
unique_flow_tag() const;
/// return true if we have a flow tag tracked.
bool
has_flow_tag(const FlowTag&) const;
public:
FlowLayer(AbstractRouter&, NetworkConfig);
FlowLayer(const FlowLayer&) = delete;
FlowLayer(FlowLayer&&) = delete;
/// get flow layer's informational stats at this moment in time.
FlowStats
current_stats() const;
/// return true if we have this flow.
bool
has_flow(const flow::FlowInfo& flow_info) const;
/// remove a flow we have tracked. does nothing if we do not have it tracked.
void
remove_flow(const flow::FlowInfo& flow_info);
/// get our flow address of our local given a flow tag.
/// if we give a nullopt flow tag we get our "default" inbound flow address we publish to the
/// network.
const FlowAddr&
local_addr(const std::optional<FlowTag>& maybe_tag = std::nullopt) const;
/// get our DEPRECATED endpointbase for our local "us"
std::shared_ptr<service::Endpoint>
local_deprecated_loki_endpoint() const;
/// autovivify a flow to a remote.
std::shared_ptr<FlowIdentity>
flow_to(const FlowAddr& to);
/// pop off all flow layer traffic that we have processed.
std::vector<FlowTraffic>
poll_flow_traffic();
/// synthetically inject flow layer traffic into the flow layer
void
offer_flow_traffic(FlowTraffic&& traff);
void
start();
void
stop();
NameResolver name_resolver;
AbstractRouter& router;
/// wake this up when you send stuff on the flow layer.
const std::shared_ptr<EventLoopWakeup>& wakeup_send{_wakeup_send};
};
} // namespace llarp::layers::flow

View file

@ -0,0 +1,40 @@
#pragma once
#include <memory>
#include "flow_auth.hpp"
namespace llarp::layers::flow
{
struct FlowState_Pimpl;
/// internal state of a flow on the flow layer.
class FlowState_Base
{
std::shared_ptr<FlowState_Pimpl> _pimpl;
public:
FlowState_Base(const FlowState_Base&) = delete;
FlowState_Base(FlowState_Base&&) = delete;
/// returns true if we have paths that are aligned to the right place on the network to send to
/// the remote.
bool
paths_aligned() const;
/// returns true if we are to do authentication with the remote.
bool
requires_authentication() const;
/// get the phase we are in with auth.
FlowAuthPhase
auth_phase() const;
/// returns true if we have an established session for sending traffic over to the remote.
/// any authentication has completed and accepted.
bool
established() const;
};
} // namespace llarp::layers::flow

View file

@ -0,0 +1,50 @@
#pragma once
#include <functional>
#include <unordered_set>
#include "flow_addr.hpp"
#include "flow_info.hpp"
namespace llarp::layers::flow
{
enum class FlowInfoState
{
good,
active,
idle,
stalled
};
/// informational data about a flow we have
struct FlowInfoStats
{
FlowInfoState state;
/// put extra info here
};
struct FlowStats
{
std::unordered_map<FlowInfo, FlowInfoStats> all_flows;
/// default inbound flow address. (our .snode / .loki address)
FlowAddr local_addr;
/// get all local addresses with the first element being local_addr.
std::vector<FlowAddr>
local_addrs() const;
/// get all flow infos that are considered "good".
std::unordered_set<FlowInfo>
good_flows() const;
/// return true if we have a flow to the remote that is "good".
bool
has_good_flow_to(const FlowAddr& remote) const;
/// all flow addrs with auth codes.
std::unordered_map<FlowAddr, std::string>
auth_map() const;
};
} // namespace llarp::layers::flow

View file

@ -0,0 +1,41 @@
#pragma once
#include <llarp/util/aligned.hpp>
#include <llarp/util/formattable.hpp>
namespace llarp::layers::flow
{
/// a flow layer tag that indicates a distinct convo between us and other entity on the flow
/// layer. was called a convotag in the past.
struct FlowTag : public AlignedBuffer<16>
{
using AlignedBuffer<16>::AlignedBuffer;
std::string
ToString() const;
/// make a random flow tag
static FlowTag
random();
};
} // namespace llarp::layers::flow
namespace llarp
{
template <>
inline constexpr bool IsToStringFormattable<llarp::layers::flow::FlowTag> = true;
}
namespace std
{
template <>
struct hash<llarp::layers::flow::FlowTag>
{
size_t
operator()(const llarp::layers::flow::FlowTag& tag) const
{
return std::hash<llarp::AlignedBuffer<16>>{}(tag);
}
};
} // namespace std

View file

@ -0,0 +1,16 @@
#pragma once
#include "flow_data_kind.hpp"
#include "flow_info.hpp"
namespace llarp::layers::flow
{
struct FlowTraffic
{
FlowInfo flow_info;
std::vector<byte_t> datum;
FlowDataKind kind;
};
} // namespace llarp::layers::flow

View file

@ -0,0 +1,28 @@
#pragma once
#include "flow_addr.hpp"
#include <llarp/util/decaying_hashtable.hpp>
namespace llarp::layers::flow
{
/// cache for in network names (ONS records) with static TTL.
/// has no negative lookup cache.
class NameCache
{
/// a cache of all the names we have looked up.
/// note: unlike other uses in the code, the decaying hashtabe this wraps uses monotonic uptime
/// instead of unix timestamps.
util::DecayingHashTable<std::string, FlowAddr> _names;
public:
NameCache(std::chrono::seconds ttl = 5min);
/// apply any cache expiry then get an entry from the cache if it exists.
std::optional<FlowAddr>
get(std::string name);
/// apply any cache expiry and then put a name into the cache.
void
put(std::string name, FlowAddr addr);
};
} // namespace llarp::layers::flow

View file

@ -0,0 +1,37 @@
#pragma once
#include "flow_addr.hpp"
#include <chrono>
#include <functional>
#include <memory>
#include <optional>
#include <oxenc/variant.h>
#include <llarp/util/types.hpp>
#include <llarp/dns/question.hpp>
#include <variant>
#include <vector>
namespace llarp::layers::flow
{
class NameCache;
class FlowLayer;
class NameResolver
{
NameCache& _name_cache;
FlowLayer& _parent;
public:
NameResolver(const NameResolver&) = delete;
NameResolver(NameResolver&&) = delete;
NameResolver(NameCache& name_cache, FlowLayer& parent);
/// resolve name using in network resolution
void
resolve_flow_addr_async(std::string name, std::function<void(std::optional<FlowAddr>)> handler);
};
} // namespace llarp::layers::flow

43
llarp/layers/layers.hpp Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include <llarp/layers/platform/platform_layer.hpp>
#include <llarp/layers/flow/flow_layer.hpp>
#include <llarp/layers/route/route_layer.hpp>
#include <llarp/layers/onion/onion_layer.hpp>
#include <memory>
namespace llarp
{
// forward declare
struct AbstractRouter;
struct Config;
} // namespace llarp
namespace llarp::layers
{
/// a holder type that abstracts out how lokinet works at each layer.
/// this is owned by AbstractRouter, and in time all the members on this will be moved to
/// AbstractRouter.
struct Layers
{
std::unique_ptr<platform::PlatformLayer> platform;
std::unique_ptr<flow::FlowLayer> flow;
std::unique_ptr<route::RouteLayer> route;
std::unique_ptr<onion::OnionLayer> onion;
// TODO: add more layers as they are wired up
/// move all owned members to a const version.
std::unique_ptr<const Layers>
freeze();
void
start_all() const;
void
stop_all() const;
};
/// this lets us hide which implementation we are using durring the refactor
std::unique_ptr<Layers>
make_layers(AbstractRouter& router, const Config& conf);
} // namespace llarp::layers

View file

@ -0,0 +1,60 @@
#pragma once
#include <unordered_map>
#include <unordered_set>
#include <llarp/path/path_types.hpp>
#include "onion_stats.hpp"
namespace llarp
{
struct AbstractRouter;
class EventLoopWakeup;
} // namespace llarp
namespace llarp::path
{
struct IHopHandler;
}
namespace llarp::layers::onion
{
class OnionLayer
{
/// all paths that exist in our lokinet.
std::unordered_set<std::shared_ptr<path::IHopHandler>> _all;
/// map from pathid to owned path.
std::unordered_map<PathID_t, std::weak_ptr<path::IHopHandler>> _id_to_owned;
/// map from pathid to transit path.
std::unordered_map<PathID_t, std::weak_ptr<path::IHopHandler>> _id_to_transit;
AbstractRouter& _router;
std::shared_ptr<EventLoopWakeup> _work;
/// submit all work items for onion layer cryptography
void
submit_work() const;
/// remove expired entires.
void
remove_expired();
public:
explicit OnionLayer(AbstractRouter&);
/// idempotently tell the onion layer that we have work to submit.
void
trigger_work() const;
void
start();
void
stop();
OnionStats
current_stats() const;
};
} // namespace llarp::layers::onion

View file

@ -0,0 +1,27 @@
#pragma once
#include <functional>
namespace llarp::layers::onion
{
/// all informational data about a singular onion path we created.
struct OnionPathInfo
{
bool
ready_to_use() const;
};
} // namespace llarp::layers::onion
namespace std
{
template <>
struct hash<llarp::layers::onion::OnionPathInfo>
{
size_t
operator()(const llarp::layers::onion::OnionPathInfo&) const
{
// TODO: implement me
return size_t{};
}
};
} // namespace std

View file

@ -0,0 +1,26 @@
#pragma once
#include <unordered_set>
#include "onion_path_info.hpp"
namespace llarp::layers::onion
{
struct OnionStats
{
std::unordered_set<OnionPathInfo> path;
double path_success_ratio;
/// set to true if we are routing transit traffic as a service node.
bool allowing_transit;
/// return true if we have enough paths built to operate as a client.
bool
ready() const;
/// return a set of all paths that are built.
std::unordered_set<OnionPathInfo>
built_paths() const;
};
} // namespace llarp::layers::onion

View file

@ -0,0 +1,217 @@
#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 <string>
#include <optional>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
namespace llarp::layers::flow
{
class FlowLayer;
}
namespace llarp::layers::platform
{
class AddrMapper;
struct AddressMapping
{
std::optional<flow::FlowInfo> flow_info;
PlatformAddr src, dst;
/// ip ranges for "exit"
std::vector<IPRange> owned_ranges;
/// return true if we own this exact range range.
bool
owns_range(const IPRange& range) const;
std::string
ToString() const;
};
/// container that holds an addressmapping and extra metadata we need in the addrmapper.
class AddressMappingEntry
{
/// the time this entry was last used.
std::chrono::steady_clock::time_point _last_used_at;
/// the entry itself.
AddressMapping _entry;
public:
/// return true if we match this src and dst address.
bool
has_addr(const PlatformAddr& src, const PlatformAddr& dst) const;
/// return true if this entry is for this flow info.
bool
has_flow_info(const flow::FlowInfo& flow_info) const;
/// return how long since we last used this entry.
inline auto
idle_for() const
{
return decltype(_last_used_at)::clock::now() - _last_used_at;
}
constexpr const auto&
last_used_at() const
{
return _last_used_at;
}
/// update last used and access the entry.
AddressMapping&
access();
/// view address mapping without updating last use.
const AddressMapping&
view() const;
};
/// in charge of mapping flow addresses and platform addresses to each other
class AddrMapper
{
// remote flow mappings
std::vector<AddressMappingEntry> _addrs;
const IPRange _our_range;
/// remove the lease recently used mapping.
void
remove_lru();
/// return a free address we can map to.
/// throws if full.
PlatformAddr
next_addr();
friend class ReservedAddressMapping;
public:
explicit AddrMapper(const IPRange& range);
const IPRange range;
/// unconditionally put an entry in.
/// stomps existing entry.
/// if full, removes the least frequenty used mapping.
void
put(AddressMapping&&);
/// prune all mappings we have no flows for
void
prune(flow::FlowLayer&);
/// get a platform for a flow if it exists. updates last used time.
std::optional<AddressMapping>
get_addr_for_flow(const flow::FlowInfo& flow);
/// return true if we have an address for this flow info, will not update last used time.
bool
has_addr_for_flow(const flow::FlowInfo& flow) const;
/// find a mapping that best matches src/dst address.
/// updates last use time.
std::optional<AddressMapping>
mapping_for(const PlatformAddr& src, const PlatformAddr& dst);
/// return all address mappings to this remote. does not update last used time.
std::vector<AddressMapping>
mappings_to(const flow::FlowAddr& remote) const;
/// return true if we have mapped as many ip addresses as we are able to.
bool
is_full() const;
/// get a new address mapping with a filled out destination address and an optionally provided
/// source address. if the source address is nullopt we will use the base address of the range
/// as the source. throws if full.
AddressMapping&
allocate_mapping(std::optional<PlatformAddr> src = std::nullopt);
/// read only iterate over all entries.
/// visit returns false to break iteration.
template <typename Viewer_t>
void
view_all_entries(Viewer_t&& visit) const
{
for (const auto& ent : _addrs)
{
if (not visit(ent.view()))
return;
}
}
/// return an iterator for first entry matching predicate.
/// this is a helper so we will not update last used.
template <typename Predicate_t>
[[nodiscard]] auto
find_if(Predicate_t&& pred)
{
return std::find_if(
_addrs.begin(), _addrs.end(), [pred](const auto& ent) { return pred(ent.view()); });
}
/// return an iterator for first entry matching predicate.
/// this is a helper so we will not update last used.
template <typename Predicate_t>
[[nodiscard]] auto
find_if(Predicate_t&& pred) const
{
return std::find_if(
_addrs.begin(), _addrs.end(), [pred](const auto& ent) { return pred(ent.view()); });
}
/// remove via predicate on entry.
template <typename Predicate_t>
void
remove_if_entry(Predicate_t&& pred)
{
auto itr = _addrs.begin();
while (itr != _addrs.end())
{
if (pred(*itr))
itr = _addrs.erase(itr);
else
++itr;
}
}
/// remove via predicate on mapping.
template <typename Predicate_t>
void
remove_if_mapping(Predicate_t&& pred)
{
remove_if_entry([pred](auto& ent) { return pred(ent.view()); });
}
/// return all exits we have.
/// last use time not updated.
std::vector<AddressMapping>
all_exits() const;
/// make an ip range map for all our exits.
net::IPRangeMap<flow::FlowAddr>
exit_map() const;
};
} // namespace llarp::layers::platform
namespace llarp
{
nlohmann::json
to_json(const llarp::layers::platform::AddressMapping&);
template <>
inline constexpr bool IsToStringFormattable<layers::platform::AddressMapping> = true;
} // namespace llarp

View file

@ -0,0 +1,95 @@
#pragma once
#include <cstdint>
#include <llarp/dns/server.hpp>
#include <memory>
#include <string_view>
#include <type_traits>
#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"
namespace llarp::layers::flow
{
class FlowLayer;
}
namespace llarp::layers::platform
{
class PlatformLayer;
class AddrMapper;
/// synthesizes authoritative RR for a .loki or .snode address.
class DNSZone
{
/// the flow address of the dns zone this is responsible for.
flow::FlowAddr _flow_addr;
/// any known cached ons names for this zone.
std::vector<std::string> _ons_names;
std::vector<dns::SRVData> _srv;
AddrMapper& _addr_mapper;
/// synth srv records rdata.
std::vector<dns::RData>
srv_records() const;
/// synth cname records rdata.
std::vector<dns::RData>
cname_records() const;
/// get ttl for a rr type.
uint32_t
ttl(dns::RRType rr_type) const;
std::string
zone_name() const;
public:
DNSZone(AddrMapper&, flow::FlowAddr addr);
/// add a new srv recrod to this dns zone.
void
add_srv_record(dns::SRVTuple record);
/// synthesize any resource records for a rr type.
std::vector<dns::ResourceRecord>
synth_rr_for(dns::RRType r_type) const;
};
/// handles dns queries, for .loki / .snode
class DNSQueryHandler : public dns::Resolver_Base
{
PlatformLayer& _plat;
std::unordered_map<flow::FlowAddr, std::unique_ptr<DNSZone>> _zones;
/// async resolve dns zone given a dns question.
void
async_obtain_dns_zone(
const dns::Question& qestion, std::function<void(std::optional<DNSZone>)> result_handler);
public:
explicit DNSQueryHandler(PlatformLayer& parent);
~DNSQueryHandler() override = default;
int
Rank() const override;
std::string_view
ResolverName() const override;
bool
MaybeHookDNS(
std::shared_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from) override;
};
} // namespace llarp::layers::platform

View file

@ -0,0 +1,25 @@
#pragma once
#include "llarp/util/formattable.hpp"
namespace llarp::layers::platform
{
/// the kind of traffic we are tunneling
enum class EtherType_t
{
/// ipv4/ipv6 unicast traffic
ip_unicast,
/// plainquic stream
plainquic,
/// flow layer auth protocol
proto_auth
};
std::string
ToString(EtherType_t kind);
} // namespace llarp::layers::platform
namespace llarp
{
template <>
inline constexpr bool IsToStringFormattable<layers::platform::EtherType_t> = true;
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <llarp/layers/flow/flow_addr.hpp>
#include <llarp/util/decaying_hashtable.hpp>
namespace llarp::layers::platform
{
/// cache for in network names (ONS records) with static TTL.
/// has no negative lookup cache.
class NameCache
{
/// a cache of all the names we have looked up.
/// note: unlike other uses in the code, the decaying hashtabe this wraps uses monotonic uptime
/// instead of unix timestamps.
util::DecayingHashTable<std::string, flow::FlowAddr> _names;
public:
NameCache(std::chrono::seconds ttl = 5min);
/// apply any cache expiry then get an entry from the cache if it exists.
std::optional<flow::FlowAddr>
get(std::string name);
/// apply any cache expiry and then put a name into the cache.
void
put(std::string name, flow::FlowAddr addr);
};
} // namespace llarp::layers::platform

View file

@ -0,0 +1,39 @@
#pragma once
#include <llarp/layers/flow/flow_layer.hpp>
#include "platform_addr.hpp"
#include "name_cache.hpp"
#include <chrono>
#include <functional>
#include <memory>
#include <optional>
#include <oxenc/variant.h>
#include <llarp/util/types.hpp>
#include <llarp/dns/question.hpp>
#include <variant>
#include <vector>
namespace llarp::layers::platform
{
class PlatformLayer;
class NameCache;
class NameResolver
{
NameCache& _name_cache;
flow::FlowLayer& _flow_layer;
public:
NameResolver(NameCache& name_cache, flow::FlowLayer& flow_layer);
/// resolve name using in network resolution
void
resolve_flow_addr_async(
std::string name, std::function<void(std::optional<flow::FlowAddr>)> handler);
};
} // namespace llarp::layers::platform

View file

@ -0,0 +1,43 @@
#pragma once
#include <llarp/util/formattable.hpp>
#include "ethertype.hpp"
#include "llarp/net/ip_packet.hpp"
#include "platform_addr.hpp"
#include <cstdint>
#include <vector>
namespace llarp::layers::platform
{
/// platfrom datum that is used to read and write to the os
struct OSTraffic
{
/// the kind of traffic it is
EtherType_t kind;
/// platform level source and destination addresses of this traffic
PlatformAddr src, dst;
/// the datum itself
std::vector<uint8_t> data;
OSTraffic() = default;
/// construct from a valid ip packet.
explicit OSTraffic(net::IPPacket);
OSTraffic(EtherType_t kind, PlatformAddr src, PlatformAddr dst, std::vector<uint8_t> data);
std::string
ToString() const;
private:
/// make sure all the checksums are correct.
void
recompute_checksums();
};
} // namespace llarp::layers::platform
namespace llarp
{
template <>
inline constexpr bool IsToStringFormattable<layers::platform::OSTraffic> = true;
}

View file

@ -0,0 +1,83 @@
#pragma once
#include <llarp/net/net_int.hpp>
#include <array>
#include <numeric>
#include <string>
#include <optional>
#include <functional>
namespace llarp::layers::platform
{
/// a (ipv6 address, flowlabel) tuple that we use for platform layer side addressing
struct PlatformAddr
{
/// ip network address
net::ipv6addr_t ip{};
/// flowlabel, ingored if ip is a v4 mapped address
/// this is used as a metric to do flow layer isolation where we can use different identities
/// and / or paths on each. defaults to zero.
net::flowlabel_t flowlabel{};
PlatformAddr() = default;
PlatformAddr(const PlatformAddr&) = default;
PlatformAddr(PlatformAddr&&) = default;
PlatformAddr&
operator=(const PlatformAddr&) = default;
PlatformAddr&
operator=(PlatformAddr&&) = default;
explicit PlatformAddr(net::ipaddr_t addr);
explicit PlatformAddr(const std::string& str);
explicit PlatformAddr(huint128_t addr);
/// string representation
std::string
ToString() const;
/// convert to variant. compacts to ipv4 if it is a mapped address.
net::ipaddr_t
as_ipaddr() const;
/// returns ipv4 address if it's a mapped address. otherwise returns nullopt.
std::optional<net::ipv4addr_t>
as_ipv4addr() const;
bool
operator==(const PlatformAddr& other) const;
bool
operator!=(const PlatformAddr& other) const;
bool
operator<(const PlatformAddr& other) const;
};
} // namespace llarp::layers::platform
namespace llarp
{
template <>
inline constexpr bool IsToStringFormattable<layers::platform::PlatformAddr> = true;
}
namespace std
{
template <>
struct hash<llarp::layers::platform::PlatformAddr>
{
size_t
operator()(const llarp::layers::platform::PlatformAddr& addr) const
{
const std::array<uint64_t, 4> data{addr.ip.n.upper, addr.ip.n.lower, addr.flowlabel.n};
int n{};
return std::accumulate(
data.begin(), data.end(), uint64_t{}, [&n](uint64_t lhs, uint64_t rhs) {
return lhs ^ (rhs << (n += (n ? 2 : 1)));
});
}
};
} // namespace std

View file

@ -0,0 +1,216 @@
#pragma once
#include "addr_mapper.hpp"
#include "platform_addr.hpp"
#include "platform_stats.hpp"
#include "os_traffic.hpp"
#include <llarp/config/config.hpp>
#include <llarp/net/ip_packet.hpp>
#include <llarp/net/ip_range.hpp>
#include <llarp/util/formattable.hpp>
#include <llarp/util/types.hpp>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace llarp
{
class EventLoop;
}
namespace llarp::dns
{
class Server;
}
namespace llarp::quic
{
// TODO: forward declared no idea if this is an existing type or not.
class TunnelManager;
} // namespace llarp::quic
namespace llarp::vpn
{
class NetworkInterface;
class IRouteManager;
} // namespace llarp::vpn
namespace llarp::layers::flow
{
class FlowLayer;
class FlowAddr;
struct FlowInfo;
struct FlowTraffic;
} // namespace llarp::layers::flow
namespace llarp::layers::platform
{
class DNSZone;
class DNSQueryHandler;
// handles reading and writing os traffic, like ip packets or whatever embedded lokinet wants to
// do.
class OSTraffic_IO_Base
{
PlatformAddr _our_addr;
std::vector<OSTraffic> _recv_queue;
std::atomic<bool> _running;
protected:
std::weak_ptr<EventLoop> _loop;
std::shared_ptr<vpn::NetworkInterface> _netif;
std::shared_ptr<quic::TunnelManager> _quic;
virtual void got_ip_packet(net::IPPacket);
public:
explicit OSTraffic_IO_Base(const EventLoop_ptr&);
virtual ~OSTraffic_IO_Base() = default;
/// get the platform address of the underlying device managed.
constexpr const PlatformAddr&
our_platform_addr() const
{
return _our_addr;
}
/// attach to a vpn interface. sets up io handlers.
/// idempotently wakes up wakeup_recv when we get packets read.
void
attach(
std::shared_ptr<vpn::NetworkInterface> netif, std::shared_ptr<EventLoopWakeup> wakeup_recv);
/// attach to a quic handler. idempotently wakes up wakeup_recv when we get any kind of data
/// from quic_tun.
void
attach(
std::shared_ptr<quic::TunnelManager> quic_tun,
std::shared_ptr<EventLoopWakeup> wakeup_recv);
/// read from the os.
/// this MUST NOT have any internal userland buffering from previous calls to
/// read_platform_traffic().
virtual std::vector<OSTraffic>
read_platform_traffic();
/// write traffic to the os.
virtual void
write_platform_traffic(std::vector<OSTraffic>&& traff);
/// stop all future io and detach from event loop.
void
detach();
};
struct platform_io_wakeup_handler;
/// responsible for bridging the os and the flow layer.
class PlatformLayer
{
protected:
friend struct platform_io_wakeup_handler;
AbstractRouter& _router;
flow::FlowLayer& _flow_layer;
/// called to wake up the loop to read os traffic
std::shared_ptr<EventLoopWakeup> _os_recv;
NetworkConfig _netconf;
/// handler of dns queries.
std::shared_ptr<DNSQueryHandler> _dns_query_handler;
std::weak_ptr<vpn::NetworkInterface> _netif;
std::unique_ptr<OSTraffic_IO_Base> _io;
std::shared_ptr<DNSZone> _local_dns_zone;
/// called once per event loop tick after platform layer event loop polled for and consumed io.
void
on_io();
/// make platform specific io with quarks.
virtual std::unique_ptr<OSTraffic_IO_Base>
make_io() const;
public:
/// holds address mapping state.
AddrMapper addr_mapper;
/// underlying io.
const std::unique_ptr<OSTraffic_IO_Base>& io{_io};
/// wake this up when the event loop reads ip packets.
const std::shared_ptr<EventLoopWakeup>& wakeup{_os_recv};
PlatformLayer(const PlatformLayer&) = delete;
PlatformLayer(PlatformLayer&&) = delete;
PlatformLayer(
AbstractRouter& router, flow::FlowLayer& flow_layer, const NetworkConfig& netconf);
void
start();
void
stop();
std::shared_ptr<vpn::NetworkInterface>
vpn_interface() const;
vpn::IRouteManager*
route_manager() const;
/// return the dns zone for our local self.
DNSZone&
local_dns_zone();
/// called each time we get a bunch of os traffic.
void
os_traffic(std::vector<OSTraffic>&& pkts);
/// give the platform layer flow layer traffic to write to the os.
void
flow_traffic(std::vector<flow::FlowTraffic>&& traff);
/// async resolve dns zone given a flow level name.
void
async_obtain_dns_zone(
std::string name, std::function<void(std::optional<DNSZone>)> result_handler);
void
map_remote(
std::string name,
std::string auth,
std::vector<IPRange> ranges,
std::optional<PlatformAddr> src = std::nullopt,
std::function<void(std::optional<flow::FlowInfo>, std::string)> result_handler = nullptr);
/// calls map_remote() for exits
void
map_exit(
std::string name,
std::string auth,
std::vector<IPRange> ranges,
std::function<void(bool, std::string)> result_handler);
/// unmap an exit from a range
void
unmap_exit(flow::FlowAddr remote, std::optional<IPRange> range = std::nullopt);
/// unmap every exit that is mapped to this range.
void
unmap_all_exits_on_range(IPRange range);
PlatformStats
current_stats() const;
};
} // namespace llarp::layers::platform

View file

@ -0,0 +1,12 @@
#pragma once
#include <unordered_set>
#include <llarp/layers/flow/flow_addr.hpp>
#include <llarp/net/ip_range_map.hpp>
namespace llarp::layers::platform
{
struct PlatformStats
{};
} // namespace llarp::layers::platform

View file

@ -0,0 +1,22 @@
#pragma once
#include <llarp/crypto/types.hpp>
#include <optional>
#include <llarp/layers/flow/flow_info.hpp>
#include <llarp/path/transit_hop.hpp>
namespace llarp::layers::route
{
/// a routable destination when we are an endpoint router transiting routing layer traffic.
class Destination
{
public:
PubKey src;
PubKey dst;
/// the info of the transit hop that we are located.
path::TransitHopInfo onion_info;
/// flow layer convo this routing destination is fascilitating. if applicable.
std::optional<flow::FlowInfo> flow_info;
};
} // namespace llarp::layers::route

View file

@ -0,0 +1,58 @@
#pragma once
#include <llarp/path/transit_hop.hpp>
#include <llarp/crypto/types.hpp>
#include <memory>
#include "destination.hpp"
namespace llarp
{
struct AbstractRouter;
}
namespace llarp::layers::route
{
class RouteLayer
{
std::vector<std::unique_ptr<Destination>> _destinations;
public:
explicit RouteLayer(AbstractRouter&);
/// create a routing destination belonging to pubkey+endpoint transithop going to a snode.
Destination*
create_destination(const path::TransitHopInfo& info, PubKey src, RouterID dst);
/// remove a routing destination for an existing endpoint transit hop.
/// returns true if we removed something.
bool
remove_destination_on(const path::TransitHopInfo& info);
/// remove all routing destination from a source to a destination.
/// returns the number of entries we removed.
size_t
remove_destinations_to(const PubKey& src, const RouterID& dst);
/// remove all routing destination from a source.
/// returns the number of entries we removed.
size_t
remove_destinations_from(const PubKey& dst);
/// get a routing destination given a local path rxID.
Destination*
destination_to(const PathID_t& rxid);
/// get a routing destination given its local path txid.
Destination*
destination_from(const PathID_t& txid);
/// get all routing destinations given the source pubkey going to a snode dst.
std::vector<Destination*>
all_destinations_from(const PubKey& src);
/// maybe fetch a routing destination that was previously created for an endpoint transit hop.
Destination*
destination_on(const path::TransitHopInfo& info) const;
};
} // namespace llarp::layers::route