djsisson comment about addressing overlapping of CIDR network overlaps.

This commit is contained in:
Iisyourdad 2026-03-03 20:30:47 -06:00
parent 91ea235b5f
commit aaf0b94bce
2 changed files with 150 additions and 0 deletions

View file

@ -79,6 +79,10 @@ class EdgeProxyRemoteRouteService
$routes = [];
$warnings = [];
$networkOverlapWarning = $this->detectDockerNetworkOverlapWarning($service, $edgeProxyServer, $tunnelHost);
if (! is_null($networkOverlapWarning)) {
$warnings[] = $networkOverlapWarning;
}
foreach ($applications as $application) {
$domains = collect(explode(',', (string) $application->fqdn))
@ -430,6 +434,88 @@ class EdgeProxyRemoteRouteService
return $protocol;
}
private function detectDockerNetworkOverlapWarning(Service $service, Server $edgeProxyServer, string $tunnelHost): ?string
{
// Only run this for persisted server models (normal runtime) to avoid noisy checks in synthetic test stubs.
if (! $edgeProxyServer->exists) {
return null;
}
$normalizedTunnelHost = trim($tunnelHost, '[]');
if (! filter_var($normalizedTunnelHost, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return null;
}
foreach ($this->resolveEdgeDockerSubnets($edgeProxyServer) as $subnet) {
if ($this->ipv4InCidr($normalizedTunnelHost, $subnet)) {
return sprintf(
'Edge proxy route warning for service %s: remote host %s overlaps edge Docker network subnet %s. This can break VPN/WireGuard routing. Configure Docker default-address-pools to a non-overlapping range (for example base 172.20.0.0/16 with size 24) and recreate overlapping networks.',
$service->uuid,
$normalizedTunnelHost,
$subnet
);
}
}
return null;
}
private function resolveEdgeDockerSubnets(Server $edgeProxyServer): array
{
try {
$subnetsOutput = $this->runRemoteCommands($edgeProxyServer, [
"docker network inspect \$(docker network ls -q) --format '{{range .IPAM.Config}}{{println .Subnet}}{{end}}' 2>/dev/null | sort -u || true",
], false);
} catch (\Throwable) {
return [];
}
if (! is_string($subnetsOutput) || trim($subnetsOutput) === '') {
return [];
}
return collect(preg_split('/\R+/', $subnetsOutput) ?: [])
->map(fn (string $line) => trim($line))
->filter(fn (string $line) => preg_match('/^\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}$/', $line) === 1)
->values()
->all();
}
private function ipv4InCidr(string $ip, string $cidr): bool
{
[$networkIp, $prefixLength] = array_pad(explode('/', $cidr, 2), 2, null);
if (! is_string($networkIp) || ! is_string($prefixLength)) {
return false;
}
if (! filter_var($networkIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return false;
}
if (! preg_match('/^\d+$/', $prefixLength)) {
return false;
}
$prefixLength = (int) $prefixLength;
if ($prefixLength < 0 || $prefixLength > 32) {
return false;
}
$ipLong = ip2long($ip);
$networkLong = ip2long($networkIp);
if ($ipLong === false || $networkLong === false) {
return false;
}
if ($prefixLength === 0) {
return true;
}
$mask = -1 << (32 - $prefixLength);
return ($ipLong & $mask) === ($networkLong & $mask);
}
private function parseDomainUrl(string $domain): ?Url
{
$normalizedDomain = trim($domain);

View file

@ -612,3 +612,67 @@ YAML;
->and($payload)->toContain('http://10.8.0.24:9050')
->and($payload)->not->toContain('minecraft.example.com');
});
it('returns warning when remote tunnel ip overlaps with an edge docker subnet', function () {
$manager = new class extends EdgeProxyRemoteRouteService
{
public array $calls = [];
protected function runRemoteCommands(Server $server, array $commands, bool $throwError = true): ?string
{
$this->calls[] = [
'commands' => $commands,
'throw_error' => $throwError,
];
if (str_contains(implode("\n", $commands), 'docker network inspect')) {
return "10.8.0.0/24\n172.18.0.0/16\n";
}
return null;
}
};
$edgeProxyServer = Mockery::mock(Server::class)->makePartial();
$edgeProxyServer->id = 0;
$edgeProxyServer->exists = true;
$edgeProxyServer->shouldReceive('proxyType')->andReturn('TRAEFIK');
$edgeProxyServer->shouldReceive('proxyPath')->andReturn('/tmp/proxy');
$deploymentServer = Mockery::mock(Server::class)->makePartial();
$deploymentServer->id = 20;
$deploymentServer->ip = '10.8.0.40';
$deploymentServer->proxy = ['type' => 'NONE'];
$service = new Service;
$service->uuid = 'service-overlap-warning';
$service->docker_compose_raw = <<<'YAML'
services:
app:
ports:
- "9060:3000"
YAML;
$application = new ServiceApplication;
$application->name = 'app';
$application->fqdn = 'https://overlap.example.com:3000';
$service->setRelation('applications', collect([$application]));
$application->setRelation('service', $service);
$warnings = $manager->syncServiceWithServers($service, $edgeProxyServer, $deploymentServer);
expect($warnings)->not->toBeEmpty()
->and(collect($warnings)->contains(fn (string $warning) => str_contains($warning, 'overlaps edge Docker network subnet 10.8.0.0/24')));
$writeCall = collect($manager->calls)->first(
fn (array $call) => str_contains(implode("\n", $call['commands']), 'base64 -d | tee')
);
expect($writeCall)->not->toBeNull();
preg_match("/echo '([^']+)' \\| base64 -d/", $writeCall['commands'][1], $payloadMatches);
$payload = base64_decode($payloadMatches[1]);
expect($payload)->toContain('Host(`overlap.example.com`)')
->and($payload)->toContain('http://10.8.0.40:9060');
});