vpn: DragonRelayBackend implementation
This commit is contained in:
parent
3d8f54a5e4
commit
fc3be9c272
1 changed files with 231 additions and 0 deletions
231
app/vpn/dragonrelaybackend.cpp
Normal file
231
app/vpn/dragonrelaybackend.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
// app/vpn/dragonrelaybackend.cpp
|
||||||
|
#include "dragonrelaybackend.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
// ── Constructor / Destructor ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
DragonRelayBackend::DragonRelayBackend(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_settings(QStringLiteral("WildDragon"), QStringLiteral("DragonMoonlight"))
|
||||||
|
{
|
||||||
|
// ── Wire RelayClient signals ──────────────────────────────────────────────
|
||||||
|
connect(&m_relay, &RelayClient::loginDone,
|
||||||
|
this, &DragonRelayBackend::onLoginDone);
|
||||||
|
connect(&m_relay, &RelayClient::vpnPeerProvisioned,
|
||||||
|
this, &DragonRelayBackend::onVPNPeerProvisioned);
|
||||||
|
connect(&m_relay, &RelayClient::vpnError,
|
||||||
|
this, &DragonRelayBackend::onVPNError);
|
||||||
|
connect(&m_relay, &RelayClient::hostsFetched,
|
||||||
|
this, &DragonRelayBackend::onHostsFetched);
|
||||||
|
connect(&m_relay, &RelayClient::hostsError,
|
||||||
|
this, &DragonRelayBackend::onHostsError);
|
||||||
|
|
||||||
|
// ── Wire TunnelManager signals ────────────────────────────────────────────
|
||||||
|
connect(&m_tunnel, &TunnelManager::tunnelUp,
|
||||||
|
this, &DragonRelayBackend::onTunnelUp);
|
||||||
|
connect(&m_tunnel, &TunnelManager::tunnelDown,
|
||||||
|
this, &DragonRelayBackend::onTunnelDown);
|
||||||
|
connect(&m_tunnel, &TunnelManager::error,
|
||||||
|
this, &DragonRelayBackend::onTunnelError);
|
||||||
|
|
||||||
|
// ── Host poll timer (every 30 s while tunnel is up) ───────────────────────
|
||||||
|
m_hostPollTimer.setInterval(30'000);
|
||||||
|
m_hostPollTimer.setSingleShot(false);
|
||||||
|
connect(&m_hostPollTimer, &QTimer::timeout,
|
||||||
|
this, &DragonRelayBackend::pollHosts);
|
||||||
|
|
||||||
|
// Restore last-used connection settings so QML can pre-fill the form
|
||||||
|
m_savedURL = m_settings.value(QStringLiteral("relay/url")).toString();
|
||||||
|
m_savedUser = m_settings.value(QStringLiteral("relay/username")).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
DragonRelayBackend::~DragonRelayBackend() {
|
||||||
|
// Best-effort cleanup — don't block the destructor
|
||||||
|
m_hostPollTimer.stop();
|
||||||
|
m_tunnel.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Status helper ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DragonRelayBackend::setStatus(Status s, const QString &text) {
|
||||||
|
bool changed = (m_status != s) || (!text.isEmpty() && text != m_statusText);
|
||||||
|
m_status = s;
|
||||||
|
if (!text.isEmpty())
|
||||||
|
m_statusText = text;
|
||||||
|
if (changed)
|
||||||
|
emit statusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── connectRelay ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DragonRelayBackend::connectRelay(const QString &url,
|
||||||
|
const QString &username,
|
||||||
|
const QString &password) {
|
||||||
|
if (m_status == Connecting || m_status == TunnelUp || m_status == Ready)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_savedURL = url;
|
||||||
|
m_savedUser = username;
|
||||||
|
m_savedPass = password;
|
||||||
|
|
||||||
|
// Persist URL + username (NOT password)
|
||||||
|
m_settings.setValue(QStringLiteral("relay/url"), url);
|
||||||
|
m_settings.setValue(QStringLiteral("relay/username"), username);
|
||||||
|
|
||||||
|
setStatus(Connecting, QStringLiteral("Logging in…"));
|
||||||
|
m_relay.setBaseURL(url);
|
||||||
|
m_relay.login(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── disconnectRelay ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DragonRelayBackend::disconnectRelay() {
|
||||||
|
stopHostPoll();
|
||||||
|
m_tunnel.stop(); // triggers onTunnelDown asynchronously
|
||||||
|
m_relay.deleteVPNPeer(); // fire-and-forget
|
||||||
|
|
||||||
|
m_hosts.clear();
|
||||||
|
m_tunnelIP.clear();
|
||||||
|
emit hostsChanged();
|
||||||
|
emit tunnelIPChanged();
|
||||||
|
setStatus(Disconnected, QStringLiteral("Disconnected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── streamHost ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DragonRelayBackend::streamHost(const QString &ip, const QString &app) {
|
||||||
|
if (m_status != Ready && m_status != TunnelUp) {
|
||||||
|
qWarning() << "DragonRelayBackend::streamHost called while not ready";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Delegate to moonlight-qt's existing stream launch mechanism.
|
||||||
|
// The standard Moonlight PC model uses ComputerManager; we invoke it
|
||||||
|
// via a QProcess for now so we don't have to patch PC discovery internals.
|
||||||
|
// TODO: wire directly into Moonlight's ComputerManager once the fork is
|
||||||
|
// more deeply integrated.
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
QProcess::startDetached(QStringLiteral("moonlight"), {
|
||||||
|
QStringLiteral("stream"),
|
||||||
|
ip,
|
||||||
|
app
|
||||||
|
});
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
QProcess::startDetached(QStringLiteral("moonlight"), {
|
||||||
|
QStringLiteral("stream"),
|
||||||
|
ip,
|
||||||
|
app
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
QProcess::startDetached(QStringLiteral("moonlight"), {
|
||||||
|
QStringLiteral("stream"),
|
||||||
|
ip,
|
||||||
|
app
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── refreshHosts ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DragonRelayBackend::refreshHosts() {
|
||||||
|
if (m_status == TunnelUp || m_status == Ready)
|
||||||
|
m_relay.fetchHosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── pollHosts ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DragonRelayBackend::pollHosts() {
|
||||||
|
m_relay.fetchHosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragonRelayBackend::startHostPoll() {
|
||||||
|
m_relay.fetchHosts(); // immediate fetch
|
||||||
|
m_hostPollTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragonRelayBackend::stopHostPoll() {
|
||||||
|
m_hostPollTimer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── RelayClient slots ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DragonRelayBackend::onLoginDone(bool ok, const QString &err) {
|
||||||
|
if (!ok) {
|
||||||
|
setStatus(Error, QStringLiteral("Login failed: ") + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStatus(Connecting, QStringLiteral("Provisioning VPN peer…"));
|
||||||
|
m_relay.provisionVPNPeer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragonRelayBackend::onVPNPeerProvisioned(const RelayVPNConf &conf) {
|
||||||
|
setStatus(Connecting, QStringLiteral("Starting WireGuard tunnel…"));
|
||||||
|
|
||||||
|
WireGuardConfig cfg;
|
||||||
|
QString parseErr;
|
||||||
|
if (!WireGuardConfig::fromConf(conf.conf, cfg, parseErr)) {
|
||||||
|
setStatus(Error, QStringLiteral("Bad VPN config: ") + parseErr);
|
||||||
|
m_relay.deleteVPNPeer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_tunnel.start(cfg);
|
||||||
|
// onTunnelUp/onTunnelError fired from TunnelManager
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragonRelayBackend::onVPNError(const QString &err) {
|
||||||
|
setStatus(Error, QStringLiteral("VPN error: ") + err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragonRelayBackend::onHostsFetched(const QList<RelayHost> &hosts) {
|
||||||
|
m_hosts.clear();
|
||||||
|
for (const auto &h : hosts) {
|
||||||
|
QVariantMap m;
|
||||||
|
m[QStringLiteral("name")] = h.name;
|
||||||
|
m[QStringLiteral("ip")] = h.ip;
|
||||||
|
m[QStringLiteral("port")] = h.port;
|
||||||
|
m[QStringLiteral("online")] = true;
|
||||||
|
m[QStringLiteral("source")] = QStringLiteral("relay");
|
||||||
|
m_hosts.append(m);
|
||||||
|
}
|
||||||
|
emit hostsChanged();
|
||||||
|
|
||||||
|
Status next = m_hosts.isEmpty() ? TunnelUp : Ready;
|
||||||
|
if (m_status != next) {
|
||||||
|
QString text = m_hosts.isEmpty()
|
||||||
|
? QStringLiteral("Tunnel up — no hosts yet")
|
||||||
|
: QStringLiteral("Connected — %1 host(s)").arg(m_hosts.size());
|
||||||
|
setStatus(next, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragonRelayBackend::onHostsError(const QString &err) {
|
||||||
|
qWarning() << "DragonRelayBackend: hosts fetch error:" << err;
|
||||||
|
// Don't downgrade to Error state — the tunnel is still up; just log it
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── TunnelManager slots ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void DragonRelayBackend::onTunnelUp(const QString &localIP) {
|
||||||
|
m_tunnelIP = localIP;
|
||||||
|
emit tunnelIPChanged();
|
||||||
|
setStatus(TunnelUp, QStringLiteral("Tunnel up (%1) — fetching hosts…").arg(localIP));
|
||||||
|
startHostPoll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragonRelayBackend::onTunnelDown() {
|
||||||
|
stopHostPoll();
|
||||||
|
m_tunnelIP.clear();
|
||||||
|
emit tunnelIPChanged();
|
||||||
|
if (m_status != Disconnected)
|
||||||
|
setStatus(Disconnected, QStringLiteral("Tunnel closed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DragonRelayBackend::onTunnelError(const QString &err) {
|
||||||
|
stopHostPoll();
|
||||||
|
m_relay.deleteVPNPeer();
|
||||||
|
setStatus(Error, QStringLiteral("Tunnel error: ") + err);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue