wg: DragonRelay registration client implementation (libcurl)
This commit is contained in:
parent
5b6d2269be
commit
3a17933beb
1 changed files with 187 additions and 0 deletions
187
src/wg/relayreg.cpp
Normal file
187
src/wg/relayreg.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
// src/wg/relayreg.cpp — DragonRelay HTTP registration client for Artemis.
|
||||||
|
//
|
||||||
|
// Depends on: libcurl, nlohmann/json (header-only, shipped with Sunshine)
|
||||||
|
//
|
||||||
|
// Build: add relayreg.cpp to the CMake target and link curl.
|
||||||
|
|
||||||
|
#include "relayreg.h"
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace wg {
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// ── libcurl write callback ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static size_t curlWrite(void *data, size_t size, size_t nmemb, void *userp) {
|
||||||
|
size_t total = size * nmemb;
|
||||||
|
static_cast<std::string *>(userp)->append(static_cast<char *>(data), total);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── RelayReg ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
RelayReg::RelayReg() {
|
||||||
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
RelayReg::~RelayReg() {
|
||||||
|
curl_global_cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── HTTP helper ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
int RelayReg::request(const std::string &method,
|
||||||
|
const std::string &path,
|
||||||
|
const std::string &body,
|
||||||
|
std::string &responseOut) {
|
||||||
|
responseOut.clear();
|
||||||
|
|
||||||
|
CURL *curl = curl_easy_init();
|
||||||
|
if (!curl) return -1;
|
||||||
|
|
||||||
|
std::string url = m_base + path;
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWrite);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseOut);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
struct curl_slist *hdrs = nullptr;
|
||||||
|
hdrs = curl_slist_append(hdrs, "Content-Type: application/json");
|
||||||
|
if (!m_jwt.empty()) {
|
||||||
|
std::string auth = "Authorization: Bearer " + m_jwt;
|
||||||
|
hdrs = curl_slist_append(hdrs, auth.c_str());
|
||||||
|
}
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
|
||||||
|
|
||||||
|
// Method + body
|
||||||
|
if (method == "POST" || method == "PUT") {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)body.size());
|
||||||
|
} else if (method == "DELETE") {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||||
|
}
|
||||||
|
// GET is default
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
long httpCode = 0;
|
||||||
|
if (res == CURLE_OK)
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||||
|
|
||||||
|
curl_slist_free_all(hdrs);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
if (m_log) m_log(std::string("curl error: ") + curl_easy_strerror(res));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return (int)httpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── login ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
bool RelayReg::login(const std::string &username, const std::string &password,
|
||||||
|
std::string &errOut) {
|
||||||
|
json body = { {"username", username}, {"password", password} };
|
||||||
|
std::string resp;
|
||||||
|
int code = request("POST", "/api/auth/login", body.dump(), resp);
|
||||||
|
if (code != 200) {
|
||||||
|
errOut = "login failed (HTTP " + std::to_string(code) + "): " + resp;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto j = json::parse(resp);
|
||||||
|
m_jwt = j.at("token").get<std::string>();
|
||||||
|
if (m_log) m_log("Logged in to DragonRelay");
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
errOut = std::string("login JSON parse: ") + e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RelayReg::logout() {
|
||||||
|
m_jwt.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── provisionVPN ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
bool RelayReg::provisionVPN(const std::string &deviceName,
|
||||||
|
VPNConf &out, std::string &errOut) {
|
||||||
|
json body = { {"device_name", deviceName} };
|
||||||
|
std::string resp;
|
||||||
|
int code = request("POST", "/api/vpn/peer", body.dump(), resp);
|
||||||
|
if (code != 200 && code != 201) {
|
||||||
|
errOut = "provisionVPN failed (HTTP " + std::to_string(code) + "): " + resp;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto j = json::parse(resp);
|
||||||
|
out.id = j.at("id").get<std::string>();
|
||||||
|
out.name = j.at("name").get<std::string>();
|
||||||
|
out.publicKey = j.at("public_key").get<std::string>();
|
||||||
|
out.allowedIP = j.at("allowed_ip").get<std::string>();
|
||||||
|
out.conf = j.at("conf").get<std::string>();
|
||||||
|
if (m_log) m_log("VPN peer provisioned: " + out.name + " @ " + out.allowedIP);
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
errOut = std::string("provisionVPN JSON: ") + e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RelayReg::deleteVPNPeer(const std::string &peerId, std::string &errOut) {
|
||||||
|
std::string resp;
|
||||||
|
int code = request("DELETE", "/api/vpn/peer/" + peerId, "", resp);
|
||||||
|
if (code != 200 && code != 204) {
|
||||||
|
errOut = "deleteVPNPeer failed (HTTP " + std::to_string(code) + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── registerHost ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
bool RelayReg::registerHost(const std::string &name, const std::string &wgIP,
|
||||||
|
int port, std::string &errOut) {
|
||||||
|
json body = { {"name", name}, {"wg_ip", wgIP}, {"port", port} };
|
||||||
|
std::string resp;
|
||||||
|
int code = request("POST", "/api/host/register", body.dump(), resp);
|
||||||
|
if (code != 200 && code != 201) {
|
||||||
|
errOut = "registerHost failed (HTTP " + std::to_string(code) + "): " + resp;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_log) m_log("Registered host '" + name + "' at " + wgIP + ":" + std::to_string(port));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RelayReg::heartbeat(std::string &errOut) {
|
||||||
|
std::string resp;
|
||||||
|
int code = request("PUT", "/api/host/heartbeat", "{}", resp);
|
||||||
|
if (code != 200 && code != 204) {
|
||||||
|
errOut = "heartbeat failed (HTTP " + std::to_string(code) + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RelayReg::unregisterHost(std::string &errOut) {
|
||||||
|
std::string resp;
|
||||||
|
int code = request("DELETE", "/api/host/unregister", "", resp);
|
||||||
|
if (code != 200 && code != 204) {
|
||||||
|
errOut = "unregisterHost failed (HTTP " + std::to_string(code) + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_log) m_log("Unregistered from DragonRelay");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace wg
|
||||||
Loading…
Reference in a new issue