mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-16 01:22:00 +00:00
fix(microvm): don't /32 AdvertiseIP via mgmt for internet-facing single-node
buildMicrovmNICs overrode resolveMgmtRoute's deliberate empty return
for the single-node internet-facing case by falling back to
(MgmtBridgeIP, AdvertiseIP). That installed a /32 route to AdvertiseIP
via the mgmt NIC inside the LB microvm. On single-node where the
AWSGW host IP equals the WAN client IP, the /32 also catches reply
traffic for inbound HTTP/TCP to the LB's floating IP — replies leave
via the mgmt NIC with src=data-NIC IP, the host kernel drops the
asymmetric flow, and same-chassis ingress hangs.
resolveMgmtRoute already encodes the correct policy:
- multi-node any scheme → explicit MgmtRoute{Gateway,Target}
- single-node internal → fallback to (MgmtBridgeIP, AdvertiseIP)
- single-node internet-facing → empty (reach AWSGW via VPC SNAT)
Honor it. Matches the legacy lbVMUserData path, whose own comment
flagged the stolen-WAN-return-path symptom this introduced.
Test covers all three branches.
This commit is contained in:
@@ -436,3 +436,35 @@ func TestBuildMicrovmNICs_ExtraENIs(t *testing.T) {
|
||||
assert.Equal(t, "02:aa:bb:cc:dd:02", nics[2].MAC)
|
||||
assert.False(t, nics[2].IsDefault)
|
||||
}
|
||||
|
||||
// TestBuildMicrovmNICs_MgmtRoute covers the same single-node/multi-node matrix
|
||||
// as TestLBVMUserData_InternalSingleNodeFallback: internet-facing single-node
|
||||
// must NOT emit a /32 via mgmt to AdvertiseIP because that steals the host's
|
||||
// WAN return path for reply traffic, breaking same-chassis ingress.
|
||||
func TestBuildMicrovmNICs_MgmtRoute(t *testing.T) {
|
||||
mk := func() *ELBv2ServiceImpl {
|
||||
return &ELBv2ServiceImpl{
|
||||
VPCService: nil,
|
||||
MgmtBridgeIP: "10.15.8.1",
|
||||
AdvertiseIP: "192.168.1.33",
|
||||
}
|
||||
}
|
||||
|
||||
internal := mk().buildMicrovmNICs("10.0.1.5", "02:aa:bb:cc:dd:01", "subnet-abc", "eni-abc", SchemeInternal, nil, testAccountID)
|
||||
require.Len(t, internal, 2)
|
||||
assert.Equal(t, "192.168.1.33", internal[1].RouteDst, "internal single-node forces /32 via mgmt")
|
||||
assert.Equal(t, "10.15.8.1", internal[1].RouteVia)
|
||||
|
||||
inet := mk().buildMicrovmNICs("10.0.1.5", "02:aa:bb:cc:dd:01", "subnet-abc", "eni-abc", SchemeInternetFacing, nil, testAccountID)
|
||||
require.Len(t, inet, 2)
|
||||
assert.Empty(t, inet[1].RouteDst, "internet-facing single-node must not /32 AdvertiseIP via mgmt")
|
||||
assert.Empty(t, inet[1].RouteVia)
|
||||
|
||||
multi := mk()
|
||||
multi.MgmtRouteGateway = "10.15.8.1"
|
||||
multi.MgmtRouteTarget = "10.15.8.100"
|
||||
nics := multi.buildMicrovmNICs("10.0.1.5", "02:aa:bb:cc:dd:01", "subnet-abc", "eni-abc", SchemeInternetFacing, nil, testAccountID)
|
||||
require.Len(t, nics, 2)
|
||||
assert.Equal(t, "10.15.8.100", nics[1].RouteDst, "multi-node uses explicit MgmtRouteTarget")
|
||||
assert.Equal(t, "10.15.8.1", nics[1].RouteVia)
|
||||
}
|
||||
|
||||
@@ -329,13 +329,10 @@ func (s *ELBv2ServiceImpl) buildMicrovmNICs(primaryIP, primaryMAC, primarySubnet
|
||||
|
||||
// NIC[1]: management NIC — daemon fills MAC/CIDR after IP allocation.
|
||||
// RouteDst = AWSGW IP (destination), RouteVia = br-mgmt IP (next-hop).
|
||||
// Microvm always uses mgmt for heartbeats regardless of scheme; VPC SNAT
|
||||
// is not reliable across all topologies for control-plane traffic.
|
||||
// Honor resolveMgmtRoute's deliberate empty return for single-node
|
||||
// internet-facing: adding a /32 to AdvertiseIP via mgmt steals the host's
|
||||
// WAN return path for reply traffic, breaking same-chassis ingress.
|
||||
mgmtRouteGW, mgmtRouteTarget := s.resolveMgmtRoute(scheme)
|
||||
if mgmtRouteGW == "" && s.MgmtBridgeIP != "" && s.AdvertiseIP != "" {
|
||||
mgmtRouteGW = s.MgmtBridgeIP
|
||||
mgmtRouteTarget = s.AdvertiseIP
|
||||
}
|
||||
nics = append(nics, NICConfig{
|
||||
IsDefault: false,
|
||||
RouteDst: mgmtRouteTarget, // AWSGW IP — destination to reach
|
||||
|
||||
Reference in New Issue
Block a user