initial commit for #1595

This commit is contained in:
Jeff Becker 2021-04-14 11:07:06 -04:00
parent 545021aa3d
commit fedc56e3f1
No known key found for this signature in database
GPG Key ID: F357B3B42F6F9B05
26 changed files with 837 additions and 243 deletions

View File

@ -154,6 +154,7 @@ add_library(liblokinet
messages/relay_status.cpp
net/address_info.cpp
net/exit_info.cpp
net/traffic_policy.cpp
nodedb.cpp
path/ihophandler.cpp
path/path_context.cpp
@ -212,6 +213,7 @@ add_library(liblokinet
service/name.cpp
service/outbound_context.cpp
service/protocol.cpp
service/protocol_type.cpp
service/router_lookup_job.cpp
service/sendcontext.cpp
service/session.cpp

View File

@ -415,19 +415,25 @@ namespace llarp
"on the server and may pose liability concerns. Enable at your own risk.",
});
// TODO: not implemented yet!
// TODO: define the order of precedence (e.g. is whitelist applied before blacklist?)
// additionally, what's default? What if I don't whitelist anything?
/*
conf.defineOption<std::string>("network", "exit-whitelist", MultiValue, Comment{
"List of destination protocol:port pairs to whitelist, example: udp:*",
"or tcp:80. Multiple values supported.",
}, FIXME-acceptor);
conf.defineOption<std::string>(
"network",
"traffic-whitelist",
MultiValue,
Comment{
"List of ip traffic whitelist, anything not specified will be dropped by us."
"examples:",
"tcp for all tcp traffic regardless of port",
"0x69 for all packets using ip protocol 0x69"
"udp/53 for udp port 53",
"tcp/smtp for smtp port",
},
[this](std::string arg) {
if (not m_TrafficPolicy)
m_TrafficPolicy = net::TrafficPolicy{};
conf.defineOption<std::string>("network", "exit-blacklist", MultiValue, Comment{
"Blacklist of destinations (same format as whitelist).",
}, FIXME-acceptor);
*/
// this will throw on error
m_TrafficPolicy->protocols.emplace(arg);
});
conf.defineOption<std::string>(
"network",

View File

@ -122,6 +122,9 @@ namespace llarp
std::optional<huint128_t> m_baseV6Address;
std::set<IPRange> m_AdvertisedRanges;
std::optional<net::TrafficPolicy> m_TrafficPolicy;
// TODO:
// on-up
// on-down

View File

@ -158,56 +158,6 @@ namespace llarp
m_AuthPolicy = std::move(auth);
}
/*
* TODO: reinstate this option (it's not even clear what section this came from...)
*
if (k == "isolate-network" && IsTrueValue(v.c_str()))
{
#if defined(__linux__)
LogInfo(Name(), " isolating network...");
if (!SpawnIsolatedNetwork())
{
LogError(Name(), " failed to spawn isolated network");
return false;
}
LogInfo(Name(), " booyeah network isolation succeeded");
return true;
#else
LogError(Name(), " network isolation is not supported on your platform");
return false;
#endif
}
*/
/*
* TODO: this is currently defined for [router] / RouterConfig, but is clearly an [endpoint]
* option. either move it to [endpoint] or plumb RouterConfig through
*
if (k == "strict-connect")
{
RouterID connect;
if (!connect.FromString(v))
{
LogError(Name(), " invalid snode for strict-connect: ", v);
return false;
}
RouterContact rc;
if (!m_router->nodedb()->Get(connect, rc))
{
LogError(
Name(), " we don't have the RC for ", v, " so we can't use it in strict-connect");
return false;
}
for (const auto& ai : rc.addrs)
{
m_StrictConnectAddrs.emplace_back(ai);
LogInfo(Name(), " added ", m_StrictConnectAddrs.back(), " to strict connect");
}
return true;
}
*/
m_LocalResolverAddr = dnsConf.m_bind;
m_UpstreamResolvers = dnsConf.m_upstreamDNS;
@ -916,34 +866,46 @@ namespace llarp
auto itr = m_IPToAddr.find(dst);
if (itr == m_IPToAddr.end())
{
const auto exits = m_ExitMap.FindAll(dst);
if (IsBogon(dst) or exits.empty())
// find all ranges that match the destination ip
const auto exitEntries = m_ExitMap.FindAllEntries(dst);
if (exitEntries.empty())
{
// send icmp unreachable
const auto icmp = pkt.MakeICMPUnreachable();
if (icmp.has_value())
// send icmp unreachable as we dont have any exits for this ip
if (const auto icmp = pkt.MakeICMPUnreachable())
{
HandleWriteIPPacket(icmp->ConstBuffer(), dst, src, 0);
}
return;
}
else
service::Address addr{};
for (const auto& [range, exitAddr] : exitEntries)
{
const auto addr = *exits.begin();
if (addr.IsZero()) // drop
return;
pkt.ZeroSourceAddress();
MarkAddressOutbound(addr);
EnsurePathToService(
addr,
[addr, pkt, self = this](service::Address, service::OutboundContext* ctx) {
if (ctx)
{
ctx->sendTimeout = 5s;
}
self->SendToOrQueue(addr, pkt.ConstBuffer(), service::ProtocolType::Exit);
},
1s);
if (range.BogonRange() and range.Contains(dst))
{
// we permit this because it matches our rules and we allow bogons
addr = exitAddr;
}
else if (not IsBogon(dst))
{
// allow because the destination is not a bogon and the mapped range is not a bogon
addr = exitAddr;
}
// we do not permit bogons when they don't explicitly match a permitted bogon range
}
if (addr.IsZero()) // drop becase no exit was found that matches our rules
return;
pkt.ZeroSourceAddress();
MarkAddressOutbound(addr);
EnsurePathToService(
addr,
[addr, pkt, self = this](service::Address, service::OutboundContext* ctx) {
if (ctx)
{
ctx->sendTimeout = 5s;
}
self->SendToOrQueue(addr, pkt.ConstBuffer(), service::ProtocolType::Exit);
},
1s);
return;
}
bool rewriteAddrs = true;
@ -979,6 +941,18 @@ namespace llarp
});
}
bool
TunEndpoint::ShouldAllowTraffic(const net::IPPacket& pkt) const
{
if (const auto exitPolicy = GetExitPolicy())
{
if (not exitPolicy->AllowsTraffic(pkt))
return false;
}
return true;
}
bool
TunEndpoint::HandleInboundPacket(
const service::ConvoTag tag,
@ -1024,6 +998,11 @@ namespace llarp
if (m_state->m_ExitEnabled)
{
// exit side from exit
// check packet against exit policy and if as needed
if (not ShouldAllowTraffic(pkt))
return false;
src = ObtainIPForAddr(addr);
if (t == service::ProtocolType::Exit)
{
@ -1055,18 +1034,22 @@ namespace llarp
src = pkt.srcv6();
}
// find what exit we think this should be for
const auto mapped = m_ExitMap.FindAll(src);
if (IsBogon(src))
return false;
if (const auto ptr = std::get_if<service::Address>(&addr))
const auto mapped = m_ExitMap.FindAllEntries(src);
bool allow = false;
for (const auto& [range, exitAddr] : mapped)
{
if (mapped.count(*ptr) == 0)
if ((range.BogonRange() and range.Contains(src)) or not IsBogon(src))
{
// we got exit traffic from someone who we should not have gotten it from
return false;
// this range is either not a bogon or is a bogon we are explicitly allowing
if (const auto* ptr = std::get_if<service::Address>(&addr))
{
// allow if this address matches the endpoint we think it should be
allow = exitAddr == *ptr;
}
}
}
if (not allow)
return false;
}
else
{

View File

@ -123,6 +123,18 @@ namespace llarp
bool
HasLocalIP(const huint128_t& ip) const;
std::optional<net::TrafficPolicy>
GetExitPolicy() const override
{
return m_ExitPolicy;
}
/// ip packet against any exit policies we have
/// returns false if this traffic is disallowed by any of those policies
/// returns true otherwise
bool
ShouldAllowTraffic(const net::IPPacket& pkt) const;
/// get a key for ip address
std::optional<std::variant<service::Address, RouterID>>
ObtainAddrForIP(huint128_t ip) const override;
@ -245,6 +257,8 @@ namespace llarp
std::shared_ptr<vpn::NetworkInterface> m_NetIf;
std::unique_ptr<vpn::PacketRouter> m_PacketRouter;
std::optional<net::TrafficPolicy> m_ExitPolicy;
};
} // namespace handlers

