#include "rpc_server.hpp" #include "rpc_request.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace llarp::rpc { // Fake packet source that serializes repsonses back into dns class DummyPacketSource : public dns::PacketSource_Base { std::function)> func; public: SockAddr dumb; template DummyPacketSource(Callable&& f) : func{std::forward(f)} {} bool WouldLoop(const SockAddr&, const SockAddr&) const override { return false; }; /// send packet with src and dst address containing buf on this packet source void SendTo(const SockAddr&, const SockAddr&, OwnedBuffer buf) const override { func(dns::MaybeParseDNSMessage(buf)); } /// stop reading packets and end operation void Stop() override{}; /// returns the sockaddr we are bound on if applicable std::optional BoundOn() const override { return std::nullopt; } }; 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(Router& r, std::string name) { if (r.is_service_node()) { return r.exitContext().get_exit_endpoint(name); } return r.hidden_service_context().GetEndpointByName(name); } template void register_rpc_command(std::unordered_map& regs) { static_assert(std::is_base_of_v); rpc_callback cback{}; cback.invoke = make_invoke(); regs.emplace(RPC::name, std::move(cback)); } RPCServer::RPCServer(LMQ_ptr lmq, Router& 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.config()->api.rpc_bind_addrs) { 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()) { SetJSONError("Router is not running", halt.response); return; } SetJSONResponse("OK", halt.response); m_Router.Stop(); } void RPCServer::invoke(Version& version) { util::StatusObject result{ {"version", llarp::LOKINET_VERSION_FULL}, {"uptime", to_json(m_Router.Uptime())}}; SetJSONResponse(result, version.response); } void RPCServer::invoke(Status& status) { (m_Router.IsRunning()) ? SetJSONResponse(m_Router.ExtractStatus(), status.response) : SetJSONError("Router is not yet ready", status.response); } void RPCServer::invoke(GetStatus& getstatus) { SetJSONResponse(m_Router.ExtractSummaryStatus(), getstatus.response); } void RPCServer::invoke(QuicConnect& quicconnect) { if (quicconnect.request.port == 0 and quicconnect.request.closeID == 0) { SetJSONError("Port not provided", quicconnect.response); return; } if (quicconnect.request.remoteHost.empty() and quicconnect.request.closeID == 0) { SetJSONError("Host not provided", quicconnect.response); return; } auto endpoint = (quicconnect.request.endpoint.empty()) ? GetEndpointByName(m_Router, "default") : GetEndpointByName(m_Router, quicconnect.request.endpoint); if (not endpoint) { SetJSONError("No such local endpoint found.", quicconnect.response); return; } auto quic = endpoint->GetQUICTunnel(); if (not quic) { SetJSONError( "No quic interface available on endpoint " + quicconnect.request.endpoint, quicconnect.response); return; } if (quicconnect.request.closeID) { // TODO: // quic->forget(quicconnect.request.closeID); SetJSONResponse("OK", quicconnect.response); return; } SockAddr laddr{quicconnect.request.bindAddr}; try { // TODO: // auto [addr, id] = quic->open( // quicconnect.request.remoteHost, quicconnect.request.port, [](auto&&) {}, laddr); util::StatusObject status; // status["addr"] = addr.ToString(); // status["id"] = id; SetJSONResponse(status, quicconnect.response); } catch (std::exception& e) { SetJSONError(e.what(), quicconnect.response); } } void RPCServer::invoke(QuicListener& quiclistener) { if (quiclistener.request.port == 0 and quiclistener.request.closeID == 0) { SetJSONError("Invalid arguments", quiclistener.response); return; } auto endpoint = (quiclistener.request.endpoint.empty()) ? GetEndpointByName(m_Router, "default") : GetEndpointByName(m_Router, quiclistener.request.endpoint); if (not endpoint) { SetJSONError("No such local endpoint found", quiclistener.response); return; } auto quic = endpoint->GetQUICTunnel(); if (not quic) { SetJSONError( "No quic interface available on endpoint " + quiclistener.request.endpoint, quiclistener.response); return; } if (quiclistener.request.closeID) { // TODO: // quic->forget(quiclistener.request.closeID); SetJSONResponse("OK", quiclistener.response); return; } if (quiclistener.request.port) { auto id = 0; try { SockAddr addr{quiclistener.request.remoteHost, huint16_t{quiclistener.request.port}}; // TODO: // id = quic->listen(addr); } catch (std::exception& e) { SetJSONError(e.what(), quiclistener.response); 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)); } SetJSONResponse(result, quiclistener.response); return; } } // TODO: fix this because it's bad void RPCServer::invoke(LookupSnode& lookupsnode) { if (not m_Router.is_service_node()) { SetJSONError("Not supported", lookupsnode.response); return; } RouterID routerID; if (lookupsnode.request.routerID.empty()) { SetJSONError("No remote ID provided", lookupsnode.response); return; } if (not routerID.from_string(lookupsnode.request.routerID)) { SetJSONError("Invalid remote: " + lookupsnode.request.routerID, lookupsnode.response); return; } m_Router.loop()->call([&]() { auto endpoint = m_Router.exitContext().get_exit_endpoint("default"); if (endpoint == nullptr) { SetJSONError("Cannot find local endpoint: default", lookupsnode.response); 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()}}; SetJSONResponse(status, lookupsnode.response); return; } SetJSONError("Failed to obtain snode session", lookupsnode.response); return; }); }); } void RPCServer::invoke(MapExit& mapexit) { MapExit exit_request; // steal replier from exit RPC endpoint exit_request.replier.emplace(mapexit.move()); m_Router.hidden_service_context().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 RPCServer::invoke(ListExits& listexits) { if (not m_Router.hidden_service_context().hasEndpoints()) { SetJSONError("No mapped endpoints found", listexits.response); return; } auto status = m_Router.hidden_service_context().GetDefault()->ExtractStatus()["exitMap"]; SetJSONResponse((status.empty()) ? "No exits" : status, listexits.response); } void RPCServer::invoke(UnmapExit& unmapexit) { try { for (auto& ip : unmapexit.request.ip_range) m_Router.hidden_service_context().GetDefault()->UnmapExitRange(ip); } catch (std::exception& e) { 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.hidden_service_context().GetDefault(); auto current_exits = endpoint->ExtractStatus()["exitMap"]; if (current_exits.empty()) { SetJSONError("Cannot swap to new exit: no exits currently mapped", swapexits.response); 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()); // 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 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) { SetJSONError("No such endpoint found for dns query", dnsquery.response); return; } if (auto dns = endpoint->DNS()) { auto packet_src = std::make_shared([&](auto result) { if (result) SetJSONResponse(result->ToJSON(), dnsquery.response); else SetJSONError("No response from DNS", dnsquery.response); }); if (not dns->MaybeHandlePacket( packet_src, packet_src->dumb, packet_src->dumb, msg.ToBuffer())) SetJSONError("DNS query not accepted by endpoint", dnsquery.response); } else SetJSONError("Endpoint does not have dns", dnsquery.response); return; } void RPCServer::invoke(Config& config) { if (config.request.filename.empty() and not config.request.ini.empty()) { SetJSONError("No filename specified for .ini file", config.response); return; } if (config.request.ini.empty() and not config.request.filename.empty()) { SetJSONError("No .ini chunk provided", config.response); return; } if (not ends_with(config.request.filename, ".ini")) { SetJSONError("Must append '.ini' to filename", config.response); return; } if (not check_path(config.request.filename)) { SetJSONError("Bad filename passed", config.response); 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) { SetJSONError(e.what(), config.response); return; } } else { try { if (not fs::exists(conf_d)) fs::create_directory(conf_d); auto parser = ConfigParser(); if (parser.load_new_from_str(config.request.ini)) { parser.set_filename(conf_d / (config.request.filename)); parser.save_new(); } } catch (std::exception& e) { SetJSONError(e.what(), config.response); return; } } SetJSONResponse("OK", config.response); } void RPCServer::HandleLogsSubRequest(oxenmq::Message& m) { if (m.data.size() != 1) { m.send_reply("Invalid subscription request: no log receipt endpoint given"); return; } auto endpoint = std::string{m.data[0]}; if (endpoint == "unsubscribe") { log::info( logcat, "New logs unsubscribe request from conn {}@{}", m.conn.to_string(), m.remote); log_subs.unsubscribe(m.conn); m.send_reply("OK"); return; } auto is_new = log_subs.subscribe(m.conn, endpoint); if (is_new) { log::info( logcat, "New logs subscription request from conn {}@{}", m.conn.to_string(), m.remote); m.send_reply("OK"); log_subs.send_all(m.conn, endpoint); } else { log::debug( logcat, "Renewed logs subscription request from conn id {}@{}", m.conn.to_string(), m.remote); m.send_reply("ALREADY"); } } } // namespace llarp::rpc