* rework exit codepath to allow multiple exits

* rework net code for ip ranges to be cleaner
* clean up endpoint auth code
* refactor config to validate network configs before setting up endpoints
* remove buildone from path/pathbuilder.cpp so we don't spam connection attempts
pull/1306/head
Jeff Becker 4 years ago
parent 2ef2e6171a
commit 0f21eeccb0
No known key found for this signature in database
GPG Key ID: F357B3B42F6F9B05

@ -1,6 +1,8 @@
#!/usr/bin/env python3
import argparse
import json
import sys
import os
from socket import AF_INET
@ -15,21 +17,75 @@ class LokinetRPC:
self._rpc_socket.setsockopt(zmq.CONNECT_TIMEOUT, 5000)
self._rpc_socket.setsockopt(zmq.HANDSHAKE_IVL, 5000)
self._rpc_socket.connect(url)
self._url = url
def rpc(self, method):
self._rpc_socket.send_multipart([method.encode(), b'lokinet_vpn'+method.encode()])
def rpc(self, method, data=None):
tag = os.urandom(16)
req = [method.encode(), tag]
if data is not None:
req.append(json.dumps(data).encode('utf-8'))
self._rpc_socket.send_multipart(req)
if not self._rpc_socket.poll(timeout=50):
return
reply = self._rpc_socket.recv_multipart()
if len(reply) >= 3 and reply[0:2] == [b'REPLY', b'lokinet_vpn'+method.encode()]:
return reply[2].decode()
if len(reply) >= 3 and reply[0:2] == [b'REPLY', tag]:
try:
return json.loads(reply[2].decode())
except:
return None
def map_exit(self, addr, maprange, token=None):
"""
map an exit range to a loki address and use token for auth if desires
"""
if token is None:
data = self.rpc("llarp.exit", {'exit': addr, 'range': maprange})
else:
data = self.rpc("llarp.exit", {'exit': addr, 'token': token, 'range': maprange})
if data is None:
print("no response from rpc")
return False
if data['error'] is not None:
print('failed to map exit range: {}'.format(data['error']))
return False
print('mapped {} to {}: {}'.format(maprange, addr, data['result']))
return True
def unmap_exit(self, maprange):
"""
unmap an ip range from exit
"""
data = self.rpc("llarp.exit", {'range': maprange, 'unmap': True})
if data is None:
print("no response from rpc")
return False
if data['error'] is not None:
print('failed to unmap exit range: {}'.format(data['error']))
return False
print('unmapped {}: {}'.format(maprange, data['result']))
return True
def get_endpoint_ifname(self, endpoint):
"""
get the network interface name belongint to the endpoint called endpoint
"""
data = self.rpc("llarp.status")
if data is None or data['error'] is not None:
return
services = data['result']['services']
if endpoint not in services:
return
return services[endpoint]['ifname']
def get_first_hops(self):
data = self.rpc("llarp.status")
for link in data['result']['links']['outbound']:
for session in link["sessions"]['established']:
yield session['remoteAddr']
if data is None:
print("no response from rpc")
elif data['error'] is not None:
print('error detecting first hops: {}'.format(data['error']))
else:
for link in data['result']['links']['outbound']:
for session in link["sessions"]['established']:
yield session['remoteAddr']
def close(self):
self._rpc_socket.close(linger=0)
@ -37,27 +93,49 @@ class LokinetRPC:
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--rpc", type=str, default='tcp://127.0.0.1:1190')
ap.add_argument("--ifname", type=str, default="lokitun0")
ap.add_argument("--rpc", type=str, default='tcp://127.0.0.1:1190', help='rpc endpoint url for local lokinet')
ap.add_argument("--exit", type=str, required=True, help='loki address of exit to use')
ap.add_argument('--token', type=str, default=None, help='optional auth token to use when talking to exit')
ap.add_argument('--range', type=str, default='0.0.0.0/0', help='optional ip range to map to exit')
ap.add_argument("--endpoint", type=str, default='default', help='lokinet endpoint name to use')
ap.add_argument("--up", action='store_const', dest='action', const='up')
ap.add_argument("--down", action='store_const', dest='action', const='down')
args = ap.parse_args()
rpc = LokinetRPC(args.rpc)
hops = dict()
for hop in rpc.get_first_hops():
ip = hop.split(':')[0]
hops[ip] = 0
rpc.close()
if len(hops) == 0:
print("lokinet is not connected yet")
rpc.close()
return 1
ifname = rpc.get_endpoint_ifname(args.endpoint)
if ifname is None:
print("cannot determine lokinet network interface name")
rpc.close()
return 1
with IPRoute() as ip:
ip.bind()
try:
idx = ip.link_lookup(ifname=args.ifname)[0]
except:
print("cannot find {}".format(args.ifname))
idx = ip.link_lookup(ifname=ifname)[0]
except Exception as ex:
print("cannot find {}: {}".format(ifname, ex))
rpc.close()
return 1
# set up exit after binding routing socket
if args.action == 'up':
if not rpc.map_exit(args.exit, args.range, args.token):
rpc.close()
print('cannot map exit')
return
elif args.action == 'down':
if not rpc.unmap_exit(args.range):
rpc.close()
print('cannot unmap exit')
return
rpc.close()
gateways = ip.get_default_routes(family=AF_INET)
gateway = None
for g in gateways:

