fix: pre-flight gateway ping must bind to the specific bridge, not the host default route
Verified live that the pre-flight check added in the previous commit had a real gap: a plain `ping <gateway>` from the Proxmox host succeeds via the HOST's own routing table (which can have routes to a subnet through paths the host alone knows about), even when the CONTAINER — attached via a plain bridge with only a naive on-link default route — can never actually ARP that gateway. Confirmed by creating a real test container on vmbr0 with gw=192.168.8.2: the host-wide ping had said "reachable," but pinging from inside the container showed 100% packet loss. Fixed by binding the pre-flight ping to the specific requested bridge (`ping -I <bridge>`), which correctly rejects vmbr0 for that gateway instead of false-positiving via the host's broader routing table. Also confirmed live: vmbr1 does exist and is up on strong (contrary to the possibly-stale host doc), matching what romm/seanime's docs already said. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -421,18 +421,25 @@ func executeApprovedAction(ctx context.Context, pool *db.Pool, execID uuid.UUID,
|
||||
}
|
||||
|
||||
// Pre-flight: for a static config, ping the gateway from the target
|
||||
// HOST before spending 5+ minutes creating the container and waiting
|
||||
// on its network. This is the check that would have caught the real
|
||||
// TypeType failure immediately instead of after a full provision
|
||||
// attempt: the gateway wasn't reachable because the chosen bridge
|
||||
// doesn't carry that subnet on this host at all (on `strong`, vmbr0
|
||||
// only reaches 192.168.178.0/24 — a 192.168.8.0/24 gateway is simply
|
||||
// not on that L2 segment, so ARP for it silently gets nothing, no
|
||||
// matter which 192.168.8.x address is picked). A 1-2s ping from the
|
||||
// host itself catches this class of misconfiguration before any
|
||||
// container work happens.
|
||||
// HOST, on the SPECIFIC BRIDGE being requested, before spending 5+
|
||||
// minutes creating the container. This is the check that would have
|
||||
// caught the real TypeType failure immediately instead of after a
|
||||
// full provision attempt.
|
||||
//
|
||||
// Binding to the bridge (`ping -I <bridge>`) matters and was found
|
||||
// live: a plain unqualified `ping <gw>` from the host can succeed via
|
||||
// the host's own routing table (multiple routes, possibly through an
|
||||
// upstream router) even when the *container* — which only gets a
|
||||
// naive on-link default route via its bridge's veth — can never ARP
|
||||
// that gateway at all. Confirmed on `strong`: bare `ping 192.168.8.2`
|
||||
// succeeded (via the host's default route), but a container actually
|
||||
// attached to vmbr0 showed 100% packet loss trying to reach the same
|
||||
// address, because vmbr0 doesn't carry that subnet's L2 segment.
|
||||
// Binding to the bridge interface reproduces what the container will
|
||||
// actually experience, not what the host's broader routing table can
|
||||
// reach.
|
||||
if isStatic && cfg.GW != "" {
|
||||
pingOut, pingErr := sshExec(ctx, host, user, fmt.Sprintf("ping -c1 -W2 %s >/dev/null 2>&1 && echo REACHABLE || echo UNREACHABLE", cfg.GW))
|
||||
pingOut, pingErr := sshExec(ctx, host, user, fmt.Sprintf("ping -I %s -c1 -W2 %s >/dev/null 2>&1 && echo REACHABLE || echo UNREACHABLE", cfg.Bridge, cfg.GW))
|
||||
if pingErr != nil || !strings.Contains(pingOut, "REACHABLE") {
|
||||
msg := fmt.Sprintf(
|
||||
"gateway %s is not reachable from %s on bridge %s — this almost always means the bridge doesn't carry that subnet on this host (each bridge only reaches the network it's physically wired to). "+
|
||||
|
||||
Reference in New Issue
Block a user