Public Access
VPN: a WireGuard tunnel (#8)
The device joins a WireGuard network over whatever Wi-Fi it is on: one peer, IPv4. A client's .conf is imported from the card (/vpn/wg0.conf) and kept in the device's settings, private key included, never shown; Settings offers to delete the file. A switch brings the tunnel up until the next restart, "Start with Wi-Fi" every time; it waits for the clock, which a handshake needs. VPN shows in the Status Bar. The protocol is esphome/wireguard 0.4.8. It calls lwIP without lwIP's lock, which this framework checks: every call into it is made with the lock held. What goes through the tunnel is everything (AllowedIPs 0.0.0.0/0) or the one subnet the device's tunnel address is in: lwIP routes by an interface's subnet or by default, nothing finer. The import says how many ranges it can't reach. Checked against a test peer in both directions and against a real server, with a configuration uploaded from a phone (docs/milestones/N1.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "event_bus.h"
|
||||
#include "service.h"
|
||||
#include "services/clock_service.h"
|
||||
#include "services/storage_service.h"
|
||||
#include "services/wifi_service.h"
|
||||
#include "settings.h"
|
||||
#include "wg_config.h"
|
||||
|
||||
namespace roro {
|
||||
|
||||
// The WireGuard tunnel (issue #8, docs/milestones/N1.md): one peer, IPv4, over whatever Wi-Fi the
|
||||
// device is on. The protocol is the `esphome/wireguard` library's; this decides when the tunnel
|
||||
// is up, takes lwIP's lock around every call into it (the library takes none), and puts the
|
||||
// tunnel's DNS servers in and out.
|
||||
//
|
||||
// It starts once Wi-Fi is connected and the clock is set: a handshake carries the time, and a
|
||||
// server refuses one older than the last it saw from this key.
|
||||
class VpnService : public Service {
|
||||
public:
|
||||
enum class State { NoConfig, Off, WaitingWifi, WaitingClock, Resolving, Trying, Up };
|
||||
|
||||
VpnService(Settings& settings, WifiService& wifi, ClockService& clock, EventBus& bus)
|
||||
: settings_(settings), wifi_(wifi), clock_(clock), bus_(bus) {}
|
||||
const char* name() const override { return "vpn"; }
|
||||
void start() override;
|
||||
void stop() override { takeDown(); }
|
||||
void tick(uint32_t nowMs) override;
|
||||
|
||||
State state() const { return state_; }
|
||||
const char* stateText() const;
|
||||
bool configured() const { return configured_; }
|
||||
const net::WgConfig& config() const { return config_; } // its keys are for the library only
|
||||
bool wanted() const { return wanted_; }
|
||||
// On or off, until the next restart; `seconds`: on for that long, then off by itself (for
|
||||
// trying a configuration from afar, when a wrong one would cut the connection it was sent over).
|
||||
void want(bool on, uint32_t seconds = 0);
|
||||
|
||||
// A `.conf`'s text, or the file itself. "" or why it wasn't taken. A tunnel that is up starts
|
||||
// again with the new one.
|
||||
std::string import(const std::string& confText);
|
||||
std::string importFile(StorageService& storage, const std::string& path);
|
||||
void forget();
|
||||
|
||||
bool dnsThroughIt() const; // the tunnel's DNS servers are the ones in use
|
||||
int64_t lastHandshake() const { return lastHandshake_; } // UTC seconds, 0: none yet
|
||||
const std::string& lastError() const { return error_; }
|
||||
|
||||
private:
|
||||
struct Tunnel; // the library's structures, kept out of this header
|
||||
|
||||
void bringUp();
|
||||
void takeDown();
|
||||
void loadConfig();
|
||||
void keepDns(); // puts the tunnel's servers back in if a DHCP renewal replaced them
|
||||
|
||||
Settings& settings_;
|
||||
WifiService& wifi_;
|
||||
ClockService& clock_;
|
||||
EventBus& bus_;
|
||||
net::WgConfig config_;
|
||||
bool configured_ = false, wanted_ = false, announced_ = false;
|
||||
State state_ = State::NoConfig;
|
||||
Tunnel* tunnel_ = nullptr;
|
||||
uint32_t untilMs_ = 0, retryMs_ = 0;
|
||||
bool timed_ = false;
|
||||
int64_t lastHandshake_ = 0;
|
||||
std::string error_;
|
||||
};
|
||||
|
||||
} // namespace roro
|
||||
Reference in New Issue
Block a user