mirror of
https://github.com/kata-containers/kata-containers.git
synced 2026-04-26 10:32:28 +00:00
network: preseed default-gateway neighbor
This change mirrors host networking into the guest as before, but now also includes the default gateway neighbor entry for each interface. Pods using overlay/synthetic gateways (e.g., 169.254.1.1) can hit a first-connect race while the guest performs the initial ARP. Preseeding the gateway neighbor removes that latency and makes early connections (e.g., to the API Service) deterministic. Signed-off-by: Saul Paredes <saulparedes@microsoft.com>
This commit is contained in:
@@ -315,13 +315,16 @@ func generateVCNetworkStructures(ctx context.Context, endpoints []Endpoint) ([]*
|
||||
routes = append(routes, &r)
|
||||
}
|
||||
|
||||
for _, neigh := range endpoint.Properties().Neighbors {
|
||||
var n pbTypes.ARPNeighbor
|
||||
gatewaySet := gatewaySetFromRoutes(endpoint.Properties().Routes)
|
||||
|
||||
if !validGuestNeighbor(neigh) {
|
||||
for _, neigh := range endpoint.Properties().Neighbors {
|
||||
|
||||
if !validGuestNeighbor(neigh, gatewaySet) {
|
||||
continue
|
||||
}
|
||||
|
||||
var n pbTypes.ARPNeighbor
|
||||
|
||||
n.Device = endpoint.Name()
|
||||
n.State = int32(neigh.State)
|
||||
n.Flags = int32(neigh.Flags)
|
||||
|
||||
@@ -100,6 +100,10 @@ func validGuestRoute(route netlink.Route) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func validGuestNeighbor(route netlink.Neigh) bool {
|
||||
func validGuestNeighbor(neigh netlink.Neigh, gatewaySet map[string]struct{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func gatewaySetFromRoutes(routes []netlink.Route) map[string]struct{} {
|
||||
return make(map[string]struct{})
|
||||
}
|
||||
|
||||
@@ -1735,7 +1735,39 @@ func validGuestRoute(route netlink.Route) bool {
|
||||
return route.Protocol != unix.RTPROT_KERNEL
|
||||
}
|
||||
|
||||
func validGuestNeighbor(neigh netlink.Neigh) bool {
|
||||
// We add only static ARP entries
|
||||
return neigh.State == netlink.NUD_PERMANENT
|
||||
// neighbor is valid if it is static or a default-gateway
|
||||
func validGuestNeighbor(neigh netlink.Neigh, gatewaySet map[string]struct{}) bool {
|
||||
// need a MAC for the guest
|
||||
if neigh.HardwareAddr == nil {
|
||||
return false
|
||||
}
|
||||
// Keep all static entries
|
||||
if neigh.State == netlink.NUD_PERMANENT {
|
||||
return true
|
||||
}
|
||||
// Gateway-only exception: allow the default-gateway IP:
|
||||
// On some setups, the pod subnet gateway does not appear in the host ARP cache as a static entry.
|
||||
// On these setups an ARP request storm happens when many Kata PODs are started at the same time and they all look for the gateway MAC address.
|
||||
// This forces the gateway to churn a lot of ARP requests and render the ARP request full, hence dropping some ARP requests.
|
||||
// Manually pre-populating the ARP entry in the UVM guest ARP cache for that gateway solves that problem.
|
||||
_, isGw := gatewaySet[neigh.IP.String()]
|
||||
return isGw && neigh.State == netlink.NUD_REACHABLE
|
||||
}
|
||||
|
||||
// helper: default routes => set of gateway IP strings
|
||||
func gatewaySetFromRoutes(routes []netlink.Route) map[string]struct{} {
|
||||
gatewaySet := make(map[string]struct{})
|
||||
for _, route := range routes {
|
||||
if route.Gw == nil {
|
||||
continue
|
||||
}
|
||||
if route.Dst == nil {
|
||||
gatewaySet[route.Gw.String()] = struct{}{}
|
||||
continue
|
||||
}
|
||||
if ones, _ := route.Dst.Mask.Size(); ones == 0 { // 0.0.0.0/0 or ::/0
|
||||
gatewaySet[route.Gw.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
return gatewaySet
|
||||
}
|
||||
|
||||
@@ -381,3 +381,236 @@ func TestAddEndpoints_Dan(t *testing.T) {
|
||||
assert.Equal(t, ep.Type(), VfioEndpointType)
|
||||
assert.Equal(t, ep.PciPath().String(), "")
|
||||
}
|
||||
|
||||
func TestValidGuestNeighbor(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Setup gateway set with a known gateway IP
|
||||
gatewaySet := map[string]struct{}{
|
||||
"10.0.0.1": {},
|
||||
}
|
||||
|
||||
neighborMAC, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
neighbor netlink.Neigh
|
||||
gateways map[string]struct{}
|
||||
expected bool
|
||||
desc string
|
||||
}{
|
||||
{
|
||||
name: "PermanentNeighborAlwaysValid",
|
||||
neighbor: netlink.Neigh{
|
||||
IP: net.ParseIP("192.168.1.1"),
|
||||
HardwareAddr: neighborMAC,
|
||||
State: netlink.NUD_PERMANENT,
|
||||
},
|
||||
gateways: gatewaySet,
|
||||
expected: true,
|
||||
desc: "PERMANENT neighbors should always be included regardless of gateway status",
|
||||
},
|
||||
{
|
||||
name: "NonPermanentGatewayReachable",
|
||||
neighbor: netlink.Neigh{
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
HardwareAddr: neighborMAC,
|
||||
State: netlink.NUD_REACHABLE,
|
||||
},
|
||||
gateways: gatewaySet,
|
||||
expected: true,
|
||||
desc: "Non-PERMANENT gateway neighbor in REACHABLE state should be included",
|
||||
},
|
||||
{
|
||||
name: "NonPermanentGatewayNotReachable",
|
||||
neighbor: netlink.Neigh{
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
HardwareAddr: neighborMAC,
|
||||
State: netlink.NUD_STALE,
|
||||
},
|
||||
gateways: gatewaySet,
|
||||
expected: false,
|
||||
desc: "Non-PERMANENT gateway neighbor in STALE state should be filtered out",
|
||||
},
|
||||
{
|
||||
name: "NonPermanentNonGateway",
|
||||
neighbor: netlink.Neigh{
|
||||
IP: net.ParseIP("192.168.1.1"),
|
||||
HardwareAddr: neighborMAC,
|
||||
State: netlink.NUD_REACHABLE,
|
||||
},
|
||||
gateways: gatewaySet,
|
||||
expected: false,
|
||||
desc: "Non-PERMANENT non-gateway neighbor should be filtered out even if REACHABLE",
|
||||
},
|
||||
{
|
||||
name: "MissingHardwareAddr",
|
||||
neighbor: netlink.Neigh{
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
State: netlink.NUD_REACHABLE,
|
||||
},
|
||||
gateways: gatewaySet,
|
||||
expected: false,
|
||||
desc: "Any neighbor without a MAC address should be filtered out",
|
||||
},
|
||||
{
|
||||
name: "PermanentNeighborWithoutMAC",
|
||||
neighbor: netlink.Neigh{
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
State: netlink.NUD_PERMANENT,
|
||||
},
|
||||
gateways: gatewaySet,
|
||||
expected: false,
|
||||
desc: "Even PERMANENT neighbors need a MAC address",
|
||||
},
|
||||
{
|
||||
name: "OtherTransientStateAsGateway",
|
||||
neighbor: netlink.Neigh{
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
HardwareAddr: neighborMAC,
|
||||
State: netlink.NUD_DELAY,
|
||||
},
|
||||
gateways: gatewaySet,
|
||||
expected: false,
|
||||
desc: "Transient states like DELAY should be filtered even for gateways",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := validGuestNeighbor(tc.neighbor, tc.gateways)
|
||||
assert.Equal(tc.expected, result, tc.desc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewaySetFromRoutes(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
gwIPv4 := net.ParseIP("10.0.0.1")
|
||||
gwIPv6 := net.ParseIP("fe80::1")
|
||||
nonGwIP := net.ParseIP("192.168.1.1")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
routes []netlink.Route
|
||||
expectedGateways map[string]struct{}
|
||||
desc string
|
||||
}{
|
||||
{
|
||||
name: "DefaultRouteIPv4",
|
||||
routes: []netlink.Route{
|
||||
{
|
||||
Dst: nil, // nil destination means default route
|
||||
Gw: gwIPv4,
|
||||
},
|
||||
},
|
||||
expectedGateways: map[string]struct{}{
|
||||
"10.0.0.1": {},
|
||||
},
|
||||
desc: "IPv4 default route (nil destination) should add gateway to set",
|
||||
},
|
||||
{
|
||||
name: "DefaultRouteIPv6",
|
||||
routes: []netlink.Route{
|
||||
{
|
||||
Dst: nil,
|
||||
Gw: gwIPv6,
|
||||
},
|
||||
},
|
||||
expectedGateways: map[string]struct{}{
|
||||
"fe80::1": {},
|
||||
},
|
||||
desc: "IPv6 default route (nil destination) should add gateway to set",
|
||||
},
|
||||
{
|
||||
name: "ExplicitDefaultRouteIPv4",
|
||||
routes: []netlink.Route{
|
||||
{
|
||||
Dst: &net.IPNet{
|
||||
IP: net.IPv4(0, 0, 0, 0),
|
||||
Mask: net.CIDRMask(0, 32),
|
||||
},
|
||||
Gw: gwIPv4,
|
||||
},
|
||||
},
|
||||
expectedGateways: map[string]struct{}{
|
||||
"10.0.0.1": {},
|
||||
},
|
||||
desc: "Explicit IPv4 default route (0.0.0.0/0) should add gateway to set",
|
||||
},
|
||||
{
|
||||
name: "ExplicitDefaultRouteIPv6",
|
||||
routes: []netlink.Route{
|
||||
{
|
||||
Dst: &net.IPNet{
|
||||
IP: net.ParseIP("::"),
|
||||
Mask: net.CIDRMask(0, 128),
|
||||
},
|
||||
Gw: gwIPv6,
|
||||
},
|
||||
},
|
||||
expectedGateways: map[string]struct{}{
|
||||
"fe80::1": {},
|
||||
},
|
||||
desc: "Explicit IPv6 default route (::/0) should add gateway to set",
|
||||
},
|
||||
{
|
||||
name: "NonDefaultRouteFiltered",
|
||||
routes: []netlink.Route{
|
||||
{
|
||||
Dst: &net.IPNet{
|
||||
IP: net.IPv4(172, 16, 0, 0),
|
||||
Mask: net.CIDRMask(16, 32),
|
||||
},
|
||||
Gw: nonGwIP,
|
||||
},
|
||||
},
|
||||
expectedGateways: map[string]struct{}{},
|
||||
desc: "Non-default routes should not add gateway to set",
|
||||
},
|
||||
{
|
||||
name: "RouteWithoutGateway",
|
||||
routes: []netlink.Route{
|
||||
{
|
||||
Dst: nil,
|
||||
Gw: nil,
|
||||
},
|
||||
},
|
||||
expectedGateways: map[string]struct{}{},
|
||||
desc: "Routes without a gateway should be skipped",
|
||||
},
|
||||
{
|
||||
name: "MultipleDefaultRoutes",
|
||||
routes: []netlink.Route{
|
||||
{
|
||||
Dst: nil,
|
||||
Gw: gwIPv4,
|
||||
},
|
||||
{
|
||||
Dst: nil,
|
||||
Gw: gwIPv6,
|
||||
},
|
||||
{
|
||||
Dst: &net.IPNet{
|
||||
IP: net.IPv4(172, 16, 0, 0),
|
||||
Mask: net.CIDRMask(16, 32),
|
||||
},
|
||||
Gw: nonGwIP,
|
||||
},
|
||||
},
|
||||
expectedGateways: map[string]struct{}{
|
||||
"10.0.0.1": {},
|
||||
"fe80::1": {},
|
||||
},
|
||||
desc: "Multiple default routes should populate the set; non-default routes should be ignored",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := gatewaySetFromRoutes(tc.routes)
|
||||
assert.Equal(tc.expectedGateways, result, tc.desc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user