fix: filter port conflict checks to only consider wildcard-bound addresses

Coolify's proxy uses Docker's 0.0.0.0 binding, which can coexist with
specific-IP bindings (e.g., Tailscale 100.x.x.x) via SO_REUSEADDR.

Previously, the port conflict check incorrectly flagged all port listeners
as conflicts, regardless of which IP address they bound to. This caused
false positives when services like Tailscale were running on internal
network interfaces.

This fix filters port conflict detection to only consider listeners on
wildcard addresses (0.0.0.0, *, [::], ::) as real conflicts, while
allowing specific-IP bindings to coexist with the proxy.

The filtering is applied across all port check paths:
- ss command path (buildPortCheckCommands)
- netstat fallback path (buildPortCheckCommands)
- isPortConflict() method for sequential checks
This commit is contained in:
RandyZhang 2026-02-08 23:00:44 +08:00
parent a2fa98deb7
commit ce2a4ba057

View file

@ -188,17 +188,24 @@ class CheckProxy
echo 'port_free'; echo 'port_free';
exit 0; exit 0;
fi; fi;
count=\$(echo \"\$ss_output\" | grep -c ':$port '); # Filter: only wildcard-bound listeners (0.0.0.0, *, [::], ::) conflict with Docker's 0.0.0.0 binding
# Specific IP bindings (e.g., Tailscale 100.x.x.x) coexist via SO_REUSEADDR
wildcard_output=\$(echo \"\$ss_output\" | grep -E '(0\\.0\\.0\\.0|\\*|\\[::\\]):{$port}[[:space:]]|:::{$port}[[:space:]]');
if [ -z \"\$wildcard_output\" ]; then
echo 'port_free';
exit 0;
fi;
count=\$(echo \"\$wildcard_output\" | grep -c ':$port ');
if [ \$count -eq 0 ]; then if [ \$count -eq 0 ]; then
echo 'port_free'; echo 'port_free';
exit 0; exit 0;
fi; fi;
# Check for dual-stack or docker processes # Check for dual-stack or docker processes
if [ \$count -le 2 ] && (echo \"\$ss_output\" | grep -q 'docker\\|coolify'); then if [ \$count -le 2 ] && (echo \"\$wildcard_output\" | grep -q 'docker\\|coolify'); then
echo 'port_free'; echo 'port_free';
exit 0; exit 0;
fi; fi;
echo \"port_conflict|\$ss_output\"; echo \"port_conflict|\$wildcard_output\";
exit 0; exit 0;
fi; fi;
@ -209,16 +216,22 @@ class CheckProxy
echo 'port_free'; echo 'port_free';
exit 0; exit 0;
fi; fi;
count=\$(echo \"\$netstat_output\" | grep -c 'LISTEN'); # Filter: only wildcard-bound listeners conflict with Docker's 0.0.0.0 binding
wildcard_output=\$(echo \"\$netstat_output\" | grep -E '(0\\.0\\.0\\.0|\\*|\\[::\\]):{$port}[[:space:]]|:::{$port}[[:space:]]');
if [ -z \"\$wildcard_output\" ]; then
echo 'port_free';
exit 0;
fi;
count=\$(echo \"\$wildcard_output\" | grep -c 'LISTEN');
if [ \$count -eq 0 ]; then if [ \$count -eq 0 ]; then
echo 'port_free'; echo 'port_free';
exit 0; exit 0;
fi; fi;
if [ \$count -le 2 ] && (echo \"\$netstat_output\" | grep -q 'docker\\|coolify'); then if [ \$count -le 2 ] && (echo \"\$wildcard_output\" | grep -q 'docker\\|coolify'); then
echo 'port_free'; echo 'port_free';
exit 0; exit 0;
fi; fi;
echo \"port_conflict|\$netstat_output\"; echo \"port_conflict|\$wildcard_output\";
exit 0; exit 0;
fi; fi;
@ -356,6 +369,19 @@ class CheckProxy
return false; return false;
} }
// Filter: only wildcard-bound listeners (0.0.0.0, *, [::], ::) conflict with Docker's 0.0.0.0 binding
// Specific IP bindings (e.g., Tailscale 100.x.x.x) coexist via SO_REUSEADDR
$detailLines = explode("\n", $details);
$wildcardPattern = '/(0\.0\.0\.0|\*|\[::\]):'.preg_quote($port, '/').'(\s|$)|:::'.preg_quote($port, '/').'(\s|$)/';
$wildcardLines = array_filter($detailLines, function ($line) use ($wildcardPattern) {
return preg_match($wildcardPattern, $line);
});
if (empty($wildcardLines)) {
return false;
}
$details = trim(implode("\n", $wildcardLines));
$count = count($wildcardLines);
// Try to detect if this is our coolify-proxy // Try to detect if this is our coolify-proxy
if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) { if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) {
// It's likely our docker or proxy, which is fine // It's likely our docker or proxy, which is fine