Repair managed gateway interface resolution
This commit is contained in:
parent
6ab168e041
commit
aaf5d18f58
4 changed files with 160 additions and 0 deletions
|
|
@ -44,6 +44,7 @@ class ServiceController extends ApiControllerBase
|
||||||
]);
|
]);
|
||||||
$runner->applyFirewall();
|
$runner->applyFirewall();
|
||||||
$runner->applyWireGuard();
|
$runner->applyWireGuard();
|
||||||
|
$configuration->repairGatewayInterface();
|
||||||
$runner->applyRouting();
|
$runner->applyRouting();
|
||||||
$runner->applyFirewall();
|
$runner->applyFirewall();
|
||||||
sleep(5);
|
sleep(5);
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,28 @@ class ManagedConfiguration
|
||||||
return $instance === null ? null : (string)$instance->interface;
|
return $instance === null ? null : (string)$instance->interface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function repairGatewayInterface(): bool
|
||||||
|
{
|
||||||
|
$interface = Config::getInstance()->object()->interfaces->{self::INTERFACE} ?? null;
|
||||||
|
if ($interface === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (empty((string)$interface->internal_dynamic)) {
|
||||||
|
throw new \RuntimeException('The NordVPN interface identifier is already in use.');
|
||||||
|
}
|
||||||
|
$device = $this->interfaceName();
|
||||||
|
if ($device === null || $device === '') {
|
||||||
|
throw new \RuntimeException('The managed WireGuard interface is unavailable.');
|
||||||
|
}
|
||||||
|
if ((string)$interface->if === $device && (string)$interface->virtual !== '1') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$interface->if = $device;
|
||||||
|
unset($interface->virtual);
|
||||||
|
Config::getInstance()->save(['description' => 'Repair NordVPN gateway interface']);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public function settings(): Settings
|
public function settings(): Settings
|
||||||
{
|
{
|
||||||
return $this->settings;
|
return $this->settings;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,10 @@ class Reconciler
|
||||||
'message' => 'Bootstrap is required.',
|
'message' => 'Bootstrap is required.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
if ($this->configuration->repairGatewayInterface()) {
|
||||||
|
$this->runner->applyRouting();
|
||||||
|
$this->runner->applyFirewall();
|
||||||
|
}
|
||||||
|
|
||||||
$endpointsChanged = false;
|
$endpointsChanged = false;
|
||||||
if ((string)($settings->general->exempt_wireguard ?? '0') === '1') {
|
if ((string)($settings->general->exempt_wireguard ?? '0') === '1') {
|
||||||
|
|
@ -83,6 +87,7 @@ class Reconciler
|
||||||
}
|
}
|
||||||
$this->configuration->updateCandidate($candidate);
|
$this->configuration->updateCandidate($candidate);
|
||||||
$this->runner->applyWireGuard();
|
$this->runner->applyWireGuard();
|
||||||
|
$this->configuration->repairGatewayInterface();
|
||||||
($this->pause)(5);
|
($this->pause)(5);
|
||||||
$health = $this->health->check((string)$this->configuration->interfaceName());
|
$health = $this->health->check((string)$this->configuration->interfaceName());
|
||||||
if ($health['available']) {
|
if ($health['available']) {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,11 @@ class TestConfiguration extends ManagedConfiguration
|
||||||
{
|
{
|
||||||
return 'wg0';
|
return 'wg0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function repairGatewayInterface(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AvailableHealth extends TunnelHealth
|
class AvailableHealth extends TunnelHealth
|
||||||
|
|
@ -70,6 +75,133 @@ class FailOnceRunner extends SystemRunner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RepairConfiguration extends TestConfiguration
|
||||||
|
{
|
||||||
|
public function repairGatewayInterface(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RepairRunner extends SystemRunner
|
||||||
|
{
|
||||||
|
public int $routingCalls = 0;
|
||||||
|
public int $firewallCalls = 0;
|
||||||
|
|
||||||
|
public function applyRouting(): void
|
||||||
|
{
|
||||||
|
$this->routingCalls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyFirewall(): void
|
||||||
|
{
|
||||||
|
$this->firewallCalls++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FailoverConfiguration extends TestConfiguration
|
||||||
|
{
|
||||||
|
public bool $candidateUpdated = false;
|
||||||
|
public int $repairCalls = 0;
|
||||||
|
|
||||||
|
public function updateCandidate(array $candidate): void
|
||||||
|
{
|
||||||
|
$this->candidateUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function repairGatewayInterface(): bool
|
||||||
|
{
|
||||||
|
if (!$this->candidateUpdated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->candidateUpdated = false;
|
||||||
|
$this->repairCalls++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoveringHealth extends TunnelHealth
|
||||||
|
{
|
||||||
|
private int $checks = 0;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function check(string $interface): array
|
||||||
|
{
|
||||||
|
$this->checks++;
|
||||||
|
return ['available' => $this->checks > 1, 'last_handshake' => $this->checks > 1 ? 1000 : 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FailoverRunner extends SystemRunner
|
||||||
|
{
|
||||||
|
public int $wireGuardCalls = 0;
|
||||||
|
public int $routingCalls = 0;
|
||||||
|
public int $firewallCalls = 0;
|
||||||
|
|
||||||
|
public function applyWireGuard(): void
|
||||||
|
{
|
||||||
|
$this->wireGuardCalls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyRouting(): void
|
||||||
|
{
|
||||||
|
$this->routingCalls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyFirewall(): void
|
||||||
|
{
|
||||||
|
$this->firewallCalls++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$repairPath = sys_get_temp_dir() . '/nordvpn-repair-' . bin2hex(random_bytes(8)) . '.json';
|
||||||
|
$repairRunner = new RepairRunner();
|
||||||
|
(new Reconciler(
|
||||||
|
new NordApi(static fn() => []),
|
||||||
|
new RepairConfiguration(),
|
||||||
|
new AvailableHealth(),
|
||||||
|
new StatusStore($repairPath),
|
||||||
|
$repairRunner
|
||||||
|
))->run();
|
||||||
|
if ($repairRunner->routingCalls !== 1 || $repairRunner->firewallCalls !== 2) {
|
||||||
|
throw new \RuntimeException('Gateway interface repair was not applied.');
|
||||||
|
}
|
||||||
|
unlink($repairPath);
|
||||||
|
echo "ok - reconciliation applies gateway interface repair\n";
|
||||||
|
|
||||||
|
$failoverPath = sys_get_temp_dir() . '/nordvpn-failover-' . bin2hex(random_bytes(8)) . '.json';
|
||||||
|
$failoverConfiguration = new FailoverConfiguration();
|
||||||
|
$failoverRunner = new FailoverRunner();
|
||||||
|
$publicKey = base64_encode(str_repeat("\0", 32));
|
||||||
|
(new Reconciler(
|
||||||
|
new NordApi(static fn() => [[
|
||||||
|
'hostname' => 'nl2.example.invalid',
|
||||||
|
'station' => '198.51.100.2',
|
||||||
|
'locations' => [['country' => ['code' => 'NL']]],
|
||||||
|
'technologies' => [[
|
||||||
|
'id' => 35,
|
||||||
|
'pivot' => ['status' => 'online'],
|
||||||
|
'metadata' => [['name' => 'public_key', 'value' => $publicKey]],
|
||||||
|
]],
|
||||||
|
]]),
|
||||||
|
$failoverConfiguration,
|
||||||
|
new RecoveringHealth(),
|
||||||
|
new StatusStore($failoverPath),
|
||||||
|
$failoverRunner,
|
||||||
|
static fn($seconds) => null
|
||||||
|
))->run();
|
||||||
|
if ($failoverRunner->wireGuardCalls !== 1 ||
|
||||||
|
$failoverConfiguration->repairCalls !== 1 ||
|
||||||
|
$failoverRunner->routingCalls !== 1 ||
|
||||||
|
$failoverRunner->firewallCalls !== 1) {
|
||||||
|
throw new \RuntimeException('Server failover did not repair the gateway interface.');
|
||||||
|
}
|
||||||
|
unlink($failoverPath);
|
||||||
|
echo "ok - server failover repairs the gateway interface\n";
|
||||||
|
|
||||||
$path = sys_get_temp_dir() . '/nordvpn-retry-' . bin2hex(random_bytes(8)) . '.json';
|
$path = sys_get_temp_dir() . '/nordvpn-retry-' . bin2hex(random_bytes(8)) . '.json';
|
||||||
$status = new StatusStore($path);
|
$status = new StatusStore($path);
|
||||||
$runner = new FailOnceRunner();
|
$runner = new FailOnceRunner();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue