From 13b01c86a6f2f9d363a547fefef3d81582fa3c47 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 10 Jan 2023 07:58:26 -0800 Subject: [PATCH 1/7] Updated RpcServer Initialization and Logic -- Moved all RPCServer initialization logic to rpcserver constructor -- Fixed config logic, fxn binding to rpc address, fxn adding rpc cats -- router hive failed CI/CD resulting from outdated reference to rpcBindAddr -- ipc socket as default hidden from windows (for now) refactored config endpoint - added rpc call script (contrib/omq-rpc.py) - added new fxns to .ini config stuff - added delete .ini file functionality to config endpoint - added edge case control for config endpoint add commented out line in clang-form for header reorg later --- .clang-format | 5 + CMakeLists.txt | 3 +- contrib/android-configure.sh | 1 - contrib/mac-configure.sh | 1 - contrib/omq-rpc.py | 99 ++ contrib/windows-configure.sh | 1 - crypto/CMakeLists.txt | 2 +- docs/refactor_notes.md | 97 ++ external/CMakeLists.txt | 25 +- external/oxen-logging | 2 +- llarp/CMakeLists.txt | 471 ++++++-- llarp/config/config.cpp | 6 +- llarp/config/config.hpp | 6 +- llarp/config/ini.cpp | 118 +- llarp/config/ini.hpp | 13 + llarp/constants/version.cpp.in | 4 +- llarp/context.cpp | 4 +- llarp/dns/rr.cpp | 2 +- llarp/exit/session.hpp | 2 +- llarp/handlers/exit.cpp | 2 +- llarp/layers/flow/stub.cpp | 0 llarp/layers/platform/stub.cpp | 0 llarp/messages/relay_commit.hpp | 2 +- llarp/net/ip_range.hpp | 8 + llarp/path/pathbuilder.cpp | 4 +- llarp/path/pathset.hpp | 2 +- llarp/path/transit_hop.cpp | 4 +- llarp/path/transit_hop.hpp | 4 +- llarp/quic/tunnel.cpp | 6 +- llarp/router/route_poker.cpp | 4 +- llarp/router/router.cpp | 5 +- llarp/router/router.hpp | 2 +- llarp/rpc/json_binary_proxy.cpp | 63 + llarp/rpc/json_binary_proxy.hpp | 181 +++ llarp/rpc/json_bt.hpp | 46 + llarp/rpc/param_parser.hpp | 359 ++++++ llarp/rpc/rpc_request.hpp | 68 ++ llarp/rpc/rpc_request_decorators.hpp | 117 ++ llarp/rpc/rpc_request_definitions.hpp | 303 +++++ llarp/rpc/rpc_request_parser.cpp | 94 ++ llarp/rpc/rpc_request_parser.hpp | 31 + llarp/rpc/rpc_server.cpp | 1045 +++++++---------- llarp/rpc/rpc_server.hpp | 142 ++- llarp/service/convotag.cpp | 2 +- llarp/service/endpoint.cpp | 21 +- llarp/service/endpoint.hpp | 29 +- llarp/service/intro.cpp | 2 +- llarp/service/outbound_context.cpp | 9 +- llarp/tooling/router_hive.hpp | 4 +- llarp/vpn/win32.cpp | 2 +- pybind/llarp/context.cpp | 3 +- test/CMakeLists.txt | 4 - test/check_main.cpp | 4 +- test/config/test_llarp_config_definition.cpp | 2 +- test/config/test_llarp_config_ini.cpp | 6 +- test/config/test_llarp_config_output.cpp | 2 +- test/crypto/test_llarp_crypto.cpp | 2 +- test/crypto/test_llarp_crypto_types.cpp | 4 +- test/crypto/test_llarp_key_manager.cpp | 11 +- test/dns/test_llarp_dns_dns.cpp | 14 +- test/llarp_test.hpp | 2 +- test/net/test_ip_address.cpp | 2 +- test/net/test_llarp_net.cpp | 8 +- test/net/test_sock_addr.cpp | 6 +- test/nodedb/test_nodedb.cpp | 6 +- test/path/test_path.cpp | 2 +- test/peerstats/test_peer_db.cpp | 10 +- test/peerstats/test_peer_types.cpp | 2 +- test/router/test_llarp_router_version.cpp | 4 +- .../test_llarp_routing_obtainexitmessage.cpp | 9 +- .../test_llarp_routing_transfer_traffic.cpp | 2 +- test/service/test_llarp_service_address.cpp | 2 +- test/service/test_llarp_service_identity.cpp | 16 +- test/service/test_llarp_service_name.cpp | 4 +- test/test_llarp_encrypted_frame.cpp | 15 +- test/test_llarp_router_contact.cpp | 8 +- test/test_util.cpp | 2 +- test/test_util.hpp | 5 +- test/util/meta/test_llarp_util_memfn.cpp | 2 +- test/util/test_llarp_util_aligned.cpp | 2 +- test/util/test_llarp_util_bencode.cpp | 4 +- test/util/test_llarp_util_bits.cpp | 2 +- .../util/test_llarp_util_decaying_hashset.cpp | 4 +- test/util/test_llarp_util_log_level.cpp | 4 +- test/util/test_llarp_util_str.cpp | 2 +- test/util/thread/test_llarp_util_queue.cpp | 6 +- .../thread/test_llarp_util_queue_manager.cpp | 2 +- 87 files changed, 2651 insertions(+), 957 deletions(-) create mode 100755 contrib/omq-rpc.py create mode 100644 docs/refactor_notes.md create mode 100644 llarp/layers/flow/stub.cpp create mode 100644 llarp/layers/platform/stub.cpp create mode 100644 llarp/rpc/json_binary_proxy.cpp create mode 100644 llarp/rpc/json_binary_proxy.hpp create mode 100644 llarp/rpc/json_bt.hpp create mode 100644 llarp/rpc/param_parser.hpp create mode 100644 llarp/rpc/rpc_request.hpp create mode 100644 llarp/rpc/rpc_request_decorators.hpp create mode 100644 llarp/rpc/rpc_request_definitions.hpp create mode 100644 llarp/rpc/rpc_request_parser.cpp create mode 100644 llarp/rpc/rpc_request_parser.hpp diff --git a/.clang-format b/.clang-format index 7109a2362..3f18c4219 100644 --- a/.clang-format +++ b/.clang-format @@ -54,3 +54,8 @@ PointerAlignment: Left # when wrapping function calls/declarations, force each parameter to have its own line BinPackParameters: 'false' BinPackArguments: 'false' + +# TODO: uncomment me when we are reading to rearrange the header includes +# IncludeBlocks: Regroup +# IncludeCategories: 'llarp/' + diff --git a/CMakeLists.txt b/CMakeLists.txt index 77d85b073..dac2c3d36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,8 +49,7 @@ endif() option(USE_AVX2 "enable avx2 code" OFF) option(USE_NETNS "enable networking namespace support. Linux only" OFF) option(NATIVE_BUILD "optimise for host system and FPU" ON) -option(EMBEDDED_CFG "optimise for older hardware or embedded systems" OFF) -option(BUILD_LIBLOKINET "build liblokinet.so" ON) +option(WITH_EMBEDDED_LOKINET "build liblokinet.so for embedded lokinet" OFF) option(XSAN "use sanitiser, if your system has it (requires -DCMAKE_BUILD_TYPE=Debug)" OFF) option(USE_JEMALLOC "Link to jemalloc for memory allocations, if found" ON) option(TESTNET "testnet build" OFF) diff --git a/contrib/android-configure.sh b/contrib/android-configure.sh index 065e83e18..5e559b5a7 100755 --- a/contrib/android-configure.sh +++ b/contrib/android-configure.sh @@ -33,7 +33,6 @@ for abi in $build_abis; do -DBUILD_PACKAGE=ON \ -DBUILD_SHARED_LIBS=OFF \ -DBUILD_TESTING=OFF \ - -DBUILD_LIBLOKINET=OFF \ -DWITH_TESTS=OFF \ -DWITH_BOOTSTRAP=OFF \ -DNATIVE_BUILD=OFF \ diff --git a/contrib/mac-configure.sh b/contrib/mac-configure.sh index fa45a960b..50c49099a 100755 --- a/contrib/mac-configure.sh +++ b/contrib/mac-configure.sh @@ -13,7 +13,6 @@ cd build-mac cmake \ -G Ninja \ -DBUILD_STATIC_DEPS=ON \ - -DBUILD_LIBLOKINET=OFF \ -DWITH_TESTS=OFF \ -DWITH_BOOTSTRAP=OFF \ -DNATIVE_BUILD=OFF \ diff --git a/contrib/omq-rpc.py b/contrib/omq-rpc.py new file mode 100755 index 000000000..2a5faec35 --- /dev/null +++ b/contrib/omq-rpc.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +import nacl.bindings as sodium +from nacl.public import PrivateKey +from nacl.signing import SigningKey, VerifyKey +import nacl.encoding +import requests +import zmq +import zmq.utils.z85 +import sys +import re +import time +import random +import shutil + + +context = zmq.Context() +socket = context.socket(zmq.DEALER) +socket.setsockopt(zmq.CONNECT_TIMEOUT, 5000) +socket.setsockopt(zmq.HANDSHAKE_IVL, 5000) +#socket.setsockopt(zmq.IMMEDIATE, 1) + +if len(sys.argv) > 1 and any(sys.argv[1].startswith(x) for x in ("ipc://", "tcp://", "curve://")): + remote = sys.argv[1] + del sys.argv[1] +else: + remote = "ipc://./rpc.sock" + +curve_pubkey = b'' +my_privkey, my_pubkey = b'', b'' + +# If given a curve://whatever/pubkey argument then transform it into 'tcp://whatever' and put the +# 'pubkey' back into argv to be handled below. +if remote.startswith("curve://"): + pos = remote.rfind('/') + pkhex = remote[pos+1:] + remote = "tcp://" + remote[8:pos] + if len(pkhex) != 64 or not all(x in "0123456789abcdefABCDEF" for x in pkhex): + print("curve:// addresses must be in the form curve://HOST:PORT/REMOTE_PUBKEY_HEX", file=sys.stderr) + sys.exit(1) + sys.argv[1:0] = [pkhex] + +if len(sys.argv) > 1 and len(sys.argv[1]) == 64 and all(x in "0123456789abcdefABCDEF" for x in sys.argv[1]): + curve_pubkey = bytes.fromhex(sys.argv[1]) + del sys.argv[1] + socket.curve_serverkey = curve_pubkey + if len(sys.argv) > 1 and len(sys.argv[1]) == 64 and all(x in "0123456789abcdefABCDEF" for x in sys.argv[1]): + my_privkey = bytes.fromhex(sys.argv[1]) + del sys.argv[1] + my_pubkey = zmq.utils.z85.decode(zmq.curve_public(zmq.utils.z85.encode(my_privkey))) + else: + my_privkey = PrivateKey.generate() + my_pubkey = my_privkey.public_key.encode() + my_privkey = my_privkey.encode() + + print("No curve client privkey given; generated a random one (pubkey: {}, privkey: {})".format( + my_pubkey.hex(), my_privkey.hex()), file=sys.stderr) + socket.curve_secretkey = my_privkey + socket.curve_publickey = my_pubkey + +if not 2 <= len(sys.argv) <= 3 or any(x in y for x in ("--help", "-h") for y in sys.argv[1:]): + print("Usage: {} [ipc:///path/to/sock|tcp://1.2.3.4:5678] [SERVER_CURVE_PUBKEY [LOCAL_CURVE_PRIVKEY]] COMMAND ['JSON']".format( + sys.argv[0]), file=sys.stderr) + sys.exit(1) + +beginning_of_time = time.clock_gettime(time.CLOCK_MONOTONIC) + +print("Connecting to {}".format(remote), file=sys.stderr) +socket.connect(remote) +to_send = [sys.argv[1].encode(), b'tagxyz123'] +to_send += (x.encode() for x in sys.argv[2:]) +print("Sending {}".format(to_send[0]), file=sys.stderr) +socket.send_multipart(to_send) +if socket.poll(timeout=5000): + m = socket.recv_multipart() + recv_time = time.clock_gettime(time.CLOCK_MONOTONIC) + if len(m) < 3 or m[0:2] != [b'REPLY', b'tagxyz123']: + print("Received unexpected {}-part reply:".format(len(m)), file=sys.stderr) + for x in m: + print("- {}".format(x)) + else: # m[2] is numeric value, m[3] is data part, and will become m[2] <- changed + print("Received reply in {:.6f}s:".format(recv_time - beginning_of_time), file=sys.stderr) + if len(m) < 3: + print("(empty reply data)", file=sys.stderr) + else: + for x in m[2:]: + print("{} bytes data part:".format(len(x)), file=sys.stderr) + if any(x.startswith(y) for y in (b'd', b'l', b'i')) and x.endswith(b'e'): + sys.stdout.buffer.write(x) + else: + print(x.decode(), end="\n\n") + +else: + print("Request timed out", file=sys.stderr) + socket.close(linger=0) + sys.exit(1) + + +# ./lmq-rpc.py ipc://$HOME/.oxen/testnet/oxend.sock 'llarp.get_service_nodes' | jq \ No newline at end of file diff --git a/contrib/windows-configure.sh b/contrib/windows-configure.sh index f41ef3af8..fa57a1aa4 100755 --- a/contrib/windows-configure.sh +++ b/contrib/windows-configure.sh @@ -32,7 +32,6 @@ cmake \ -DBUILD_PACKAGE=ON \ -DBUILD_SHARED_LIBS=OFF \ -DBUILD_TESTING=OFF \ - -DBUILD_LIBLOKINET=OFF \ -DWITH_TESTS=OFF \ -DWITH_BOOTSTRAP=OFF \ -DNATIVE_BUILD=OFF \ diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index e4cedd9b6..c73a6ed14 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -20,7 +20,7 @@ add_library(lokinet-cryptography libntrup/src/ref/rq.c ) -target_include_directories(lokinet-cryptography PUBLIC libntrup/include) +target_include_directories(lokinet-cryptography PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libntrup/include) # The avx implementation uses runtime CPU feature detection to enable itself, so we *always* want to # compile it with avx2/fma support when supported by the compiler even if we aren't compiling with diff --git a/docs/refactor_notes.md b/docs/refactor_notes.md new file mode 100644 index 000000000..efc186ff4 --- /dev/null +++ b/docs/refactor_notes.md @@ -0,0 +1,97 @@ +# High Level Iterative Approach + +the desired outcome of this refactor will be splitting the existing code up into a stack of new components. +a layer hides all functionality of the layer below it to reduce the complexity like the OSI stack intends to. +the refactor starts at the top layer, wiring up the old implementation piecewise to the top layer. +once the top layer is wired up to the old implementation we will move down to the next layer. +this will repeat until we reach the bottom layer. +once the old implementation is wired up into these new clearly defined layers, we can fixup or replace different parts of each layer one at a time as needed. + +working down from each layer will let us pick apart the old implementation (if needed) that we would wire up to the new base classes for that layer we are defining now without worrying about what is below it (yet). + +this refactor is very able to be split up into small work units that (ideally) do not confict with each other. + + +PDU: https://en.wikipedia.org/wiki/Protocol_data_unit + +# The New Layers + +from top to bottom the new layers are: + +* Platform Layer +* Flow Layer +* Routing Layer +* Onion Layer +* Link Layer +* Wire Layer + + +## Platform Layer + +this is the top layer, it is responsibile ONLY to act as a handler of reading data from the "user" (via tun interface or whatever) to forward to the flow layer as desired, and to take data from the flow layer and send it to the "user". +any kind of IP/dns mapping or traffic isolation details are done here. embedded lokinet would be implemented in this layer as well, as it is without a full tun interface. + +Platform layer PDU are what the OS gives us and we internally convert them into flow layer PDU and hand them off to the flow layer. + + +## Flow Layer + +this layer is tl;dr mean to multiplex data from the platform layer across the routing layer and propagating PDU from the routing to the platform layer if needed. + +the flow layer is responsible for sending platform layer PDU across path we have already established. +this layer is informed by the routing layer below it of state changes in what paths are available for use. +the flow layer requests from the layer below to make new paths if it wishes to get new ones on demand. +this layer will recieve routing layer PDU from the routing layer and apply any congestion control needed to buffer things to the os if it is needed at all. + +flow layer PDU are (data, ethertype, src-pubkey, dst-pubkey, isolation-metric) tuples. +data is the datum we are tunneling over lokinet. ethertype tells us what kind of datum this is, e.g. plainquic/ipv4/ipv6/auth/etc. +src-pubkey and dst-pubkey are public the ed25519 public keys of each end of the flow in use. +the isolation metric is a piece of metadata we use to distinguish unique flows (convotag). in this new seperation convotags explicitly do not hand over across paths. + + +## Routing Layer + +this layer is tl;dr meant for path management but not path building. + +the routing layer is responsible for sending/recieving flow layer PDU, DHT requests/responses, latency testing PDU and any other kind of PDU we send/recieve over the onion layer. +this layer will be responsible for managing paths we have already built across lokinet. +the routing layer will periodically measure path status/latency, and do any other kinds of perioidic path related tasks post build. +this layer when asked for a new path from the flow layer will use one that has been prebuilt already and if the number of prebuilt paths is below a threshold we will tell the onion layer to build more paths. +the routing layer will recieve path build results be their success/fail/timeout from the onion layer that were requested and apply any congestion control needed at the pivot router. + +routing layer PDU are (data, src-path, dst-path) tuples. +data is the datum we are transferring between paths. +src-path and dst-path are (pathid, router id) tuples, the source being which path this routing layer PDU originated from, destination being which path it is going to. +in the old model, router id is always the router that recieves it as the pivot router, this remains the same unless we explicitly provide router-id. +this lets us propagate hints to DHT related PDU held inside the datum. + + +## Onion Layer + +the onion layer is repsonsible for path builds, path selection logic and low level details of encrypted/decrypting PDU that are onion routed over paths. +this layer is requested by the routing layer to build a path to a pivot router with an optional additional constraints (e.g. unique cidr/operator/geoip/etc, latency constaints, hop length, path lifetime). +the onion layer will encrypt PDU and send them to link layer as (frame/edge router id) tuples, and recieve link layer frames from edge routers, decrypt them and propagate them as needed to the routing layer. +this layer also handles transit onion traffic and transit path build responsibilities as a snode and apply congestion control as needed per transit path. + +the onion layer PDU are (data, src-path, dst-path) tuples. +src-path and dst-path are (router-id, path-id) tuples which contain the ed25519 pubkey of the node and the 128 bit path-id it was associated with. +data is some datum we are onion routing that we would apply symettric encryption as needed before propagating to upper or lower layers. + + +## Link Layer + +the link layer is responsbile for transmission of frames between nodes. +this layer will handle queuing and congestion control between wire proto sessions between nodes. +the link layer is will initate and recieve wire session to/from remote nodes. + +the link layer PDU is (data, src-router-id, dst-router-id) tuples. +data is a datum of a link layer frame. +src-router-id and dst-router-id are (ed25519-pubkey, net-addr, wire-proto-info) tuples. +the ed25519 pubkey is a .snode address, (clients have these too but they are ephemeral). +net-addr is an (ip, port) tuple the node is reachable via the wire protocol. +wire-proto-info is dialect specific wire protocol specific info. + +## Wire Layer + +the wire layer is responsible for transmitting link layer frames between nodes. +all details here are specific to each wire proto dialect. diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 9b33431f9..c2226bd77 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,4 +1,3 @@ - option(SUBMODULE_CHECK "Enables checking that vendored library submodules are up to date" ON) if(SUBMODULE_CHECK) find_package(Git) @@ -140,3 +139,27 @@ if(WITH_BOOTSTRAP) endif() endif() + + +# libcrypt defaults, only on with macos and non static linux +set(default_libcrypt OFF) + +if(LINUX AND NOT STATIC_LINK) + set(default_libcrypt ON) +endif() +if(MACOS) + set(default_libcrypt ON) +endif() + +option(WITH_LIBCRYPT "enable fast password hash with libcrypt" ${default_libcrypt}) + +add_library(lokinet-libcrypt INTERFACE) +if(WITH_LIBCRYPT) + pkg_check_modules(LIBCRYPT libcrypt IMPORTED_TARGET REQUIRED) + add_definitions(-DHAVE_CRYPT) + target_link_libraries(lokinet-libcrypt INTERFACE PkgConfig::LIBCRYPT) + message(STATUS "using libcrypt ${LIBCRYPT_VERSION}") +else() + # TODO static build lib crypt? + message(STATUS "not building with libcrypt") +endif() diff --git a/external/oxen-logging b/external/oxen-logging index 12c17d6ea..9f2323a2d 160000 --- a/external/oxen-logging +++ b/external/oxen-logging @@ -1 +1 @@ -Subproject commit 12c17d6eab754908cd88f05d09b9388381e47515 +Subproject commit 9f2323a2db5fc54fe8394892769eff859967f735 diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index bb4b3bef2..5c3ade29a 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -1,5 +1,12 @@ include(Version) +target_sources(lokinet-cryptography PRIVATE + crypto/crypto_libsodium.cpp + crypto/crypto.cpp + crypto/encrypted_frame.cpp + crypto/types.cpp +) + add_library(lokinet-util STATIC ${CMAKE_CURRENT_BINARY_DIR}/constants/version.cpp @@ -15,19 +22,9 @@ add_library(lokinet-util util/thread/threading.cpp util/time.cpp) - add_dependencies(lokinet-util genversion) -target_include_directories(lokinet-util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}) - -target_link_libraries(lokinet-util PUBLIC - lokinet-cryptography - nlohmann_json::nlohmann_json - filesystem - oxenc::oxenc - oxen::logging -) - +# lokinet-platform holds all platform specific code add_library(lokinet-platform STATIC # for networking @@ -45,9 +42,6 @@ add_library(lokinet-platform vpn/platform.cpp ) -target_link_libraries(lokinet-platform PUBLIC lokinet-cryptography lokinet-util Threads::Threads base_libs uvw) -target_link_libraries(lokinet-platform PRIVATE oxenmq::oxenmq) - if (ANDROID) target_sources(lokinet-platform PRIVATE android/ifaddrs.c util/nop_service_manager.cpp) endif() @@ -66,36 +60,26 @@ if (WIN32) net/win32.cpp vpn/win32.cpp win32/service_manager.cpp - win32/exec.cpp) - add_library(lokinet-win32 STATIC + win32/exec.cpp win32/dll.cpp - win32/exception.cpp) - add_library(lokinet-wintun STATIC - win32/wintun.cpp) - add_library(lokinet-windivert STATIC + win32/exception.cpp + win32/wintun.cpp win32/windivert.cpp) - - # wintun and windivert are privated linked by lokinet-platform - # this is so their details do not leak out to deps of lokinet-platform - # wintun and windivert still need things from lokinet-platform - target_compile_options(lokinet-wintun PUBLIC -I${CMAKE_BINARY_DIR}/wintun/include/) - target_compile_options(lokinet-windivert PUBLIC -I${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/include/) - target_include_directories(lokinet-windivert PUBLIC ${PROJECT_SOURCE_DIR}) - target_link_libraries(lokinet-wintun PUBLIC lokinet-platform lokinet-util lokinet-config) - target_link_libraries(lokinet-win32 PUBLIC lokinet-util) - target_link_libraries(lokinet-windivert PUBLIC oxen-logging) - target_link_libraries(lokinet-windivert PRIVATE lokinet-win32) - target_link_libraries(lokinet-platform PRIVATE lokinet-win32 lokinet-wintun lokinet-windivert) + target_include_directories(lokinet-platform PRIVATE ${CMAKE_BINARY_DIR}/wintun/include/ ${CMAKE_BINARY_DIR}/WinDivert-${WINDIVERT_VERSION}/include/) else() target_sources(lokinet-platform PRIVATE net/posix.cpp) endif() - -if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - target_include_directories(lokinet-platform SYSTEM PUBLIC /usr/local/include) +if(APPLE) + add_subdirectory(apple) + target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp) endif() +# lokinet-dns is the dns parsing and hooking library that we use to +# parse modify and reconstitute dns wire proto, dns queries and RR +# should have no concept of dns caching, this is left as an implementation +# detail of dns resolvers (LATER: make separate lib for dns resolvers) add_library(lokinet-dns STATIC dns/message.cpp @@ -107,13 +91,50 @@ add_library(lokinet-dns dns/server.cpp dns/srv_data.cpp) +# platform specific bits and bobs for setting dns +add_library(lokinet-dns-platform INTERFACE) if(WITH_SYSTEMD) - target_sources(lokinet-dns PRIVATE dns/nm_platform.cpp dns/sd_platform.cpp) + add_library(lokinet-dns-systemd STATIC dns/nm_platform.cpp dns/sd_platform.cpp) + target_link_libraries(lokinet-dns-platform INTERFACE lokinet-dns-systemd) endif() -target_link_libraries(lokinet-dns PUBLIC lokinet-platform uvw) -target_link_libraries(lokinet-dns PRIVATE libunbound lokinet-config) +# lokinet-nodedb holds all types and logic for storing parsing and constructing +# nodedb data published to the network and versions of it stored locally +add_library(lokinet-nodedb + STATIC + bootstrap.cpp + net/address_info.cpp + net/exit_info.cpp + net/traffic_policy.cpp + nodedb.cpp + pow.cpp + profiling.cpp + router_contact.cpp + router_id.cpp + router_version.cpp +) + +set(BOOTSTRAP_FALLBACKS) +foreach(bs IN ITEMS MAINNET TESTNET) + if(BOOTSTRAP_FALLBACK_${bs}) + message(STATUS "Building with ${bs} fallback boostrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"") + file(READ "${BOOTSTRAP_FALLBACK_${bs}}" bs_data HEX) + if(bs STREQUAL TESTNET) + set(network "gamma") + elseif(bs STREQUAL MAINNET) + set(network "lokinet") + else() + string(TOLOWER "${bs}" network) + endif() + string(REGEX REPLACE "([0-9a-f][0-9a-f])" "\\\\x\\1" bs_data "${bs_data}") + set(BOOTSTRAP_FALLBACKS "${BOOTSTRAP_FALLBACKS}{\"${network}\"s, \"${bs_data}\"sv},\n") + endif() +endforeach() +configure_file("bootstrap-fallbacks.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp" @ONLY) +target_sources(lokinet-nodedb PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp") + +# lokinet-config is for all configuration types and parsers add_library(lokinet-config STATIC config/config.cpp @@ -121,18 +142,15 @@ add_library(lokinet-config config/ini.cpp config/key_manager.cpp) -target_link_libraries(lokinet-config PUBLIC lokinet-dns lokinet-platform oxenmq::oxenmq) - -add_library(lokinet-amalgum +# lokinet-consensus is for deriving and tracking network consensus state for both service nodes and clients +add_library(lokinet-consensus STATIC consensus/reachability_testing.cpp +) - bootstrap.cpp - context.cpp - crypto/crypto_libsodium.cpp - crypto/crypto.cpp - crypto/encrypted_frame.cpp - crypto/types.cpp +# lokinet-dht holds all logic related to interacting with and participating in the DHT hashring +add_library(lokinet-dht + STATIC dht/context.cpp dht/dht.cpp dht/explorenetworkjob.cpp @@ -151,44 +169,56 @@ add_library(lokinet-amalgum dht/recursiverouterlookup.cpp dht/serviceaddresslookup.cpp dht/taglookup.cpp +) - endpoint_base.cpp +# lokinet-layer-flow is the flow layer which sits atop the routing layer which manages +# flows between lokinet snapp endpoints be they .loki or .snode +add_library(lokinet-layer-flow + STATIC + layers/flow/stub.cpp # todo: remove me +) - exit/context.cpp - exit/endpoint.cpp - exit/exit_messages.cpp - exit/policy.cpp - exit/session.cpp - handlers/exit.cpp - handlers/tun.cpp + +# lokinet-layer-onion is the "dumb" onion routing layer with builds manages and does i/o +# with onion paths. onion paths anonymize routing layer pdu. +add_library(lokinet-layer-onion + STATIC + path/ihophandler.cpp + path/path_context.cpp + path/path.cpp + path/pathbuilder.cpp + path/pathset.cpp + path/transit_hop.cpp + messages/relay.cpp + messages/relay_commit.cpp + messages/relay_status.cpp +) + +# lokinet-layer-wire is a layer 1 analog which splits up +# layer 2 frames into layer 1 symbols which in the case of iwp are encrypted udp/ip packets +add_library(lokinet-layer-wire + STATIC iwp/iwp.cpp iwp/linklayer.cpp iwp/message_buffer.cpp iwp/session.cpp +) + +# lokinet-layer-link is for our layer 2 analog which splits up layer 2 frames into +# a series of layer 1 symbols which are then transmitted between lokinet instances +add_library(lokinet-layer-link + STATIC link/link_manager.cpp link/session.cpp link/server.cpp messages/dht_immediate.cpp messages/link_intro.cpp messages/link_message_parser.cpp - messages/relay.cpp - messages/relay_commit.cpp - 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 - path/path.cpp - path/pathbuilder.cpp - path/pathset.cpp - path/transit_hop.cpp - peerstats/peer_db.cpp - peerstats/types.cpp - pow.cpp - profiling.cpp - +) + +# lokinet-plainquic is for holding the tunneled plainquic code, not quic wire protocol code +add_library(lokinet-plainquic + STATIC quic/address.cpp quic/client.cpp quic/connection.cpp @@ -197,27 +227,63 @@ add_library(lokinet-amalgum quic/server.cpp quic/stream.cpp quic/tunnel.cpp +) - router_contact.cpp - router_id.cpp - router_version.cpp - service/name.cpp +# lokinet-context holds the contextualized god objects for a lokinet instance +# it is what any main function would link to in practice but it is hidden behind an interface library (lokinet-amalgum) +add_library(lokinet-context + STATIC + context.cpp + link/link_manager.cpp router/outbound_message_handler.cpp router/outbound_session_maker.cpp router/rc_lookup_handler.cpp router/rc_gossiper.cpp router/router.cpp router/route_poker.cpp +) + +# lokinet-rpc holds all rpc related compilation units +add_library(lokinet-rpc + STATIC + rpc/json_binary_proxy.cpp + rpc/lokid_rpc_client.cpp + rpc/rpc_request_parser.cpp + rpc/rpc_server.cpp + rpc/endpoint_rpc.cpp +) + +# optional peer stats library +add_library(lokinet-peerstats + STATIC + peerstats/peer_db.cpp + peerstats/types.cpp +) +# lokinet-layer-routing holds logic related to the routing layer +# routing layer is anonymized over the onion layer +add_library(lokinet-layer-routing + STATIC routing/dht_message.cpp routing/message_parser.cpp routing/path_confirm_message.cpp routing/path_latency_message.cpp routing/path_transfer_message.cpp routing/transfer_traffic_message.cpp - rpc/lokid_rpc_client.cpp - rpc/rpc_server.cpp - rpc/endpoint_rpc.cpp +) + +# kitchen sink to be removed after refactor +add_library(lokinet-service-deprecated-kitchensink + STATIC + endpoint_base.cpp + exit/context.cpp + exit/endpoint.cpp + exit/exit_messages.cpp + exit/policy.cpp + exit/session.cpp + handlers/exit.cpp + handlers/tun.cpp + service/name.cpp service/address.cpp service/async_key_exchange.cpp service/auth.cpp @@ -242,65 +308,216 @@ add_library(lokinet-amalgum service/tag.cpp ) -set(BOOTSTRAP_FALLBACKS) -foreach(bs IN ITEMS MAINNET TESTNET) - if(BOOTSTRAP_FALLBACK_${bs}) - message(STATUS "Building with ${bs} fallback boostrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"") - file(READ "${BOOTSTRAP_FALLBACK_${bs}}" bs_data HEX) - if(bs STREQUAL TESTNET) - set(network "gamma") - elseif(bs STREQUAL MAINNET) - set(network "lokinet") - else() - string(TOLOWER "${bs}" network) - endif() - string(REGEX REPLACE "([0-9a-f][0-9a-f])" "\\\\x\\1" bs_data "${bs_data}") - set(BOOTSTRAP_FALLBACKS "${BOOTSTRAP_FALLBACKS}{\"${network}\"s, \"${bs_data}\"sv},\n") - endif() -endforeach() -configure_file("bootstrap-fallbacks.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp" @ONLY) -target_sources(lokinet-amalgum PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-fallbacks.cpp") +add_library(lokinet-layer-platform + STATIC + layers/platform/stub.cpp # todo: remove me +) -if(WITH_PEERSTATS_BACKEND) - target_compile_definitions(lokinet-amalgum PRIVATE -DLOKINET_PEERSTATS_BACKEND) - target_link_libraries(lokinet-amalgum PUBLIC sqlite_orm) -endif() +# interal tooling for pybind +add_library(lokinet-tooling INTERFACE) if(WITH_HIVE) - target_sources(lokinet-amalgum PRIVATE + add_library(lokinet-hive-tooling + STATIC tooling/router_hive.cpp tooling/hive_router.cpp tooling/hive_context.cpp ) + target_link_libraries(lokinet-tooling INTERFACE lokinet-hive-tooling) endif() -# TODO: make libunbound hidden behind a feature flag like sqlite for embedded lokinet -target_link_libraries(lokinet-amalgum PRIVATE libunbound) -target_link_libraries(lokinet-amalgum PUBLIC - CLI11 - oxenc::oxenc +# interface library for setting commone includes, linkage and flags. +add_library(lokinet-base INTERFACE) +target_include_directories(lokinet-base + INTERFACE ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include +) +target_link_libraries(lokinet-base INTERFACE oxen::logging lokinet-cryptography) + +if(WITH_PEERSTATS) + target_compile_definitions(lokinet-base INTERFACE -DLOKINET_PEERSTATS_BACKEND) + target_link_libraries(lokinet-base INTERFACE sqlite_orm) +endif() + +# interface libraries for internal linkage +add_library(lokinet-layers INTERFACE) +add_library(lokinet-amalgum INTERFACE) + + +# helper function to link a library to lokinet-base, enable lto, add to lokinet-amalgum and then link to other libs +function(lokinet_link_lib libname) + message(DEBUG "created target: ${libname}") + enable_lto(${libname}) + target_link_libraries(${libname} PUBLIC lokinet-base ${ARGN}) + target_link_libraries(lokinet-amalgum INTERFACE ${libname}) +endfunction() + +# internal public linkages of components +lokinet_link_lib(lokinet-util) +lokinet_link_lib(lokinet-cryptography lokinet-libcrypt lokinet-util) +lokinet_link_lib(lokinet-peerstats lokinet-context) +lokinet_link_lib(lokinet-consensus lokinet-context) +lokinet_link_lib(lokinet-layer-link lokinet-peerstats) + +if(TARGET lokinet-hive-tooling) + lokinet_link_lib(lokinet-hive-tooling lokinet-context) +endif() + +if(TARGET lokinet-dns-systemd) + lokinet_link_lib(lokinet-dns-systemd + lokinet-dns lokinet-platform - lokinet-config + ) +endif() + +lokinet_link_lib(lokinet-platform lokinet-util) + +lokinet_link_lib(lokinet-config + lokinet-util + lokinet-nodedb + lokinet-dns + lokinet-platform +) + +lokinet_link_lib(lokinet-context + lokinet-config + lokinet-platform + lokinet-peerstats + lokinet-layers + lokinet-consensus + lokinet-rpc +) + +lokinet_link_lib(lokinet-dht + lokinet-util + lokinet-nodedb +) + +lokinet_link_lib(lokinet-plainquic + lokinet-platform + lokinet-config +) + +lokinet_link_lib(lokinet-dns + lokinet-platform + lokinet-dns-platform + lokinet-config +) + +lokinet_link_lib(lokinet-nodedb + lokinet-util + lokinet-platform +) + +lokinet_link_lib(lokinet-util + lokinet-nodedb + lokinet-platform +) + +lokinet_link_lib(lokinet-rpc + lokinet-context + lokinet-peerstats + lokinet-util +) + +# inter lokinet-layer public/private linkage. +# when linking each layer, we consider the layer directly below private linkage and the layer above public linkage. +# this lets us hide functionality of layers below us when depended on by another component. +# +# from highest to lowest layer, the above layers are stacked as follows: +# +# platform (what lokinet snapps interact with, be it l3 os interaction or embedded lokinet) +# flow (how we want to route and stripe over our onion routing) +# routing (what we are onion routing) +# onion (how the onion routing happens) +# link (what we want to send over the wire and to where) +# wire (what is actually sent over the wire) +# +function(link_lokinet_layers) + set(lib ${ARGV0}) + if(${ARGC} GREATER 1) + lokinet_link_lib(${ARGV1} ${lib}) + list(REMOVE_AT ARGV 1) + target_link_libraries(${lib} PRIVATE ${ARGV1}) + # recursion :D + link_lokinet_layers(${ARGV}) + else() + lokinet_link_lib(${lib}) + endif() +endfunction() + +link_lokinet_layers( + lokinet-layer-platform + lokinet-layer-flow + lokinet-layer-routing + lokinet-layer-onion + lokinet-layer-link + lokinet-layer-wire +) + +# set me to OFF to disable old codepath +set(use_old_impl ON) +if(use_old_impl) + # flow layer deprecated-kitchensink (remove me after refactor) + lokinet_link_lib(lokinet-service-deprecated-kitchensink lokinet-dns - lokinet-util - lokinet-cryptography - ngtcp2_static - oxenmq::oxenmq) - -enable_lto(lokinet-util lokinet-platform lokinet-dns lokinet-config lokinet-amalgum) - -pkg_check_modules(CRYPT libcrypt IMPORTED_TARGET) -if(CRYPT_FOUND AND NOT CMAKE_CROSSCOMPILING) - add_definitions(-DHAVE_CRYPT) - add_library(libcrypt INTERFACE) - target_link_libraries(libcrypt INTERFACE PkgConfig::CRYPT) - target_link_libraries(lokinet-amalgum PRIVATE libcrypt) - message(STATUS "using libcrypt ${CRYPT_VERSION}") + lokinet-nodedb + lokinet-context + lokinet-plainquic + lokinet-layer-routing + lokinet-layer-onion + lokinet-dht + lokinet-platform + lokinet-rpc + ) + target_link_libraries(lokinet-layers INTERFACE lokinet-service-deprecated-kitchensink) endif() +target_link_libraries(lokinet-layers INTERFACE + lokinet-layer-platform + lokinet-layer-flow + lokinet-layer-routing + lokinet-layer-onion + lokinet-layer-link + lokinet-layer-wire +) + + +# per component external deps + +target_link_libraries(lokinet-config PUBLIC oxenmq::oxenmq) +target_link_libraries(lokinet-platform PUBLIC oxenmq::oxenmq) +target_link_libraries(lokinet-dns PUBLIC libunbound) + +target_link_libraries(lokinet-cryptography PUBLIC + oxenc::oxenc + sodium +) + +target_link_libraries(lokinet-context PUBLIC + CLI11 + oxenmq::oxenmq + uvw +) + +target_link_libraries(lokinet-platform PUBLIC + Threads::Threads + base_libs + uvw +) + +target_link_libraries(lokinet-util PUBLIC + nlohmann_json::nlohmann_json + filesystem + oxenc::oxenc +) + +target_link_libraries(lokinet-plainquic PUBLIC + ngtcp2_static + uvw +) -if(BUILD_LIBLOKINET) +if(WITH_EMBEDDED_LOKINET) include(GNUInstallDirs) add_library(lokinet-shared SHARED lokinet_shared.cpp) target_link_libraries(lokinet-shared PUBLIC lokinet-amalgum) @@ -316,11 +533,5 @@ if(BUILD_LIBLOKINET) endif() endif() -if(APPLE) - add_subdirectory(apple) - target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp) -endif() - file(GLOB_RECURSE docs_SRC */*.hpp *.hpp) - set(DOCS_SRC ${docs_SRC} PARENT_SCOPE) diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 98a5f1571..eda628dda 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -1,8 +1,7 @@ -#include #include "config.hpp" - -#include "config/definition.hpp" +#include "definition.hpp" #include "ini.hpp" + #include #include #include @@ -18,6 +17,7 @@ #include +#include #include #include #include diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 76540f19f..3165f0354 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -1,14 +1,15 @@ #pragma once +#include "ini.hpp" +#include "definition.hpp" #include + #include #include #include #include #include #include -#include "ini.hpp" -#include "definition.hpp" #include #include #include @@ -16,7 +17,6 @@ #include #include #include - #include #include diff --git a/llarp/config/ini.cpp b/llarp/config/ini.cpp index 72cff9f61..06be65b56 100644 --- a/llarp/config/ini.cpp +++ b/llarp/config/ini.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace llarp { @@ -30,6 +31,14 @@ namespace llarp return Parse(); } + bool + ConfigParser::LoadNewFromStr(std::string_view str) + { + m_Data.resize(str.size()); + std::copy(str.begin(), str.end(), m_Data.begin()); + return ParseAll(); + } + bool ConfigParser::LoadFromStr(std::string_view str) { @@ -52,6 +61,78 @@ namespace llarp return std::isspace(static_cast(ch)) != 0; } + /// Differs from Parse() as ParseAll() does NOT skip comments + /// ParseAll() is only used by RPC endpoint 'config' for + /// reading new .ini files from string and writing them + bool + ConfigParser::ParseAll() + { + std::list lines; + { + auto itr = m_Data.begin(); + // split into lines + while (itr != m_Data.end()) + { + auto beg = itr; + while (itr != m_Data.end() && *itr != '\n' && *itr != '\r') + ++itr; + lines.emplace_back(std::addressof(*beg), std::distance(beg, itr)); + if (itr == m_Data.end()) + break; + ++itr; + } + } + + std::string_view sectName; + size_t lineno = 0; + for (auto line : lines) + { + lineno++; + // Trim whitespace + while (!line.empty() && whitespace(line.front())) + line.remove_prefix(1); + while (!line.empty() && whitespace(line.back())) + line.remove_suffix(1); + + // Skip blank lines but NOT comments + if (line.empty()) + continue; + + if (line.front() == '[' && line.back() == ']') + { + // section header + line.remove_prefix(1); + line.remove_suffix(1); + sectName = line; + } + else if (auto kvDelim = line.find('='); kvDelim != std::string_view::npos) + { + // key value pair + std::string_view k = line.substr(0, kvDelim); + std::string_view v = line.substr(kvDelim + 1); + // Trim inner whitespace + while (!k.empty() && whitespace(k.back())) + k.remove_suffix(1); + while (!v.empty() && whitespace(v.front())) + v.remove_prefix(1); + + if (k.empty()) + { + throw std::runtime_error( + fmt::format("{} invalid line ({}): '{}'", m_FileName, lineno, line)); + } + LogDebug(m_FileName, ": [", sectName, "]:", k, "=", v); + m_Config[std::string{sectName}].emplace(k, v); + } + else // malformed? + { + throw std::runtime_error( + fmt::format("{} invalid line ({}): '{}'", m_FileName, lineno, line)); + } + } + return true; + } + bool ConfigParser::Parse() { @@ -82,7 +163,7 @@ namespace llarp while (!line.empty() && whitespace(line.back())) line.remove_suffix(1); - // Skip blank lines and comments + // Skip blank lines if (line.empty() or line.front() == ';' or line.front() == '#') continue; @@ -106,16 +187,16 @@ namespace llarp if (k.empty()) { - LogError(m_FileName, " invalid line (", lineno, "): '", line, "'"); - return false; + throw std::runtime_error( + fmt::format("{} invalid line ({}): '{}'", m_FileName, lineno, line)); } LogDebug(m_FileName, ": [", sectName, "]:", k, "=", v); m_Config[std::string{sectName}].emplace(k, v); } else // malformed? { - LogError(m_FileName, " invalid line (", lineno, "): '", line, "'"); - return false; + throw std::runtime_error( + fmt::format("{} invalid line ({}): '{}'", m_FileName, lineno, line)); } } return true; @@ -168,4 +249,31 @@ namespace llarp m_Overrides.clear(); } + void + ConfigParser::SaveNew() const + { + if (not m_Overrides.empty()) + { + throw std::invalid_argument("Override specified when attempting new .ini save"); + } + if (m_Config.empty()) + { + throw std::invalid_argument("New config not loaded when attempting new .ini save"); + } + if (m_FileName.empty()) + { + throw std::invalid_argument("New config cannot be saved with filepath specified"); + } + + std::ofstream ofs(m_FileName); + for (const auto& [section, values] : m_Config) + { + ofs << std::endl << "[" << section << "]" << std::endl; + for (const auto& [key, value] : values) + { + ofs << key << "=" << value << std::endl; + } + } + } + } // namespace llarp diff --git a/llarp/config/ini.hpp b/llarp/config/ini.hpp index 847e77c94..d32761393 100644 --- a/llarp/config/ini.hpp +++ b/llarp/config/ini.hpp @@ -24,6 +24,12 @@ namespace llarp bool LoadFile(const fs::path& fname); + /// load new .ini file from string (calls ParseAll() rather than Parse()) + /// return true on success + /// return false on error + bool + LoadNewFromStr(std::string_view str); + /// load from string /// return true on success /// return false on error @@ -47,6 +53,10 @@ namespace llarp void Save(); + /// save new .ini config file to path + void + SaveNew() const; + inline void Filename(fs::path f) { @@ -54,6 +64,9 @@ namespace llarp }; private: + bool + ParseAll(); + bool Parse(); diff --git a/llarp/constants/version.cpp.in b/llarp/constants/version.cpp.in index bca06675c..e07223705 100644 --- a/llarp/constants/version.cpp.in +++ b/llarp/constants/version.cpp.in @@ -1,5 +1,5 @@ -#include -#include +#include +#include namespace llarp { diff --git a/llarp/context.cpp b/llarp/context.cpp index b8652a256..e38a1adcd 100644 --- a/llarp/context.cpp +++ b/llarp/context.cpp @@ -25,10 +25,10 @@ #include #endif -static auto logcat = llarp::log::Cat("llarp-context"); - namespace llarp { + static auto logcat = llarp::log::Cat("llarp-context"); + bool Context::CallSafe(std::function f) { diff --git a/llarp/dns/rr.cpp b/llarp/dns/rr.cpp index 495656cb2..8851ace30 100644 --- a/llarp/dns/rr.cpp +++ b/llarp/dns/rr.cpp @@ -1,6 +1,6 @@ #include "rr.hpp" #include "dns.hpp" -#include "util/formattable.hpp" +#include #include #include diff --git a/llarp/exit/session.hpp b/llarp/exit/session.hpp index def9115ac..7191cc104 100644 --- a/llarp/exit/session.hpp +++ b/llarp/exit/session.hpp @@ -1,7 +1,7 @@ #pragma once #include "exit_messages.hpp" -#include "service/protocol_type.hpp" +#include #include #include #include diff --git a/llarp/handlers/exit.cpp b/llarp/handlers/exit.cpp index 64e002e3a..a7840c090 100644 --- a/llarp/handlers/exit.cpp +++ b/llarp/handlers/exit.cpp @@ -11,7 +11,7 @@ #include #include -#include "service/protocol_type.hpp" +#include namespace llarp { diff --git a/llarp/layers/flow/stub.cpp b/llarp/layers/flow/stub.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/llarp/layers/platform/stub.cpp b/llarp/layers/platform/stub.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/llarp/messages/relay_commit.hpp b/llarp/messages/relay_commit.hpp index b019c1e53..164933917 100644 --- a/llarp/messages/relay_commit.hpp +++ b/llarp/messages/relay_commit.hpp @@ -2,7 +2,7 @@ #include #include -#include "link_message.hpp" +#include #include #include diff --git a/llarp/net/ip_range.hpp b/llarp/net/ip_range.hpp index a4e9b0be3..e758ab384 100644 --- a/llarp/net/ip_range.hpp +++ b/llarp/net/ip_range.hpp @@ -24,6 +24,14 @@ namespace llarp : addr{std::move(address)}, netmask_bits{std::move(netmask)} {} + static IPRange + StringInit(std::string _range) + { + IPRange range{}; + range.FromString(_range); + return range; + } + static constexpr IPRange V4MappedRange() { diff --git a/llarp/path/pathbuilder.cpp b/llarp/path/pathbuilder.cpp index abf149024..1710b9d33 100644 --- a/llarp/path/pathbuilder.cpp +++ b/llarp/path/pathbuilder.cpp @@ -1,10 +1,10 @@ #include "pathbuilder.hpp" +#include "path_context.hpp" #include #include #include -#include "path_context.hpp" -#include "util/logging.hpp" +#include #include #include #include diff --git a/llarp/path/pathset.hpp b/llarp/path/pathset.hpp index 3e431bf41..f7fe5690e 100644 --- a/llarp/path/pathset.hpp +++ b/llarp/path/pathset.hpp @@ -1,7 +1,7 @@ #pragma once #include "path_types.hpp" -#include "service/protocol_type.hpp" +#include #include #include #include diff --git a/llarp/path/transit_hop.cpp b/llarp/path/transit_hop.cpp index 4efe3540f..ff912aae0 100644 --- a/llarp/path/transit_hop.cpp +++ b/llarp/path/transit_hop.cpp @@ -33,7 +33,9 @@ namespace llarp } TransitHop::TransitHop() - : m_UpstreamGather(transit_hop_queue_size), m_DownstreamGather(transit_hop_queue_size) + : IHopHandler{} + , m_UpstreamGather{transit_hop_queue_size} + , m_DownstreamGather{transit_hop_queue_size} { m_UpstreamGather.enable(); m_DownstreamGather.enable(); diff --git a/llarp/path/transit_hop.hpp b/llarp/path/transit_hop.hpp index eb828af9b..fb628929f 100644 --- a/llarp/path/transit_hop.hpp +++ b/llarp/path/transit_hop.hpp @@ -1,8 +1,8 @@ #pragma once #include -#include "ihophandler.hpp" -#include "path_types.hpp" +#include +#include #include #include #include diff --git a/llarp/quic/tunnel.cpp b/llarp/quic/tunnel.cpp index 9b09e5e2f..914b963ea 100644 --- a/llarp/quic/tunnel.cpp +++ b/llarp/quic/tunnel.cpp @@ -1,7 +1,7 @@ #include "tunnel.hpp" -#include "service/convotag.hpp" -#include "service/endpoint.hpp" -#include "service/name.hpp" +#include +#include +#include #include "stream.hpp" #include #include diff --git a/llarp/router/route_poker.cpp b/llarp/router/route_poker.cpp index 3fdc0e4bb..1471ac911 100644 --- a/llarp/router/route_poker.cpp +++ b/llarp/router/route_poker.cpp @@ -1,6 +1,6 @@ #include "route_poker.hpp" -#include "abstractrouter.hpp" -#include "net/sock_addr.hpp" +#include +#include #include #include #include diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 38de414e0..cc9e0e735 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -623,8 +623,7 @@ namespace llarp } if (IsServiceNode()) return SaveRC(); - else - return true; + return true; } bool @@ -1256,7 +1255,7 @@ namespace llarp Router::StartRpcServer() { if (m_Config->api.m_enableRPCServer) - m_RPCServer = std::make_unique(m_lmq, this); + m_RPCServer = std::make_unique(m_lmq, *this); return true; } diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index a5dd11a66..16efb1fcc 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -298,7 +298,7 @@ namespace llarp void PumpLL(); - std::unique_ptr m_RPCServer; + std::unique_ptr m_RPCServer; const llarp_time_t _randomStartDelay; diff --git a/llarp/rpc/json_binary_proxy.cpp b/llarp/rpc/json_binary_proxy.cpp new file mode 100644 index 000000000..cc766dc22 --- /dev/null +++ b/llarp/rpc/json_binary_proxy.cpp @@ -0,0 +1,63 @@ +#include "json_binary_proxy.hpp" +#include +#include + +namespace llarp::rpc +{ + + void + load_binary_parameter_impl( + std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data) + { + if (allow_raw && bytes.size() == raw_size) + { + std::memcpy(val_data, bytes.data(), bytes.size()); + return; + } + else if (bytes.size() == raw_size * 2) + { + if (oxenc::is_hex(bytes)) + { + oxenc::from_hex(bytes.begin(), bytes.end(), val_data); + return; + } + } + else + { + const size_t b64_padded = (raw_size + 2) / 3 * 4; + const size_t b64_padding = raw_size % 3 == 1 ? 2 : raw_size % 3 == 2 ? 1 : 0; + const size_t b64_unpadded = b64_padded - b64_padding; + const std::string_view b64_padding_string = b64_padding == 2 ? "=="sv + : b64_padding == 1 ? "="sv + : ""sv; + if (bytes.size() == b64_unpadded + || (b64_padding > 0 && bytes.size() == b64_padded + && bytes.substr(b64_unpadded) == b64_padding_string)) + { + if (oxenc::is_base64(bytes)) + { + oxenc::from_base64(bytes.begin(), bytes.end(), val_data); + return; + } + } + } + + throw std::runtime_error{"Invalid binary value: unexpected size and/or encoding"}; + } + + nlohmann::json& + json_binary_proxy::operator=(std::string_view binary_data) + { + switch (format) + { + case fmt::bt: + return e = binary_data; + case fmt::hex: + return e = oxenc::to_hex(binary_data); + case fmt::base64: + return e = oxenc::to_base64(binary_data); + } + throw std::runtime_error{"Internal error: invalid binary encoding"}; + } + +} // namespace llarp::rpc diff --git a/llarp/rpc/json_binary_proxy.hpp b/llarp/rpc/json_binary_proxy.hpp new file mode 100644 index 000000000..f0903a9b6 --- /dev/null +++ b/llarp/rpc/json_binary_proxy.hpp @@ -0,0 +1,181 @@ +#pragma once + +#include +#include +#include + +using namespace std::literals; + +namespace llarp::rpc +{ + + // Binary types that we support for rpc input/output. For json, these must be specified as hex or + // base64; for bt-encoded requests these can be accepted as binary, hex, or base64. + template + inline constexpr bool json_is_binary = false; + + template + inline constexpr bool json_is_binary_container = false; + template + inline constexpr bool json_is_binary_container> = json_is_binary; + template + inline constexpr bool json_is_binary_container> = json_is_binary; + + // De-referencing wrappers around the above: + template + inline constexpr bool json_is_binary = json_is_binary; + template + inline constexpr bool json_is_binary = json_is_binary; + template + inline constexpr bool json_is_binary_container = json_is_binary_container; + template + inline constexpr bool json_is_binary_container = json_is_binary_container; + + void + load_binary_parameter_impl( + std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data); + + // Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw + // bytes. + template >> + void + load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) + { + load_binary_parameter_impl(bytes, sizeof(T), allow_raw, reinterpret_cast(&val)); + } + + // Wrapper around a nlohmann::json that assigns a binary value either as binary (for bt-encoding); + // or as hex or base64 (for json-encoding). + class json_binary_proxy + { + public: + nlohmann::json& e; + enum class fmt + { + bt, + hex, + base64 + } format; + explicit json_binary_proxy(nlohmann::json& elem, fmt format) : e{elem}, format{format} + {} + json_binary_proxy() = delete; + + json_binary_proxy(const json_binary_proxy&) = default; + json_binary_proxy(json_binary_proxy&&) = default; + + /// Dereferencing a proxy element accesses the underlying nlohmann::json + nlohmann::json& + operator*() + { + return e; + } + nlohmann::json* + operator->() + { + return &e; + } + + /// Descends into the json object, returning a new binary value proxy around the child element. + template + json_binary_proxy + operator[](T&& key) + { + return json_binary_proxy{e[std::forward(key)], format}; + } + + /// Returns a binary value proxy around the first/last element (requires an underlying list) + json_binary_proxy + front() + { + return json_binary_proxy{e.front(), format}; + } + json_binary_proxy + back() + { + return json_binary_proxy{e.back(), format}; + } + + /// Assigns binary data from a string_view/string/etc. + nlohmann::json& + operator=(std::string_view binary_data); + + /// Assigns binary data from a string_view over a 1-byte, non-char type (e.g. unsigned char or + /// uint8_t). + template < + typename Char, + std::enable_if_t, int> = 0> + nlohmann::json& + operator=(std::basic_string_view binary_data) + { + return *this = std::string_view{ + reinterpret_cast(binary_data.data()), binary_data.size()}; + } + + /// Takes a trivial, no-padding data structure (e.g. a crypto::hash) as the value and dumps its + /// contents as the binary value. + template , int> = 0> + nlohmann::json& + operator=(const T& val) + { + return *this = std::string_view{reinterpret_cast(&val), sizeof(val)}; + } + + /// Takes a vector of some json_binary_proxy-assignable type and builds an array by assigning + /// each one into a new array of binary values. + template , int> = 0> + nlohmann::json& + operator=(const T& vals) + { + auto a = nlohmann::json::array(); + for (auto& val : vals) + json_binary_proxy{a.emplace_back(), format} = val; + return e = std::move(a); + } + /// Emplaces a new nlohman::json to the end of an underlying list and returns a + /// json_binary_proxy wrapping it. + /// + /// Example: + /// + /// auto child = wrappedelem.emplace_back({"key1": 1}, {"key2": 2}); + /// child["binary-key"] = some_binary_thing; + template + json_binary_proxy + emplace_back(Args&&... args) + { + return json_binary_proxy{e.emplace_back(std::forward(args)...), format}; + } + + /// Adds an element to an underlying list, then copies or moves the given argument onto it via + /// json_binary_proxy assignment. + template + void + push_back(T&& val) + { + emplace_back() = std::forward(val); + } + }; + +} // namespace llarp::rpc + +// Specializations of binary types for deserialization; when receiving these from json we expect +// them encoded in hex or base64. These may *not* be used for serialization, and will throw if so +// invoked; for serialization you need to use RPC_COMMAND::response_hex (or _b64) instead. +namespace nlohmann +{ + template + struct adl_serializer>> + { + static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); + + static void + to_json(const T&) + { + throw std::logic_error{"Internal error: binary types are not directly serializable"}; + } + static void + from_json(const json& j, T& val) + { + llarp::rpc::load_binary_parameter(j.get(), false /*no raw*/, val); + } + }; +} // namespace nlohmann diff --git a/llarp/rpc/json_bt.hpp b/llarp/rpc/json_bt.hpp new file mode 100644 index 000000000..0a87f99a0 --- /dev/null +++ b/llarp/rpc/json_bt.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +using nlohmann::json; + +namespace llarp::rpc +{ + + inline oxenc::bt_value + json_to_bt(json&& j) + { + if (j.is_object()) + { + oxenc::bt_dict res; + for (auto& [k, v] : j.items()) + { + if (v.is_null()) + continue; // skip k-v pairs with a null v (for other nulls we fail). + res[k] = json_to_bt(std::move(v)); + } + return res; + } + if (j.is_array()) + { + oxenc::bt_list res; + for (auto& v : j) + res.push_back(json_to_bt(std::move(v))); + return res; + } + if (j.is_string()) + { + return std::move(j.get_ref()); + } + if (j.is_boolean()) + return j.get() ? 1 : 0; + if (j.is_number_unsigned()) + return j.get(); + if (j.is_number_integer()) + return j.get(); + throw std::domain_error{ + "internal error: encountered some unhandled/invalid type in json-to-bt translation"}; + } + +} // namespace llarp::rpc diff --git a/llarp/rpc/param_parser.hpp b/llarp/rpc/param_parser.hpp new file mode 100644 index 000000000..744ac0b22 --- /dev/null +++ b/llarp/rpc/param_parser.hpp @@ -0,0 +1,359 @@ +#pragma once + +#include "json_binary_proxy.hpp" +#include +#include +#include +#include + +namespace llarp::rpc +{ + + using json_range = std::pair; + using rpc_input = std::variant; + + // Checks that key names are given in ascending order + template + void + check_ascending_names(std::string_view name1, std::string_view name2, const Ignore&...) + { + if (!(name2 > name1)) + throw std::runtime_error{ + "Internal error: request values must be retrieved in ascending order"}; + } + + // Wrapper around a reference for get_values that is used to indicate that the value is + // required, in which case an exception will be raised if the value is not found. Usage: + // + // int a_optional = 0, b_required; + // get_values(input, + // "a", a_optional, + // "b", required{b_required}, + // // ... + // ); + template + struct required + { + T& value; + required(T& ref) : value{ref} + {} + }; + template + constexpr bool is_required_wrapper = false; + template + constexpr bool is_required_wrapper> = true; + + template + constexpr bool is_std_optional = false; + template + constexpr bool is_std_optional> = true; + + // Wrapper around a reference for get_values that adds special handling to act as if the value was + // not given at all if the value is given as an empty string. This sucks, but is necessary for + // backwards compatibility (especially with wallet2 clients). + // + // Usage: + // + // std::string x; + // get_values(input, + // "x", ignore_empty_string{x}, + // // ... + // ); + template + struct ignore_empty_string + { + T& value; + ignore_empty_string(T& ref) : value{ref} + {} + + bool + should_ignore(oxenc::bt_dict_consumer& d) + { + if (d.is_string()) + { + auto d2{d}; // Copy because we want to leave d intact + if (d2.consume_string_view().empty()) + return true; + } + return false; + } + + bool + should_ignore(json_range& it_range) + { + auto& e = *it_range.first; + return (e.is_string() && e.get().empty()); + } + }; + + template + constexpr bool is_ignore_empty_string_wrapper = false; + template + constexpr bool is_ignore_empty_string_wrapper> = true; + + // Advances the dict consumer to the first element >= the given name. Returns true if found, + // false if it advanced beyond the requested name. This is exactly the same as + // `d.skip_until(name)`, but is here so we can also overload an equivalent function for json + // iteration. + inline bool + skip_until(oxenc::bt_dict_consumer& d, std::string_view name) + { + return d.skip_until(name); + } + // Equivalent to the above but for a json object iterator. + inline bool + skip_until(json_range& it_range, std::string_view name) + { + auto& [it, end] = it_range; + while (it != end && it.key() < name) + ++it; + return it != end && it.key() == name; + } + + // List types that are expandable; for these we emplace_back for each element of the input + template + constexpr bool is_expandable_list = false; + template + constexpr bool is_expandable_list> = true; + + // Fixed size elements: tuples, pairs, and std::array's; we accept list input as long as the + // list length matches exactly. + template + constexpr bool is_tuple_like = false; + template + constexpr bool is_tuple_like> = true; + template + constexpr bool is_tuple_like> = true; + template + constexpr bool is_tuple_like> = true; + + // True if T is a `std::unordered_map` + template + constexpr bool is_unordered_string_map = false; + template + constexpr bool is_unordered_string_map> = true; + + template + void + load_tuple_values(oxenc::bt_list_consumer&, TupleLike&, std::index_sequence); + + // Consumes the next value from the dict consumer into `val` + template < + typename BTConsumer, + typename T, + std::enable_if_t< + std::is_same_v< + BTConsumer, + oxenc::bt_dict_consumer> || std::is_same_v, + int> = 0> + void + load_value(BTConsumer& c, T& val) + { + if constexpr (std::is_integral_v) + val = c.template consume_integer(); + else if constexpr (std::is_same_v || std::is_same_v) + val = c.consume_string_view(); + else if constexpr (llarp::rpc::json_is_binary) + llarp::rpc::load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); + else if constexpr (is_expandable_list) + { + auto lc = c.consume_list_consumer(); + val.clear(); + while (!lc.is_finished()) + load_value(lc, val.emplace_back()); + } + else if constexpr (is_tuple_like) + { + auto lc = c.consume_list_consumer(); + load_tuple_values(lc, val, std::make_index_sequence>{}); + } + else if constexpr (is_unordered_string_map) + { + auto dc = c.consume_dict_consumer(); + val.clear(); + while (!dc.is_finished()) + load_value(dc, val[std::string{dc.key()}]); + } + else + static_assert(std::is_same_v, "Unsupported load_value type"); + } + + // Copies the next value from the json range into `val`, and advances the iterator. Throws + // on unconvertible values. + template + void + load_value(json_range& r, T& val) + { + auto& key = r.first.key(); + auto& e = *r.first; + if constexpr (std::is_same_v) + { + if (e.is_boolean()) + val = e.get(); + else if (e.is_number_unsigned()) + { + // Also accept 0 or 1 for bools (mainly to be compatible with bt-encoding which doesn't + // have a distinct bool type). + auto b = e.get(); + if (b <= 1) + val = b; + else + throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; + } + else + { + throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; + } + } + else if constexpr (std::is_unsigned_v) + { + if (!e.is_number_unsigned()) + throw std::domain_error{"Invalid value for '" + key + "': non-negative value required"}; + auto i = e.get(); + if (sizeof(T) < sizeof(uint64_t) && i > std::numeric_limits::max()) + throw std::domain_error{"Invalid value for '" + key + "': value too large"}; + val = i; + } + else if constexpr (std::is_integral_v) + { + if (!e.is_number_integer()) + throw std::domain_error{"Invalid value for '" + key + "': value is not an integer"}; + auto i = e.get(); + if (sizeof(T) < sizeof(int64_t)) + { + if (i < std::numeric_limits::lowest()) + throw std::domain_error{ + "Invalid value for '" + key + "': negative value magnitude is too large"}; + if (i > std::numeric_limits::max()) + throw std::domain_error{"Invalid value for '" + key + "': value is too large"}; + } + val = i; + } + else if constexpr (std::is_same_v || std::is_same_v) + { + val = e.get(); + } + else if constexpr ( + llarp::rpc::json_is_binary< + T> || is_expandable_list || is_tuple_like || is_unordered_string_map) + { + try + { + e.get_to(val); + } + catch (const std::exception& e) + { + throw std::domain_error{"Invalid values in '" + key + "'"}; + } + } + else + { + static_assert(std::is_same_v, "Unsupported load type"); + } + ++r.first; + } + + template + void + load_tuple_values(oxenc::bt_list_consumer& c, TupleLike& val, std::index_sequence) + { + (load_value(c, std::get(val)), ...); + } + + // Takes a json object iterator or bt_dict_consumer and loads the current value at the iterator. + // This calls itself recursively, if needed, to unwrap optional/required/ignore_empty_string + // wrappers. + template + void + load_curr_value(In& in, T& val) + { + if constexpr (is_required_wrapper) + { + load_curr_value(in, val.value); + } + else if constexpr (is_ignore_empty_string_wrapper) + { + if (!val.should_ignore(in)) + load_curr_value(in, val.value); + } + else if constexpr (is_std_optional) + { + load_curr_value(in, val.emplace()); + } + else + { + load_value(in, val); + } + } + + // Gets the next value from a json object iterator or bt_dict_consumer. Leaves the iterator at + // the next value, i.e. found + 1 if found, or the next greater value if not found. (NB: + // nlohmann::json objects are backed by an *ordered* map and so both nlohmann iterators and + // bt_dict_consumer behave analogously here). + template + void + get_next_value(In& in, [[maybe_unused]] std::string_view name, T& val) + { + if constexpr (std::is_same_v) + ; + else if (skip_until(in, name)) + load_curr_value(in, val); + else if constexpr (is_required_wrapper) + throw std::runtime_error{"Required key '" + std::string{name} + "' not found"}; + } + + // Accessor for simple, flat value retrieval from a json or bt_dict_consumer. In the later + // case note that the given bt_dict_consumer will be advanced, so you *must* take care to + // process keys in order, both for the keys passed in here *and* for use before and after this + // call. + template + void + get_values(Input& in, std::string_view name, T&& val, More&&... more) + { + if constexpr (std::is_same_v) + { + if (auto* json_in = std::get_if(&in)) + { + json_range r{json_in->cbegin(), json_in->cend()}; + get_values(r, name, val, std::forward(more)...); + } + else if (auto* dict = std::get_if(&in)) + { + get_values(*dict, name, val, std::forward(more)...); + } + else + { + // A monostate indicates that no parameters field was provided at all + get_values(var::get(in), name, val, std::forward(more)...); + } + } + else if constexpr (std::is_same_v) + { + if (in.front() == 'd') + { + oxenc::bt_dict_consumer d{in}; + get_values(d, name, val, std::forward(more)...); + } + else + { + auto json_in = nlohmann::json::parse(in); + json_range r{json_in.cbegin(), json_in.cend()}; + get_values(r, name, val, std::forward(more)...); + } + } + else + { + static_assert( + std::is_same_v< + json_range, + Input> || std::is_same_v || std::is_same_v); + get_next_value(in, name, val); + if constexpr (sizeof...(More) > 0) + { + check_ascending_names(name, more...); + get_values(in, std::forward(more)...); + } + } + } + +} // namespace llarp::rpc \ No newline at end of file diff --git a/llarp/rpc/rpc_request.hpp b/llarp/rpc/rpc_request.hpp new file mode 100644 index 000000000..9c543aafe --- /dev/null +++ b/llarp/rpc/rpc_request.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "rpc_server.hpp" +#include "rpc_request_parser.hpp" +#include "rpc_request_decorators.hpp" +#include "rpc_request_definitions.hpp" +#include "json_bt.hpp" +#include +#include +#include +#include +#include +#include + +namespace llarp::rpc +{ + + using nlohmann::json; + + template + auto + make_invoke() + { + return [](oxenmq::Message& m, RPCServer& server) { + EndpointHandler handler{server, m.send_later()}; + auto& rpc = handler.rpc; + + if (m.data.size() > 1) + m.send_reply(CreateJSONError( + "Bad Request: RPC requests must have at most one data part (received {})"_format( + m.data.size()))); + + // parsing input as bt or json + // hand off to parse_request (overloaded versions) + try + { + if (m.data.empty() or m.data[0].empty()) + { + parse_request(rpc, nlohmann::json::object()); + } + else if (m.data[0].front() == 'd') + { + rpc.set_bt(); + parse_request(rpc, oxenc::bt_dict_consumer{m.data[0]}); + } + else + { + parse_request(rpc, nlohmann::json::parse(m.data[0])); + } + } + catch (const std::exception& e) + { + m.send_reply(CreateJSONError("Failed to parse request parameters: "s + e.what())); + return; + } + + if (not std::is_base_of_v) + { + server.m_Router.loop()->call_soon(std::move(handler)); + } + else + { + handler(); + } + }; + } + +} // namespace llarp::rpc diff --git a/llarp/rpc/rpc_request_decorators.hpp b/llarp/rpc/rpc_request_decorators.hpp new file mode 100644 index 000000000..f3ccf4966 --- /dev/null +++ b/llarp/rpc/rpc_request_decorators.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include "json_binary_proxy.hpp" +#include "json_bt.hpp" +#include +#include +#include +#include +#include +#include + +namespace tools +{ + // Type wrapper that contains an arbitrary list of types. + template + struct type_list + {}; +} // namespace tools + +namespace llarp::rpc +{ + // Base class that all RPC requests will expand for each endpoint type + struct RPCRequest + { + private: + bool bt = false; + + public: + // Returns true if response is bt-encoded, and false for json + // Note: do not set value + bool + is_bt() const + { + return bt; + } + + // Callable method to indicate request is bt-encoded + void + set_bt() + { + bt = true; + response_b64.format = llarp::rpc::json_binary_proxy::fmt::bt; + response_hex.format = llarp::rpc::json_binary_proxy::fmt::bt; + } + + // Invoked if this.replier is still present. If it is "stolen" by endpoint (moved from + // RPC struct), then endpoint handles sending reply + void + send_response() + { + replier->reply(is_bt() ? oxenc::bt_serialize(json_to_bt(std::move(response))) + : response.dump()); + } + + void + send_response(nlohmann::json _response) + { + response = std::move(_response); + send_response(); + } + + // Response Data: + // bt-encoded are converted in real-time + // - bool becomes 0 or 1 + // - key:value where value == null are omitted + // - other nulls will raise an exception if found in json + // - no doubles + // - to store doubles: encode bt in endpoint-specific way + // - binary strings will fail json serialization; caller must + // + // std::string binary = some_binary_data(); + // request.response["binary_value"] = is_bt ? binary : oxenmq::to_hex(binary) + // + nlohmann::json response; + + // Proxy Object: + // Sets binary data in "response" + // - if return type is json, encodes as hex + // - if return type is bt, then binary is untouched + // + // Usage: + // std::string data = "abc"; + // request.response_hex["foo"]["bar"] = data; // json: "616263", bt: "abc" + // + llarp::rpc::json_binary_proxy response_hex{response, llarp::rpc::json_binary_proxy::fmt::hex}; + + // Proxy Object: + // Encodes binary data as base_64 for json-encoded responses, leaves as binary for bt-encoded + // responses + // + // Usage: + // std::string data = "abc" + // request.response_b64["foo"]["bar"] = data; json: "YWJj", bt: "abc" + // + llarp::rpc::json_binary_proxy response_b64{ + response, llarp::rpc::json_binary_proxy::fmt::base64}; + + // The oxenmq deferred send object into which the response will be set. If this optional is + // still set when the `invoke` call returns then the response is sent at that point; if it has + // been moved out (i.e. either just this instance or the whole request struct is stolen/moved by + // the invoke function) then it is the invoke function's job to send a reply. Typically this is + // done when a response cannot be sent immediately + std::optional replier; + }; + + // Tag types that are inherited to set RPC endpoint properties + + // RPC call wil take no input arguments + // Parameter dict can be passed, but will be ignored + struct NoArgs : virtual RPCRequest + {}; + + // RPC call will be executed immediately + struct Immediate : virtual RPCRequest + {}; + +} // namespace llarp::rpc diff --git a/llarp/rpc/rpc_request_definitions.hpp b/llarp/rpc/rpc_request_definitions.hpp new file mode 100644 index 000000000..adeac4486 --- /dev/null +++ b/llarp/rpc/rpc_request_definitions.hpp @@ -0,0 +1,303 @@ +#pragma once + +#include "rpc_request_decorators.hpp" +#include "net/ip_range.hpp" +#include "router/abstractrouter.hpp" +#include "router/route_poker.hpp" +#include "service/address.hpp" +#include "service/endpoint.hpp" +#include "service/outbound_context.hpp" +#include +#include +#include +#include +#include +#include + +namespace llarp::rpc +{ + // RPC: halt + // Stops lokinet router + // + // Inputs: none + // + struct Halt : NoArgs, Immediate + { + static constexpr auto name = "halt"sv; + }; + + // RPC: version + // Returns version and uptime information + // + // Inputs: none + // + // Returns: "OK" + // "uptime" + // "version" + // + struct Version : NoArgs, Immediate + { + static constexpr auto name = "version"sv; + }; + + // RPC: status + // Returns that current activity status of lokinet router + // Calls router::extractstatus + // + // Inputs: none + // + // Returns: massive dump of status info including + // "running" + // "numNodesKnown" + // "dht" + // "services" + // "exit" + // "links" + // "outboundMessages" + // etc + // + struct Status : NoArgs + { + static constexpr auto name = "status"sv; + }; + + // RPC: get_status + // Returns current summary status + // + // Inputs: none + // + // Returns: slightly smaller dump of status info including + // "authcodes" + // "exitMap" + // "lokiAddress" + // "networkReady" + // "numPathsBuilt" + // "numPeersConnected" + // etc + // + struct GetStatus : NoArgs + { + static constexpr auto name = "get_status"sv; + }; + + // RPC: quic_connect + // Initializes QUIC connection tunnel + // Passes request parameters in nlohmann::json format + // + // Inputs: + // "endpoint" : endpoint id (string) + // "bindAddr" : bind address (string, ex: "127.0.0.1:1142") + // "host" : remote host ID (string) + // "port" : port to bind to (int) + // "close" : close connection to port or host ID + // + // Returns: + // "id" : connection ID + // "addr" : connection local address + // + struct QuicConnect : RPCRequest + { + static constexpr auto name = "quic_connect"sv; + + struct request_parameters + { + std::string bindAddr; + int closeID; + std::string endpoint; + uint16_t port; + std::string remoteHost; + } request; + }; + + // RPC: quick_listener + // Connects to QUIC interface on local endpoint + // Passes request parameters in nlohmann::json format + // + // Inputs: + // "endpoint" : endpoint id (string) + // "host" : remote host ID (string) + // "port" : port to bind to (int) + // "close" : close connection to port or host ID + // "srv-proto" : + // + // Returns: + // "id" : connection ID + // "addr" : connection local address + // + struct QuicListener : RPCRequest + { + static constexpr auto name = "quic_listener"sv; + + struct request_parameters + { + int closeID; + std::string endpoint; + uint16_t port; + std::string remoteHost; + std::string srvProto; + } request; + }; + + // RPC: lookup_snode + // Look up service node + // Passes request parameters in nlohmann::json format + // + // Inputs: + // "routerID" : router ID to query (string) + // + // Returns: + // "ip" : snode IP address + // + struct LookupSnode : RPCRequest + { + static constexpr auto name = "lookup_snode"sv; + + struct request_parameters + { + std::string routerID; + } request; + }; + + // RPC: exit + // Seems like this adds an exit node? + // + // Note: ask Jason about the internals of this + // + // Inputs: + // "endpoint" : + // "unmap" : if true, unmaps connection to exit node (bool) + // "range" : IP range to map to exit node + // "token" : + // + // Returns: + // + struct Exit : RPCRequest + { + static constexpr auto name = "exit"sv; + + struct request_parameters + { + std::string address; + std::string ip_range; + std::string token; + bool unmap; + } request; + + void + onGoodResult(std::string reason, bool hasClient) + { + response = (hasClient) ? + nlohmann::json{{"result", reason}}.dump() : + nlohmann::json{{"error", "We don't have an exit?"}}.dump(); + } + + void + onBadResult(std::string reason, AbstractRouter& abs, llarp::service::Endpoint_ptr eptr, IPRange range) + { + abs.routePoker()->Down(); + eptr->UnmapExitRange(range); + response = nlohmann::json{{"result", reason}}.dump(); + } + + void + mapExit(service::Address addr, AbstractRouter& router, llarp::service::Endpoint_ptr eptr, IPRange range, service::Address exitAddr) + { + eptr->MapExitRange(range, addr); + + bool sendAuth = (request.token.empty()) ? false : true; + if (sendAuth) + eptr->SetAuthInfoForEndpoint(exitAddr, service::AuthInfo{request.token}); + + if (addr.IsZero()) + { + onGoodResult("Null exit added", router.HasClientExit()); + return; + } + + eptr->MarkAddressOutbound(addr); + + eptr->EnsurePathToService(addr, [&](auto, service::OutboundContext* ctx) { + if (ctx == nullptr) + { + onBadResult("Could not find exit", router, eptr, range); + return; + } + if (not sendAuth) + { + onGoodResult("OK: connected to " + addr.ToString(), router.HasClientExit()); + return; + } + // only lambda that we will keep + ctx->AsyncSendAuth([&](service::AuthResult result) { + if (result.code != service::AuthResultCode::eAuthAccepted) + { + onBadResult(result.reason, router, eptr, range); + return; + } + onGoodResult(result.reason, router.HasClientExit()); + return; + }); + }); + } + }; + + // RPC: dns_query + // Attempts to query endpoint by domain name + // + // Note: ask Jason about the internals of this + // + // Inputs: + // "endpoint" : endpoint ID to query (string) + // "qname" : query name (string) + // "qtype" : query type (int) + // + // Returns: + // + struct DNSQuery : Immediate + { + static constexpr auto name = "dns_query"sv; + + struct request_parameters + { + std::string endpoint; + uint16_t qtype; + std::string qname; + } request; + }; + + // RPC: config + // Runs lokinet router using .ini config file passed as path + // + // Inputs: + // "filename" : name of .ini file to either save or delete + // "ini" : .ini chunk to save in new file + // "del" : boolean specifying whether to delete file "filename" or save it + // + // Returns: + // + struct Config : Immediate + { + static constexpr auto name = "config"sv; + + struct request_parameters + { + bool del; + std::string filename; + std::string ini; + } request; + }; + + // List of all RPC request structs to allow compile-time enumeration of all supported types + using rpc_request_types = tools::type_list< + Halt, + Version, + Status, + GetStatus, + QuicConnect, + QuicListener, + LookupSnode, + Exit, + DNSQuery, + Config>; + +} // namespace llarp::rpc diff --git a/llarp/rpc/rpc_request_parser.cpp b/llarp/rpc/rpc_request_parser.cpp new file mode 100644 index 000000000..8a193df1a --- /dev/null +++ b/llarp/rpc/rpc_request_parser.cpp @@ -0,0 +1,94 @@ +#include "rpc_request_parser.hpp" +#include "param_parser.hpp" +#include +#include +#include +#include +#include + +namespace llarp::rpc +{ + using nlohmann::json; + + void + parse_request(QuicConnect& quicconnect, rpc_input input) + { + get_values( + input, + "bindAddr", + quicconnect.request.bindAddr, + "closeID", + quicconnect.request.closeID, + "endpoint", + quicconnect.request.endpoint, + "port", + quicconnect.request.port, + "remoteHost", + quicconnect.request.remoteHost); + } + + void + parse_request(QuicListener& quiclistener, rpc_input input) + { + get_values( + input, + "closeID", + quiclistener.request.closeID, + "endpoint", + quiclistener.request.endpoint, + "port", + quiclistener.request.port, + "remoteHost", + quiclistener.request.remoteHost, + "srvProto", + quiclistener.request.srvProto); + } + + void + parse_request(LookupSnode& lookupsnode, rpc_input input) + { + get_values(input, "routerID", lookupsnode.request.routerID); + } + + void + parse_request(Exit& exit, rpc_input input) + { + get_values( + input, + "address", + exit.request.address, + "IP_range", + exit.request.ip_range, + "token", + exit.request.token, + "unmap", + exit.request.unmap); + } + + void + parse_request(DNSQuery& dnsquery, rpc_input input) + { + get_values( + input, + "endpoint", + dnsquery.request.endpoint, + "qname", + dnsquery.request.qname, + "qtype", + dnsquery.request.qtype); + } + + void + parse_request(Config& config, rpc_input input) + { + get_values( + input, + "delete", + config.request.del, + "filename", + config.request.filename, + "ini", + config.request.ini); + } + +} // namespace llarp::rpc \ No newline at end of file diff --git a/llarp/rpc/rpc_request_parser.hpp b/llarp/rpc/rpc_request_parser.hpp new file mode 100644 index 000000000..f7a71747c --- /dev/null +++ b/llarp/rpc/rpc_request_parser.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "rpc_request_definitions.hpp" +#include +#include +#include +#include +#include + +namespace llarp::rpc +{ + using rpc_input = std::variant; + + inline void + parse_request(NoArgs&, rpc_input) + {} + + void + parse_request(QuicConnect& quicconnect, rpc_input input); + void + parse_request(QuicListener& quiclistener, rpc_input input); + void + parse_request(LookupSnode& lookupsnode, rpc_input input); + void + parse_request(Exit& exit, rpc_input input); + void + parse_request(DNSQuery& dnsquery, rpc_input input); + void + parse_request(Config& config, rpc_input input); + +} // namespace llarp::rpc \ No newline at end of file diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 954c6d7fe..216c255eb 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -1,6 +1,9 @@ #include "rpc_server.hpp" +#include "rpc_request.hpp" +#include "service/address.hpp" #include #include +#include #include #include #include @@ -15,62 +18,9 @@ #include #include -namespace -{ - static auto logcat = llarp::log::Cat("lokinet.rpc"); -} // namespace - namespace llarp::rpc { - RpcServer::RpcServer(LMQ_ptr lmq, AbstractRouter* r) - : m_LMQ{std::move(lmq)}, m_Router{r}, log_subs{*m_LMQ, llarp::logRingBuffer} - { - for (const auto& addr : r->GetConfig()->api.m_rpcBindAddresses) - { - m_LMQ->listen_plain(addr.zmq_address()); - LogInfo("Bound RPC server to ", addr.full_address()); - } - - this->AddRPCCategories(); - } - - /// maybe parse json from message paramter at index - std::optional - MaybeParseJSON(const oxenmq::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 - 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(); - } - - /// fake packet source that serializes repsonses back into dns - + // Fake packet source that serializes repsonses back into dns class DummyPacketSource : public dns::PacketSource_Base { std::function)> func; @@ -107,626 +57,429 @@ namespace llarp::rpc } }; - /// a function that replies to an rpc request - using ReplyFunction_t = std::function; + bool + check_path(std::string path) + { + for (auto c : path) + { + if (not((c >= '0' and c <= '9') or (c >= 'A' and c <= 'Z') or (c >= 'a' and c <= 'z') + or (c == '_') or (c == '-'))) + { + return false; + } + } + + return true; + } std::shared_ptr - GetEndpointByName(AbstractRouter* r, std::string name) + GetEndpointByName(AbstractRouter& r, std::string name) { - if (r->IsServiceNode()) + if (r.IsServiceNode()) { - return r->exitContext().GetExitEndpoint(name); + return r.exitContext().GetExitEndpoint(name); } - else + + return r.hiddenServiceContext().GetEndpointByName(name); + } + + template + void + register_rpc_command(std::unordered_map>& regs) + { + static_assert(std::is_base_of_v); + auto cback = std::make_shared(); + + cback->invoke = make_invoke(); + + regs.emplace(RPC::name, cback); + } + + RPCServer::RPCServer(LMQ_ptr lmq, AbstractRouter& r) + : m_LMQ{std::move(lmq)}, m_Router(r), log_subs{*m_LMQ, llarp::logRingBuffer} + { + // copied logic loop as placeholder + for (const auto& addr : r.GetConfig()->api.m_rpcBindAddresses) { - return r->hiddenServiceContext().GetEndpointByName(name); + m_LMQ->listen_plain(addr.zmq_address()); + LogInfo("Bound RPC server to ", addr.full_address()); } + + AddCategories(); + } + + template + std::unordered_map> + register_rpc_requests(tools::type_list) + { + std::unordered_map> regs; + + (register_rpc_command(regs), ...); + + return regs; + } + + const std::unordered_map> rpc_request_map = + register_rpc_requests(rpc::rpc_request_types{}); + + void + RPCServer::AddCategories() + { + m_LMQ->add_category("llarp", oxenmq::AuthLevel::none) + .add_request_command("logs", [this](oxenmq::Message& msg) { HandleLogsSubRequest(msg); }); + + for (auto& req : rpc_request_map) + { + m_LMQ->add_request_command( + "llarp", + req.first, + [name = std::string_view{req.first}, &call = *req.second, this](oxenmq::Message& m) { + call.invoke(m, *this); + }); + } + } + + void + RPCServer::invoke(Halt& halt) + { + if (not m_Router.IsRunning()) + { + halt.response = CreateJSONError("Router is not running"); + return; + } + halt.response = CreateJSONResponse("OK"); + m_Router.Stop(); } void - HandleJSONRequest( - oxenmq::Message& msg, std::function handleRequest) + RPCServer::invoke(Version& version) { - const auto maybe = MaybeParseJSON(msg); - if (not maybe.has_value()) + util::StatusObject result{ + {"version", llarp::VERSION_FULL}, {"uptime", to_json(m_Router.Uptime())}}; + + version.response = CreateJSONResponse(result); + } + + void + RPCServer::invoke(Status& status) + { + status.response = (m_Router.IsRunning()) ? CreateJSONResponse(m_Router.ExtractStatus()) + : CreateJSONError("Router is not yet ready"); + } + + void + RPCServer::invoke(GetStatus& getstatus) + { + getstatus.response = CreateJSONResponse(m_Router.ExtractSummaryStatus()); + } + + void + RPCServer::invoke(QuicConnect& quicconnect) + { + if (quicconnect.request.port == 0 and quicconnect.request.closeID == 0) + { + quicconnect.response = CreateJSONError("Port not provided"); + return; + } + + if (quicconnect.request.remoteHost.empty() and quicconnect.request.closeID == 0) + { + quicconnect.response = CreateJSONError("Host not provided"); + return; + } + + auto endpoint = (quicconnect.request.endpoint.empty()) + ? GetEndpointByName(m_Router, "default") + : GetEndpointByName(m_Router, quicconnect.request.endpoint); + + if (not endpoint) + { + quicconnect.response = CreateJSONError("No such local endpoint found."); + return; + } + + auto quic = endpoint->GetQUICTunnel(); + + if (not quic) { - msg.send_reply(CreateJSONError("failed to parse json")); + quicconnect.response = CreateJSONError( + "No quic interface available on endpoint " + quicconnect.request.endpoint); return; } - if (not maybe->is_object()) + + if (quicconnect.request.closeID) { - msg.send_reply(CreateJSONError("request data not a json object")); + quic->forget(quicconnect.request.closeID); + quicconnect.response = CreateJSONResponse("OK"); return; } + + SockAddr laddr{quicconnect.request.bindAddr}; + try { - handleRequest( - *maybe, [defer = msg.send_later()](std::string result) { defer.reply(result); }); + auto [addr, id] = quic->open( + quicconnect.request.remoteHost, quicconnect.request.port, [](auto&&) {}, laddr); + + util::StatusObject status; + status["addr"] = addr.ToString(); + status["id"] = id; + + quicconnect.response = CreateJSONResponse(status); } - catch (std::exception& ex) + catch (std::exception& e) { - msg.send_reply(CreateJSONError(ex.what())); + quicconnect.response = CreateJSONError(e.what()); } } void - RpcServer::AddRPCCategories() + RPCServer::invoke(QuicListener& quiclistener) { - m_LMQ->add_category("llarp", oxenmq::AuthLevel::none) - .add_request_command("logs", [this](oxenmq::Message& msg) { HandleLogsSubRequest(msg); }) - .add_request_command( - "halt", - [&](oxenmq::Message& msg) { - if (not m_Router->IsRunning()) - { - msg.send_reply(CreateJSONError("router is not running")); - return; - } - msg.send_reply(CreateJSONResponse("OK")); - m_Router->Stop(); - }) - .add_request_command( - "version", - [r = m_Router](oxenmq::Message& msg) { - util::StatusObject result{ - {"version", llarp::VERSION_FULL}, {"uptime", to_json(r->Uptime())}}; - msg.send_reply(CreateJSONResponse(result)); - }) - .add_request_command( - "status", - [&](oxenmq::Message& msg) { - m_Router->loop()->call([defer = msg.send_later(), r = m_Router]() { - std::string data; - if (r->IsRunning()) - { - data = CreateJSONResponse(r->ExtractStatus()); - } - else - { - data = CreateJSONError("router not yet ready"); - } - defer.reply(data); - }); - }) - .add_request_command( - "get_status", - [&](oxenmq::Message& msg) { - m_Router->loop()->call([defer = msg.send_later(), r = m_Router]() { - defer.reply(CreateJSONResponse(r->ExtractSummaryStatus())); - }); - }) - .add_request_command( - "quic_connect", - [&](oxenmq::Message& msg) { - HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { - std::string endpoint = "default"; - if (auto itr = obj.find("endpoint"); itr != obj.end()) - endpoint = itr->get(); - - std::string bindAddr = "127.0.0.1:0"; - if (auto itr = obj.find("bind"); itr != obj.end()) - bindAddr = itr->get(); - - std::string remoteHost; - if (auto itr = obj.find("host"); itr != obj.end()) - remoteHost = itr->get(); - - uint16_t port = 0; - if (auto itr = obj.find("port"); itr != obj.end()) - port = itr->get(); - - int closeID = 0; - if (auto itr = obj.find("close"); itr != obj.end()) - closeID = itr->get(); - - if (port == 0 and closeID == 0) - { - reply(CreateJSONError("port not provided")); - return; - } - if (remoteHost.empty() and closeID == 0) - { - reply(CreateJSONError("host not provided")); - return; - } - SockAddr laddr{}; - laddr.fromString(bindAddr); - - r->loop()->call([reply, endpoint, r, remoteHost, port, closeID, laddr]() { - auto ep = GetEndpointByName(r, endpoint); - if (not ep) - { - reply(CreateJSONError("no such local endpoint")); - return; - } - auto quic = ep->GetQUICTunnel(); - if (not quic) - { - reply(CreateJSONError("local endpoint has no quic tunnel")); - return; - } - if (closeID) - { - quic->close(closeID); - reply(CreateJSONResponse("OK")); - return; - } - - try - { - auto [addr, id] = quic->open( - remoteHost, port, [](auto&&) {}, laddr); - util::StatusObject status; - status["addr"] = addr.ToString(); - status["id"] = id; - reply(CreateJSONResponse(status)); - } - catch (std::exception& ex) - { - reply(CreateJSONError(ex.what())); - } - }); - }); - }) - .add_request_command( - "quic_listener", - [&](oxenmq::Message& msg) { - HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { - std::string endpoint = "default"; - if (auto itr = obj.find("endpoint"); itr != obj.end()) - endpoint = itr->get(); - - std::string remote = "127.0.0.1"; - if (auto itr = obj.find("host"); itr != obj.end()) - remote = itr->get(); - - uint16_t port = 0; - if (auto itr = obj.find("port"); itr != obj.end()) - port = itr->get(); - - int closeID = 0; - if (auto itr = obj.find("close"); itr != obj.end()) - closeID = itr->get(); - - std::string srvProto; - if (auto itr = obj.find("srv-proto"); itr != obj.end()) - srvProto = itr->get(); - - if (port == 0 and closeID == 0) - { - reply(CreateJSONError("invalid arguments")); - return; - } - r->loop()->call([reply, endpoint, remote, port, r, closeID, srvProto]() { - auto ep = GetEndpointByName(r, endpoint); - if (not ep) - { - reply(CreateJSONError("no such local endpoint")); - return; - } - auto quic = ep->GetQUICTunnel(); - if (not quic) - { - reply(CreateJSONError("no quic interface available on endpoint " + endpoint)); - return; - } - if (port) - { - int id = 0; - try - { - SockAddr addr{remote + ":" + std::to_string(port)}; - id = quic->listen(addr); - } - catch (std::exception& ex) - { - reply(CreateJSONError(ex.what())); - return; - } - util::StatusObject result; - result["id"] = id; - std::string localAddress; - var::visit( - [&](auto&& addr) { localAddress = addr.ToString(); }, ep->LocalAddress()); - result["addr"] = localAddress + ":" + std::to_string(port); - if (not srvProto.empty()) - { - auto srvData = - dns::SRVData::fromTuple(std::make_tuple(srvProto, 1, 1, port, "")); - ep->PutSRVRecord(std::move(srvData)); - } - reply(CreateJSONResponse(result)); - } - else if (closeID) - { - quic->forget(closeID); - reply(CreateJSONResponse("OK")); - } - }); - }); - }) - .add_request_command( - "lookup_snode", - [&](oxenmq::Message& msg) { - HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { - if (not r->IsServiceNode()) - { - reply(CreateJSONError("not supported")); - return; - } - RouterID routerID; - if (auto itr = obj.find("snode"); itr != obj.end()) - { - std::string remote = itr->get(); - if (not routerID.FromString(remote)) - { - reply(CreateJSONError("invalid remote: " + remote)); - return; - } - } - else - { - reply(CreateJSONError("no remote provided")); - return; - } - std::string endpoint = "default"; - r->loop()->call([r, endpoint, routerID, reply]() { - auto ep = r->exitContext().GetExitEndpoint(endpoint); - if (ep == nullptr) - { - reply(CreateJSONError("cannot find local endpoint: " + endpoint)); - return; - } - ep->ObtainSNodeSession(routerID, [routerID, ep, reply](auto session) { - if (session and session->IsReady()) - { - const auto ip = net::TruncateV6(ep->GetIPForIdent(PubKey{routerID})); - util::StatusObject status{{"ip", ip.ToString()}}; - reply(CreateJSONResponse(status)); - } - else - reply(CreateJSONError("failed to obtain snode session")); - }); - }); - }); - }) - .add_request_command( - "endpoint", - [&](oxenmq::Message& msg) { - HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { - if (r->IsServiceNode()) - { - reply(CreateJSONError("not supported")); - return; - } - std::string endpoint = "default"; - std::unordered_set kills; - { - const auto itr = obj.find("endpoint"); - if (itr != obj.end()) - endpoint = itr->get(); - } - { - const auto itr = obj.find("kill"); - if (itr != obj.end()) - { - if (itr->is_array()) - { - for (auto kill_itr = itr->begin(); kill_itr != itr->end(); ++kill_itr) - { - if (kill_itr->is_string()) - kills.emplace(kill_itr->get()); - } - } - else if (itr->is_string()) - { - kills.emplace(itr->get()); - } - } - } - if (kills.empty()) - { - reply(CreateJSONError("no action taken")); - return; - } - r->loop()->call([r, endpoint, kills, reply]() { - auto ep = r->hiddenServiceContext().GetEndpointByName(endpoint); - if (ep == nullptr) - { - reply(CreateJSONError("no endpoint with name " + endpoint)); - return; - } - std::size_t removed = 0; - for (auto kill : kills) - { - removed += ep->RemoveAllConvoTagsFor(std::move(kill)); - } - reply(CreateJSONResponse( - "removed " + std::to_string(removed) + " flow" + (removed == 1 ? "" : "s"))); - }); - }); - }) - .add_request_command( - "exit", - [&](oxenmq::Message& msg) { - HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { - if (r->IsServiceNode()) - { - reply(CreateJSONError("not supported")); - return; - } - std::optional exit; - std::optional lnsExit; - IPRange range; - bool map = true; - const auto exit_itr = obj.find("exit"); - if (exit_itr != obj.end()) - { - service::Address addr; - const auto exit_str = exit_itr->get(); - if (service::NameIsValid(exit_str) or exit_str == "null") - { - lnsExit = exit_str; - } - else if (not addr.FromString(exit_str)) - { - reply(CreateJSONError("invalid exit address")); - return; - } - else - { - exit = addr; - } - } - - const auto unmap_itr = obj.find("unmap"); - if (unmap_itr != obj.end() and unmap_itr->get()) - { - map = false; - } - const auto range_itr = obj.find("range"); - if (range_itr == obj.end() or range_itr->is_null()) - { - // platforms without ipv6 support will shit themselves - // here if we give them an exit mapping that is ipv6 - if constexpr (platform::supports_ipv6) - { - range.FromString("::/0"); - } - else - { - range.FromString("0.0.0.0/0"); - } - } - else if (not range.FromString(range_itr->get())) - { - reply(CreateJSONError("invalid ip range")); - return; - } - if (not platform::supports_ipv6 and not range.IsV4()) - { - reply(CreateJSONError("ipv6 ranges not supported on this platform")); - return; - } - std::string token; - const auto token_itr = obj.find("token"); - if (token_itr != obj.end() and not token_itr->is_null()) - { - token = token_itr->get(); - } - - std::string endpoint = "default"; - const auto endpoint_itr = obj.find("endpoint"); - if (endpoint_itr != obj.end()) - { - endpoint = endpoint_itr->get(); - } - r->loop()->call([map, exit, lnsExit, range, token, endpoint, r, reply]() mutable { - auto ep = r->hiddenServiceContext().GetEndpointByName(endpoint); - if (ep == nullptr) - { - reply(CreateJSONError("no endpoint with name " + endpoint)); - return; - } - if (map and (exit.has_value() or lnsExit.has_value())) - { - auto mapExit = [=](service::Address addr) mutable { - ep->MapExitRange(range, addr); - - bool shouldSendAuth = false; - if (not token.empty()) - { - shouldSendAuth = true; - ep->SetAuthInfoForEndpoint(*exit, service::AuthInfo{token}); - } - auto onGoodResult = [r, reply](std::string reason) { - if (r->HasClientExit()) - reply(CreateJSONResponse(reason)); - else - reply(CreateJSONError("we dont have an exit?")); - }; - auto onBadResult = [r, reply, ep, range](std::string reason) { - r->routePoker()->Down(); - ep->UnmapExitRange(range); - reply(CreateJSONError(reason)); - }; - if (addr.IsZero()) - { - onGoodResult("added null exit"); - return; - } - ep->MarkAddressOutbound(addr); - ep->EnsurePathToService( - addr, - [onBadResult, onGoodResult, shouldSendAuth, addrStr = addr.ToString()]( - auto, service::OutboundContext* ctx) { - if (ctx == nullptr) - { - onBadResult("could not find exit"); - return; - } - if (not shouldSendAuth) - { - onGoodResult("OK: connected to " + addrStr); - return; - } - ctx->AsyncSendAuth( - [onGoodResult, onBadResult](service::AuthResult result) { - // TODO: refactor this code. We are 5 lambdas deep here! - if (result.code != service::AuthResultCode::eAuthAccepted) - { - onBadResult(result.reason); - return; - } - onGoodResult(result.reason); - }); - }); - }; - if (exit.has_value()) - { - mapExit(*exit); - } - else if (lnsExit.has_value()) - { - const std::string name = *lnsExit; - if (name == "null") - { - service::Address nullAddr{}; - mapExit(nullAddr); - return; - } - ep->LookupNameAsync(name, [reply, mapExit](auto maybe) mutable { - if (not maybe.has_value()) - { - reply(CreateJSONError("we could not find an exit with that name")); - return; - } - if (auto ptr = std::get_if(&*maybe)) - { - if (ptr->IsZero()) - reply(CreateJSONError("name does not exist")); - else - mapExit(*ptr); - } - else - { - reply(CreateJSONError("lns name resolved to a snode")); - } - }); - } - else - { - reply( - CreateJSONError("WTF inconsistent request, no exit address or lns " - "name provided?")); - } - return; - } - else if (map and not exit.has_value()) - { - reply(CreateJSONError("no exit address provided")); - return; - } - else if (not map) - { - r->routePoker()->Down(); - ep->UnmapExitRange(range); - reply(CreateJSONResponse("OK")); - } - }); - }); - }) - .add_request_command( - "dns_query", - [&](oxenmq::Message& msg) { - HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { - std::string endpoint{"default"}; - if (const auto itr = obj.find("endpoint"); itr != obj.end()) - { - endpoint = itr->get(); - } - std::string qname{}; - dns::QType_t qtype = dns::qTypeA; - if (const auto itr = obj.find("qname"); itr != obj.end()) - { - qname = itr->get(); - } - - if (const auto itr = obj.find("qtype"); itr != obj.end()) - { - qtype = itr->get(); - } - - dns::Message msg{dns::Question{qname, qtype}}; - - if (auto ep_ptr = GetEndpointByName(r, endpoint)) - { - if (auto dns = ep_ptr->DNS()) - { - auto src = std::make_shared([reply](auto result) { - if (result) - reply(CreateJSONResponse(result->ToJSON())); - else - reply(CreateJSONError("no response from dns")); - }); - if (not dns->MaybeHandlePacket(src, src->dumb, src->dumb, msg.ToBuffer())) - { - reply(CreateJSONError("dns query not accepted by endpoint")); - } - } - else - reply(CreateJSONError("endpoint does not have dns")); - return; - } - reply(CreateJSONError("no such endpoint for dns query")); - }); - }) - .add_request_command("config", [&](oxenmq::Message& msg) { - HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { - { - const auto itr = obj.find("override"); - if (itr != obj.end()) - { - if (not itr->is_object()) - { - reply(CreateJSONError("override is not an object")); - return; - } - for (const auto& [section, value] : itr->items()) - { - if (not value.is_object()) - { - reply(CreateJSONError( - fmt::format("failed to set [{}]: section is not an object", section))); - return; - } - for (const auto& [key, value] : value.items()) - { - if (not value.is_string()) - { - reply(CreateJSONError(fmt::format( - "failed to set [{}]:{}: value is not a string", section, key))); - return; - } - r->GetConfig()->Override(section, key, value.get()); - } - } - } - } - { - const auto itr = obj.find("reload"); - if (itr != obj.end() and itr->get()) - { - r->QueueDiskIO([conf = r->GetConfig()]() { conf->Save(); }); - } - } - reply(CreateJSONResponse("OK")); - }); - }); + if (quiclistener.request.port == 0 and quiclistener.request.closeID == 0) + { + quiclistener.response = CreateJSONError("Invalid arguments"); + return; + } + + auto endpoint = (quiclistener.request.endpoint.empty()) + ? GetEndpointByName(m_Router, "default") + : GetEndpointByName(m_Router, quiclistener.request.endpoint); + + if (not endpoint) + { + quiclistener.response = CreateJSONError("No such local endpoint found"); + return; + } + + auto quic = endpoint->GetQUICTunnel(); + + if (not quic) + { + quiclistener.response = CreateJSONError( + "No quic interface available on endpoint " + quiclistener.request.endpoint); + return; + } + + if (quiclistener.request.closeID) + { + quic->forget(quiclistener.request.closeID); + quiclistener.response = CreateJSONResponse("OK"); + return; + } + + if (quiclistener.request.port) + { + auto id = 0; + try + { + SockAddr addr{quiclistener.request.remoteHost, huint16_t{quiclistener.request.port}}; + id = quic->listen(addr); + } + catch (std::exception& e) + { + quiclistener.response = CreateJSONError(e.what()); + return; + } + + util::StatusObject result; + result["id"] = id; + std::string localAddress; + var::visit([&](auto&& addr) { localAddress = addr.ToString(); }, endpoint->LocalAddress()); + result["addr"] = localAddress + ":" + std::to_string(quiclistener.request.port); + + if (not quiclistener.request.srvProto.empty()) + { + auto srvData = dns::SRVData::fromTuple( + std::make_tuple(quiclistener.request.srvProto, 1, 1, quiclistener.request.port, "")); + endpoint->PutSRVRecord(std::move(srvData)); + } + + quiclistener.response = CreateJSONResponse(result); + return; + } + } + + void + RPCServer::invoke(LookupSnode& lookupsnode) + { + if (not m_Router.IsServiceNode()) + { + lookupsnode.response = CreateJSONError("Not supported"); + return; + } + + RouterID routerID; + if (lookupsnode.request.routerID.empty()) + { + lookupsnode.response = CreateJSONError("No remote ID provided"); + return; + } + + if (not routerID.FromString(lookupsnode.request.routerID)) + { + lookupsnode.response = CreateJSONError("Invalid remote: " + lookupsnode.request.routerID); + return; + } + + m_Router.loop()->call([&]() { + auto endpoint = m_Router.exitContext().GetExitEndpoint("default"); + + if (endpoint == nullptr) + { + lookupsnode.response = CreateJSONError("Cannot find local endpoint: default"); + return; + } + + endpoint->ObtainSNodeSession(routerID, [&](auto session) { + if (session and session->IsReady()) + { + const auto ip = net::TruncateV6(endpoint->GetIPForIdent(PubKey{routerID})); + util::StatusObject status{{"ip", ip.ToString()}}; + lookupsnode.response = CreateJSONResponse(status); + return; + } + + lookupsnode.response = CreateJSONError("Failed to obtain snode session"); + return; + }); + }); + } + + // get a ptr/ref to something in the lokinet service endpoint + // call some fxn like "obtain_exit_to" and pass exit info + // plus optional additional info on how to map + // plus callback to report when this happens + // callback is called much later (1-2s) when exit node flow is secured + // exit is now ready to use + // + void + RPCServer::invoke(Exit& exit) + { + Exit exit_request; + // steal replier from exit RPC endpoint + exit_request.replier.emplace(std::move(*exit.replier)); + + IPRange range = IPRange::StringInit(exit.request.ip_range); + service::Address exitAddr{exit.request.address}; + + m_Router.hiddenServiceContext().GetDefault()->MapExitRange(range, exitAddr); + + } + + void + RPCServer::invoke(DNSQuery& dnsquery) + { + std::string qname = (dnsquery.request.qname.empty()) ? "" : dnsquery.request.qname; + dns::QType_t qtype = (dnsquery.request.qtype) ? dnsquery.request.qtype : dns::qTypeA; + + dns::Message msg{dns::Question{qname, qtype}}; + + auto endpoint = (dnsquery.request.endpoint.empty()) + ? GetEndpointByName(m_Router, "default") + : GetEndpointByName(m_Router, dnsquery.request.endpoint); + + if (endpoint == nullptr) + { + dnsquery.response = CreateJSONError("No such endpoint found for dns query"); + return; + } + + if (auto dns = endpoint->DNS()) + { + auto packet_src = std::make_shared([&](auto result) { + if (result) + dnsquery.response = CreateJSONResponse(result->ToJSON()); + else + dnsquery.response = CreateJSONError("No response from DNS"); + }); + if (not dns->MaybeHandlePacket( + packet_src, packet_src->dumb, packet_src->dumb, msg.ToBuffer())) + dnsquery.response = CreateJSONError("DNS query not accepted by endpoint"); + } + else + dnsquery.response = CreateJSONError("Endpoint does not have dns"); + return; + } + + /* + only have simple filename ex: "persist_key.ini" + create .ini files inside conf.d + + add delete functionality + delete parameter (bool) + use same filename parameter + + */ + + void + RPCServer::invoke(Config& config) + { + if (config.request.filename.empty() and not config.request.ini.empty()) + { + config.response = CreateJSONError("No filename specified for .ini file"); + return; + } + if (config.request.ini.empty() and not config.request.filename.empty()) + { + config.response = CreateJSONError("No .ini chunk provided"); + return; + } + + if (not ends_with(config.request.filename, ".ini")) + { + config.response = CreateJSONError("Must append '.ini' to filename"); + return; + } + + if (not check_path(config.request.filename)) + { + config.response = CreateJSONError("Bad filename passed"); + return; + } + + fs::path conf_d{"conf.d"}; + + if (config.request.del and not config.request.filename.empty()) + { + try + { + if (fs::exists(conf_d / (config.request.filename))) + fs::remove(conf_d / (config.request.filename)); + } + catch (std::exception& e) + { + config.response = CreateJSONError(e.what()); + return; + } + } + else + { + try + { + if (not fs::exists(conf_d)) + fs::create_directory(conf_d); + + auto parser = ConfigParser(); + + if (parser.LoadNewFromStr(config.request.ini)) + { + parser.Filename(conf_d / (config.request.filename)); + parser.SaveNew(); + } + } + catch (std::exception& e) + { + config.response = CreateJSONError(e.what()); + return; + } + } + + config.response = CreateJSONResponse("OK"); } void - RpcServer::HandleLogsSubRequest(oxenmq::Message& m) + RPCServer::HandleLogsSubRequest(oxenmq::Message& m) { if (m.data.size() != 1) { @@ -759,4 +512,4 @@ namespace llarp::rpc } } -} // namespace llarp::rpc +} // namespace llarp::rpc \ No newline at end of file diff --git a/llarp/rpc/rpc_server.hpp b/llarp/rpc/rpc_server.hpp index 02c44202f..4250327b3 100644 --- a/llarp/rpc/rpc_server.hpp +++ b/llarp/rpc/rpc_server.hpp @@ -1,35 +1,159 @@ #pragma once +#include "rpc_request_definitions.hpp" +#include "json_bt.hpp" #include #include #include +#include #include #include namespace llarp { struct AbstractRouter; -} +} // namespace llarp + +namespace +{ + static auto logcat = llarp::log::Cat("lokinet.rpc"); +} // namespace namespace llarp::rpc { using LMQ_ptr = std::shared_ptr; + using DeferredSend = oxenmq::Message::DeferredSend; + + class RPCServer; - struct RpcServer + // Stores RPC request callback + struct rpc_callback { - explicit RpcServer(LMQ_ptr, AbstractRouter*); - ~RpcServer() = default; + using result_type = std::variant; + // calls with incoming request data; returns response body or throws exception + void (*invoke)(oxenmq::Message&, RPCServer&); + }; - void - AddRPCCategories(); + // RPC request registration + // Stores references to RPC requests in a unordered map for ease of reference + // when adding to server. To add endpoints, define in rpc_request_definitions.hpp + // and register in rpc_server.cpp + extern const std::unordered_map> rpc_request_map; + + // Exception used to signal various types of errors with a request back to the caller. This + // exception indicates that the caller did something wrong: bad data, invalid value, etc., but + // don't indicate a local problem (and so we'll log them only at debug). For more serious, + // internal errors a command should throw some other stl error (e.g. std::runtime_error or + // perhaps std::logic_error), which will result in a local daemon warning (and a generic internal + // error response to the user). + // + // For JSON RPC these become an error response with the code as the error.code value and the + // string as the error.message. + // For HTTP JSON these become a 500 Internal Server Error response with the message as the body. + // For OxenMQ the code becomes the first part of the response and the message becomes the + // second part of the response. + struct rpc_error : std::runtime_error + { + /// \param message - a message to send along with the error code (see general description + /// above). + rpc_error(std::string message) + : std::runtime_error{"RPC error: " + message}, message{std::move(message)} + {} + + std::string message; + }; + + template + std::string + CreateJSONResponse(Result_t result) + { + return nlohmann::json{{"result", result}}.dump(); + } + + inline std::string + CreateJSONError(std::string_view msg) + { + return nlohmann::json{{"error", msg}}.dump(); + } + + class RPCServer + { + public: + explicit RPCServer(LMQ_ptr, AbstractRouter&); + ~RPCServer() = default; - private: void HandleLogsSubRequest(oxenmq::Message& m); - LMQ_ptr m_LMQ; - AbstractRouter* const m_Router; + void + AddCategories(); + void + invoke(Halt& halt); + void + invoke(Version& version); + void + invoke(Status& status); + void + invoke(GetStatus& getstatus); + void + invoke(QuicConnect& quicconnect); + void + invoke(QuicListener& quiclistener); + void + invoke(LookupSnode& lookupsnode); + void + invoke(Exit& exit); + void + invoke(DNSQuery& dnsquery); + void + invoke(Config& config); + + LMQ_ptr m_LMQ; + AbstractRouter& m_Router; oxen::log::PubsubLogger log_subs; }; + + template + class EndpointHandler + { + public: + RPCServer& server; + RPC rpc{}; + + EndpointHandler(RPCServer& _server, DeferredSend _replier) + : server{_server} + { + rpc.replier.emplace(std::move(_replier)); + } + + void + operator()() + { + try + { + server.invoke(rpc); + } + catch (const rpc_error& e) + { + log::info(logcat, "RPC request 'rpc.{}' failed with: {}", rpc.name, e.what()); + rpc.response = CreateJSONError( + fmt::format("RPC request 'rpc.{}' failed with: {}", rpc.name, e.what())); + } + catch (const std::exception& e) + { + log::info(logcat, "RPC request 'rpc.{}' raised an exception: {}", rpc.name, e.what()); + rpc.response = CreateJSONError( + fmt::format("RPC request 'rpc.{}' raised an exception: {}", rpc.name, e.what())); + }; + + // check if std::optional in rpc is present + // then rpc.send_response + // else + // do nothing because invoke stole RPC + if (rpc.replier.has_value()) + rpc.send_response(); + } + }; + } // namespace llarp::rpc diff --git a/llarp/service/convotag.cpp b/llarp/service/convotag.cpp index 727751f00..b4b4eec61 100644 --- a/llarp/service/convotag.cpp +++ b/llarp/service/convotag.cpp @@ -1,5 +1,5 @@ #include "convotag.hpp" -#include "net/ip.hpp" +#include namespace llarp::service { diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index 24839ff4d..7768c8685 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -1,7 +1,15 @@ #include #include #include "endpoint.hpp" +#include "endpoint_state.hpp" +#include "endpoint_util.hpp" +#include "hidden_service_address_lookup.hpp" +#include "outbound_context.hpp" +#include "protocol.hpp" +#include "info.hpp" +#include "protocol_type.hpp" +#include #include #include #include @@ -16,27 +24,18 @@ #include #include #include -#include "endpoint_state.hpp" -#include "endpoint_util.hpp" -#include "hidden_service_address_lookup.hpp" -#include "net/ip.hpp" -#include "outbound_context.hpp" -#include "protocol.hpp" -#include "service/info.hpp" -#include "service/protocol_type.hpp" + #include #include #include #include #include +#include #include #include #include #include - -#include -#include #include #include diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index dd61c74b9..619f58fdd 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -6,24 +6,27 @@ #include #include #include -#include "address.hpp" -#include "handler.hpp" -#include "identity.hpp" -#include "pendingbuffer.hpp" -#include "protocol.hpp" -#include "sendcontext.hpp" -#include "service/protocol_type.hpp" -#include "session.hpp" -#include "lookup.hpp" #include + +// --- begin kitchen sink headers ---- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// ----- end kitchen sink headers ----- + #include #include #include #include -#include "endpoint_types.hpp" -#include "llarp/endpoint_base.hpp" - -#include "auth.hpp" #include #include diff --git a/llarp/service/intro.cpp b/llarp/service/intro.cpp index 54de28805..026d49dee 100644 --- a/llarp/service/intro.cpp +++ b/llarp/service/intro.cpp @@ -1,5 +1,5 @@ #include "intro.hpp" -#include "util/time.hpp" +#include namespace llarp { diff --git a/llarp/service/outbound_context.cpp b/llarp/service/outbound_context.cpp index 3494bceee..28b4a37af 100644 --- a/llarp/service/outbound_context.cpp +++ b/llarp/service/outbound_context.cpp @@ -1,16 +1,15 @@ #include "outbound_context.hpp" - -#include #include "async_key_exchange.hpp" #include "hidden_service_address_lookup.hpp" #include "endpoint.hpp" +#include "endpoint_util.hpp" +#include "protocol_type.hpp" + +#include #include #include #include -#include "endpoint_util.hpp" -#include "service/protocol_type.hpp" - #include #include diff --git a/llarp/tooling/router_hive.hpp b/llarp/tooling/router_hive.hpp index 31bfdffcf..760205ea6 100644 --- a/llarp/tooling/router_hive.hpp +++ b/llarp/tooling/router_hive.hpp @@ -3,8 +3,8 @@ #include "router_event.hpp" #include -#include -#include +#include +#include #include #include diff --git a/llarp/vpn/win32.cpp b/llarp/vpn/win32.cpp index 1562cef1c..f6f4a452e 100644 --- a/llarp/vpn/win32.cpp +++ b/llarp/vpn/win32.cpp @@ -1,4 +1,4 @@ -#include "vpn/win32.hpp" +#include "win32.hpp" #include #include #include diff --git a/pybind/llarp/context.cpp b/pybind/llarp/context.cpp index db9fad6f3..9a1cf194b 100644 --- a/pybind/llarp/context.cpp +++ b/pybind/llarp/context.cpp @@ -3,7 +3,8 @@ #include #include #include -#include "service/protocol_type.hpp" +#include + namespace llarp { void diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c11104139..e6270599e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -60,8 +60,4 @@ if(WIN32) target_link_libraries(testAll PUBLIC ws2_32 iphlpapi shlwapi) endif() -if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") - target_link_directories(testAll PRIVATE /usr/local/lib) -endif() - add_custom_target(check COMMAND testAll) diff --git a/test/check_main.cpp b/test/check_main.cpp index 87299c833..c98dadd9a 100644 --- a/test/check_main.cpp +++ b/test/check_main.cpp @@ -1,8 +1,8 @@ #define CATCH_CONFIG_RUNNER #include -#include -#include +#include +#include #ifdef _WIN32 #include diff --git a/test/config/test_llarp_config_definition.cpp b/test/config/test_llarp_config_definition.cpp index 80005d58c..29cd9b795 100644 --- a/test/config/test_llarp_config_definition.cpp +++ b/test/config/test_llarp_config_definition.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/test/config/test_llarp_config_ini.cpp b/test/config/test_llarp_config_ini.cpp index 06c187e2c..b0b79eb91 100644 --- a/test/config/test_llarp_config_ini.cpp +++ b/test/config/test_llarp_config_ini.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -42,12 +42,12 @@ TEST_CASE("ConfigParser", "[config]") SECTION("No key") { - REQUIRE_FALSE(parser.LoadFromStr("[test]\n=1090\n")); + REQUIRE_THROWS(parser.LoadFromStr("[test]\n=1090\n")); } SECTION("Parse invalid") { - REQUIRE_FALSE( + REQUIRE_THROWS( parser.LoadFromStr("srged5ghe5\nf34wtge5\nw34tgfs4ygsd5yg=4;\n#" "g4syhgd5\n")); } diff --git a/test/config/test_llarp_config_output.cpp b/test/config/test_llarp_config_output.cpp index 5ba47bf21..3324ef0f6 100644 --- a/test/config/test_llarp_config_output.cpp +++ b/test/config/test_llarp_config_output.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/test/crypto/test_llarp_crypto.cpp b/test/crypto/test_llarp_crypto.cpp index 13105a87e..b78752375 100644 --- a/test/crypto/test_llarp_crypto.cpp +++ b/test/crypto/test_llarp_crypto.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/test/crypto/test_llarp_crypto_types.cpp b/test/crypto/test_llarp_crypto_types.cpp index 8477352f7..359f1911b 100644 --- a/test/crypto/test_llarp_crypto_types.cpp +++ b/test/crypto/test_llarp_crypto_types.cpp @@ -1,9 +1,9 @@ -#include +#include #include #include -#include +#include "test_util.hpp" #include extern "C" { diff --git a/test/crypto/test_llarp_key_manager.cpp b/test/crypto/test_llarp_key_manager.cpp index e5082bf58..459ed309b 100644 --- a/test/crypto/test_llarp_key_manager.cpp +++ b/test/crypto/test_llarp_key_manager.cpp @@ -1,14 +1,15 @@ -#include +#include "llarp_test.hpp" +#include "test_util.hpp" -#include -#include -#include +#include + +#include +#include #include #include #include -#include #include using namespace ::llarp; diff --git a/test/dns/test_llarp_dns_dns.cpp b/test/dns/test_llarp_dns_dns.cpp index ff800b4ec..e130dc634 100644 --- a/test/dns/test_llarp_dns_dns.cpp +++ b/test/dns/test_llarp_dns_dns.cpp @@ -1,11 +1,11 @@ #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/test/llarp_test.hpp b/test/llarp_test.hpp index 8199ff569..914c4a224 100644 --- a/test/llarp_test.hpp +++ b/test/llarp_test.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include namespace llarp::test diff --git a/test/net/test_ip_address.cpp b/test/net/test_ip_address.cpp index 1366ebb76..8cbc26b1e 100644 --- a/test/net/test_ip_address.cpp +++ b/test/net/test_ip_address.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/test/net/test_llarp_net.cpp b/test/net/test_llarp_net.cpp index 0c237a90b..90f2f901c 100644 --- a/test/net/test_llarp_net.cpp +++ b/test/net/test_llarp_net.cpp @@ -1,7 +1,7 @@ -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/test/net/test_sock_addr.cpp b/test/net/test_sock_addr.cpp index 9d13b48a4..c1c175d7e 100644 --- a/test/net/test_sock_addr.cpp +++ b/test/net/test_sock_addr.cpp @@ -1,6 +1,6 @@ -#include -#include -#include +#include +#include +#include #include #include diff --git a/test/nodedb/test_nodedb.cpp b/test/nodedb/test_nodedb.cpp index 2d23bd27d..9286e345b 100644 --- a/test/nodedb/test_nodedb.cpp +++ b/test/nodedb/test_nodedb.cpp @@ -1,8 +1,8 @@ #include -#include "config/config.hpp" -#include -#include +#include +#include +#include using llarp_nodedb = llarp::NodeDB; diff --git a/test/path/test_path.cpp b/test/path/test_path.cpp index d5ad3d56c..d25a1c68e 100644 --- a/test/path/test_path.cpp +++ b/test/path/test_path.cpp @@ -1,4 +1,4 @@ -#include +#include #include using Path_t = llarp::path::Path; diff --git a/test/peerstats/test_peer_db.cpp b/test/peerstats/test_peer_db.cpp index 52889514c..c3ed14c79 100644 --- a/test/peerstats/test_peer_db.cpp +++ b/test/peerstats/test_peer_db.cpp @@ -1,12 +1,12 @@ -#include +#include #include #include #include -#include "peerstats/types.hpp" -#include "router_contact.hpp" -#include "util/logging.hpp" -#include "util/time.hpp" +#include +#include +#include +#include TEST_CASE("Test PeerDb PeerStats memory storage", "[PeerDb]") { diff --git a/test/peerstats/test_peer_types.cpp b/test/peerstats/test_peer_types.cpp index 46b8bc834..b2c398563 100644 --- a/test/peerstats/test_peer_types.cpp +++ b/test/peerstats/test_peer_types.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/test/router/test_llarp_router_version.cpp b/test/router/test_llarp_router_version.cpp index 260a16198..8f00ef03c 100644 --- a/test/router/test_llarp_router_version.cpp +++ b/test/router/test_llarp_router_version.cpp @@ -1,5 +1,5 @@ -#include -#include "router/router.hpp" +#include +#include #include diff --git a/test/routing/test_llarp_routing_obtainexitmessage.cpp b/test/routing/test_llarp_routing_obtainexitmessage.cpp index fca9842e6..631652072 100644 --- a/test/routing/test_llarp_routing_obtainexitmessage.cpp +++ b/test/routing/test_llarp_routing_obtainexitmessage.cpp @@ -1,8 +1,7 @@ -#include - -#include -#include -#include +#include "llarp_test.hpp" +#include +#include +#include #include diff --git a/test/routing/test_llarp_routing_transfer_traffic.cpp b/test/routing/test_llarp_routing_transfer_traffic.cpp index 915aecef0..3f7b44dee 100644 --- a/test/routing/test_llarp_routing_transfer_traffic.cpp +++ b/test/routing/test_llarp_routing_transfer_traffic.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/test/service/test_llarp_service_address.cpp b/test/service/test_llarp_service_address.cpp index f95e398c9..62c6cd0d7 100644 --- a/test/service/test_llarp_service_address.cpp +++ b/test/service/test_llarp_service_address.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/test/service/test_llarp_service_identity.cpp b/test/service/test_llarp_service_identity.cpp index 2fe8db9d7..560754280 100644 --- a/test/service/test_llarp_service_identity.cpp +++ b/test/service/test_llarp_service_identity.cpp @@ -1,13 +1,13 @@ -#include -#include +#include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include -#include +#include "test_util.hpp" #include using namespace llarp; diff --git a/test/service/test_llarp_service_name.cpp b/test/service/test_llarp_service_name.cpp index 3f9daef61..86f0324f3 100644 --- a/test/service/test_llarp_service_name.cpp +++ b/test/service/test_llarp_service_name.cpp @@ -1,6 +1,6 @@ #include "catch2/catch.hpp" -#include -#include +#include +#include #include using namespace std::literals; diff --git a/test/test_llarp_encrypted_frame.cpp b/test/test_llarp_encrypted_frame.cpp index a1e850644..6648ba9b1 100644 --- a/test/test_llarp_encrypted_frame.cpp +++ b/test/test_llarp_encrypted_frame.cpp @@ -1,12 +1,9 @@ -#include - -#include -#include -#include -#include - -#include - +#include "llarp_test.hpp" +#include "test_util.hpp" +#include +#include +#include +#include #include using namespace ::llarp; diff --git a/test/test_llarp_router_contact.cpp b/test/test_llarp_router_contact.cpp index eac96f332..f94e7904b 100644 --- a/test/test_llarp_router_contact.cpp +++ b/test/test_llarp_router_contact.cpp @@ -1,9 +1,9 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include namespace diff --git a/test/test_util.cpp b/test/test_util.cpp index 100af14a9..b73a0e445 100644 --- a/test/test_util.cpp +++ b/test/test_util.cpp @@ -1,4 +1,4 @@ -#include +#include "test_util.hpp" #include diff --git a/test/test_util.hpp b/test/test_util.hpp index 4433789ab..36f09a912 100644 --- a/test/test_util.hpp +++ b/test/test_util.hpp @@ -1,9 +1,8 @@ #ifndef TEST_UTIL_HPP #define TEST_UTIL_HPP -#include -#include - +#include +#include #include #include diff --git a/test/util/meta/test_llarp_util_memfn.cpp b/test/util/meta/test_llarp_util_memfn.cpp index 165115570..a75cde7c5 100644 --- a/test/util/meta/test_llarp_util_memfn.cpp +++ b/test/util/meta/test_llarp_util_memfn.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/test/util/test_llarp_util_aligned.cpp b/test/util/test_llarp_util_aligned.cpp index 970a676d5..3e6cc0c5a 100644 --- a/test/util/test_llarp_util_aligned.cpp +++ b/test/util/test_llarp_util_aligned.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include diff --git a/test/util/test_llarp_util_bencode.cpp b/test/util/test_llarp_util_bencode.cpp index 890ddce30..cff95e7d2 100644 --- a/test/util/test_llarp_util_bencode.cpp +++ b/test/util/test_llarp_util_bencode.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/test/util/test_llarp_util_bits.cpp b/test/util/test_llarp_util_bits.cpp index befa3965f..e24eb33a4 100644 --- a/test/util/test_llarp_util_bits.cpp +++ b/test/util/test_llarp_util_bits.cpp @@ -1,5 +1,5 @@ #include -#include +#include using namespace llarp::bits; diff --git a/test/util/test_llarp_util_decaying_hashset.cpp b/test/util/test_llarp_util_decaying_hashset.cpp index 28cc9371c..5a9339bd8 100644 --- a/test/util/test_llarp_util_decaying_hashset.cpp +++ b/test/util/test_llarp_util_decaying_hashset.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include TEST_CASE("DecayingHashSet test decay static time", "[decaying-hashset]") diff --git a/test/util/test_llarp_util_log_level.cpp b/test/util/test_llarp_util_log_level.cpp index 048c6324f..8335e986a 100644 --- a/test/util/test_llarp_util_log_level.cpp +++ b/test/util/test_llarp_util_log_level.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #include using TestString = std::string; diff --git a/test/util/test_llarp_util_str.cpp b/test/util/test_llarp_util_str.cpp index b53173660..caa324967 100644 --- a/test/util/test_llarp_util_str.cpp +++ b/test/util/test_llarp_util_str.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/test/util/thread/test_llarp_util_queue.cpp b/test/util/thread/test_llarp_util_queue.cpp index 4410b5ab7..70f71a974 100644 --- a/test/util/thread/test_llarp_util_queue.cpp +++ b/test/util/thread/test_llarp_util_queue.cpp @@ -1,6 +1,6 @@ -#include -#include -#include +#include +#include +#include #include #include diff --git a/test/util/thread/test_llarp_util_queue_manager.cpp b/test/util/thread/test_llarp_util_queue_manager.cpp index 526c6b289..8085a3240 100644 --- a/test/util/thread/test_llarp_util_queue_manager.cpp +++ b/test/util/thread/test_llarp_util_queue_manager.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include From d37398a915c1adcc61ebbc17b73fa63e6fda3c8e Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 24 Jan 2023 07:23:01 -0800 Subject: [PATCH 2/7] review comments --- llarp/net/ip_range.hpp | 2 +- llarp/rpc/rpc_request_decorators.hpp | 14 ++++++------ llarp/rpc/rpc_request_definitions.hpp | 33 +++++++++++++++------------ llarp/rpc/rpc_server.cpp | 17 ++++++-------- llarp/rpc/rpc_server.hpp | 7 +++--- 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/llarp/net/ip_range.hpp b/llarp/net/ip_range.hpp index e758ab384..d0d494c37 100644 --- a/llarp/net/ip_range.hpp +++ b/llarp/net/ip_range.hpp @@ -29,7 +29,7 @@ namespace llarp { IPRange range{}; range.FromString(_range); - return range; + return range; } static constexpr IPRange diff --git a/llarp/rpc/rpc_request_decorators.hpp b/llarp/rpc/rpc_request_decorators.hpp index f3ccf4966..ace72d784 100644 --- a/llarp/rpc/rpc_request_decorators.hpp +++ b/llarp/rpc/rpc_request_decorators.hpp @@ -43,15 +43,15 @@ namespace llarp::rpc response_hex.format = llarp::rpc::json_binary_proxy::fmt::bt; } - // Invoked if this.replier is still present. If it is "stolen" by endpoint (moved from + // Invoked if this.replier is still present. If it is "stolen" by endpoint (moved from // RPC struct), then endpoint handles sending reply void send_response() { - replier->reply(is_bt() ? oxenc::bt_serialize(json_to_bt(std::move(response))) - : response.dump()); + replier->reply( + is_bt() ? oxenc::bt_serialize(json_to_bt(std::move(response))) : response.dump()); } - + void send_response(nlohmann::json _response) { @@ -97,9 +97,9 @@ namespace llarp::rpc // The oxenmq deferred send object into which the response will be set. If this optional is // still set when the `invoke` call returns then the response is sent at that point; if it has - // been moved out (i.e. either just this instance or the whole request struct is stolen/moved by - // the invoke function) then it is the invoke function's job to send a reply. Typically this is - // done when a response cannot be sent immediately + // been moved out (i.e. either just this instance or the whole request struct is stolen/moved + // by the invoke function) then it is the invoke function's job to send a reply. Typically + // this is done when a response cannot be sent immediately std::optional replier; }; diff --git a/llarp/rpc/rpc_request_definitions.hpp b/llarp/rpc/rpc_request_definitions.hpp index adeac4486..7f40588a8 100644 --- a/llarp/rpc/rpc_request_definitions.hpp +++ b/llarp/rpc/rpc_request_definitions.hpp @@ -1,12 +1,12 @@ #pragma once #include "rpc_request_decorators.hpp" -#include "net/ip_range.hpp" -#include "router/abstractrouter.hpp" -#include "router/route_poker.hpp" -#include "service/address.hpp" -#include "service/endpoint.hpp" -#include "service/outbound_context.hpp" +#include "llarp/net/ip_range.hpp" +#include "llarp/router/abstractrouter.hpp" +#include "llarp/router/route_poker.hpp" +#include "llarp/service/address.hpp" +#include "llarp/service/endpoint.hpp" +#include "llarp/service/outbound_context.hpp" #include #include #include @@ -164,10 +164,10 @@ namespace llarp::rpc // Note: ask Jason about the internals of this // // Inputs: - // "endpoint" : + // "endpoint" : // "unmap" : if true, unmaps connection to exit node (bool) // "range" : IP range to map to exit node - // "token" : + // "token" : // // Returns: // @@ -186,21 +186,26 @@ namespace llarp::rpc void onGoodResult(std::string reason, bool hasClient) { - response = (hasClient) ? - nlohmann::json{{"result", reason}}.dump() : - nlohmann::json{{"error", "We don't have an exit?"}}.dump(); + response = (hasClient) ? nlohmann::json{{"result", reason}}.dump() + : nlohmann::json{{"error", "We don't have an exit?"}}.dump(); } void - onBadResult(std::string reason, AbstractRouter& abs, llarp::service::Endpoint_ptr eptr, IPRange range) + onBadResult( + std::string reason, AbstractRouter& abs, llarp::service::Endpoint_ptr eptr, IPRange range) { abs.routePoker()->Down(); eptr->UnmapExitRange(range); response = nlohmann::json{{"result", reason}}.dump(); } - void - mapExit(service::Address addr, AbstractRouter& router, llarp::service::Endpoint_ptr eptr, IPRange range, service::Address exitAddr) + void + mapExit( + service::Address addr, + AbstractRouter& router, + llarp::service::Endpoint_ptr eptr, + IPRange range, + service::Address exitAddr) { eptr->MapExitRange(range, addr); diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 216c255eb..0aec94c47 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -1,6 +1,6 @@ #include "rpc_server.hpp" #include "rpc_request.hpp" -#include "service/address.hpp" +#include "llarp/service/address.hpp" #include #include #include @@ -85,7 +85,7 @@ namespace llarp::rpc template void - register_rpc_command(std::unordered_map>& regs) + register_rpc_command(std::unordered_map& regs) { static_assert(std::is_base_of_v); auto cback = std::make_shared(); @@ -109,17 +109,17 @@ namespace llarp::rpc } template - std::unordered_map> + std::unordered_map register_rpc_requests(tools::type_list) { - std::unordered_map> regs; + std::unordered_map regs; (register_rpc_command(regs), ...); return regs; } - const std::unordered_map> rpc_request_map = + const std::unordered_map rpc_request_map = register_rpc_requests(rpc::rpc_request_types{}); void @@ -133,7 +133,7 @@ namespace llarp::rpc m_LMQ->add_request_command( "llarp", req.first, - [name = std::string_view{req.first}, &call = *req.second, this](oxenmq::Message& m) { + [name = std::string_view{req.first}, &call = req.second, this](oxenmq::Message& m) { call.invoke(m, *this); }); } @@ -352,7 +352,7 @@ namespace llarp::rpc // plus callback to report when this happens // callback is called much later (1-2s) when exit node flow is secured // exit is now ready to use - // + // void RPCServer::invoke(Exit& exit) { @@ -362,9 +362,6 @@ namespace llarp::rpc IPRange range = IPRange::StringInit(exit.request.ip_range); service::Address exitAddr{exit.request.address}; - - m_Router.hiddenServiceContext().GetDefault()->MapExitRange(range, exitAddr); - } void diff --git a/llarp/rpc/rpc_server.hpp b/llarp/rpc/rpc_server.hpp index 4250327b3..fe64ca1b0 100644 --- a/llarp/rpc/rpc_server.hpp +++ b/llarp/rpc/rpc_server.hpp @@ -38,7 +38,7 @@ namespace llarp::rpc // Stores references to RPC requests in a unordered map for ease of reference // when adding to server. To add endpoints, define in rpc_request_definitions.hpp // and register in rpc_server.cpp - extern const std::unordered_map> rpc_request_map; + extern const std::unordered_map rpc_request_map; // Exception used to signal various types of errors with a request back to the caller. This // exception indicates that the caller did something wrong: bad data, invalid value, etc., but @@ -121,8 +121,7 @@ namespace llarp::rpc RPCServer& server; RPC rpc{}; - EndpointHandler(RPCServer& _server, DeferredSend _replier) - : server{_server} + EndpointHandler(RPCServer& _server, DeferredSend _replier) : server{_server} { rpc.replier.emplace(std::move(_replier)); } @@ -145,7 +144,7 @@ namespace llarp::rpc log::info(logcat, "RPC request 'rpc.{}' raised an exception: {}", rpc.name, e.what()); rpc.response = CreateJSONError( fmt::format("RPC request 'rpc.{}' raised an exception: {}", rpc.name, e.what())); - }; + } // check if std::optional in rpc is present // then rpc.send_response From d3e69fe3c5baa5d00baf5938f33998996f13ba48 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 24 Jan 2023 09:44:02 -0800 Subject: [PATCH 3/7] added unmapexit and listexit endpoints --- daemon/lokinet-vpn.cpp | 6 +-- llarp/rpc/rpc_request_definitions.hpp | 55 ++++++++++++++++++----- llarp/rpc/rpc_request_parser.cpp | 20 ++++++--- llarp/rpc/rpc_request_parser.hpp | 4 +- llarp/rpc/rpc_server.cpp | 64 ++++++++++++++++++++++----- llarp/rpc/rpc_server.hpp | 6 ++- 6 files changed, 120 insertions(+), 35 deletions(-) diff --git a/daemon/lokinet-vpn.cpp b/daemon/lokinet-vpn.cpp index c1c78f4d1..98d551f3a 100644 --- a/daemon/lokinet-vpn.cpp +++ b/daemon/lokinet-vpn.cpp @@ -223,11 +223,11 @@ main(int argc, char* argv[]) } if (options.vpnUp) { - nlohmann::json opts{{"exit", options.exitAddress}, {"token", options.token}}; + nlohmann::json opts{{"address", options.exitAddress}, {"token", options.token}}; if (options.range) - opts["range"] = *options.range; + opts["IP_range"] = *options.range; - auto maybe_result = OMQ_Request(omq, connectionID, "llarp.exit", std::move(opts)); + auto maybe_result = OMQ_Request(omq, connectionID, "llarp.map_exit", std::move(opts)); if (not maybe_result) return exit_error("could not add exit"); diff --git a/llarp/rpc/rpc_request_definitions.hpp b/llarp/rpc/rpc_request_definitions.hpp index 7f40588a8..0b7401840 100644 --- a/llarp/rpc/rpc_request_definitions.hpp +++ b/llarp/rpc/rpc_request_definitions.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace llarp::rpc { @@ -158,29 +159,25 @@ namespace llarp::rpc } request; }; - // RPC: exit - // Seems like this adds an exit node? - // - // Note: ask Jason about the internals of this + // RPC: map_exit + // Map a new connection to an exit node // // Inputs: - // "endpoint" : - // "unmap" : if true, unmaps connection to exit node (bool) + // "address" : ID of endpoint to map // "range" : IP range to map to exit node - // "token" : + // "token" : auth token // // Returns: // - struct Exit : RPCRequest + struct MapExit : RPCRequest { - static constexpr auto name = "exit"sv; + static constexpr auto name = "map_exit"sv; struct request_parameters { std::string address; - std::string ip_range; + std::vector ip_range; std::string token; - bool unmap; } request; void @@ -246,6 +243,38 @@ namespace llarp::rpc } }; + // RPC: list_exits + // List all currently mapped exit node connections + // + // Inputs: none + // + // Returns: + // + struct ListExits : NoArgs + { + static constexpr auto name = "list_exits"sv; + }; + + // RPC: unmap_exit + // Unmap a connection to an exit node + // + // Inputs: + // "endpoint" : ID of endpoint to map + // "range" : IP range to map to exit node + // "token" : auth token + // + // Returns: + // + struct UnmapExit : RPCRequest + { + static constexpr auto name = "unmap_exit"sv; + + struct request_parameters + { + std::vector ip_range; + } request; + }; + // RPC: dns_query // Attempts to query endpoint by domain name // @@ -301,7 +330,9 @@ namespace llarp::rpc QuicConnect, QuicListener, LookupSnode, - Exit, + MapExit, + ListExits, + UnmapExit, DNSQuery, Config>; diff --git a/llarp/rpc/rpc_request_parser.cpp b/llarp/rpc/rpc_request_parser.cpp index 8a193df1a..1a0732d77 100644 --- a/llarp/rpc/rpc_request_parser.cpp +++ b/llarp/rpc/rpc_request_parser.cpp @@ -1,4 +1,5 @@ #include "rpc_request_parser.hpp" +#include "llarp/rpc/rpc_request_definitions.hpp" #include "param_parser.hpp" #include #include @@ -51,18 +52,25 @@ namespace llarp::rpc } void - parse_request(Exit& exit, rpc_input input) + parse_request(MapExit& mapexit, rpc_input input) { get_values( input, "address", - exit.request.address, + mapexit.request.address, "IP_range", - exit.request.ip_range, + mapexit.request.ip_range, "token", - exit.request.token, - "unmap", - exit.request.unmap); + mapexit.request.token); + } + + void + parse_request(UnmapExit& unmapexit, rpc_input input) + { + get_values( + input, + "IP_range", + unmapexit.request.ip_range); } void diff --git a/llarp/rpc/rpc_request_parser.hpp b/llarp/rpc/rpc_request_parser.hpp index f7a71747c..ac1e0ee71 100644 --- a/llarp/rpc/rpc_request_parser.hpp +++ b/llarp/rpc/rpc_request_parser.hpp @@ -22,7 +22,9 @@ namespace llarp::rpc void parse_request(LookupSnode& lookupsnode, rpc_input input); void - parse_request(Exit& exit, rpc_input input); + parse_request(MapExit& mapexit, rpc_input input); + void + parse_request(UnmapExit& unmapexit, rpc_input input); void parse_request(DNSQuery& dnsquery, rpc_input input); void diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 0aec94c47..40a4ae6af 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -1,6 +1,8 @@ #include "rpc_server.hpp" +#include "llarp/rpc/rpc_request_definitions.hpp" #include "rpc_request.hpp" #include "llarp/service/address.hpp" +#include #include #include #include @@ -16,6 +18,7 @@ #include #include #include +#include #include namespace llarp::rpc @@ -346,22 +349,59 @@ namespace llarp::rpc }); } - // get a ptr/ref to something in the lokinet service endpoint - // call some fxn like "obtain_exit_to" and pass exit info - // plus optional additional info on how to map - // plus callback to report when this happens - // callback is called much later (1-2s) when exit node flow is secured - // exit is now ready to use - // void - RPCServer::invoke(Exit& exit) + RPCServer::invoke(MapExit& mapexit) { - Exit exit_request; + MapExit exit_request; // steal replier from exit RPC endpoint - exit_request.replier.emplace(std::move(*exit.replier)); + exit_request.replier.emplace(std::move(*mapexit.replier)); + + // + // + // + // + // + } + + void + RPCServer::invoke(ListExits& listexits) + { + if (not m_Router.hiddenServiceContext().hasEndpoints()) + listexits.response = CreateJSONError("No mapped endpoints found"); + else + listexits.response = CreateJSONResponse( + m_Router.hiddenServiceContext().GetDefault()->ExtractStatus()["m_ExitMap"]); + } + + void + RPCServer::invoke(UnmapExit& unmapexit) + { + if (unmapexit.request.ip_range.empty()) + { + unmapexit.response = CreateJSONError("No IP range provided"); + return; + } + + std::vector range{}; + + for (auto& ip : unmapexit.request.ip_range) + { + try { + range.push_back(IPRange::StringInit(ip)); + } catch (std::exception& e) { + unmapexit.response = CreateJSONError(e.what()); + } + } + + try { + m_Router.routePoker()->Down(); + for (auto& ip : range) + m_Router.hiddenServiceContext().GetDefault()->UnmapExitRange(ip); + } catch (std::exception& e) { + unmapexit.response = CreateJSONError("Unable to unmap to given range"); + } - IPRange range = IPRange::StringInit(exit.request.ip_range); - service::Address exitAddr{exit.request.address}; + unmapexit.response = CreateJSONResponse("OK"); } void diff --git a/llarp/rpc/rpc_server.hpp b/llarp/rpc/rpc_server.hpp index fe64ca1b0..dc07db30f 100644 --- a/llarp/rpc/rpc_server.hpp +++ b/llarp/rpc/rpc_server.hpp @@ -103,7 +103,11 @@ namespace llarp::rpc void invoke(LookupSnode& lookupsnode); void - invoke(Exit& exit); + invoke(MapExit& mapexit); + void + invoke(ListExits& listexits); + void + invoke(UnmapExit& unmapexit); void invoke(DNSQuery& dnsquery); void From 02b392881b89e9c1be5682024f22a5e6ca37a979 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 24 Jan 2023 13:14:00 -0500 Subject: [PATCH 4/7] add llarp::service::Endpoint::map_exit --- llarp/service/endpoint.cpp | 80 ++++++++++++++++++++++++++++++++++- llarp/service/endpoint.hpp | 7 +++ llarp/service/sendcontext.cpp | 5 +-- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index 7768c8685..8bb148589 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -1,15 +1,15 @@ -#include -#include #include "endpoint.hpp" #include "endpoint_state.hpp" #include "endpoint_util.hpp" #include "hidden_service_address_lookup.hpp" +#include "auth.hpp" #include "outbound_context.hpp" #include "protocol.hpp" #include "info.hpp" #include "protocol_type.hpp" #include +#include #include #include #include @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,7 @@ #include #include +#include #include #include #include @@ -215,6 +217,75 @@ namespace llarp return std::nullopt; } + void + Endpoint::map_exit( + std::string name, + std::string token, + std::vector ranges, + std::function result_handler) + { + if (ranges.empty()) + { + result_handler(false, "no ranges provided"); + return; + } + + LookupNameAsync( + name, + [ptr = std::static_pointer_cast(GetSelf()), + name, + auth = AuthInfo{token}, + ranges, + result_handler, + poker = m_router->routePoker()](auto maybe_addr) { + if (not maybe_addr) + { + result_handler(false, "exit not found: {}"_format(name)); + return; + } + if (auto* addr_ptr = std::get_if
(&*maybe_addr)) + { + Address addr{*addr_ptr}; + + ptr->SetAuthInfoForEndpoint(addr, auth); + ptr->MarkAddressOutbound(addr); + auto result = ptr->EnsurePathToService( + addr, + [ptr, name, ranges, result_handler, poker](auto addr, auto* ctx) { + if (ctx == nullptr) + { + result_handler(false, "could not establish flow to {}"_format(name)); + return; + } + + // make a lambda that sends the reply after doing auth + auto apply_result = + [ptr, poker, addr, result_handler, ranges](AuthResult result) { + if (result.code != AuthResultCode::eAuthAccepted) + { + result_handler(false, result.reason); + return; + } + for (const auto& range : ranges) + ptr->MapExitRange(range, addr); + + if (poker) + poker->Up(); + result_handler(true, result.reason); + }; + + ctx->AsyncSendAuth(apply_result); + }, + ptr->PathAlignmentTimeout()); + + if (not result) + result_handler(false, "did not build path to {}"_format(name)); + } + else + result_handler(false, "exit via snode not supported"); + }); + } + void Endpoint::LookupServiceAsync( std::string name, @@ -2086,6 +2157,11 @@ namespace llarp void Endpoint::SetAuthInfoForEndpoint(Address addr, AuthInfo info) { + if (info.token.empty()) + { + m_RemoteAuthInfos.erase(addr); + return; + } m_RemoteAuthInfos[addr] = std::move(info); } diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index 619f58fdd..74de81355 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -284,6 +284,13 @@ namespace llarp void UnmapExitRange(IPRange range); + void + map_exit( + std::string name, + std::string token, + std::vector ranges, + std::function result); + void PutLookup(IServiceLookup* lookup, uint64_t txid) override; diff --git a/llarp/service/sendcontext.cpp b/llarp/service/sendcontext.cpp index 20fb744a0..0629ba3d0 100644 --- a/llarp/service/sendcontext.cpp +++ b/llarp/service/sendcontext.cpp @@ -125,8 +125,7 @@ namespace llarp void SendContext::AsyncSendAuth(std::function resultHandler) { - const auto maybe = m_Endpoint->MaybeGetAuthInfoForEndpoint(remoteIdent.Addr()); - if (maybe.has_value()) + if (const auto maybe = m_Endpoint->MaybeGetAuthInfoForEndpoint(remoteIdent.Addr())) { // send auth message const llarp_buffer_t authdata{maybe->token}; @@ -134,7 +133,7 @@ namespace llarp authResultListener = resultHandler; } else - resultHandler({AuthResultCode::eAuthFailed, "no auth for given endpoint"}); + resultHandler({AuthResultCode::eAuthAccepted, "no auth needed"}); } void From 0632e88de0b2d85d869b28ec145f7f13f7dc98b9 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 26 Jan 2023 09:51:55 -0800 Subject: [PATCH 5/7] Make new header for json type conversions --- daemon/lokinet-vpn.cpp | 8 +-- llarp/CMakeLists.txt | 1 + llarp/net/ip_range.hpp | 14 ++++++ llarp/rpc/json_binary_proxy.hpp | 23 --------- llarp/rpc/json_conversions.cpp | 18 +++++++ llarp/rpc/json_conversions.hpp | 37 ++++++++++++++ llarp/rpc/param_parser.hpp | 66 ++++++++++++++----------- llarp/rpc/rpc_request.hpp | 1 - llarp/rpc/rpc_request_definitions.hpp | 70 ++------------------------- llarp/rpc/rpc_request_parser.cpp | 7 +-- llarp/rpc/rpc_server.cpp | 57 +++++++++------------- llarp/rpc/rpc_server.hpp | 4 -- 12 files changed, 140 insertions(+), 166 deletions(-) create mode 100644 llarp/rpc/json_conversions.cpp create mode 100644 llarp/rpc/json_conversions.hpp diff --git a/daemon/lokinet-vpn.cpp b/daemon/lokinet-vpn.cpp index 98d551f3a..26d2f47b2 100644 --- a/daemon/lokinet-vpn.cpp +++ b/daemon/lokinet-vpn.cpp @@ -225,7 +225,7 @@ main(int argc, char* argv[]) { nlohmann::json opts{{"address", options.exitAddress}, {"token", options.token}}; if (options.range) - opts["IP_range"] = *options.range; + opts["ip_range"] = *options.range; auto maybe_result = OMQ_Request(omq, connectionID, "llarp.map_exit", std::move(opts)); @@ -240,10 +240,10 @@ main(int argc, char* argv[]) } if (options.vpnDown) { - nlohmann::json opts{{"unmap", true}}; + nlohmann::json opts{{"unmap_exit", true}}; if (options.range) - opts["range"] = *options.range; - if (not OMQ_Request(omq, connectionID, "llarp.exit", std::move(opts))) + opts["ip_range"] = *options.range; + if (not OMQ_Request(omq, connectionID, "llarp.unmap_exit", std::move(opts))) return exit_error("failed to unmap exit"); } diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 5c3ade29a..6653a5f08 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -247,6 +247,7 @@ add_library(lokinet-context add_library(lokinet-rpc STATIC rpc/json_binary_proxy.cpp + rpc/json_conversions.cpp rpc/lokid_rpc_client.cpp rpc/rpc_request_parser.cpp rpc/rpc_server.cpp diff --git a/llarp/net/ip_range.hpp b/llarp/net/ip_range.hpp index d0d494c37..d7045bb74 100644 --- a/llarp/net/ip_range.hpp +++ b/llarp/net/ip_range.hpp @@ -8,6 +8,7 @@ #include #include +#include #include namespace llarp @@ -24,6 +25,12 @@ namespace llarp : addr{std::move(address)}, netmask_bits{std::move(netmask)} {} + explicit IPRange(std::string _range) + { + if (not FromString(_range)) + throw std::invalid_argument{"IP string '{}' cannot be parsed as IP range"_format(_range)}; + } + static IPRange StringInit(std::string _range) { @@ -32,6 +39,13 @@ namespace llarp return range; } + static bool + IsValidString(std::string _range) + { + IPRange range; + return (range.FromString(_range)); + } + static constexpr IPRange V4MappedRange() { diff --git a/llarp/rpc/json_binary_proxy.hpp b/llarp/rpc/json_binary_proxy.hpp index f0903a9b6..dc4800dfb 100644 --- a/llarp/rpc/json_binary_proxy.hpp +++ b/llarp/rpc/json_binary_proxy.hpp @@ -156,26 +156,3 @@ namespace llarp::rpc }; } // namespace llarp::rpc - -// Specializations of binary types for deserialization; when receiving these from json we expect -// them encoded in hex or base64. These may *not* be used for serialization, and will throw if so -// invoked; for serialization you need to use RPC_COMMAND::response_hex (or _b64) instead. -namespace nlohmann -{ - template - struct adl_serializer>> - { - static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); - - static void - to_json(const T&) - { - throw std::logic_error{"Internal error: binary types are not directly serializable"}; - } - static void - from_json(const json& j, T& val) - { - llarp::rpc::load_binary_parameter(j.get(), false /*no raw*/, val); - } - }; -} // namespace nlohmann diff --git a/llarp/rpc/json_conversions.cpp b/llarp/rpc/json_conversions.cpp new file mode 100644 index 000000000..d3e036468 --- /dev/null +++ b/llarp/rpc/json_conversions.cpp @@ -0,0 +1,18 @@ +#include "json_conversions.hpp" +#include + +namespace llarp +{ + void + to_json(nlohmann::json& j, const IPRange& ipr) + { + j = ipr.ToString(); + } + + void + from_json(const nlohmann::json& j, IPRange& ipr) + { + ipr = IPRange{j.get()}; + } + +} // namespace llarp diff --git a/llarp/rpc/json_conversions.hpp b/llarp/rpc/json_conversions.hpp new file mode 100644 index 000000000..95b9d547c --- /dev/null +++ b/llarp/rpc/json_conversions.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include "json_binary_proxy.hpp" + +namespace llarp +{ + void + to_json(nlohmann::json& j, const IPRange& ipr); + void + from_json(const nlohmann::json& j, IPRange& ipr); +} // namespace llarp + +namespace nlohmann +{ + // Specializations of binary types for deserialization; when receiving these from json we expect + // them encoded in hex or base64. These may *not* be used for serialization, and will throw if so + // invoked; for serialization you need to use RPC_COMMAND::response_hex (or _b64) instead. + template + struct adl_serializer>> + { + static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); + + static void + to_json(json&, const T&) + { + throw std::logic_error{"Internal error: binary types are not directly serializable"}; + } + static void + from_json(const json& j, T& val) + { + llarp::rpc::load_binary_parameter(j.get(), false /*no raw*/, val); + } + }; + +} // namespace nlohmann diff --git a/llarp/rpc/param_parser.hpp b/llarp/rpc/param_parser.hpp index 744ac0b22..64779979c 100644 --- a/llarp/rpc/param_parser.hpp +++ b/llarp/rpc/param_parser.hpp @@ -1,14 +1,17 @@ #pragma once #include "json_binary_proxy.hpp" +#include "json_bt.hpp" +#include "json_conversions.hpp" #include #include +#include +#include #include #include namespace llarp::rpc { - using json_range = std::pair; using rpc_input = std::variant; @@ -116,6 +119,12 @@ namespace llarp::rpc template constexpr bool is_expandable_list> = true; + // Types that are constructible from string + template + constexpr bool is_string_constructible = false; + template <> + inline constexpr bool is_string_constructible = true; + // Fixed size elements: tuples, pairs, and std::array's; we accept list input as long as the // list length matches exactly. template @@ -147,32 +156,34 @@ namespace llarp::rpc oxenc::bt_dict_consumer> || std::is_same_v, int> = 0> void - load_value(BTConsumer& c, T& val) + load_value(BTConsumer& c, T& target) { if constexpr (std::is_integral_v) - val = c.template consume_integer(); + target = c.template consume_integer(); else if constexpr (std::is_same_v || std::is_same_v) - val = c.consume_string_view(); + target = c.consume_string_view(); + else if constexpr (is_string_constructible) + target = T{c.consume_string()}; else if constexpr (llarp::rpc::json_is_binary) - llarp::rpc::load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); + llarp::rpc::load_binary_parameter(c.consume_string_view(), true /*allow raw*/, target); else if constexpr (is_expandable_list) { auto lc = c.consume_list_consumer(); - val.clear(); + target.clear(); while (!lc.is_finished()) - load_value(lc, val.emplace_back()); + load_value(lc, target.emplace_back()); } else if constexpr (is_tuple_like) { auto lc = c.consume_list_consumer(); - load_tuple_values(lc, val, std::make_index_sequence>{}); + load_tuple_values(lc, target, std::make_index_sequence>{}); } else if constexpr (is_unordered_string_map) { auto dc = c.consume_dict_consumer(); - val.clear(); + target.clear(); while (!dc.is_finished()) - load_value(dc, val[std::string{dc.key()}]); + load_value(dc, target[std::string{dc.key()}]); } else static_assert(std::is_same_v, "Unsupported load_value type"); @@ -182,21 +193,21 @@ namespace llarp::rpc // on unconvertible values. template void - load_value(json_range& r, T& val) + load_value(json_range& range_itr, T& target) { - auto& key = r.first.key(); - auto& e = *r.first; + auto& key = range_itr.first.key(); + auto& current = *range_itr.first; // value currently pointed to by range_itr.first if constexpr (std::is_same_v) { - if (e.is_boolean()) - val = e.get(); - else if (e.is_number_unsigned()) + if (current.is_boolean()) + target = current.get(); + else if (current.is_number_unsigned()) { // Also accept 0 or 1 for bools (mainly to be compatible with bt-encoding which doesn't // have a distinct bool type). - auto b = e.get(); + auto b = current.get(); if (b <= 1) - val = b; + target = b; else throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; } @@ -207,18 +218,18 @@ namespace llarp::rpc } else if constexpr (std::is_unsigned_v) { - if (!e.is_number_unsigned()) + if (!current.is_number_unsigned()) throw std::domain_error{"Invalid value for '" + key + "': non-negative value required"}; - auto i = e.get(); + auto i = current.get(); if (sizeof(T) < sizeof(uint64_t) && i > std::numeric_limits::max()) throw std::domain_error{"Invalid value for '" + key + "': value too large"}; - val = i; + target = i; } else if constexpr (std::is_integral_v) { - if (!e.is_number_integer()) + if (!current.is_number_integer()) throw std::domain_error{"Invalid value for '" + key + "': value is not an integer"}; - auto i = e.get(); + auto i = current.get(); if (sizeof(T) < sizeof(int64_t)) { if (i < std::numeric_limits::lowest()) @@ -227,11 +238,11 @@ namespace llarp::rpc if (i > std::numeric_limits::max()) throw std::domain_error{"Invalid value for '" + key + "': value is too large"}; } - val = i; + target = i; } else if constexpr (std::is_same_v || std::is_same_v) { - val = e.get(); + target = current.get(); } else if constexpr ( llarp::rpc::json_is_binary< @@ -239,7 +250,7 @@ namespace llarp::rpc { try { - e.get_to(val); + current.get_to(target); } catch (const std::exception& e) { @@ -250,7 +261,7 @@ namespace llarp::rpc { static_assert(std::is_same_v, "Unsupported load type"); } - ++r.first; + ++range_itr.first; } template @@ -355,5 +366,4 @@ namespace llarp::rpc } } } - } // namespace llarp::rpc \ No newline at end of file diff --git a/llarp/rpc/rpc_request.hpp b/llarp/rpc/rpc_request.hpp index 9c543aafe..847bb8856 100644 --- a/llarp/rpc/rpc_request.hpp +++ b/llarp/rpc/rpc_request.hpp @@ -14,7 +14,6 @@ namespace llarp::rpc { - using nlohmann::json; template diff --git a/llarp/rpc/rpc_request_definitions.hpp b/llarp/rpc/rpc_request_definitions.hpp index 0b7401840..cc40387a1 100644 --- a/llarp/rpc/rpc_request_definitions.hpp +++ b/llarp/rpc/rpc_request_definitions.hpp @@ -176,78 +176,16 @@ namespace llarp::rpc struct request_parameters { std::string address; - std::vector ip_range; + std::vector ip_range; std::string token; } request; - - void - onGoodResult(std::string reason, bool hasClient) - { - response = (hasClient) ? nlohmann::json{{"result", reason}}.dump() - : nlohmann::json{{"error", "We don't have an exit?"}}.dump(); - } - - void - onBadResult( - std::string reason, AbstractRouter& abs, llarp::service::Endpoint_ptr eptr, IPRange range) - { - abs.routePoker()->Down(); - eptr->UnmapExitRange(range); - response = nlohmann::json{{"result", reason}}.dump(); - } - - void - mapExit( - service::Address addr, - AbstractRouter& router, - llarp::service::Endpoint_ptr eptr, - IPRange range, - service::Address exitAddr) - { - eptr->MapExitRange(range, addr); - - bool sendAuth = (request.token.empty()) ? false : true; - if (sendAuth) - eptr->SetAuthInfoForEndpoint(exitAddr, service::AuthInfo{request.token}); - - if (addr.IsZero()) - { - onGoodResult("Null exit added", router.HasClientExit()); - return; - } - - eptr->MarkAddressOutbound(addr); - - eptr->EnsurePathToService(addr, [&](auto, service::OutboundContext* ctx) { - if (ctx == nullptr) - { - onBadResult("Could not find exit", router, eptr, range); - return; - } - if (not sendAuth) - { - onGoodResult("OK: connected to " + addr.ToString(), router.HasClientExit()); - return; - } - // only lambda that we will keep - ctx->AsyncSendAuth([&](service::AuthResult result) { - if (result.code != service::AuthResultCode::eAuthAccepted) - { - onBadResult(result.reason, router, eptr, range); - return; - } - onGoodResult(result.reason, router.HasClientExit()); - return; - }); - }); - } }; // RPC: list_exits // List all currently mapped exit node connections // // Inputs: none - // + // // Returns: // struct ListExits : NoArgs @@ -271,15 +209,13 @@ namespace llarp::rpc struct request_parameters { - std::vector ip_range; + std::vector ip_range; } request; }; // RPC: dns_query // Attempts to query endpoint by domain name // - // Note: ask Jason about the internals of this - // // Inputs: // "endpoint" : endpoint ID to query (string) // "qname" : query name (string) diff --git a/llarp/rpc/rpc_request_parser.cpp b/llarp/rpc/rpc_request_parser.cpp index 1a0732d77..4d961df3c 100644 --- a/llarp/rpc/rpc_request_parser.cpp +++ b/llarp/rpc/rpc_request_parser.cpp @@ -58,7 +58,7 @@ namespace llarp::rpc input, "address", mapexit.request.address, - "IP_range", + "ip_range", mapexit.request.ip_range, "token", mapexit.request.token); @@ -67,10 +67,7 @@ namespace llarp::rpc void parse_request(UnmapExit& unmapexit, rpc_input input) { - get_values( - input, - "IP_range", - unmapexit.request.ip_range); + get_values(input, "ip_range", unmapexit.request.ip_range); } void diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 40a4ae6af..e7254376e 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -91,11 +91,11 @@ namespace llarp::rpc register_rpc_command(std::unordered_map& regs) { static_assert(std::is_base_of_v); - auto cback = std::make_shared(); + rpc_callback cback{}; - cback->invoke = make_invoke(); + cback.invoke = make_invoke(); - regs.emplace(RPC::name, cback); + regs.emplace(RPC::name, std::move(cback)); } RPCServer::RPCServer(LMQ_ptr lmq, AbstractRouter& r) @@ -355,12 +355,17 @@ namespace llarp::rpc MapExit exit_request; // steal replier from exit RPC endpoint exit_request.replier.emplace(std::move(*mapexit.replier)); - - // - // - // - // - // + + m_Router.hiddenServiceContext().GetDefault()->map_exit( + mapexit.request.address, + mapexit.request.token, + mapexit.request.ip_range, + [exit = std::move(exit_request)](bool success, std::string result) mutable { + if (success) + exit.send_response({{"result"}, std::move(result)}); + else + exit.send_response({{"error"}, std::move(result)}); + }); } void @@ -369,8 +374,10 @@ namespace llarp::rpc if (not m_Router.hiddenServiceContext().hasEndpoints()) listexits.response = CreateJSONError("No mapped endpoints found"); else - listexits.response = CreateJSONResponse( - m_Router.hiddenServiceContext().GetDefault()->ExtractStatus()["m_ExitMap"]); + listexits.response = + CreateJSONResponse(m_Router.hiddenServiceContext().GetDefault()->ExtractStatus()["m_" + "ExitMa" + "p"]); } void @@ -382,22 +389,14 @@ namespace llarp::rpc return; } - std::vector range{}; - - for (auto& ip : unmapexit.request.ip_range) + try { - try { - range.push_back(IPRange::StringInit(ip)); - } catch (std::exception& e) { - unmapexit.response = CreateJSONError(e.what()); - } - } - - try { m_Router.routePoker()->Down(); - for (auto& ip : range) + for (auto& ip : unmapexit.request.ip_range) m_Router.hiddenServiceContext().GetDefault()->UnmapExitRange(ip); - } catch (std::exception& e) { + } + catch (std::exception& e) + { unmapexit.response = CreateJSONError("Unable to unmap to given range"); } @@ -439,16 +438,6 @@ namespace llarp::rpc return; } - /* - only have simple filename ex: "persist_key.ini" - create .ini files inside conf.d - - add delete functionality - delete parameter (bool) - use same filename parameter - - */ - void RPCServer::invoke(Config& config) { diff --git a/llarp/rpc/rpc_server.hpp b/llarp/rpc/rpc_server.hpp index dc07db30f..3d504673d 100644 --- a/llarp/rpc/rpc_server.hpp +++ b/llarp/rpc/rpc_server.hpp @@ -150,10 +150,6 @@ namespace llarp::rpc fmt::format("RPC request 'rpc.{}' raised an exception: {}", rpc.name, e.what())); } - // check if std::optional in rpc is present - // then rpc.send_response - // else - // do nothing because invoke stole RPC if (rpc.replier.has_value()) rpc.send_response(); } From b2e8cde64bc11c5f08fdf69fd6c518d6f12054ad Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 27 Jan 2023 15:08:43 -0800 Subject: [PATCH 6/7] working new endpoints - added hotswap functionality - map_exit and unmap_exit working --- contrib/omq-rpc.py | 4 +- daemon/lokinet-vpn.cpp | 51 +++++-- llarp/net/ip_range.hpp | 20 +-- llarp/net/ip_range_map.hpp | 1 + llarp/rpc/rpc_request.hpp | 14 +- llarp/rpc/rpc_request_decorators.hpp | 21 ++- llarp/rpc/rpc_request_definitions.hpp | 38 +++++- llarp/rpc/rpc_request_parser.cpp | 11 ++ llarp/rpc/rpc_request_parser.hpp | 2 + llarp/rpc/rpc_server.cpp | 187 ++++++++++++++++++-------- llarp/rpc/rpc_server.hpp | 27 ++-- llarp/service/endpoint.cpp | 21 +++ llarp/service/endpoint.hpp | 3 + 13 files changed, 289 insertions(+), 111 deletions(-) diff --git a/contrib/omq-rpc.py b/contrib/omq-rpc.py index 2a5faec35..2f2a8cded 100755 --- a/contrib/omq-rpc.py +++ b/contrib/omq-rpc.py @@ -95,5 +95,5 @@ else: socket.close(linger=0) sys.exit(1) - -# ./lmq-rpc.py ipc://$HOME/.oxen/testnet/oxend.sock 'llarp.get_service_nodes' | jq \ No newline at end of file +# sample usage: +# ./omq-rpc.py ipc://$HOME/.oxen/testnet/oxend.sock 'llarp.get_service_nodes' | jq diff --git a/daemon/lokinet-vpn.cpp b/daemon/lokinet-vpn.cpp index 26d2f47b2..0809ad620 100644 --- a/daemon/lokinet-vpn.cpp +++ b/daemon/lokinet-vpn.cpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include #include +#include "oxenmq/address.h" #ifdef _WIN32 // add the unholy windows headers for iphlpapi @@ -56,7 +58,6 @@ OMQ_Request( namespace { - struct command_line_options { // bool options @@ -64,6 +65,7 @@ namespace bool help = false; bool vpnUp = false; bool vpnDown = false; + bool swap = false; bool printStatus = false; bool killDaemon = false; @@ -73,9 +75,10 @@ namespace std::string endpoint = "default"; std::string token; std::optional range; + std::vector swapExits; // oxenmq - oxenmq::address rpcURL{"tcp://127.0.0.1:1190"}; + oxenmq::address rpcURL{}; oxenmq::LogLevel logLevel = oxenmq::LogLevel::warn; }; @@ -109,15 +112,23 @@ main(int argc, char* argv[]) // flags: boolean values in command_line_options struct cli.add_flag("-v,--verbose", options.verbose, "Verbose"); - cli.add_flag("--up", options.vpnUp, "Put VPN up"); - cli.add_flag("--down", options.vpnDown, "Put VPN down"); + cli.add_flag("--add,--up", options.vpnUp, "Map VPN connection to exit node [--up is deprecated]"); + cli.add_flag( + "--remove,--down", + options.vpnDown, + "Unmap VPN connection to exit node [--down is deprecated]"); cli.add_flag("--status", options.printStatus, "Print VPN status and exit"); cli.add_flag("-k,--kill", options.killDaemon, "Kill lokinet daemon"); // options: string values in command_line_options struct cli.add_option("--exit", options.exitAddress, "Specify exit node address")->capture_default_str(); cli.add_option("--endpoint", options.endpoint, "Endpoint to use")->capture_default_str(); - cli.add_option("--token", options.token, "Exit auth token to use")->capture_default_str(); + cli.add_option("--token,--auth", options.token, "Exit auth token to use")->capture_default_str(); + cli.add_option("--range", options.range, "IP range to map exit to")->capture_default_str(); + cli.add_option( + "--swap", options.swapExits, "Exit addresses to swap mapped connection to [old] [new]") + ->expected(2) + ->capture_default_str(); // options: oxenmq values in command_line_options struct cli.add_option("--rpc", options.rpc, "Specify RPC URL for lokinet")->capture_default_str(); @@ -149,16 +160,17 @@ main(int argc, char* argv[]) cli.exit(e); }; - int numCommands = options.vpnUp + options.vpnDown + options.printStatus + options.killDaemon; + int numCommands = options.vpnUp + options.vpnDown + options.printStatus + options.killDaemon + + (not options.swapExits.empty()); switch (numCommands) { case 0: - return exit_error(3, "One of --up/--down/--status/--kill must be specified"); + return exit_error(3, "One of --add/--remove/--swap/--status/--kill must be specified"); case 1: break; default: - return exit_error(3, "Only one of --up/--down/--status/--kill may be specified"); + return exit_error(3, "Only one of --add/--remove/--swap/--status/--kill may be specified"); } if (options.vpnUp and options.exitAddress.empty()) @@ -170,12 +182,14 @@ main(int argc, char* argv[]) }, options.logLevel}; + options.rpcURL = oxenmq::address{(options.rpc.empty()) ? "tcp://127.0.0.1:1190" : options.rpc}; + omq.start(); std::promise connectPromise; const auto connectionID = omq.connect_remote( - options.rpc, + options.rpcURL, [&connectPromise](auto) { connectPromise.set_value(true); }, [&connectPromise](auto, std::string_view msg) { std::cout << "Failed to connect to lokinet RPC: " << msg << std::endl; @@ -201,15 +215,15 @@ main(int argc, char* argv[]) try { - const auto& ep = maybe_status->at("result").at("services").at(options.endpoint); - const auto exitMap = ep.at("exitMap"); - if (exitMap.empty()) + const auto& ep = maybe_status->at("result").at("services").at(options.endpoint).at("exitMap"); + + if (ep.empty()) { std::cout << "no exits" << std::endl; } else { - for (const auto& [range, exit] : exitMap.items()) + for (const auto& [range, exit] : ep.items()) { std::cout << range << " via " << exit.get() << std::endl; } @@ -221,6 +235,15 @@ main(int argc, char* argv[]) } return 0; } + + if (not options.swapExits.empty()) + { + nlohmann::json opts{{"exit_addresses", std::move(options.swapExits)}}; + + if (not OMQ_Request(omq, connectionID, "llarp.swap_exits", std::move(opts))) + return exit_error("Failed to swap exit node connections"); + } + if (options.vpnUp) { nlohmann::json opts{{"address", options.exitAddress}, {"token", options.token}}; @@ -244,7 +267,7 @@ main(int argc, char* argv[]) if (options.range) opts["ip_range"] = *options.range; if (not OMQ_Request(omq, connectionID, "llarp.unmap_exit", std::move(opts))) - return exit_error("failed to unmap exit"); + return exit_error("Failed to unmap exit node connection"); } return 0; diff --git a/llarp/net/ip_range.hpp b/llarp/net/ip_range.hpp index d7045bb74..f085048b2 100644 --- a/llarp/net/ip_range.hpp +++ b/llarp/net/ip_range.hpp @@ -31,21 +31,6 @@ namespace llarp throw std::invalid_argument{"IP string '{}' cannot be parsed as IP range"_format(_range)}; } - static IPRange - StringInit(std::string _range) - { - IPRange range{}; - range.FromString(_range); - return range; - } - - static bool - IsValidString(std::string _range) - { - IPRange range; - return (range.FromString(_range)); - } - static constexpr IPRange V4MappedRange() { @@ -62,7 +47,8 @@ namespace llarp FromIPv4(net::ipv4addr_t addr, net::ipv4addr_t netmask) { return IPRange{ - net::ExpandV4(ToHost(addr)), netmask_ipv6_bits(bits::count_bits(netmask) + 96)}; + net::ExpandV4(llarp::net::ToHost(addr)), + netmask_ipv6_bits(bits::count_bits(netmask) + 96)}; } /// return true if this iprange is in the IPv4 mapping range for containing ipv4 addresses @@ -125,7 +111,7 @@ namespace llarp inline bool Contains(const net::ipaddr_t& ip) const { - return var::visit([this](auto&& ip) { return Contains(ToHost(ip)); }, ip); + return var::visit([this](auto&& ip) { return Contains(llarp::net::ToHost(ip)); }, ip); } /// get the highest address on this range diff --git a/llarp/net/ip_range_map.hpp b/llarp/net/ip_range_map.hpp index bf659ae78..d38bcf83e 100644 --- a/llarp/net/ip_range_map.hpp +++ b/llarp/net/ip_range_map.hpp @@ -2,6 +2,7 @@ #include "ip_range.hpp" #include +#include #include namespace llarp diff --git a/llarp/rpc/rpc_request.hpp b/llarp/rpc/rpc_request.hpp index 847bb8856..caf50d9f3 100644 --- a/llarp/rpc/rpc_request.hpp +++ b/llarp/rpc/rpc_request.hpp @@ -25,10 +25,14 @@ namespace llarp::rpc auto& rpc = handler.rpc; if (m.data.size() > 1) - m.send_reply(CreateJSONError( - "Bad Request: RPC requests must have at most one data part (received {})"_format( - m.data.size()))); - + { + m.send_reply(nlohmann::json{ + {"error", + "Bad Request: RPC requests must have at most one data part (received {})"_format( + m.data.size())}} + .dump()); + return; + } // parsing input as bt or json // hand off to parse_request (overloaded versions) try @@ -49,7 +53,7 @@ namespace llarp::rpc } catch (const std::exception& e) { - m.send_reply(CreateJSONError("Failed to parse request parameters: "s + e.what())); + m.send_reply(nlohmann::json{{"Failed to parse request parameters: "s + e.what()}}.dump()); return; } diff --git a/llarp/rpc/rpc_request_decorators.hpp b/llarp/rpc/rpc_request_decorators.hpp index ace72d784..5973c06dc 100644 --- a/llarp/rpc/rpc_request_decorators.hpp +++ b/llarp/rpc/rpc_request_decorators.hpp @@ -95,12 +95,23 @@ namespace llarp::rpc llarp::rpc::json_binary_proxy response_b64{ response, llarp::rpc::json_binary_proxy::fmt::base64}; - // The oxenmq deferred send object into which the response will be set. If this optional is - // still set when the `invoke` call returns then the response is sent at that point; if it has - // been moved out (i.e. either just this instance or the whole request struct is stolen/moved - // by the invoke function) then it is the invoke function's job to send a reply. Typically - // this is done when a response cannot be sent immediately + // The oxenmq deferred send object into which the response will be sent when the `invoke` + // method returns. If the response needs to happen later (i.e. not immediately after `invoke` + // returns) then you should call `defer()` to extract and clear this and then send the response + // via the returned DeferredSend object yourself. std::optional replier; + + // Called to clear the current replier and return it. After this call the automatic reply will + // not be generated; the caller is responsible for calling `->reply` on the returned optional + // itself. This is typically used where a call has to be deferred, for example because it + // depends on some network response to build the reply. + oxenmq::Message::DeferredSend + move() + { + auto r{std::move(*replier)}; + replier.reset(); + return r; + } }; // Tag types that are inherited to set RPC endpoint properties diff --git a/llarp/rpc/rpc_request_definitions.hpp b/llarp/rpc/rpc_request_definitions.hpp index cc40387a1..565f34572 100644 --- a/llarp/rpc/rpc_request_definitions.hpp +++ b/llarp/rpc/rpc_request_definitions.hpp @@ -110,7 +110,7 @@ namespace llarp::rpc } request; }; - // RPC: quick_listener + // RPC: quic_listener // Connects to QUIC interface on local endpoint // Passes request parameters in nlohmann::json format // @@ -171,6 +171,14 @@ namespace llarp::rpc // struct MapExit : RPCRequest { + MapExit() + { + if constexpr (platform::supports_ipv6) + request.ip_range.emplace_back("::/0"); + else + request.ip_range.emplace_back("0.0.0.0/0"); + } + static constexpr auto name = "map_exit"sv; struct request_parameters @@ -205,6 +213,14 @@ namespace llarp::rpc // struct UnmapExit : RPCRequest { + UnmapExit() + { + if constexpr (platform::supports_ipv6) + request.ip_range.emplace_back("::/0"); + else + request.ip_range.emplace_back("0.0.0.0/0"); + } + static constexpr auto name = "unmap_exit"sv; struct request_parameters @@ -213,6 +229,25 @@ namespace llarp::rpc } request; }; + // RPC: swap_exit + // Swap a connection from one exit to another + // + // Inputs: + // "exits" : exit nodes to swap mappings from (index 0 = old exit, index 1 = new exit) + // + // Returns: + // + struct SwapExits : RPCRequest + { + static constexpr auto name = "swap_exits"sv; + + struct request_parameters + { + std::vector exit_addresses; + std::string token; + } request; + }; + // RPC: dns_query // Attempts to query endpoint by domain name // @@ -268,6 +303,7 @@ namespace llarp::rpc LookupSnode, MapExit, ListExits, + SwapExits, UnmapExit, DNSQuery, Config>; diff --git a/llarp/rpc/rpc_request_parser.cpp b/llarp/rpc/rpc_request_parser.cpp index 4d961df3c..53eee05ae 100644 --- a/llarp/rpc/rpc_request_parser.cpp +++ b/llarp/rpc/rpc_request_parser.cpp @@ -70,6 +70,17 @@ namespace llarp::rpc get_values(input, "ip_range", unmapexit.request.ip_range); } + void + parse_request(SwapExits& swapexits, rpc_input input) + { + get_values( + input, + "exit_addresses", + swapexits.request.exit_addresses, + "token", + swapexits.request.token); + } + void parse_request(DNSQuery& dnsquery, rpc_input input) { diff --git a/llarp/rpc/rpc_request_parser.hpp b/llarp/rpc/rpc_request_parser.hpp index ac1e0ee71..e28c12855 100644 --- a/llarp/rpc/rpc_request_parser.hpp +++ b/llarp/rpc/rpc_request_parser.hpp @@ -26,6 +26,8 @@ namespace llarp::rpc void parse_request(UnmapExit& unmapexit, rpc_input input); void + parse_request(SwapExits& swapexits, rpc_input input); + void parse_request(DNSQuery& dnsquery, rpc_input input); void parse_request(Config& config, rpc_input input); diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index e7254376e..f0bbd78cd 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -2,6 +2,7 @@ #include "llarp/rpc/rpc_request_definitions.hpp" #include "rpc_request.hpp" #include "llarp/service/address.hpp" +#include #include #include #include @@ -147,10 +148,10 @@ namespace llarp::rpc { if (not m_Router.IsRunning()) { - halt.response = CreateJSONError("Router is not running"); + SetJSONError("Router is not running", halt.response); return; } - halt.response = CreateJSONResponse("OK"); + SetJSONResponse("OK", halt.response); m_Router.Stop(); } @@ -160,20 +161,20 @@ namespace llarp::rpc util::StatusObject result{ {"version", llarp::VERSION_FULL}, {"uptime", to_json(m_Router.Uptime())}}; - version.response = CreateJSONResponse(result); + SetJSONResponse(result, version.response); } void RPCServer::invoke(Status& status) { - status.response = (m_Router.IsRunning()) ? CreateJSONResponse(m_Router.ExtractStatus()) - : CreateJSONError("Router is not yet ready"); + (m_Router.IsRunning()) ? SetJSONResponse(m_Router.ExtractStatus(), status.response) + : SetJSONError("Router is not yet ready", status.response); } void RPCServer::invoke(GetStatus& getstatus) { - getstatus.response = CreateJSONResponse(m_Router.ExtractSummaryStatus()); + SetJSONResponse(m_Router.ExtractSummaryStatus(), getstatus.response); } void @@ -181,13 +182,13 @@ namespace llarp::rpc { if (quicconnect.request.port == 0 and quicconnect.request.closeID == 0) { - quicconnect.response = CreateJSONError("Port not provided"); + SetJSONError("Port not provided", quicconnect.response); return; } if (quicconnect.request.remoteHost.empty() and quicconnect.request.closeID == 0) { - quicconnect.response = CreateJSONError("Host not provided"); + SetJSONError("Host not provided", quicconnect.response); return; } @@ -197,7 +198,7 @@ namespace llarp::rpc if (not endpoint) { - quicconnect.response = CreateJSONError("No such local endpoint found."); + SetJSONError("No such local endpoint found.", quicconnect.response); return; } @@ -205,15 +206,16 @@ namespace llarp::rpc if (not quic) { - quicconnect.response = CreateJSONError( - "No quic interface available on endpoint " + quicconnect.request.endpoint); + SetJSONError( + "No quic interface available on endpoint " + quicconnect.request.endpoint, + quicconnect.response); return; } if (quicconnect.request.closeID) { quic->forget(quicconnect.request.closeID); - quicconnect.response = CreateJSONResponse("OK"); + SetJSONResponse("OK", quicconnect.response); return; } @@ -228,11 +230,11 @@ namespace llarp::rpc status["addr"] = addr.ToString(); status["id"] = id; - quicconnect.response = CreateJSONResponse(status); + SetJSONResponse(status, quicconnect.response); } catch (std::exception& e) { - quicconnect.response = CreateJSONError(e.what()); + SetJSONError(e.what(), quicconnect.response); } } @@ -241,7 +243,7 @@ namespace llarp::rpc { if (quiclistener.request.port == 0 and quiclistener.request.closeID == 0) { - quiclistener.response = CreateJSONError("Invalid arguments"); + SetJSONError("Invalid arguments", quiclistener.response); return; } @@ -251,7 +253,7 @@ namespace llarp::rpc if (not endpoint) { - quiclistener.response = CreateJSONError("No such local endpoint found"); + SetJSONError("No such local endpoint found", quiclistener.response); return; } @@ -259,15 +261,16 @@ namespace llarp::rpc if (not quic) { - quiclistener.response = CreateJSONError( - "No quic interface available on endpoint " + quiclistener.request.endpoint); + SetJSONError( + "No quic interface available on endpoint " + quiclistener.request.endpoint, + quiclistener.response); return; } if (quiclistener.request.closeID) { quic->forget(quiclistener.request.closeID); - quiclistener.response = CreateJSONResponse("OK"); + SetJSONResponse("OK", quiclistener.response); return; } @@ -281,7 +284,7 @@ namespace llarp::rpc } catch (std::exception& e) { - quiclistener.response = CreateJSONError(e.what()); + SetJSONError(e.what(), quiclistener.response); return; } @@ -298,30 +301,31 @@ namespace llarp::rpc endpoint->PutSRVRecord(std::move(srvData)); } - quiclistener.response = CreateJSONResponse(result); + SetJSONResponse(result, quiclistener.response); return; } } + // TODO: fix this because it's bad void RPCServer::invoke(LookupSnode& lookupsnode) { if (not m_Router.IsServiceNode()) { - lookupsnode.response = CreateJSONError("Not supported"); + SetJSONError("Not supported", lookupsnode.response); return; } RouterID routerID; if (lookupsnode.request.routerID.empty()) { - lookupsnode.response = CreateJSONError("No remote ID provided"); + SetJSONError("No remote ID provided", lookupsnode.response); return; } if (not routerID.FromString(lookupsnode.request.routerID)) { - lookupsnode.response = CreateJSONError("Invalid remote: " + lookupsnode.request.routerID); + SetJSONError("Invalid remote: " + lookupsnode.request.routerID, lookupsnode.response); return; } @@ -330,7 +334,7 @@ namespace llarp::rpc if (endpoint == nullptr) { - lookupsnode.response = CreateJSONError("Cannot find local endpoint: default"); + SetJSONError("Cannot find local endpoint: default", lookupsnode.response); return; } @@ -339,11 +343,11 @@ namespace llarp::rpc { const auto ip = net::TruncateV6(endpoint->GetIPForIdent(PubKey{routerID})); util::StatusObject status{{"ip", ip.ToString()}}; - lookupsnode.response = CreateJSONResponse(status); + SetJSONResponse(status, lookupsnode.response); return; } - lookupsnode.response = CreateJSONError("Failed to obtain snode session"); + SetJSONError("Failed to obtain snode session", lookupsnode.response); return; }); }); @@ -353,8 +357,8 @@ namespace llarp::rpc RPCServer::invoke(MapExit& mapexit) { MapExit exit_request; - // steal replier from exit RPC endpoint - exit_request.replier.emplace(std::move(*mapexit.replier)); + // steal replier from exit RPC endpoint + exit_request.replier.emplace(mapexit.move()); m_Router.hiddenServiceContext().GetDefault()->map_exit( mapexit.request.address, @@ -372,35 +376,104 @@ namespace llarp::rpc RPCServer::invoke(ListExits& listexits) { if (not m_Router.hiddenServiceContext().hasEndpoints()) - listexits.response = CreateJSONError("No mapped endpoints found"); - else - listexits.response = - CreateJSONResponse(m_Router.hiddenServiceContext().GetDefault()->ExtractStatus()["m_" - "ExitMa" - "p"]); + { + SetJSONError("No mapped endpoints found", listexits.response); + return; + } + + auto status = m_Router.hiddenServiceContext().GetDefault()->ExtractStatus()["exitMap"]; + + SetJSONResponse((status.empty()) ? "No exits" : status, listexits.response); } void RPCServer::invoke(UnmapExit& unmapexit) { - if (unmapexit.request.ip_range.empty()) - { - unmapexit.response = CreateJSONError("No IP range provided"); - return; - } - try { - m_Router.routePoker()->Down(); for (auto& ip : unmapexit.request.ip_range) m_Router.hiddenServiceContext().GetDefault()->UnmapExitRange(ip); } catch (std::exception& e) { - unmapexit.response = CreateJSONError("Unable to unmap to given range"); + SetJSONError("Unable to unmap to given range", unmapexit.response); + return; + } + + SetJSONResponse("OK", unmapexit.response); + } + + // Sequentially calls map_exit and unmap_exit to hotswap mapped connection from old exit + // to new exit. Similar to how map_exit steals the oxenmq deferredsend object, swapexit + // moves the replier object to the unmap_exit struct, as that is called second. Rather than + // the nested lambda within map_exit making the reply call, it instead calls the unmap_exit logic + // and leaves the message handling to the unmap_exit struct + void + RPCServer::invoke(SwapExits& swapexits) + { + MapExit map_request; + UnmapExit unmap_request; + auto endpoint = m_Router.hiddenServiceContext().GetDefault(); + auto current_exits = endpoint->ExtractStatus()["exitMap"]; + + if (current_exits.empty()) + { + SetJSONError("Cannot swap to new exit: no exits currently mapped", swapexits.response); + return; } - unmapexit.response = CreateJSONResponse("OK"); + // steal replier from swapexit RPC endpoint + unmap_request.replier.emplace(swapexits.move()); + + // set map_exit request to new address + map_request.request.address = swapexits.request.exit_addresses[1]; + + // set token for new exit node mapping + if (not swapexits.request.token.empty()) + map_request.request.token = swapexits.request.token; + + // populate map_exit request with old IP ranges + for (auto& [range, exit] : current_exits.items()) + { + if (exit.get() == swapexits.request.exit_addresses[0]) + { + map_request.request.ip_range.emplace_back(range); + unmap_request.request.ip_range.emplace_back(range); + } + } + + if (map_request.request.ip_range.empty() or unmap_request.request.ip_range.empty()) + { + SetJSONError("No mapped ranges found matching requested swap", swapexits.response); + return; + } + + endpoint->map_exit( + map_request.request.address, + map_request.request.token, + map_request.request.ip_range, + [unmap = std::move(unmap_request), + ep = endpoint, + old_exit = swapexits.request.exit_addresses[0]](bool success, std::string result) mutable { + if (not success) + unmap.send_response({{"error"}, std::move(result)}); + else + { + try + { + for (auto& ip : unmap.request.ip_range) + ep->UnmapRangeByExit(ip, old_exit); + } + catch (std::exception& e) + { + SetJSONError("Unable to unmap to given range", unmap.response); + return; + } + + SetJSONResponse("OK", unmap.response); + unmap.send_response(); + } + }); } void @@ -417,7 +490,7 @@ namespace llarp::rpc if (endpoint == nullptr) { - dnsquery.response = CreateJSONError("No such endpoint found for dns query"); + SetJSONError("No such endpoint found for dns query", dnsquery.response); return; } @@ -425,16 +498,16 @@ namespace llarp::rpc { auto packet_src = std::make_shared([&](auto result) { if (result) - dnsquery.response = CreateJSONResponse(result->ToJSON()); + SetJSONResponse(result->ToJSON(), dnsquery.response); else - dnsquery.response = CreateJSONError("No response from DNS"); + SetJSONError("No response from DNS", dnsquery.response); }); if (not dns->MaybeHandlePacket( packet_src, packet_src->dumb, packet_src->dumb, msg.ToBuffer())) - dnsquery.response = CreateJSONError("DNS query not accepted by endpoint"); + SetJSONError("DNS query not accepted by endpoint", dnsquery.response); } else - dnsquery.response = CreateJSONError("Endpoint does not have dns"); + SetJSONError("Endpoint does not have dns", dnsquery.response); return; } @@ -443,24 +516,24 @@ namespace llarp::rpc { if (config.request.filename.empty() and not config.request.ini.empty()) { - config.response = CreateJSONError("No filename specified for .ini file"); + SetJSONError("No filename specified for .ini file", config.response); return; } if (config.request.ini.empty() and not config.request.filename.empty()) { - config.response = CreateJSONError("No .ini chunk provided"); + SetJSONError("No .ini chunk provided", config.response); return; } if (not ends_with(config.request.filename, ".ini")) { - config.response = CreateJSONError("Must append '.ini' to filename"); + SetJSONError("Must append '.ini' to filename", config.response); return; } if (not check_path(config.request.filename)) { - config.response = CreateJSONError("Bad filename passed"); + SetJSONError("Bad filename passed", config.response); return; } @@ -475,7 +548,7 @@ namespace llarp::rpc } catch (std::exception& e) { - config.response = CreateJSONError(e.what()); + SetJSONError(e.what(), config.response); return; } } @@ -496,12 +569,12 @@ namespace llarp::rpc } catch (std::exception& e) { - config.response = CreateJSONError(e.what()); + SetJSONError(e.what(), config.response); return; } } - config.response = CreateJSONResponse("OK"); + SetJSONResponse("OK", config.response); } void @@ -538,4 +611,4 @@ namespace llarp::rpc } } -} // namespace llarp::rpc \ No newline at end of file +} // namespace llarp::rpc diff --git a/llarp/rpc/rpc_server.hpp b/llarp/rpc/rpc_server.hpp index 3d504673d..bda5d6888 100644 --- a/llarp/rpc/rpc_server.hpp +++ b/llarp/rpc/rpc_server.hpp @@ -2,6 +2,8 @@ #include "rpc_request_definitions.hpp" #include "json_bt.hpp" +#include +#include #include #include #include @@ -64,16 +66,16 @@ namespace llarp::rpc }; template - std::string - CreateJSONResponse(Result_t result) + void + SetJSONResponse(Result_t result, json& j) { - return nlohmann::json{{"result", result}}.dump(); + j["result"] = result; } - inline std::string - CreateJSONError(std::string_view msg) + inline void + SetJSONError(std::string_view msg, json& j) { - return nlohmann::json{{"error", msg}}.dump(); + j["error"] = msg; } class RPCServer @@ -109,6 +111,8 @@ namespace llarp::rpc void invoke(UnmapExit& unmapexit); void + invoke(SwapExits& swapexits); + void invoke(DNSQuery& dnsquery); void invoke(Config& config); @@ -140,18 +144,21 @@ namespace llarp::rpc catch (const rpc_error& e) { log::info(logcat, "RPC request 'rpc.{}' failed with: {}", rpc.name, e.what()); - rpc.response = CreateJSONError( - fmt::format("RPC request 'rpc.{}' failed with: {}", rpc.name, e.what())); + SetJSONError( + fmt::format("RPC request 'rpc.{}' failed with: {}", rpc.name, e.what()), rpc.response); } catch (const std::exception& e) { log::info(logcat, "RPC request 'rpc.{}' raised an exception: {}", rpc.name, e.what()); - rpc.response = CreateJSONError( - fmt::format("RPC request 'rpc.{}' raised an exception: {}", rpc.name, e.what())); + SetJSONError( + fmt::format("RPC request 'rpc.{}' raised an exception: {}", rpc.name, e.what()), + rpc.response); } if (rpc.replier.has_value()) + { rpc.send_response(); + } } }; diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index 8bb148589..89c24bc6b 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -3,6 +3,7 @@ #include "endpoint_util.hpp" #include "hidden_service_address_lookup.hpp" #include "auth.hpp" +#include "llarp/util/logging.hpp" #include "outbound_context.hpp" #include "protocol.hpp" #include "info.hpp" @@ -2188,6 +2189,26 @@ namespace llarp LogInfo(Name(), " unmap ", item.first, " exit range mapping"); return true; }); + + if (m_ExitMap.Empty()) + m_router->routePoker()->Down(); + } + + void + Endpoint::UnmapRangeByExit(IPRange range, std::string exit) + { + // unmap all ranges that match the given exit when hot swapping + m_ExitMap.RemoveIf([&](const auto& item) -> bool { + if ((range.Contains(item.first)) and (item.second.ToString() == exit)) + { + log::info(logcat, "{} unmap {} range mapping to exit node {}", Name(), item.first, exit); + return true; + } + return false; + }); + + if (m_ExitMap.Empty()) + m_router->routePoker()->Down(); } std::optional diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index 74de81355..46fd42e7b 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -284,6 +284,9 @@ namespace llarp void UnmapExitRange(IPRange range); + void + UnmapRangeByExit(IPRange range, std::string exit); + void map_exit( std::string name, From 9bfe881a352f48816a16e82726075fd6fda0b075 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 1 Feb 2023 14:26:58 -0800 Subject: [PATCH 7/7] OMQ_Request handling logic change --- daemon/lokinet-vpn.cpp | 41 ++++++++++++++++++++++++++++++++-------- llarp/rpc/rpc_server.cpp | 6 ++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/daemon/lokinet-vpn.cpp b/daemon/lokinet-vpn.cpp index 0809ad620..083ba8b91 100644 --- a/daemon/lokinet-vpn.cpp +++ b/daemon/lokinet-vpn.cpp @@ -202,16 +202,24 @@ main(int argc, char* argv[]) if (options.killDaemon) { - if (not OMQ_Request(omq, connectionID, "llarp.halt")) + auto maybe_halt = OMQ_Request(omq, connectionID, "llarp.halt"); + + if (not maybe_halt) return exit_error("Call to llarp.halt failed"); - return 0; + + if (auto err_it = maybe_halt->find("error"); + err_it != maybe_halt->end() and not err_it.value().is_null()) + { + return exit_error("{}", err_it.value()); + } } if (options.printStatus) { const auto maybe_status = OMQ_Request(omq, connectionID, "llarp.status"); + if (not maybe_status) - return exit_error("call to llarp.status failed"); + return exit_error("Call to llarp.status failed"); try { @@ -219,7 +227,7 @@ main(int argc, char* argv[]) if (ep.empty()) { - std::cout << "no exits" << std::endl; + std::cout << "No exits found" << std::endl; } else { @@ -231,7 +239,7 @@ main(int argc, char* argv[]) } catch (std::exception& ex) { - return exit_error("failed to parse result: {}", ex.what()); + return exit_error("Failed to parse result: {}", ex.what()); } return 0; } @@ -240,8 +248,16 @@ main(int argc, char* argv[]) { nlohmann::json opts{{"exit_addresses", std::move(options.swapExits)}}; - if (not OMQ_Request(omq, connectionID, "llarp.swap_exits", std::move(opts))) + auto maybe_swap = OMQ_Request(omq, connectionID, "llarp.swap_exits", std::move(opts)); + + if (not maybe_swap) return exit_error("Failed to swap exit node connections"); + + if (auto err_it = maybe_swap->find("error"); + err_it != maybe_swap->end() and not err_it.value().is_null()) + { + return exit_error("{}", err_it.value()); + } } if (options.vpnUp) @@ -253,7 +269,7 @@ main(int argc, char* argv[]) auto maybe_result = OMQ_Request(omq, connectionID, "llarp.map_exit", std::move(opts)); if (not maybe_result) - return exit_error("could not add exit"); + return exit_error("Could not add exit"); if (auto err_it = maybe_result->find("error"); err_it != maybe_result->end() and not err_it.value().is_null()) @@ -266,8 +282,17 @@ main(int argc, char* argv[]) nlohmann::json opts{{"unmap_exit", true}}; if (options.range) opts["ip_range"] = *options.range; - if (not OMQ_Request(omq, connectionID, "llarp.unmap_exit", std::move(opts))) + + auto maybe_down = OMQ_Request(omq, connectionID, "llarp.unmap_exit", std::move(opts)); + + if (not maybe_down) return exit_error("Failed to unmap exit node connection"); + + if (auto err_it = maybe_down->find("error"); + err_it != maybe_down->end() and not err_it.value().is_null()) + { + return exit_error("{}", err_it.value()); + } } return 0; diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index f0bbd78cd..746557f63 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -422,6 +422,12 @@ namespace llarp::rpc return; } + if (swapexits.request.exit_addresses.size() < 2) + { + SetJSONError("Exit addresses not passed", swapexits.response); + return; + } + // steal replier from swapexit RPC endpoint unmap_request.replier.emplace(swapexits.move());