From fedc56e3f1acad18b95c1a8076eee12b1b6d7fe2 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 14 Apr 2021 11:07:06 -0400 Subject: [PATCH] initial commit for #1595 --- llarp/CMakeLists.txt | 2 + llarp/config/config.cpp | 30 +-- llarp/config/config.hpp | 3 + llarp/handlers/tun.cpp | 141 ++++++------ llarp/handlers/tun.hpp | 14 ++ llarp/net/exit_info.hpp | 1 + llarp/net/ip_packet.cpp | 42 +++- llarp/net/ip_packet.hpp | 29 +++ llarp/net/ip_range.cpp | 31 +++ llarp/net/ip_range.hpp | 24 +++ llarp/net/ip_range_map.hpp | 17 +- llarp/net/traffic_policy.cpp | 213 +++++++++++++++++++ llarp/net/traffic_policy.hpp | 70 ++++++ llarp/quic/tunnel.hpp | 7 + llarp/service/endpoint.cpp | 39 +++- llarp/service/endpoint.hpp | 16 ++ llarp/service/identity.cpp | 24 +-- llarp/service/intro_set.cpp | 211 +++++++++++------- llarp/service/intro_set.hpp | 49 ++++- llarp/service/outbound_context.cpp | 25 +-- llarp/service/protocol.cpp | 13 -- llarp/service/protocol.hpp | 3 - llarp/service/protocol_type.cpp | 18 ++ llarp/service/protocol_type.hpp | 7 + llarp/util/bencode.hpp | 41 +++- test/service/test_llarp_service_identity.cpp | 10 +- 26 files changed, 837 insertions(+), 243 deletions(-) create mode 100644 llarp/net/traffic_policy.cpp create mode 100644 llarp/net/traffic_policy.hpp create mode 100644 llarp/service/protocol_type.cpp diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 40067810f..de55720f5 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -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 diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 7ab3f6c58..4fe024a44 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -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("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( + "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("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( "network", diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 8d6194b51..37d67b4f8 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -122,6 +122,9 @@ namespace llarp std::optional m_baseV6Address; + std::set m_AdvertisedRanges; + std::optional m_TrafficPolicy; + // TODO: // on-up // on-down diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 66c5b59ba..753c4dd36 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -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(&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(&addr)) + { + // allow if this address matches the endpoint we think it should be + allow = exitAddr == *ptr; + } } } + if (not allow) + return false; } else { diff --git a/llarp/handlers/tun.hpp b/llarp/handlers/tun.hpp index 5c039f894..ba803d2d4 100644 --- a/llarp/handlers/tun.hpp +++ b/llarp/handlers/tun.hpp @@ -123,6 +123,18 @@ namespace llarp bool HasLocalIP(const huint128_t& ip) const; + std::optional + 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> ObtainAddrForIP(huint128_t ip) const override; @@ -245,6 +257,8 @@ namespace llarp std::shared_ptr m_NetIf; std::unique_ptr m_PacketRouter; + + std::optional m_ExitPolicy; }; } // namespace handlers diff --git a/llarp/net/exit_info.hpp b/llarp/net/exit_info.hpp index 60021fa69..7265e181a 100644 --- a/llarp/net/exit_info.hpp +++ b/llarp/net/exit_info.hpp @@ -15,6 +15,7 @@ /// Exit info model namespace llarp { + /// deprecated don't use me , this is only for backwards compat struct ExitInfo { IpAddress ipAddress; diff --git a/llarp/net/ip_packet.cpp b/llarp/net/ip_packet.cpp index 353024b74..c31c2332d 100644 --- a/llarp/net/ip_packet.cpp +++ b/llarp/net/ip_packet.cpp @@ -4,7 +4,7 @@ #include #include #include - +#include #ifndef _WIN32 #include #endif @@ -32,6 +32,33 @@ namespace llarp { namespace net { + std::string + IPProtocolName(IPProtocol proto) + { + if (const auto* ent = ::getprotobynumber(static_cast(proto))) + { + return ent->p_name; + } + throw std::invalid_argument{ + "cannot determine protocol name for ip proto '" + std::to_string(static_cast(proto)) + + "'"}; + } + + IPProtocol + ParseIPProtocol(std::string data) + { + if (const auto* ent = ::getprotobyname(data.c_str())) + { + return static_cast(ent->p_proto); + } + if (starts_with(data, "0x")) + { + if (const int intVal = std::stoi(data.substr(2), nullptr, 16); intVal > 0) + return static_cast(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 + IPPacket::DstPort() const + { + switch (IPProtocol{Header()->protocol}) + { + case IPProtocol::TCP: + case IPProtocol::UDP: + return nuint16_t{*reinterpret_cast(buf + (Header()->ihl * 4) + 2)}; + default: + return std::nullopt; + } + } + huint32_t IPPacket::srcv4() const { diff --git a/llarp/net/ip_packet.hpp b/llarp/net/ip_packet.hpp index bf58e974a..9f4b91fb6 100644 --- a/llarp/net/ip_packet.hpp +++ b/llarp/net/ip_packet.hpp @@ -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 + DstPort() const; + void UpdateIPv4Address(nuint32_t src, nuint32_t dst); diff --git a/llarp/net/ip_range.cpp b/llarp/net/ip_range.cpp index 1d65c0d8b..4b83e9d02 100644 --- a/llarp/net/ip_range.cpp +++ b/llarp/net/ip_range.cpp @@ -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(start), static_cast(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) { diff --git a/llarp/net/ip_range.hpp b/llarp/net/ip_range.hpp index 7564b5a63..6e172bde5 100644 --- a/llarp/net/ip_range.hpp +++ b/llarp/net/ip_range.hpp @@ -3,11 +3,16 @@ #include "ip.hpp" #include "net_bits.hpp" #include +#include #include #include 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 diff --git a/llarp/net/ip_range_map.hpp b/llarp/net/ip_range_map.hpp index e76be4861..a5644e78c 100644 --- a/llarp/net/ip_range_map.hpp +++ b/llarp/net/ip_range_map.hpp @@ -2,7 +2,7 @@ #include "ip_range.hpp" #include -#include +#include namespace llarp { @@ -19,7 +19,7 @@ namespace llarp using IP_t = Range_t::Addr_t; using Entry_t = std::pair; - using Container_t = std::list; + using Container_t = std::vector; /// get a set of all values std::set @@ -89,15 +89,15 @@ namespace llarp return std::nullopt; } - /// return a set of all values who's range contains this IP - std::set - FindAll(const IP_t& addr) const + /// return a set of all entries who's range contains this IP + std::set + FindAllEntries(const IP_t& addr) const { - std::set found; + std::set 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 diff --git a/llarp/net/traffic_policy.cpp b/llarp/net/traffic_policy.cpp new file mode 100644 index 000000000..939d876e4 --- /dev/null +++ b/llarp/net/traffic_policy.cpp @@ -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>(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 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(vals[0]); + } + if (vals.size() >= 2) + { + if (vals[1] > 65536) + return false; + port = ToNet(huint16_t{static_cast(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>(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(protocol)}, + }; + if (port) + status["port"] = ToHost(*port).h; + return status; + } + + util::StatusObject + TrafficPolicy::ExtractStatus() const + { + std::vector rangesStatus; + std::transform( + ranges.begin(), ranges.end(), std::back_inserter(rangesStatus), [](const auto& range) { + return range.ToString(); + }); + + std::vector 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 diff --git a/llarp/net/traffic_policy.hpp b/llarp/net/traffic_policy.hpp new file mode 100644 index 000000000..1368d05d4 --- /dev/null +++ b/llarp/net/traffic_policy.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "ip_range.hpp" +#include "ip_packet.hpp" +#include "llarp/util/status.hpp" + +#include + +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 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 ranges; + + /// protocols that are explicity allowed + std::set 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 diff --git a/llarp/quic/tunnel.hpp b/llarp/quic/tunnel.hpp index ac466d134..c6fae5ea5 100644 --- a/llarp/quic/tunnel.hpp +++ b/llarp/quic/tunnel.hpp @@ -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_; diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index e57d28006..33ee7ea60 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -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; diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index 53ef31c65..850bdb79e 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -113,6 +113,22 @@ namespace llarp return {0}; } + /// get the exit policy for our exit if we have one + /// override me + virtual std::optional + GetExitPolicy() const + { + return std::nullopt; + }; + + /// get the ip ranges we claim to own + /// override me + virtual std::vector + GetOwnedRanges() const + { + return {}; + }; + virtual void Thaw(){}; diff --git a/llarp/service/identity.cpp b/llarp/service/identity.cpp index 362c0e56d..d91da7411 100644 --- a/llarp/service/identity.cpp +++ b/llarp/service/identity.cpp @@ -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 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 diff --git a/llarp/service/intro_set.cpp b/llarp/service/intro_set.cpp index 426125efb..9ad78f86c 100644 --- a/llarp/service/intro_set.cpp +++ b/llarp/service/intro_set.cpp @@ -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 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 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 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(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(begin), static_cast(end - begin)}; + std::string_view srvString( + reinterpret_cast(begin), static_cast(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(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 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; } diff --git a/llarp/service/intro_set.hpp b/llarp/service/intro_set.hpp index 6fcb086b6..06557d4d9 100644 --- a/llarp/service/intro_set.hpp +++ b/llarp/service/intro_set.hpp @@ -5,11 +5,15 @@ #include "info.hpp" #include "intro.hpp" #include "tag.hpp" +#include "protocol_type.hpp" #include #include #include #include +#include +#include + #include #include #include @@ -26,20 +30,31 @@ namespace llarp struct IntroSet { - ServiceInfo A; - std::vector I; - PQPubKey K; + ServiceInfo addressKeys; + std::vector intros; + PQPubKey sntrupKey; Tag topic; std::vector SRVs; - llarp_time_t T = 0s; - std::optional W; - Signature Z; + llarp_time_t timestampSignedAt = 0s; + + /// ethertypes we advertise that we speak + std::vector supportedProtocols; + /// aonnuce that these ranges are reachable via our endpoint + /// only set when we support exit traffic ethertype is supported + std::vector 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 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 diff --git a/llarp/service/outbound_context.cpp b/llarp/service/outbound_context.cpp index cd4c9f7c7..b35be22bf 100644 --- a/llarp/service/outbound_context.cpp +++ b/llarp/service/outbound_context.cpp @@ -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 intros = currentIntroSet.I; + std::vector 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; diff --git a/llarp/service/protocol.cpp b/llarp/service/protocol.cpp index 6f0275ae9..f7543a3c8 100644 --- a/llarp/service/protocol.cpp +++ b/llarp/service/protocol.cpp @@ -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(); diff --git a/llarp/service/protocol.hpp b/llarp/service/protocol.hpp index 4093555e8..623e2afa7 100644 --- a/llarp/service/protocol.hpp +++ b/llarp/service/protocol.hpp @@ -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 { diff --git a/llarp/service/protocol_type.cpp b/llarp/service/protocol_type.cpp new file mode 100644 index 000000000..4a6861681 --- /dev/null +++ b/llarp/service/protocol_type.cpp @@ -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 diff --git a/llarp/service/protocol_type.hpp b/llarp/service/protocol_type.hpp index 756c04d18..8af8614a8 100644 --- a/llarp/service/protocol_type.hpp +++ b/llarp/service/protocol_type.hpp @@ -2,6 +2,8 @@ #include +#include + 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 diff --git a/llarp/util/bencode.hpp b/llarp/util/bencode.hpp index 48c977ee3..6f8160244 100644 --- a/llarp/util/bencode.hpp +++ b/llarp/util/bencode.hpp @@ -260,7 +260,6 @@ namespace llarp }, buf); } - template 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 + 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 + 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 bool BEncodeWriteDictList(const char* k, List_t& list, llarp_buffer_t* buf) diff --git a/test/service/test_llarp_service_identity.cpp b/test/service/test_llarp_service_identity.cpp index 9654ae2a4..dc271fcd5 100644 --- a/test/service/test_llarp_service_identity.cpp +++ b/test/service/test_llarp_service_identity.cpp @@ -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;