@ -203,11 +203,25 @@ namespace llarp
if (arg.empty())
return;
service::Address exit;
IPRange range;
const auto pos = arg.find(":");
if (pos == std::string::npos)
{
range.FromString("0.0.0.0/0");
}
else if (not range.FromString(arg.substr(pos + 1)))
{
throw std::invalid_argument("[network]:exit-node invalid ip range for exit provided");
}
if (pos != std::string::npos)
{
arg = arg.substr(0, pos);
}
if (not exit.FromString(arg))
{
throw std::invalid_argument(stringify("[endpoint]:exit-node bad address: ", arg));
throw std::invalid_argument(stringify("[network]:exit-node bad address: ", arg));
}
m_exitNode = exit;
m_ExitMap.Insert(range, exit);
});
conf.defineOption<std::string>("network", "mapaddr", false, true, "", [this](std::string arg) {
@ -248,9 +262,13 @@ namespace llarp
const auto maybe = llarp::FindFreeRange();
if (not maybe)
throw std::invalid_argument("cannot determine free ip range");
arg = *maybe;
m_ifaddr = *maybe;
return;
}
if (not m_ifaddr.FromString(arg))
{
throw std::invalid_argument(stringify("[network]:ifaddr invalid value: ", arg));
}
m_ifaddr = arg;
});
conf.defineOption<std::string>("network", "ifname", false, "", [this](std::string arg) {

@ -11,6 +11,7 @@
#include <constants/files.hpp>
#include <net/ip_address.hpp>
#include <net/net_int.hpp>
#include <net/ip_range_map.hpp>
#include <service/address.hpp>
#include <cstdlib>
@ -70,7 +71,7 @@ namespace llarp
std::string m_routerProfilesFile;
std::string m_strictConnect;
std::string m_ifname;
std::string m_ifaddr;
IPRange m_ifaddr;
std::optional<fs::path> m_keyfile;
std::string m_endpointType;
@ -79,7 +80,7 @@ namespace llarp
std::optional<int> m_Paths;
bool m_AllowExit = false;
std::set<RouterID> m_snodeBlacklist;
std::optional<service::Address> m_exitNode;
net::IPRangeMap<service::Address> m_ExitMap;
std::unordered_map<huint128_t, service::Address> m_mapAddrs;
bool m_AuthEnabled = false;

@ -550,23 +550,11 @@ namespace llarp
m_LocalResolverAddr = dnsConfig.m_bind;
m_UpstreamResolvers = dnsConfig.m_upstreamDNS;
if (!m_OurRange.FromString(networkConfig.m_ifaddr))
{
throw std::invalid_argument(
stringify(Name(), " has invalid address range: ", networkConfig.m_ifaddr));
}
// TODO: clean this up (make a util function for handling CIDR, etc.)
auto pos = networkConfig.m_ifaddr.find("/");
if (pos == std::string::npos)
{
throw std::invalid_argument(
stringify(Name(), " ifaddr is not a cidr: ", networkConfig.m_ifaddr));
}
std::string nmask_str = networkConfig.m_ifaddr.substr(1 + pos);
std::string host_str = networkConfig.m_ifaddr.substr(0, pos);
m_OurRange = networkConfig.m_ifaddr;
const auto host_str = m_OurRange.BaseAddressString();
// string, or just a plain char array?
strncpy(m_Tun.ifaddr, host_str.c_str(), sizeof(m_Tun.ifaddr) - 1);
m_Tun.netmask = std::atoi(nmask_str.c_str());
m_Tun.netmask = m_OurRange.HostmaskBits();
m_IfAddr = m_OurRange.addr;
m_NextAddr = m_IfAddr;
m_HigestAddr = m_OurRange.HighestAddr();
@ -580,7 +568,7 @@ namespace llarp
m_IfAddr,
" hi=",
m_HigestAddr);
m_UseV6 = false;
m_UseV6 = not m_OurRange.IsV4();
if (networkConfig.m_ifname.length() >= sizeof(m_Tun.ifname))
{

@ -28,7 +28,6 @@ namespace llarp
void
TunEndpoint::FlushToUser(std::function<bool(net::IPPacket&)> send)
{
m_ExitMap.ForEachValue([r = Router()](const auto& exit) { exit->DownstreamFlush(r); });
// flush network to user
m_NetworkToUserPktQueue.Process(send);
}
@ -74,7 +73,10 @@ namespace llarp
{
auto obj = service::Endpoint::ExtractStatus();
obj["ifaddr"] = m_OurRange.ToString();
if (tunif)
{
obj["ifname"] = tunif->ifname;
}
std::vector<std::string> resolvers;
for (const auto& addr : m_UpstreamResolvers)
resolvers.emplace_back(addr.toString());
@ -196,37 +198,11 @@ namespace llarp
}
strncpy(tunif->ifname, ifname.c_str(), sizeof(tunif->ifname) - 1);
llarp::LogInfo(Name() + " setting ifname to ", tunif->ifname);
}
std::string ifaddr = conf.m_ifaddr;
if (tunif)
{
std::string addr;
m_UseV6 = addr.find(":") != std::string::npos;
auto pos = ifaddr.find("/");
if (pos != std::string::npos)
{
std::string part = ifaddr.substr(pos + 1);
int num = std::stoi(part);
if (num > 0)
{
tunif->netmask = num;
addr = ifaddr.substr(0, pos);
}
else
{
llarp::LogError("bad ifaddr value: ", ifaddr);
return false;
}
}
else
{
if (m_UseV6)
tunif->netmask = 128;
else
tunif->netmask = 32;
addr = ifaddr;
}
m_OurRange = conf.m_ifaddr;
m_UseV6 = not m_OurRange.IsV4();
tunif->netmask = m_OurRange.HostmaskBits();
const auto addr = m_OurRange.BaseAddressString();
llarp::LogInfo(Name() + " set ifaddr to ", addr, " with netmask ", tunif->netmask);
strncpy(tunif->ifaddr, addr.c_str(), sizeof(tunif->ifaddr) - 1);
}
@ -251,7 +227,6 @@ namespace llarp
TunEndpoint::Flush()
{
FlushSend();
m_ExitMap.ForEachValue([r = Router()](const auto& exit) { exit->UpstreamFlush(r); });
Pump(Now());
}
@ -513,7 +488,6 @@ namespace llarp
TunEndpoint::ResetInternalState()
{
service::Endpoint::ResetInternalState();
m_ExitMap.ForEachValue([](const auto& exit) { exit->ResetInternalState(); });
}
bool
@ -581,11 +555,6 @@ namespace llarp
llarp::LogWarn("Couldn't start endpoint");
return false;
}
const auto blacklist = SnodeBlacklist();
m_ExitMap.ForEachValue([blacklist](const auto& exit) {
for (const auto& snode : blacklist)
exit->BlacklistSNode(snode);
});
return SetupNetworking();
}
@ -769,14 +738,12 @@ namespace llarp
void
TunEndpoint::Tick(llarp_time_t now)
{
m_ExitMap.ForEachValue([&](const auto& exit) { exit->Tick(now); });
Endpoint::Tick(now);
}
bool
TunEndpoint::Stop()
{
m_ExitMap.ForEachValue([](const auto& exit) { exit->Stop(); });
return llarp::service::Endpoint::Stop();
}
@ -800,7 +767,8 @@ namespace llarp
auto itr = m_IPToAddr.find(dst);
if (itr == m_IPToAddr.end())
{
if (IsBogon(dst) or not m_state->m_ExitNode.has_value())
const auto exits = m_ExitMap.FindAll(dst);
if (IsBogon(dst) or exits.empty())
{
// send icmp unreachable
const auto icmp = pkt.MakeICMPUnreachable();
@ -811,13 +779,13 @@ namespace llarp
}
else
{
const auto addr = *exits.begin();
pkt.ZeroSourceAddress();
MarkAddressOutbound(*m_state->m_ExitNode);
MarkAddressOutbound(addr);
EnsurePathToService(
*m_state->m_ExitNode,
[pkt, self = this](service::Address, service::OutboundContext*) {
self->SendToServiceOrQueue(
*self->m_state->m_ExitNode, pkt.ConstBuffer(), service::eProtocolExit);
addr,
[addr, pkt, self = this](service::Address, service::OutboundContext*) {
self->SendToServiceOrQueue(addr, pkt.ConstBuffer(), service::eProtocolExit);
},
1s);
}
@ -883,17 +851,7 @@ namespace llarp
net::IPPacket pkt;
if (not pkt.Load(buf))
return false;
if (m_state->m_ExitNode == service::Address{addr.as_array()} and t == service::eProtocolExit)
{
// client side from exit
if (pkt.IsV4())
src = pkt.src4to6();
else if (pkt.IsV6())
src = pkt.srcv6();
dst = m_OurIP;
}
else if (m_state->m_ExitEnabled)
if (m_state->m_ExitEnabled)
{
// exit side from exit
src = ObtainIPForAddr(addr, snode);
@ -902,6 +860,22 @@ namespace llarp
else if (pkt.IsV6())
dst = pkt.dstv6();
}
else if (t == service::eProtocolExit)
{
// client side exit traffic from exit
if (pkt.IsV4())
src = pkt.src4to6();
else if (pkt.IsV6())
src = pkt.srcv6();
dst = m_OurIP;
// find what exit we think this should be for
const auto mapped = m_ExitMap.FindAll(src);
if (mapped.count(service::Address{addr}) == 0 or IsBogon(src))
{
// we got exit traffic from someone who we should not have gotten it from
return false;
}
}
else
{
// snapp traffic

@ -40,7 +40,7 @@ namespace llarp
Tick(llarp_time_t now) override;
util::StatusObject
ExtractStatus() const;
ExtractStatus() const override;
std::unordered_map<std::string, std::string>
NotifyParams() const override;

@ -1,6 +1,5 @@
#pragma once
#include <net/net_int.hpp>
#include <net/net.hpp>
#include <cstdint>
namespace llarp::net
@ -14,7 +13,7 @@ namespace llarp::net
constexpr huint128_t
ExpandV4(huint32_t x)
{
return huint128_t{0xffff'0000'0000UL} | huint128_t{x.h};
return huint128_t{0x0000'ffff'0000'0000UL} | huint128_t{x.h};
}
constexpr huint32_t

@ -2,12 +2,6 @@
namespace llarp
{
bool
IPRange::ContainsV4(const huint32_t& ip) const
{
return Contains(net::ExpandV4(ip));
}
bool
IPRange::FromString(std::string str)
{
@ -27,7 +21,7 @@ namespace llarp
addr = net::ExpandV4(ip);
if (!bitsstr.empty())
{
auto bits = atoi(bitsstr.c_str());
const auto bits = stoi(bitsstr);
if (bits < 0 || bits > 32)
return false;
netmask_bits = netmask_ipv6_bits(96 + bits);
@ -55,21 +49,14 @@ namespace llarp
}
std::string
IPRange::ToString() const
IPRange::BaseAddressString() const
{
char buf[INET6_ADDRSTRLEN + 1] = {0};
std::string str;
in6_addr inaddr = {};
size_t numset = 0;
uint128_t bits = netmask_bits.h;
while (bits)
if (IsV4())
{
if (bits & 1)
numset++;
bits >>= 1;
const huint32_t addr4 = net::TruncateV6(addr);
return addr4.ToString();
}
str += inet_ntop(AF_INET6, &inaddr, buf, sizeof(buf));
return str + "/" + std::to_string(numset);
return addr.ToString();
}
} // namespace llarp

@ -1,6 +1,7 @@
#pragma once
#include <ostream>
#include <net/ip.hpp>
#include <net/net_bits.hpp>
#include <util/bits.hpp>
#include <util/types.hpp>
#include <string>
@ -19,6 +20,32 @@ namespace llarp
return IPRange{net::ExpandV4(ipaddr_ipv4_bits(a, b, c, d)), netmask_ipv6_bits(mask + 96)};
}
/// return true if this iprange is in the SIIT range for containing ipv4 addresses
constexpr bool
IsV4() const
{
constexpr auto siit = IPRange{huint128_t{0x0000'ffff'0000'0000UL}, netmask_ipv6_bits(96)};
return siit.Contains(addr);
}
/// return the number of bits set in the hostmask
constexpr int
HostmaskBits() const
{
if (IsV4())
{
return bits::count_bits(net::TruncateV6(netmask_bits));
}
return bits::count_bits(netmask_bits);
}
/// return true if the other range is inside our range
constexpr bool
Contains(const IPRange& other) const
{
return Contains(other.addr) and Contains(other.HighestAddr());
}
/// return true if ip is contained in this ip range
constexpr bool
Contains(const Addr_t& ip) const
@ -26,8 +53,14 @@ namespace llarp
return (addr & netmask_bits) == (ip & netmask_bits);
}
bool
ContainsV4(const huint32_t& ip) const;
/// return true if we are a ipv4 range and contains this ip
constexpr bool
Contains(const huint32_t& ip) const
{
if (not IsV4())
return false;
return Contains(net::ExpandV4(ip));
}
friend std::ostream&
operator<<(std::ostream& out, const IPRange& a)
@ -36,7 +69,7 @@ namespace llarp
}
/// get the highest address on this range
huint128_t
constexpr huint128_t
HighestAddr() const
{
return (addr & netmask_bits) + (huint128_t{1} << (128 - bits::count_bits_128(netmask_bits.h)))
@ -51,7 +84,13 @@ namespace llarp
}
std::string
ToString() const;
ToString() const
{
return BaseAddressString() + "/" + std::to_string(HostmaskBits());
}
std::string
BaseAddressString() const;
bool
FromString(std::string str);

@ -2,6 +2,7 @@
#define LLARP_NET_IP_RANGE_MAP_HPP
#include <net/ip_range.hpp>
#include <list>
namespace llarp
{
@ -18,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::forward_list<Entry_t>;
using Container_t = std::list<Entry_t>;
/// get a set of all values
std::set<Value_t>
@ -30,6 +31,17 @@ namespace llarp
return all;
}
bool
ContainsValue(const Value_t& val) const
{
for (const auto& entry : m_Entries)
{
if (entry.second == val)
return true;
}
return false;
}
void
ForEachValue(std::function<void(const Value_t&)> functor) const
{
@ -37,6 +49,14 @@ namespace llarp
functor(entry.second);
}
template <typename Visit_t>
void
ForEachEntry(Visit_t visit) const
{
for (const auto& entry : m_Entries)
visit(entry.first, entry.second);
}
/// convert all values into type T using a transformer
template <typename T, typename Transformer>
std::set<T>
@ -80,6 +100,20 @@ namespace llarp
m_Entries.sort(CompareEntry{});
}
template <typename Visit_t>
void
RemoveIf(Visit_t visit)
{
auto itr = m_Entries.begin();
while (itr != m_Entries.end())
{
if (visit(*itr))
itr = m_Entries.erase(itr);
else
++itr;
}
}
private:
Container_t m_Entries;
};

@ -20,6 +20,7 @@
#include <util/str.hpp>
#include <cstdio>
#include <list>
bool
operator==(const sockaddr& a, const sockaddr& b)
@ -428,7 +429,7 @@ namespace llarp
if (i->ifa_addr and i->ifa_addr->sa_family == AF_INET)
{
llarp::nuint32_t addr{((sockaddr_in*)i->ifa_addr)->sin_addr.s_addr};
if (loopback.ContainsV4(xntohl(addr)))
if (loopback.Contains(xntohl(addr)))
{
ifname = i->ifa_name;
}
@ -469,10 +470,10 @@ namespace llarp
}
// TODO: ipv6?
std::optional<std::string>
std::optional<IPRange>
FindFreeRange()
{
std::vector<IPRange> currentRanges;
std::list<IPRange> currentRanges;
IterAllNetworkInterfaces([&](ifaddrs* i) {
if (i && i->ifa_addr)
{
@ -494,49 +495,33 @@ namespace llarp
IPRange{net::ExpandV4(xntohl(ifaddr)), net::ExpandV4(xntohl(ifmask))});
}
});
// try 10.x.0.0/16
byte_t oct = 0;
while (oct < 255)
{
const huint32_t loaddr = ipaddr_ipv4_bits(10, oct, 0, 1);
const huint32_t hiaddr = ipaddr_ipv4_bits(10, oct, 255, 255);
bool hit = false;
for (const auto& range : currentRanges)
auto ownsRange = [&currentRanges](IPRange range) -> bool {
for (const auto& ownRange : currentRanges)
{
hit = hit || range.ContainsV4(loaddr) || range.ContainsV4(hiaddr);
if (ownRange.Contains(range))
return true;
}
if (!hit)
return loaddr.ToString() + "/16";
++oct;
return false;
};
// generate possible ranges to in order of attempts
std::list<IPRange> possibleRanges;
for (byte_t oct = 0; oct < 255; ++oct)
{
possibleRanges.emplace_back(IPRange::FromIPv4(10, oct, 0, 1, 16));
}
// try 192.168.x.0/24
oct = 0;
while (oct < 255)
for (byte_t oct = 0; oct < 255; ++oct)
{
const huint32_t loaddr = ipaddr_ipv4_bits(192, 168, oct, 1);
const huint32_t hiaddr = ipaddr_ipv4_bits(192, 168, oct, 255);
bool hit = false;
for (const auto& range : currentRanges)
{
hit = hit || range.ContainsV4(loaddr) || range.ContainsV4(hiaddr);
}
if (!hit)
return loaddr.ToString() + "/24";
possibleRanges.emplace_back(IPRange::FromIPv4(192, 168, oct, 1, 24));
}
// try 172.16.x.0/24
oct = 0;
while (oct < 255)
for (byte_t oct = 0; oct < 255; ++oct)
{
const huint32_t loaddr = ipaddr_ipv4_bits(172, 16, oct, 1);
const huint32_t hiaddr = ipaddr_ipv4_bits(172, 16, oct, 255);
bool hit = false;
for (const auto& range : currentRanges)
{
hit = hit || range.ContainsV4(loaddr) || range.ContainsV4(hiaddr);
}
if (!hit)
return loaddr.ToString() + "/24";
++oct;
possibleRanges.emplace_back(IPRange::FromIPv4(172, 16, oct, 1, 24));
}
// for each possible range pick the first one we don't own
for (const auto& range : possibleRanges)
{
if (not ownsRange(range))
return range;
}
return std::nullopt;
}
@ -660,7 +645,7 @@ namespace llarp
{
for (const auto& bogon : bogonRanges)
{
if (bogon.ContainsV4(addr))
if (bogon.Contains(addr))
{
return true;
}

@ -6,6 +6,7 @@
#include <net/ip_address.hpp>
#include <net/net_int.hpp>
#include <net/net.h>
#include <net/ip_range.hpp>
#include <util/mem.hpp>
#include <util/bits.hpp>
@ -45,52 +46,9 @@ operator==(const in6_addr& a, const in6_addr& b);
namespace llarp
{
/// get a netmask with the higest numset bits set
constexpr huint128_t
_netmask_ipv6_bits(uint32_t numset)
{
return (128 - numset) ? (huint128_t{1} << numset) | _netmask_ipv6_bits(numset + 1)
: huint128_t{0};
}
constexpr huint128_t
netmask_ipv6_bits(uint32_t numset)
{
return _netmask_ipv6_bits(128 - numset);
}
/// get a netmask with the higest numset bits set
constexpr uint32_t
_netmask_ipv4_bits(uint32_t numset)
{
return (32 - numset) ? (1 << numset) | _netmask_ipv4_bits(numset + 1) : 0;
}
/// get a netmask given some /N range
constexpr huint32_t
netmask_ipv4_bits(uint32_t num)
{
return huint32_t{_netmask_ipv4_bits(32 - num)};
}
constexpr huint32_t
ipaddr_ipv4_bits(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
{
return huint32_t{(d) | (c << 8) | (b << 16) | (a << 24)};
}
bool
IsIPv4Bogon(const huint32_t& addr);
constexpr bool
ipv6_is_siit(const in6_addr& addr)
{
return addr.s6_addr[11] == 0xff && addr.s6_addr[10] == 0xff && addr.s6_addr[9] == 0
&& addr.s6_addr[8] == 0 && addr.s6_addr[7] == 0 && addr.s6_addr[6] == 0
&& addr.s6_addr[5] == 0 && addr.s6_addr[4] == 0 && addr.s6_addr[3] == 0
&& addr.s6_addr[2] == 0 && addr.s6_addr[1] == 0 && addr.s6_addr[0] == 0;
}
bool
IsBogon(const in6_addr& addr);
@ -108,7 +66,7 @@ namespace llarp
GetBestNetIF(std::string& ifname, int af = AF_INET);
/// look at adapter ranges and find a free one
std::optional<std::string>
std::optional<IPRange>
FindFreeRange();
/// look at adapter names and find a free one

@ -0,0 +1,48 @@
#pragma once
#include <net/net_int.hpp>
namespace llarp
{
/// get a netmask with the higest numset bits set
constexpr huint128_t
_netmask_ipv6_bits(uint32_t numset)
{
return (128 - numset) ? (huint128_t{1} << numset) | _netmask_ipv6_bits(numset + 1)
: huint128_t{0};
}
constexpr huint128_t
netmask_ipv6_bits(uint32_t numset)
{
return _netmask_ipv6_bits(128 - numset);
}
/// get a netmask with the higest numset bits set
constexpr uint32_t
_netmask_ipv4_bits(uint32_t numset)
{
return (32 - numset) ? (1 << numset) | _netmask_ipv4_bits(numset + 1) : 0;
}
/// get a netmask given some /N range
constexpr huint32_t
netmask_ipv4_bits(uint32_t num)
{
return huint32_t{_netmask_ipv4_bits(32 - num)};
}
constexpr huint32_t
ipaddr_ipv4_bits(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
{
return huint32_t{(d) | (c << 8) | (b << 16) | (a << 24)};
}
constexpr bool
ipv6_is_siit(const in6_addr& addr)
{
return addr.s6_addr[11] == 0xff && addr.s6_addr[10] == 0xff && addr.s6_addr[9] == 0
&& addr.s6_addr[8] == 0 && addr.s6_addr[7] == 0 && addr.s6_addr[6] == 0
&& addr.s6_addr[5] == 0 && addr.s6_addr[4] == 0 && addr.s6_addr[3] == 0
&& addr.s6_addr[2] == 0 && addr.s6_addr[1] == 0 && addr.s6_addr[0] == 0;
}
} // namespace llarp

@ -214,8 +214,6 @@ namespace llarp
{
if (m_router->NumberOfConnectedRouters() == 0)
{
// persist connection
m_router->ConnectToRandomRouters(1);
return false;
}
bool got = false;

@ -20,7 +20,7 @@ namespace llarp::rpc
void
EndpointAuthRPC::Start()
{
if (m_AuthURL.empty())
if (m_AuthURL.empty() or m_AuthMethod.empty())
return;
m_LMQ->connect_remote(
m_AuthURL,
@ -40,41 +40,37 @@ namespace llarp::rpc
std::function<void(service::AuthResult)> hook)
{
const auto from = msg->sender.Addr();
auto reply = [logic = m_Endpoint->RouterLogic(), hook](service::AuthResult result) {
logic->Call([hook, result]() { hook(result); });
};
if (m_AuthWhitelist.count(from))
{
// explicitly whitelisted source
m_Endpoint->RouterLogic()->Call([hook]() { hook(service::AuthResult::eAuthAccepted); });
reply(service::AuthResult::eAuthAccepted);
return;
}
if (not m_Conn.has_value())
{
// we don't have a connection to the backend so it's failed
m_Endpoint->RouterLogic()->Call([hook]() { hook(service::AuthResult::eAuthFailed); });
reply(service::AuthResult::eAuthFailed);
return;
}
if (msg->proto != llarp::service::eProtocolAuth)
{
// not an auth message, reject
m_Endpoint->RouterLogic()->Call([hook]() { hook(service::AuthResult::eAuthRejected); });
reply(service::AuthResult::eAuthRejected);
return;
}
const auto maybe = msg->MaybeEncodeAuthInfo();
if (not maybe.has_value())
{
// cannot generate meta info, failed
m_Endpoint->RouterLogic()->Call([hook]() { hook(service::AuthResult::eAuthFailed); });
return;
}
std::string_view metaInfo{(char*)maybe->data(), maybe->size()};
const auto authinfo = msg->EncodeAuthInfo();
std::string_view metainfo{authinfo.data(), authinfo.size()};
std::string_view payload{(char*)msg->payload.data(), msg->payload.size()};
// call method with 2 parameters: metainfo and userdata
m_LMQ->request(
*m_Conn,
m_AuthMethod,
[self = shared_from_this(), hook, from = from.ToString()](
bool success, std::vector<std::string> data) {
[self = shared_from_this(), reply](bool success, std::vector<std::string> data) {
service::AuthResult result = service::AuthResult::eAuthFailed;
if (success and not data.empty())
{
@ -84,10 +80,10 @@ namespace llarp::rpc
result = *maybe;
}
}
self->m_Endpoint->RouterLogic()->Call([hook, result]() { hook(result); });
reply(result);
},
metaInfo,
metainfo,
payload);
} // namespace llarp::rpc
}
} // namespace llarp::rpc

@ -2,6 +2,10 @@
#include <router/abstractrouter.hpp>
#include <util/thread/logic.hpp>
#include <constants/version.hpp>
#include <nlohmann/json.hpp>
#include <net/ip_range.hpp>
#include <service/context.hpp>
#include <service/auth.hpp>
namespace llarp::rpc
{
@ -9,6 +13,72 @@ namespace llarp::rpc
{
}
/// maybe parse json from message paramter at index
std::optional<nlohmann::json>
MaybeParseJSON(const lokimq::Message& msg, size_t index = 0)
{
try
{
const auto& str = msg.data.at(index);
return nlohmann::json::parse(str);
}
catch (std::exception&)
{
return std::nullopt;
}
}
template <typename Result_t>
std::string
CreateJSONResponse(Result_t result)
{
const auto obj = nlohmann::json{
{"error", nullptr},
{"result", result},
};
return obj.dump();
}
std::string
CreateJSONError(std::string_view msg)
{
const auto obj = nlohmann::json{
{"error", msg},
};
return obj.dump();
}
/// a function that replies to an rpc request
using ReplyFunction_t = std::function<void(std::string)>;
void
HandleJSONRequest(
lokimq::Message& msg, std::function<void(nlohmann::json, ReplyFunction_t)> handleRequest)
{
const auto maybe = MaybeParseJSON(msg);
if (not maybe.has_value())
{
msg.send_reply(CreateJSONError("failed to parse json"));
return;
}
if (not maybe->is_object())
{
msg.send_reply(CreateJSONError("request data not a json object"));
return;
}
try
{
std::promise<std::string> reply;
handleRequest(*maybe, [&reply](std::string result) { reply.set_value(result); });
auto ftr = reply.get_future();
msg.send_reply(ftr.get());
}
catch (std::exception& ex)
{
msg.send_reply(CreateJSONError(ex.what()));
}
}
void
RpcServer::AsyncServeRPC(lokimq::address url)
{
@ -19,23 +89,102 @@ namespace llarp::rpc
[&](lokimq::Message& msg) {
if (not m_Router->IsRunning())
{
msg.send_reply("router is not running");
msg.send_reply(CreateJSONError("router is not running"));
return;
}
m_Router->Stop();
msg.send_reply("OK");
msg.send_reply(CreateJSONResponse("OK"));
})
.add_request_command(
"version", [](lokimq::Message& msg) { msg.send_reply(llarp::VERSION_FULL); })
.add_request_command("status", [&](lokimq::Message& msg) {
std::promise<std::string> result;
LogicCall(m_Router->logic(), [&result, r = m_Router]() {
const auto state = r->ExtractStatus();
result.set_value(state.dump());
"version",
[](lokimq::Message& msg) { msg.send_reply(CreateJSONResponse(llarp::VERSION_FULL)); })
.add_request_command(
"status",
[&](lokimq::Message& msg) {
std::promise<util::StatusObject> result;
LogicCall(m_Router->logic(), [&result, r = m_Router]() {
const auto state = r->ExtractStatus();
result.set_value(state);
});
auto ftr = result.get_future();
msg.send_reply(CreateJSONResponse(ftr.get()));
})
.add_request_command("exit", [&](lokimq::Message& msg) {
HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) {
std::optional<service::Address> exit;
IPRange range;
bool map = true;
const auto exit_itr = obj.find("exit");
if (exit_itr != obj.end())
{
service::Address addr;
if (not addr.FromString(exit_itr->get<std::string>()))
{
reply(CreateJSONError("invalid exit address"));
return;
}
exit = addr;
}
const auto unmap_itr = obj.find("unmap");
if (unmap_itr != obj.end() and unmap_itr->get<bool>())
{
map = false;
}
const auto range_itr = obj.find("range");
if (range_itr == obj.end())
{
range.FromString("0.0.0.0/0");
}
else if (not range.FromString(range_itr->get<std::string>()))
{
reply(CreateJSONError("invalid ip range"));
return;
}
std::optional<std::string> token;
const auto token_itr = obj.find("token");
if (token_itr != obj.end())
{
token = token_itr->get<std::string>();
}
std::string endpoint = "default";
const auto endpoint_itr = obj.find("endpoint");
if (endpoint_itr != obj.end())
{
endpoint = endpoint_itr->get<std::string>();
}
std::promise<std::string> result;
LogicCall(r->logic(), [map, exit, range, token, endpoint, r, &result]() {
auto ep = r->hiddenServiceContext().GetEndpointByName(endpoint);
if (ep == nullptr)
{
result.set_value(CreateJSONError("no endpoint with name " + endpoint));
return;
}
if (map and exit.has_value())
{
ep->MapExitRange(range, *exit);
if (token.has_value())
{
ep->SetAuthInfoForEndpoint(*exit, service::AuthInfo{*token});
}
}
else if (map and not exit.has_value())
{
result.set_value(CreateJSONError("no exit address provided"));
return;
}
else if (not map)
{
ep->UnmapExitRange(range);
}
result.set_value(CreateJSONResponse("OK"));
});
auto ftr = result.get_future();
reply(ftr.get());
});
auto ftr = result.get_future();
msg.send_reply(ftr.get());
});
}
} // namespace llarp::rpc

@ -38,4 +38,11 @@ namespace llarp::service
std::shared_ptr<llarp::service::ProtocolMessage> msg,
std::function<void(AuthResult)> hook) = 0;
};
/// info needed by clients in order to authenticate to a remote endpoint
struct AuthInfo
{
std::string token;
};
} // namespace llarp::service

@ -51,6 +51,9 @@ namespace llarp
if (conf.m_Hops.has_value())
numHops = *conf.m_Hops;
conf.m_ExitMap.ForEachEntry(
[&](const IPRange& range, const service::Address& addr) { MapExitRange(range, addr); });
return m_state->Configure(conf);
}
@ -837,7 +840,7 @@ namespace llarp
Endpoint::ProcessDataMessage(std::shared_ptr<ProtocolMessage> msg)
{
if ((msg->proto == eProtocolExit
&& (m_state->m_ExitEnabled || msg->sender.Addr() == m_state->m_ExitNode))
&& (m_state->m_ExitEnabled || m_ExitMap.ContainsValue(msg->sender.Addr())))
|| msg->proto == eProtocolTrafficV4 || msg->proto == eProtocolTrafficV6)
{
util::Lock l(m_state->m_InboundTrafficQueueMutex);
@ -1376,5 +1379,40 @@ namespace llarp
{
return m_state->m_Sessions;
}
void
Endpoint::SetAuthInfoForEndpoint(Address addr, AuthInfo info)
{
m_RemoteAuthInfos[addr] = std::move(info);
}
void
Endpoint::MapExitRange(IPRange range, Address exit)
{
LogInfo(Name(), " map ", range, " to exit at ", exit);
m_ExitMap.Insert(range, exit);
}
void
Endpoint::UnmapExitRange(IPRange range)
{
// unmap all ranges that fit in the range we gave
m_ExitMap.RemoveIf([&](const auto& item) -> bool {
if (not range.Contains(item.first))
return false;
LogInfo(Name(), " unmap ", item.first, " from exit at ", item.second);
return true;
});
}
std::optional<AuthInfo>
Endpoint::MaybeGetAuthInfoForEndpoint(Address remote)
{
const auto itr = m_RemoteAuthInfos.find(remote);
if (itr == m_RemoteAuthInfos.end())
return std::nullopt;
return itr->second;
}
} // namespace service
} // namespace llarp

@ -89,7 +89,7 @@ namespace llarp
virtual std::unordered_map<std::string, std::string>
NotifyParams() const;
util::StatusObject
virtual util::StatusObject
ExtractStatus() const;
void
@ -178,6 +178,10 @@ namespace llarp
void
SetEndpointAuth(std::shared_ptr<IAuthPolicy> policy);
/// sets how we authenticate with remote address
void
SetAuthInfoForEndpoint(Address remote, AuthInfo info);
// virtual huint128_t
// ObtainIPForAddr(const AlignedBuffer< 32 >& addr, bool serviceNode) = 0;
@ -226,6 +230,12 @@ namespace llarp
return m_Identity;
}
void
MapExitRange(IPRange range, service::Address exit);
void
UnmapExitRange(IPRange range);
void
PutLookup(IServiceLookup* lookup, uint64_t txid) override;
@ -373,6 +383,9 @@ namespace llarp
bool
SendToSNodeOrQueue(const RouterID& addr, const llarp_buffer_t& payload);
std::optional<AuthInfo>
MaybeGetAuthInfoForEndpoint(service::Address addr);
protected:
/// parent context that owns this endpoint
Context* const context;
@ -432,13 +445,14 @@ namespace llarp
protected:
IDataHandler* m_DataHandler = nullptr;
Identity m_Identity;
net::IPRangeMap<path::PathSet_ptr> m_ExitMap;
net::IPRangeMap<service::Address> m_ExitMap;
hooks::Backend_ptr m_OnUp;
hooks::Backend_ptr m_OnDown;
hooks::Backend_ptr m_OnReady;
bool m_PublishIntroSet = true;
std::unique_ptr<EndpointState> m_state;
std::shared_ptr<IAuthPolicy> m_AuthPolicy;
std::unordered_map<Address, AuthInfo, Address::Hash> m_RemoteAuthInfos;
private:
void

@ -17,7 +17,6 @@ namespace llarp
m_Keyfile = conf.m_keyfile->string();
m_SnodeBlacklist = conf.m_snodeBlacklist;
m_ExitEnabled = conf.m_AllowExit;
m_ExitNode = conf.m_exitNode;
// TODO:
/*
if (k == "on-up")

@ -53,7 +53,6 @@ namespace llarp
std::string m_Name;
std::string m_NetNS;
bool m_ExitEnabled = false;
std::optional<service::Address> m_ExitNode;
util::Mutex m_SendQueueMutex; // protects m_SendQueue
std::deque<SendEvent_t> m_SendQueue GUARDED_BY(m_SendQueueMutex);

@ -96,27 +96,27 @@ namespace llarp
return bencode_end(buf);
}
std::optional<std::vector<byte_t>>
ProtocolMessage::MaybeEncodeAuthInfo() const
std::vector<char>
ProtocolMessage::EncodeAuthInfo() const
{
std::array<byte_t, 1024> info;
llarp_buffer_t buf{info};
if (not bencode_start_dict(&buf))
return std::nullopt;
throw std::runtime_error("impossibly small buffer");
if (not BEncodeWriteDictInt("a", proto, &buf))
return std::nullopt;
throw std::runtime_error("impossibly small buffer");
if (not BEncodeWriteDictEntry("i", introReply, &buf))
return std::nullopt;
throw std::runtime_error("impossibly small buffer");
if (not BEncodeWriteDictEntry("s", sender, &buf))
return std::nullopt;
throw std::runtime_error("impossibly small buffer");
if (not BEncodeWriteDictEntry("t", tag, &buf))
return std::nullopt;
throw std::runtime_error("impossibly small buffer");
if (not BEncodeWriteDictInt("v", version, &buf))
return std::nullopt;
throw std::runtime_error("impossibly small buffer");
if (not bencode_end(&buf))
return std::nullopt;
throw std::runtime_error("impossibly small buffer");
const std::size_t encodedSize = buf.cur - buf.base;
std::vector<byte_t> data;
std::vector<char> data;
data.resize(encodedSize);
std::copy_n(buf.base, encodedSize, data.data());
return data;

@ -50,9 +50,9 @@ namespace llarp
uint64_t seqno = 0;
uint64_t version = LLARP_PROTO_VERSION;
/// maybe encode metainfo for lmq endpoint auth
std::optional<std::vector<byte_t>>
MaybeEncodeAuthInfo() const;
/// encode metainfo for lmq endpoint auth
std::vector<char>
EncodeAuthInfo() const;
bool
DecodeKey(const llarp_buffer_t& key, llarp_buffer_t* val);

@ -114,6 +114,14 @@ namespace llarp
if (lastGoodSend != 0s)
{
EncryptAndSendTo(data, protocol);
return;
}
const auto maybe = m_Endpoint->MaybeGetAuthInfoForEndpoint(remoteIdent.Addr());
if (maybe.has_value())
{
// send auth message
const llarp_buffer_t authdata(maybe->token);
AsyncGenIntro(authdata, eProtocolAuth);
}
else
{

@ -7,6 +7,7 @@
#include <type_traits>
#include <limits>
#include <net/uint128.hpp>
#include <net/net_int.hpp>
namespace llarp
{
@ -27,6 +28,34 @@ namespace llarp
return count_bits(i.upper) + count_bits(i.lower);
}
template <>
constexpr std::size_t
count_bits(huint32_t x)
{
return count_bits(x.h);
}
template <>
constexpr std::size_t
count_bits(nuint32_t x)
{
return count_bits(x.n);
}
template <>
constexpr std::size_t
count_bits(huint128_t x)
{
return count_bits_128(x.h);
}
template <>
constexpr std::size_t
count_bits(nuint128_t x)
{
return count_bits_128(x.n);
}
template <typename InputIt>
constexpr std::size_t
count_array_bits_impl(InputIt begin, InputIt end)

@ -3,12 +3,12 @@
#include <net/net_int.hpp>
#include <net/ip.hpp>
#include <net/ip_range.hpp>
#include <net/net.hpp>
struct TestNet : public ::testing::Test
{
};
TEST_F(TestNet, TestIn6AddrFromString)
{
llarp::huint128_t ip;
@ -34,30 +34,29 @@ TEST_F(TestNet, TestIn6AddrToHUInt)
{
llarp::huint128_t huint_parsed = {0};
ASSERT_TRUE(huint_parsed.FromString("fd00::1"));
in6_addr addr = { { { 0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } };
in6_addr addr = {{{0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}};
auto huint = llarp::net::In6ToHUInt(addr);
ASSERT_EQ(huint, huint_parsed);
huint_parsed.h ++;
huint_parsed.h++;
ASSERT_NE(huint, huint_parsed);
}
TEST_F(TestNet, TestRangeContains8)
{
ASSERT_TRUE(llarp::IPRange::FromIPv4(10, 0, 0, 1, 8)
.ContainsV4(llarp::ipaddr_ipv4_bits(10, 40, 11, 6)));
ASSERT_TRUE(
llarp::IPRange::FromIPv4(10, 0, 0, 1, 8).Contains(llarp::ipaddr_ipv4_bits(10, 40, 11, 6)));
}
TEST_F(TestNet, TestRangeContains24)
{
ASSERT_TRUE(llarp::IPRange::FromIPv4(10, 200, 0, 1, 24)
.ContainsV4(llarp::ipaddr_ipv4_bits(10, 200, 0, 253)));
.Contains(llarp::ipaddr_ipv4_bits(10, 200, 0, 253)));
}
TEST_F(TestNet, TestRangeContainsFail)
{
ASSERT_TRUE(!llarp::IPRange::FromIPv4(192, 168, 0, 1, 24)
.ContainsV4(llarp::ipaddr_ipv4_bits(10, 200, 0, 253)));
.Contains(llarp::ipaddr_ipv4_bits(10, 200, 0, 253)));
}
TEST_F(TestNet, TestIPv4Netmask)

Loading…
Cancel
Save