-
-
Notifications
You must be signed in to change notification settings - Fork 387
Expand file tree
/
Copy pathKillSwitchController.hpp
More file actions
223 lines (186 loc) · 8.2 KB
/
Copy pathKillSwitchController.hpp
File metadata and controls
223 lines (186 loc) · 8.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#pragma once
#include <QMutex>
#include <QString>
#include <QStringList>
#include <QtTypes>
#include <optional>
namespace Configs_sys {
// The controller deliberately knows nothing about WFP, Windows Firewall, or
// another platform implementation. In particular, it cannot make a blocking
// policy safe by itself: backend operations which fail must leave the OS in the
// previous state or in a stricter state, never in a less restrictive state.
struct KillSwitchResult {
bool ok = false;
QString error;
[[nodiscard]] static KillSwitchResult Success();
[[nodiscard]] static KillSwitchResult Failure(QString error);
[[nodiscard]] explicit operator bool() const { return ok; }
};
struct KillSwitchTrustedCorePlan {
// The baseline deliberately exempts only these trusted core executables so
// they can establish and carry the tunnel. Current profile formats do not
// expose a complete, static endpoint set (custom cores, Tailscale/DERP and
// domain rotation are examples), so this is an application-scoped permit,
// not an endpoint-scoped one. Keep this list minimal and canonicalized.
QStringList executablePaths;
[[nodiscard]] bool isValid() const;
friend bool operator==(const KillSwitchTrustedCorePlan &,
const KillSwitchTrustedCorePlan &) = default;
};
struct KillSwitchTunInterface {
QString name;
// Platform interface index, not a process-owned handle. A name is retained
// as a diagnostic/fallback identity for platforms without numeric indices.
quint64 interfaceIndex = 0;
bool ipv4 = true;
bool ipv6 = false;
[[nodiscard]] bool isValid() const;
friend bool operator==(const KillSwitchTunInterface &,
const KillSwitchTunInterface &) = default;
};
struct KillSwitchBackendState {
bool baselineActive = false;
bool dynamicCoreActive = false;
bool tunAllowanceActive = false;
[[nodiscard]] bool anyActive() const {
return baselineActive || dynamicCoreActive || tunAllowanceActive;
}
};
struct KillSwitchReconcileResult {
KillSwitchResult result;
KillSwitchBackendState state;
};
class KillSwitchBackend {
public:
virtual ~KillSwitchBackend() = default;
// Discover/reconcile only Throne-owned state. The implementation must not
// touch unrelated firewall configuration. Any stale transient session or
// TUN allow should be made safe before returning its observed state.
[[nodiscard]] virtual KillSwitchReconcileResult reconcile() = 0;
// Install or verify the persistent, dual-stack fail-closed baseline.
[[nodiscard]] virtual KillSwitchResult ensureBaseline() = 0;
// Create or replace the trusted application permits needed by the core.
// This is normally installed once at application startup and remains valid
// across profile changes. The persistent baseline remains installed.
[[nodiscard]] virtual KillSwitchResult startDynamicCore(
const KillSwitchTrustedCorePlan &plan) = 0;
// These calls are idempotent. removeTunAllowance must be safe when the TUN
// has already vanished, and addTunAllowance must never weaken the baseline
// for another interface.
[[nodiscard]] virtual KillSwitchResult removeTunAllowance() = 0;
[[nodiscard]] virtual KillSwitchResult addTunAllowance(
const KillSwitchTunInterface &tunInterface) = 0;
// Remove only Throne-owned persistent and transient objects.
[[nodiscard]] virtual KillSwitchResult disable() = 0;
};
class KillSwitchController {
public:
enum class State {
Disabled,
Connecting,
Connected,
Switching,
Reconnecting,
Stopping,
Disconnected,
Error,
Exiting,
};
enum class StartIntent {
Connect,
Switch,
Reconnect,
};
struct PrepareResult {
// Callers must not stop a working profile unless this is true.
bool mayTearDownCurrentProfile = false;
quint64 operationId = 0;
State state = State::Disabled;
QString error;
[[nodiscard]] explicit operator bool() const {
return mayTearDownCurrentProfile;
}
};
struct Snapshot {
bool initialized = false;
bool enabled = false;
bool recoveredStaleProtection = false;
State state = State::Disabled;
KillSwitchBackendState backend;
KillSwitchTunInterface allowedTun;
quint64 activeOperationId = 0;
QString lastError;
};
struct InitializationResult {
KillSwitchResult result;
bool enabled = false;
bool recoveredStaleProtection = false;
State state = State::Disabled;
[[nodiscard]] explicit operator bool() const {
return static_cast<bool>(result);
}
};
explicit KillSwitchController(KillSwitchBackend &backend);
// Must be called once after settings are loaded. Reconciliation runs even
// when shouldEnable is false. Discovered Throne protection is retained and
// promoted to enabled; only an explicit disable() removes persistent rules.
[[nodiscard]] InitializationResult initialize(
bool shouldEnable, KillSwitchTrustedCorePlan trustedCorePlan);
[[nodiscard]] KillSwitchResult enable();
[[nodiscard]] KillSwitchResult disable();
// The successful return is the prepare-before-stop security boundary:
// baseline -> constrained core session -> remove old TUN allow. No caller
// may tear down the old profile before it receives success.
[[nodiscard]] PrepareResult prepareForProfileStart(
StartIntent intent);
// operationId rejects late readiness/failure callbacks from an older start.
// System Proxy profiles pass std::nullopt; TUN profiles pass the ready
// interface including the IP families it carries.
[[nodiscard]] KillSwitchResult profileBecameReady(
quint64 operationId,
std::optional<KillSwitchTunInterface> tunInterface = std::nullopt);
[[nodiscard]] KillSwitchResult profileStartFailed(
quint64 operationId, QString error);
[[nodiscard]] PrepareResult prepareForProfileStop();
[[nodiscard]] KillSwitchResult profileStopped();
// Rolls back a prepared stop when the stop RPC failed and the old profile
// is still operational. TUN profiles pass the still-live interface so its
// allowance can be restored; System Proxy profiles pass std::nullopt. A
// failed add is safe to retry and leaves the connection blocked meanwhile.
[[nodiscard]] KillSwitchResult profileStopFailed(
std::optional<KillSwitchTunInterface> stillActiveTun = std::nullopt,
QString error = {});
// Called after an unplanned daemon/core exit. The baseline is re-verified
// and the obsolete TUN permission is removed; it is never disabled.
[[nodiscard]] KillSwitchResult coreTerminatedUnexpectedly(
bool reconnectPlanned);
// With the kill switch enabled, normal application exit intentionally keeps
// the persistent baseline for fail-closed crash/exit semantics.
[[nodiscard]] PrepareResult prepareForExit();
[[nodiscard]] Snapshot snapshot() const;
[[nodiscard]] bool invariantHolds(QString *reason = nullptr) const;
[[nodiscard]] static QString stateName(State state);
private:
[[nodiscard]] KillSwitchResult ensureBaselineLocked();
[[nodiscard]] KillSwitchResult removeTunAllowanceLocked();
[[nodiscard]] KillSwitchResult backendFailureLocked(
const QString &action, const KillSwitchResult &result);
[[nodiscard]] PrepareResult prepareFailureLocked(
State originalState, const QString &error) const;
[[nodiscard]] bool invariantHoldsLocked(QString *reason) const;
[[nodiscard]] bool startAllowedLocked(StartIntent intent) const;
[[nodiscard]] quint64 nextOperationIdLocked();
KillSwitchBackend &backend_;
mutable QMutex mutex_;
bool initialized_ = false;
bool enabled_ = false;
bool recoveredStaleProtection_ = false;
State state_ = State::Disabled;
KillSwitchBackendState backendState_;
KillSwitchTunInterface allowedTun_;
quint64 operationCounter_ = 0;
quint64 activeOperationId_ = 0;
KillSwitchTrustedCorePlan trustedCorePlan_;
QString lastError_;
};
} // namespace Configs_sys