From b2e8cde64bc11c5f08fdf69fd6c518d6f12054ad Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 27 Jan 2023 15:08:43 -0800 Subject: [PATCH] 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,