View File

@ -15,6 +15,7 @@
/// Exit info model
namespace llarp
{
/// deprecated don't use me , this is only for backwards compat
struct ExitInfo
{
IpAddress ipAddress;

View File

@ -4,7 +4,7 @@
#include <llarp/util/buffer.hpp>
#include <llarp/util/endian.hpp>
#include <llarp/util/mem.hpp>
#include <llarp/util/str.hpp>
#ifndef _WIN32
#include <netinet/in.h>
#endif
@ -32,6 +32,33 @@ namespace llarp
{
namespace net
{
std::string
IPProtocolName(IPProtocol proto)
{
if (const auto* ent = ::getprotobynumber(static_cast<uint8_t>(proto)))
{
return ent->p_name;
}
throw std::invalid_argument{
"cannot determine protocol name for ip proto '" + std::to_string(static_cast<int>(proto))
+ "'"};
}
IPProtocol
ParseIPProtocol(std::string data)
{
if (const auto* ent = ::getprotobyname(data.c_str()))
{
return static_cast<IPProtocol>(ent->p_proto);
}
if (starts_with(data, "0x"))
{
if (const int intVal = std::stoi(data.substr(2), nullptr, 16); intVal > 0)
return static_cast<IPProtocol>(intVal);
}
throw std::invalid_argument{"no such ip protocol: '" + data + "'"};
}
inline static uint32_t*
in6_uint32_ptr(in6_addr& addr)
{
@ -88,6 +115,19 @@ namespace llarp
return ManagedBuffer(b);
}
std::optional<nuint16_t>
IPPacket::DstPort() const
{
switch (IPProtocol{Header()->protocol})
{
case IPProtocol::TCP:
case IPProtocol::UDP:
return nuint16_t{*reinterpret_cast<const uint16_t*>(buf + (Header()->ihl * 4) + 2)};
default:
return std::nullopt;
}
}
huint32_t
IPPacket::srcv4() const
{

View File

@ -112,6 +112,31 @@ namespace llarp
{
namespace net
{
/// "well known" ip protocols
/// TODO: extend this to non "well known values"
enum class IPProtocol : uint8_t
{
ICMP = 0x01,
IGMP = 0x02,
IPIP = 0x04,
TCP = 0x06,
UDP = 0x11,
GRE = 0x2F,
ICMP6 = 0x3A,
OSFP = 0x59,
PGM = 0x71,
};
/// get string representation of this protocol
/// throws std::invalid_argument if we don't know the name of this ip protocol
std::string
IPProtocolName(IPProtocol proto);
/// parse a string to an ip protocol
/// throws std::invalid_argument if cannot be parsed
IPProtocol
ParseIPProtocol(std::string data);
/// an Packet
struct IPPacket
{
@ -264,6 +289,10 @@ namespace llarp
huint128_t
dst4to6Lan() const;
/// get destination port if applicable
std::optional<nuint16_t>
DstPort() const;
void
UpdateIPv4Address(nuint32_t src, nuint32_t dst);

View File

@ -1,7 +1,38 @@
#include "ip_range.hpp"
#include "oxenmq/bt_serialize.h"
#include "llarp/util/bencode.h"
namespace llarp
{
bool
IPRange::BEncode(llarp_buffer_t* buf) const
{
const auto str = oxenmq::bt_serialize(ToString());
return buf->write(str.begin(), str.end());
}
bool
IPRange::BDecode(llarp_buffer_t* buf)
{
const auto* start = buf->cur;
if (not bencode_discard(buf))
return false;
std::string_view data{
reinterpret_cast<const char*>(start), static_cast<size_t>(buf->cur - start)};
std::string str;
try
{
oxenmq::bt_deserialize(data, str);
}
catch (std::exception&)
{
return false;
}
return FromString(str);
}
bool
IPRange::FromString(std::string str)
{

View File

@ -3,11 +3,16 @@
#include "ip.hpp"
#include "net_bits.hpp"
#include <llarp/util/bits.hpp>
#include <llarp/util/buffer.hpp>
#include <llarp/util/types.hpp>
#include <string>
namespace llarp
{
/// forward declare
bool
IsBogon(huint128_t ip);
struct IPRange
{
using Addr_t = huint128_t;
@ -34,6 +39,19 @@ namespace llarp
return ipv4_map.Contains(addr);
}
/// return true if we intersect with a bogon range
constexpr bool
BogonRange() const
{
// special case for 0.0.0.0/0
if (IsV4() and netmask_bits == netmask_ipv6_bits(96))
return false;
// special case for ::/0
if (netmask_bits == huint128_t{0})
return false;
return IsBogon(addr) or IsBogon(HighestAddr());
}
/// return the number of bits set in the hostmask
constexpr int
HostmaskBits() const
@ -106,6 +124,12 @@ namespace llarp
bool
FromString(std::string str);
bool
BEncode(llarp_buffer_t* buf) const;
bool
BDecode(llarp_buffer_t* buf);
};
} // namespace llarp

View File

@ -2,7 +2,7 @@
#include "ip_range.hpp"
#include <llarp/util/status.hpp>
#include <list>
#include <vector>
namespace llarp
{
@ -19,7 +19,7 @@ namespace llarp
using IP_t = Range_t::Addr_t;
using Entry_t = std::pair<Range_t, Value_t>;
using Container_t = std::list<Entry_t>;
using Container_t = std::vector<Entry_t>;
/// get a set of all values
std::set<Value_t>
@ -89,15 +89,15 @@ namespace llarp
return std::nullopt;
}
/// return a set of all values who's range contains this IP
std::set<Value_t>
FindAll(const IP_t& addr) const
/// return a set of all entries who's range contains this IP
std::set<Entry_t>
FindAllEntries(const IP_t& addr) const
{
std::set<Value_t> found;
std::set<Entry_t> found;
for (const auto& entry : m_Entries)
{
if (entry.first.Contains(addr))
found.insert(entry.second);
found.insert(entry);
}
return found;
}
@ -114,8 +114,7 @@ namespace llarp
void
Insert(const Range_t& addr, const Value_t& val)
{
m_Entries.emplace_front(addr, val);
m_Entries.sort(CompareEntry{});
m_Entries.emplace_back(addr, val);
}
template <typename Visit_t>

View File

@ -0,0 +1,213 @@
#include "traffic_policy.hpp"
#include "llarp/util/str.hpp"
namespace llarp::net
{
ProtocolInfo::ProtocolInfo(std::string_view data)
{
const auto parts = split(data, "/");
protocol = ParseIPProtocol(std::string{parts[0]});
if (parts.size() == 2)
{
huint16_t portHost{};
std::string portStr{parts[1]};
std::string protoName = IPProtocolName(protocol);
if (const auto* serv = ::getservbyname(portStr.c_str(), protoName.c_str()))
{
portHost.h = serv->s_port;
}
else if (const auto portInt = std::stoi(portStr); portInt > 0)
{
portHost.h = portInt;
}
else
throw std::invalid_argument{"invalid port in protocol info: " + portStr};
port = ToNet(portHost);
}
else
port = std::nullopt;
}
bool
ProtocolInfo::MatchesPacket(const IPPacket& pkt) const
{
if (pkt.Header()->protocol != static_cast<std::underlying_type_t<IPProtocol>>(protocol))
return false;
if (not port)
return true;
if (const auto maybe = pkt.DstPort())
{
return *port == *maybe;
}
// we can't tell what the port is but the protocol matches and that's good enough
return true;
}
bool
TrafficPolicy::AllowsTraffic(const IPPacket& pkt) const
{
if (protocols.empty() and ranges.empty())
return true;
for (const auto& proto : protocols)
{
if (proto.MatchesPacket(pkt))
return true;
}
for (const auto& range : ranges)
{
huint128_t dst;
if (pkt.IsV6())
dst = pkt.dstv6();
else if (pkt.IsV4())
dst = pkt.dst4to6();
else
return false;
if (range.Contains(dst))
return true;
}
return false;
}
bool
ProtocolInfo::BDecode(llarp_buffer_t* buf)
{
port = std::nullopt;
std::vector<uint64_t> vals;
if (not bencode_read_list(
[&vals](llarp_buffer_t* buf, bool more) {
if (more)
{
uint64_t intval;
if (not bencode_read_integer(buf, &intval))
return false;
vals.push_back(intval);
}
return true;
},
buf))
return false;
if (vals.empty())
return false;
if (vals.size() >= 1)
{
if (vals[0] > 255)
return false;
protocol = static_cast<IPProtocol>(vals[0]);
}
if (vals.size() >= 2)
{
if (vals[1] > 65536)
return false;
port = ToNet(huint16_t{static_cast<uint16_t>(vals[1])});
}
return true;
}
bool
ProtocolInfo::BEncode(llarp_buffer_t* buf) const
{
if (not bencode_start_list(buf))
return false;
if (not bencode_write_uint64(buf, static_cast<std::underlying_type_t<IPProtocol>>(protocol)))
return false;
if (port)
{
const auto hostint = ToHost(*port);
if (not bencode_write_uint64(buf, hostint.h))
return false;
}
return bencode_end(buf);
}
bool
TrafficPolicy::BEncode(llarp_buffer_t* buf) const
{
if (not bencode_start_dict(buf))
return false;
if (not bencode_write_bytestring(buf, "p", 1))
return false;
if (not bencode_start_list(buf))
return false;
for (const auto& item : protocols)
{
if (not item.BEncode(buf))
return false;
}
if (not bencode_end(buf))
return false;
if (not bencode_write_bytestring(buf, "r", 1))
return false;
if (not bencode_start_list(buf))
return false;
for (const auto& item : ranges)
{
if (not item.BEncode(buf))
return false;
}
if (not bencode_end(buf))
return false;
return bencode_end(buf);
}
bool
TrafficPolicy::BDecode(llarp_buffer_t* buf)
{
return bencode_read_dict(
[&](llarp_buffer_t* buffer, llarp_buffer_t* key) -> bool {
if (key == nullptr)
return true;
if (*key == "p")
{
return BEncodeReadSet(protocols, buffer);
}
if (*key == "r")
{
return BEncodeReadSet(ranges, buffer);
}
return bencode_discard(buffer);
},
buf);
}
util::StatusObject
ProtocolInfo::ExtractStatus() const
{
util::StatusObject status{
{"protocol", static_cast<uint>(protocol)},
};
if (port)
status["port"] = ToHost(*port).h;
return status;
}
util::StatusObject
TrafficPolicy::ExtractStatus() const
{
std::vector<util::StatusObject> rangesStatus;
std::transform(
ranges.begin(), ranges.end(), std::back_inserter(rangesStatus), [](const auto& range) {
return range.ToString();
});
std::vector<util::StatusObject> protosStatus;
std::transform(
protocols.begin(),
protocols.end(),
std::back_inserter(protosStatus),
[](const auto& proto) { return proto.ExtractStatus(); });
return util::StatusObject{{"ranges", rangesStatus}, {"protocols", protosStatus}};
}
} // namespace llarp::net

View File

@ -0,0 +1,70 @@
#pragma once
#include "ip_range.hpp"
#include "ip_packet.hpp"
#include "llarp/util/status.hpp"
#include <set>
namespace llarp::net
{
/// information about an IP protocol
struct ProtocolInfo
{
/// ip protocol byte of this protocol
IPProtocol protocol;
/// the layer 3 port if applicable
std::optional<nuint16_t> port;
bool
BEncode(llarp_buffer_t* buf) const;
bool
BDecode(llarp_buffer_t* buf);
util::StatusObject
ExtractStatus() const;
/// returns true if an ip packet looks like it matches this protocol info
/// returns false otherwise
bool
MatchesPacket(const IPPacket& pkt) const;
bool
operator<(const ProtocolInfo& other) const
{
if (port and other.port)
{
return protocol < other.protocol or *port < *other.port;
}
return protocol < other.protocol;
}
ProtocolInfo() = default;
explicit ProtocolInfo(std::string_view spec);
};
/// information about what traffic an endpoint will carry
struct TrafficPolicy
{
/// ranges that are explicitly allowed
std::set<IPRange> ranges;
/// protocols that are explicity allowed
std::set<ProtocolInfo> protocols;
bool
BEncode(llarp_buffer_t* buf) const;
bool
BDecode(llarp_buffer_t* buf);
util::StatusObject
ExtractStatus() const;
/// returns true if we allow the traffic in this ip packet
/// returns false otherwise
bool
AllowsTraffic(const IPPacket& pkt) const;
};
} // namespace llarp::net

View File

@ -131,6 +131,13 @@ namespace llarp::quic
void
receive_packet(const service::ConvoTag& tag, const llarp_buffer_t& buf);
/// return true if we have any listeners added
inline bool
hasListeners() const
{
return not incoming_handlers_.empty();
}
private:
EndpointBase& service_endpoint_;

View File

@ -113,12 +113,41 @@ namespace llarp
BuildOne();
return;
}
introSet().I.clear();
// add supported ethertypes
if (HasIfAddr())
{
const auto ourIP = net::HUIntToIn6(GetIfAddr());
if (ipv6_is_mapped_ipv4(ourIP))
{
introSet().supportedProtocols.push_back(ProtocolType::TrafficV4);
}
else
{
introSet().supportedProtocols.push_back(ProtocolType::TrafficV6);
}
// exit related stuffo
if (m_state->m_ExitEnabled)
{
introSet().supportedProtocols.push_back(ProtocolType::Exit);
introSet().exitTrafficPolicy = GetExitPolicy();
introSet().ownedRanges = GetOwnedRanges();
}
}
// add quic ethertype if we have listeners set up
if (auto* quic = GetQUICTunnel())
{
if (quic->hasListeners())
introSet().supportedProtocols.push_back(ProtocolType::QUIC);
}
introSet().intros.clear();
for (auto& intro : introset)
{
introSet().I.emplace_back(std::move(intro));
introSet().intros.emplace_back(std::move(intro));
}
if (introSet().I.size() == 0)
if (introSet().intros.empty())
{
LogWarn("not enough intros to publish introset for ", Name());
if (ShouldBuildMore(now))
@ -145,7 +174,7 @@ namespace llarp
Endpoint::IsReady() const
{
const auto now = Now();
if (introSet().I.size() == 0)
if (introSet().intros.empty())
return false;
if (introSet().IsExpired(now))
return false;
@ -716,7 +745,7 @@ namespace llarp
void
Endpoint::PutNewOutboundContext(const service::IntroSet& introset, llarp_time_t left)
{
Address addr{introset.A.Addr()};
Address addr{introset.addressKeys.Addr()};
auto& remoteSessions = m_state->m_RemoteSessions;
auto& serviceLookups = m_state->m_PendingServiceLookups;

View File

@ -113,6 +113,22 @@ namespace llarp
return {0};
}
/// get the exit policy for our exit if we have one
/// override me
virtual std::optional<net::TrafficPolicy>
GetExitPolicy() const
{
return std::nullopt;
};
/// get the ip ranges we claim to own
/// override me
virtual std::vector<IPRange>
GetOwnedRanges() const
{
return {};
};
virtual void
Thaw(){};

View File

@ -155,31 +155,31 @@ namespace llarp
{
EncryptedIntroSet encrypted;
if (other_i.I.size() == 0)
return {};
IntroSet i(other_i);
if (other_i.intros.empty())
return std::nullopt;
IntroSet i{other_i};
encrypted.nounce.Randomize();
// set timestamp
// TODO: round to nearest 1000 ms
i.T = now;
i.timestampSignedAt = now;
encrypted.signedAt = now;
// set service info
i.A = pub;
i.addressKeys = pub;
// set public encryption key
i.K = pq_keypair_to_public(pq);
i.sntrupKey = pq_keypair_to_public(pq);
std::array<byte_t, MAX_INTROSET_SIZE> tmp;
llarp_buffer_t buf(tmp);
llarp_buffer_t buf{tmp};
if (not i.BEncode(&buf))
return {};
return std::nullopt;
// rewind and resize buffer
buf.sz = buf.cur - buf.base;
buf.cur = buf.base;
const SharedSecret k(i.A.Addr());
const SharedSecret k{i.addressKeys.Addr()};
CryptoManager::instance()->xchacha20(buf, k, encrypted.nounce);
encrypted.introsetPayload.resize(buf.sz);
std::copy_n(buf.base, buf.sz, encrypted.introsetPayload.data());
encrypted.introsetPayload = buf.copy();
if (not encrypted.Sign(derivedSignKey))
return {};
return std::nullopt;
return encrypted;
}
} // namespace service

View File

@ -90,7 +90,7 @@ namespace llarp::service
llarp_buffer_t buf(payload);
CryptoManager::instance()->xchacha20(buf, k, nounce);
if (not i.BDecode(&buf))
return std::nullopt;
return {};
return i;
}
@ -139,16 +139,39 @@ namespace llarp::service
util::StatusObject
IntroSet::ExtractStatus() const
{
util::StatusObject obj{{"published", to_json(T)}};
util::StatusObject obj{{"published", to_json(timestampSignedAt)}};
std::vector<util::StatusObject> introsObjs;
std::transform(
I.begin(),
I.end(),
intros.begin(),
intros.end(),
std::back_inserter(introsObjs),
[](const auto& intro) -> util::StatusObject { return intro.ExtractStatus(); });
obj["intros"] = introsObjs;
if (!topic.IsZero())
obj["topic"] = topic.ToString();
std::vector<util::StatusObject> protocols;
std::transform(
supportedProtocols.begin(),
supportedProtocols.end(),
std::back_inserter(protocols),
[](const auto& proto) -> util::StatusObject {
std::stringstream ss;
ss << proto;
return ss.str();
});
obj["protos"] = protocols;
std::vector<util::StatusObject> ranges;
std::transform(
ownedRanges.begin(),
ownedRanges.end(),
std::back_inserter(ranges),
[](const auto& range) -> util::StatusObject { return range.ToString(); });
obj["advertisedRanges"] = ranges;
if (exitTrafficPolicy)
obj["exitPolicy"] = exitTrafficPolicy->ExtractStatus();
return obj;
}
@ -156,19 +179,49 @@ namespace llarp::service
IntroSet::DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* buf)
{
bool read = false;
if (!BEncodeMaybeReadDictEntry("a", A, read, key, buf))
if (!BEncodeMaybeReadDictEntry("a", addressKeys, read, key, buf))
return false;
if (key == "e")
{
net::TrafficPolicy policy;
if (not policy.BDecode(buf))
return false;
exitTrafficPolicy = policy;
return true;
}
if (key == "i")
{
return BEncodeReadList(I, buf);
return BEncodeReadList(intros, buf);
}
if (!BEncodeMaybeReadDictEntry("k", K, read, key, buf))
if (!BEncodeMaybeReadDictEntry("k", sntrupKey, read, key, buf))
return false;
if (!BEncodeMaybeReadDictEntry("n", topic, read, key, buf))
return false;
if (key == "p")
{
return bencode_read_list(
[&](llarp_buffer_t* buf, bool more) {
if (more)
{
uint64_t protoval;
if (not bencode_read_integer(buf, &protoval))
return false;
supportedProtocols.emplace_back(static_cast<ProtocolType>(protoval));
}
return true;
},
buf);
}
if (key == "r")
{
return BEncodeReadList(ownedRanges, buf);
}
if (key == "s")
{
byte_t* begin = buf->cur;
@ -177,8 +230,8 @@ namespace llarp::service
byte_t* end = buf->cur;
std::string_view srvString{
reinterpret_cast<char*>(begin), static_cast<std::size_t>(end - begin)};
std::string_view srvString(
reinterpret_cast<const char*>(begin), static_cast<size_t>(end - begin));
try
{
@ -192,53 +245,71 @@ namespace llarp::service
read = true;
}
if (!BEncodeMaybeReadDictInt("t", T, read, key, buf))
if (!BEncodeMaybeReadDictInt("t", timestampSignedAt, read, key, buf))
return false;
if (key == "w")
{
W.emplace();
return bencode_decode_dict(*W, buf);
}
if (!BEncodeMaybeReadDictInt("v", version, read, key, buf))
return false;
if (!BEncodeMaybeReadDictEntry("z", Z, read, key, buf))
if (!BEncodeMaybeReadDictEntry("z", signature, read, key, buf))
return false;
if (read)
return true;
return bencode_discard(buf);
return read or bencode_discard(buf);
}
bool
IntroSet::BEncode(llarp_buffer_t* buf) const
{
if (!bencode_start_dict(buf))
if (not bencode_start_dict(buf))
return false;
if (!BEncodeWriteDictEntry("a", A, buf))
if (not BEncodeWriteDictEntry("a", addressKeys, buf))
return false;
// exit policy if applicable
if (exitTrafficPolicy)
{
if (not BEncodeWriteDictEntry("e", *exitTrafficPolicy, buf))
return false;
}
// start introduction list
if (!bencode_write_bytestring(buf, "i", 1))
if (not bencode_write_bytestring(buf, "i", 1))
return false;
if (!BEncodeWriteList(I.begin(), I.end(), buf))
if (not BEncodeWriteList(intros.begin(), intros.end(), buf))
return false;
// end introduction list
// pq pubkey
if (!BEncodeWriteDictEntry("k", K, buf))
if (not BEncodeWriteDictEntry("k", sntrupKey, buf))
return false;
// topic tag
if (topic.ToString().size())
if (not topic.ToString().empty())
{
if (!BEncodeWriteDictEntry("n", topic, buf))
if (not BEncodeWriteDictEntry("n", topic, buf))
return false;
}
if (SRVs.size())
// supported ethertypes
if (not supportedProtocols.empty())
{
if (not bencode_write_bytestring(buf, "p", 1))
return false;
if (not bencode_start_list(buf))
return false;
for (const auto& proto : supportedProtocols)
{
if (not bencode_write_uint64(buf, static_cast<uint64_t>(proto)))
return false;
}
if (not bencode_end(buf))
return false;
}
// srv records
if (not SRVs.empty())
{
std::string serial = oxenmq::bt_serialize(SRVs);
if (!bencode_write_bytestring(buf, "s", 1))
@ -247,19 +318,22 @@ namespace llarp::service
return false;
}
// Timestamp published
if (!BEncodeWriteDictInt("t", T.count(), buf))
// owned ranges
if (not ownedRanges.empty())
{
if (not BEncodeWriteDictArray("r", ownedRanges, buf))
return false;
}
// timestamp
if (!BEncodeWriteDictInt("t", timestampSignedAt.count(), buf))
return false;
// write version
if (!BEncodeWriteDictInt("v", version, buf))
return false;
if (W)
{
if (!BEncodeWriteDictEntry("w", *W, buf))
return false;
}
if (!BEncodeWriteDictEntry("z", Z, buf))
if (!BEncodeWriteDictEntry("z", signature, buf))
return false;
return bencode_end(buf);
@ -268,8 +342,8 @@ namespace llarp::service
bool
IntroSet::HasExpiredIntros(llarp_time_t now) const
{
for (const auto& i : I)
if (now >= i.expiresAt)
for (const auto& intro : intros)
if (now >= intro.expiresAt)
return true;
return false;
}
@ -300,10 +374,10 @@ namespace llarp::service
IntroSet::Verify(llarp_time_t now) const
{
std::array<byte_t, MAX_INTROSET_SIZE> tmp;
llarp_buffer_t buf(tmp);
llarp_buffer_t buf{tmp};
IntroSet copy;
copy = *this;
copy.Z.Zero();
copy.signature.Zero();
if (!copy.BEncode(&buf))
{
return false;
@ -311,57 +385,39 @@ namespace llarp::service
// rewind and resize buffer
buf.sz = buf.cur - buf.base;
buf.cur = buf.base;
if (!A.Verify(buf, Z))
{
return false;
}
// validate PoW
if (W && !W->IsValid(now))
if (!addressKeys.Verify(buf, signature))
{
return false;
}
// valid timestamps
// add max clock skew
now += MAX_INTROSET_TIME_DELTA;
for (const auto& intro : I)
for (const auto& intro : intros)
{
if (intro.expiresAt > now && intro.expiresAt - now > path::default_lifetime)
{
if (!W)
{
LogWarn("intro has too high expire time");
return false;
}
if (intro.expiresAt - W->extendedLifetime > path::default_lifetime)
{
return false;
}
return false;
}
}
if (IsExpired(now))
{
LogWarn("introset expired: ", *this);
return false;
}
return true;
return not IsExpired(now);
}
llarp_time_t
IntroSet::GetNewestIntroExpiration() const
{
llarp_time_t t = 0s;
for (const auto& intro : I)
t = std::max(intro.expiresAt, t);
return t;
llarp_time_t maxTime = 0s;
for (const auto& intro : intros)
maxTime = std::max(intro.expiresAt, maxTime);
return maxTime;
}
std::ostream&
IntroSet::print(std::ostream& stream, int level, int spaces) const
{
Printer printer(stream, level, spaces);
printer.printAttribute("A", A);
printer.printAttribute("I", I);
printer.printAttribute("K", K);
printer.printAttribute("addressKeys", addressKeys);
printer.printAttribute("intros", intros);
printer.printAttribute("sntrupKey", sntrupKey);
std::string _topic = topic.ToString();
@ -374,17 +430,10 @@ namespace llarp::service
printer.printAttribute("topic", topic);
}
printer.printAttribute("T", T.count());
if (W)
{
printer.printAttribute("W", *W);
}
else
{
printer.printAttribute("W", "NULL");
}
printer.printAttribute("V", version);
printer.printAttribute("Z", Z);
printer.printAttribute("signedAt", timestampSignedAt.count());
printer.printAttribute("version", version);
printer.printAttribute("sig", signature);
return stream;
}

View File

@ -5,11 +5,15 @@
#include "info.hpp"
#include "intro.hpp"
#include "tag.hpp"
#include "protocol_type.hpp"
#include <llarp/util/bencode.hpp>
#include <llarp/util/time.hpp>
#include <llarp/util/status.hpp>
#include <llarp/dns/srv_data.hpp>
#include <llarp/net/ip_range.hpp>
#include <llarp/net/traffic_policy.hpp>
#include <optional>
#include <algorithm>
#include <functional>
@ -26,20 +30,31 @@ namespace llarp
struct IntroSet
{
ServiceInfo A;
std::vector<Introduction> I;
PQPubKey K;
ServiceInfo addressKeys;
std::vector<Introduction> intros;
PQPubKey sntrupKey;
Tag topic;
std::vector<llarp::dns::SRVTuple> SRVs;
llarp_time_t T = 0s;
std::optional<PoW> W;
Signature Z;
llarp_time_t timestampSignedAt = 0s;
/// ethertypes we advertise that we speak
std::vector<ProtocolType> supportedProtocols;
/// aonnuce that these ranges are reachable via our endpoint
/// only set when we support exit traffic ethertype is supported
std::vector<IPRange> ownedRanges;
/// policies about traffic that we are willing to carry
/// a protocol/range whitelist or blacklist
/// only set when we support exit traffic ethertype
std::optional<net::TrafficPolicy> exitTrafficPolicy;
Signature signature;
uint64_t version = LLARP_PROTO_VERSION;
bool
OtherIsNewer(const IntroSet& other) const
{
return T < other.T;
return timestampSignedAt < other.timestampSignedAt;
}
std::ostream&
@ -82,14 +97,28 @@ namespace llarp
inline bool
operator<(const IntroSet& lhs, const IntroSet& rhs)
{
return lhs.A < rhs.A;
return lhs.addressKeys < rhs.addressKeys;
}
inline bool
operator==(const IntroSet& lhs, const IntroSet& rhs)
{
return std::tie(lhs.A, lhs.I, lhs.K, lhs.T, lhs.version, lhs.topic, lhs.W, lhs.Z)
== std::tie(rhs.A, rhs.I, rhs.K, rhs.T, rhs.version, rhs.topic, rhs.W, rhs.Z);
return std::tie(
lhs.addressKeys,
lhs.intros,
lhs.sntrupKey,
lhs.timestampSignedAt,
lhs.version,
lhs.topic,
lhs.signature)
== std::tie(
rhs.addressKeys,
rhs.intros,
rhs.sntrupKey,
rhs.timestampSignedAt,
rhs.version,
rhs.topic,
rhs.signature);
}
inline bool

View File

@ -56,13 +56,13 @@ namespace llarp
OutboundContext::OutboundContext(const IntroSet& introset, Endpoint* parent)
: path::Builder(parent->Router(), 4, parent->numHops)
, SendContext(introset.A, {}, this, parent)
, location(introset.A.Addr().ToKey())
, SendContext(introset.addressKeys, {}, this, parent)
, location(introset.addressKeys.Addr().ToKey())
, currentIntroSet(introset)
{
updatingIntroSet = false;
for (const auto& intro : introset.I)
for (const auto& intro : introset.intros)
{
if (intro.expiresAt > m_NextIntro.expiresAt)
m_NextIntro = intro;
@ -80,7 +80,7 @@ namespace llarp
if (remoteIntro != m_NextIntro)
{
remoteIntro = m_NextIntro;
m_DataHandler->PutSenderFor(currentConvoTag, currentIntroSet.A, false);
m_DataHandler->PutSenderFor(currentConvoTag, currentIntroSet.addressKeys, false);
m_DataHandler->PutIntroFor(currentConvoTag, remoteIntro);
}
}
@ -94,12 +94,12 @@ namespace llarp
updatingIntroSet = false;
if (foundIntro)
{
if (foundIntro->T == 0s)
if (foundIntro->timestampSignedAt == 0s)
{
LogWarn(Name(), " got introset with zero timestamp: ", *foundIntro);
return true;
}
if (currentIntroSet.T > foundIntro->T)
if (currentIntroSet.timestampSignedAt > foundIntro->timestampSignedAt)
{
LogInfo("introset is old, dropping");
return true;
@ -136,7 +136,7 @@ namespace llarp
{
const auto now = Now();
Introduction selectedIntro;
for (const auto& intro : currentIntroSet.I)
for (const auto& intro : currentIntroSet.intros)
{
if (intro.expiresAt > selectedIntro.expiresAt && intro.router != r)
{
@ -215,7 +215,7 @@ namespace llarp
m_Endpoint->Loop(),
remoteIdent,
m_Endpoint->GetIdentity(),
currentIntroSet.K,
currentIntroSet.sntrupKey,
remoteIntro,
m_DataHandler,
currentConvoTag,
@ -236,7 +236,8 @@ namespace llarp
std::string
OutboundContext::Name() const
{
return "OBContext:" + m_Endpoint->Name() + "-" + currentIntroSet.A.Addr().ToString();
return "OBContext:" + m_Endpoint->Name() + "-"
+ currentIntroSet.addressKeys.Addr().ToString();
}
void
@ -244,7 +245,7 @@ namespace llarp
{
if (updatingIntroSet || markedBad)
return;
const auto addr = currentIntroSet.A.Addr();
const auto addr = currentIntroSet.addressKeys.Addr();
// we want to use the parent endpoint's paths because outbound context
// does not implement path::PathSet::HandleGotIntroMessage
const auto paths = GetManyPathsWithUniqueEndpoints(m_Endpoint, 2);
@ -453,7 +454,7 @@ namespace llarp
if (now - lastShift < MIN_SHIFT_INTERVAL)
return false;
bool shifted = false;
std::vector<Introduction> intros = currentIntroSet.I;
std::vector<Introduction> intros = currentIntroSet.intros;
if (intros.size() > 1)
{
std::shuffle(intros.begin(), intros.end(), CSRNG{});
@ -543,7 +544,7 @@ namespace llarp
// hop off it
Introduction picked;
// get the latest intro that isn't on that endpoint
for (const auto& intro : currentIntroSet.I)
for (const auto& intro : currentIntroSet.intros)
{
if (intro.router == endpoint)
continue;

View File

@ -12,19 +12,6 @@ namespace llarp
{
namespace service
{
std::ostream&
operator<<(std::ostream& o, ProtocolType t)
{
return o
<< (t == ProtocolType::Control ? "Control"
: t == ProtocolType::TrafficV4 ? "TrafficV4"
: t == ProtocolType::TrafficV6 ? "TrafficV6"
: t == ProtocolType::Exit ? "Exit"
: t == ProtocolType::Auth ? "Auth"
: t == ProtocolType::QUIC ? "QUIC"
: "(unknown-protocol-type)");
}
ProtocolMessage::ProtocolMessage()
{
tag.Zero();

View File

@ -31,9 +31,6 @@ namespace llarp
constexpr std::size_t MAX_PROTOCOL_MESSAGE_SIZE = 2048 * 2;
std::ostream&
operator<<(std::ostream& o, ProtocolType t);
/// inner message
struct ProtocolMessage
{

View File

@ -0,0 +1,18 @@
#include "protocol_type.hpp"
namespace llarp::service
{
std::ostream&
operator<<(std::ostream& o, ProtocolType t)
{
return o
<< (t == ProtocolType::Control ? "Control"
: t == ProtocolType::TrafficV4 ? "TrafficV4"
: t == ProtocolType::TrafficV6 ? "TrafficV6"
: t == ProtocolType::Exit ? "Exit"
: t == ProtocolType::Auth ? "Auth"
: t == ProtocolType::QUIC ? "QUIC"
: "(unknown-protocol-type)");
}
} // namespace llarp::service

View File

@ -2,6 +2,8 @@
#include <cstdint>
#include <ostream>
namespace llarp::service
{
// Supported protocol types; the values are given explicitly because they are specifically used
@ -14,5 +16,10 @@ namespace llarp::service
Exit = 3UL,
Auth = 4UL,
QUIC = 5UL,
};
std::ostream&
operator<<(std::ostream& o, ProtocolType t);
} // namespace llarp::service

View File

@ -260,7 +260,6 @@ namespace llarp
},
buf);
}
template <typename List_t>
bool
BEncodeReadList(List_t& result, llarp_buffer_t* buf)
@ -279,6 +278,46 @@ namespace llarp
buf);
}
/// read a std::set of decodable entities and deny duplicates
template <typename Set_t>
bool
BEncodeReadSet(Set_t& set, llarp_buffer_t* buffer)
{
return bencode_read_list(
[&set](llarp_buffer_t* buf, bool more) {
if (more)
{
typename Set_t::value_type item;
if (not item.BDecode(buf))
return false;
// deny duplicates
return set.emplace(std::move(item)).second;
}
return true;
},
buffer);
}
/// read a std::set of decodable entities and deny duplicates
template <typename Set_t>
bool
BEncodeWriteSet(Set_t& set, llarp_buffer_t* buffer)
{
return bencode_read_list(
[&set](llarp_buffer_t* buf, bool more) {
if (more)
{
typename Set_t::value_type item;
if (not item.BDecode(buf))
return false;
// deny duplicates
return set.emplace(std::move(item)).second;
}
return true;
},
buffer);
}
template <typename List_t>
bool
BEncodeWriteDictList(const char* k, List_t& list, llarp_buffer_t* buf)

View File

@ -210,19 +210,19 @@ TEST_CASE("Test sign and encrypt introset", "[crypto]")
ident.RegenerateKeys();
service::Address addr;
CHECK(ident.pub.CalculateAddress(addr.as_array()));
service::IntroSet I;
service::IntroSet introset;
auto now = time_now_ms();
I.T = now;
while(I.I.size() < 10)
introset.timestampSignedAt = now;
while(introset.intros.size() < 10)
{
service::Introduction intro;
intro.expiresAt = now + (path::default_lifetime / 2);
intro.router.Randomize();
intro.pathID.Randomize();
I.I.emplace_back(std::move(intro));
introset.intros.emplace_back(std::move(intro));
}
const auto maybe = ident.EncryptAndSignIntroSet(I, now);
const auto maybe = ident.EncryptAndSignIntroSet(introset, now);
CHECK(maybe.has_value());
CHECK(maybe->Verify(now));
PubKey blind_key;