Summary
Following up on #7884 (external-to-Pod flow support): there is no test coverage for flows whose source is a local hostNetwork Pod (i.e. a real process in the host network namespace using the Node's own addressing), as opposed to a simulated external client. Reading the code, I believe these flows do not take the FROM_EXTERNAL path that #7884 added, and are instead classified as INTER_NODE records that can never be correlated by the Flow Aggregator.
I have not confirmed this on a live cluster — see "What is not verified" below. Filing this mainly so the behavior gets pinned down by tests one way or the other.
All code references are to main @ 50e3fe8469a481fb205f6bceb3c31fd3707a2602.
What #7884 added
findFlowType in pkg/agent/flowexporter/destination.go:318-363 gained two branches:
if srcIsGw {
if conn.DestinationPodNamespace == "" {
return utils.FlowTypeUnsupported
}
// The source IP is the remote node's gateway: this is the destination-node half of an
// inter-node FROM_EXTERNAL connection. Export it as INTER_NODE [...]
return utils.FlowTypeInterNode
}
if !srcIsPod {
if dstIsPod {
// Any source that is not a Pod IP and not a gateway IP is treated as FromExternal.
// This covers external-to-Pod traffic (e.g. a host-network process on the node
// connecting to a local Pod via the node's transport IP rather than antrea-gw0).
return utils.FlowTypeFromExternal
}
return utils.FlowTypeUnsupported
}
Before #7884 both of these returned FlowTypeUnsupported, so neither shape was exported at all.
The comment on the second branch explicitly mentions the host-network case, but is conditioned on the source being the Node's transport IP rather than antrea-gw0. That condition looks load-bearing, and I do not think it holds for the common hostNetwork-Pod-to-Pod cases.
The gap
srcIsGw is true for the local gateway, not just remote ones. LookupIPInPodSubnets (pkg/agent/controller/noderoute/node_route_controller.go:822-846) finds the Pod subnet containing the IP and compares it against util.GetGatewayIPForPodPrefix(prefix). podSubnets is seeded with this Node's own PodIPv4CIDR/PodIPv6CIDR at controller construction (node_route_controller.go:145-154), so the local antrea-gw0 IP yields srcIsGw == true exactly like a remote gateway IP does.
antrea-gw0 carries the gateway IP with the Pod CIDR mask. allocateGatewayAddresses builds gwIP := &net.IPNet{IP: ip.NextIP(subnetID), Mask: localSubnet.Mask} (pkg/agent/agent.go:1273) and assigns it to the gateway link, so there is a connected route for the local Pod CIDR on antrea-gw0. For remote Pod CIDRs in encap mode, AddRoutes installs the route with LinkIndex = gateway and Gw = peer gateway IP and no explicit Src (pkg/agent/route/route_linux.go:2028-2031); in WireGuard mode Src is set explicitly to the local gateway IP (route_linux.go:2008-2016).
If the kernel therefore selects the antrea-gw0 IP as the source address for host-originated traffic to a Pod IP (this is the part I have not verified empirically), then:
- hostNetwork Pod → Pod, same Node:
srcIsGw is true and the destination Pod is local, so DestinationPodNamespace != "" → INTER_NODE. The !srcIsPod / FROM_EXTERNAL branch is never reached.
- hostNetwork Pod → Pod, different Node: on the source Node the destination Pod is remote, so
DestinationPodNamespace == "" → FlowTypeUnsupported, nothing exported. On the destination Node the source is the remote gateway IP, so again → INTER_NODE.
In both cases the exported record has DestinationPodName set and SourcePodName empty. In the Flow Aggregator that record is isRecordFromDst (pkg/flowaggregator/intermediate/aggregate.go:760-762) and isCorrelationRequired returns true for INTER_NODE (aggregate.go:806-813), so it waits for a source-Node half that is never produced.
The different-Node case is the more interesting one: on the destination Node such a record is indistinguishable from the destination half of a genuine inter-Node external-to-Pod flow — which is precisely the record shape the srcIsGw branch was added to produce — but it has no peer to merge with.
The source Pod is unidentifiable regardless of flow type. The Pod store filters out hostNetwork Pods (pkg/util/objectstore/podstore.go:49):
return !pod.Spec.HostNetwork && !k8s.IsPodTerminated(pod)
This is the same store used by the agent (fillPodInfo) and by the Flow Aggregator (fillK8sMetadata), so sourcePodName/sourcePodNamespace will be empty for these flows no matter which branch they take. Any test must assert on flow type and destination metadata, not on source Pod identity.
Existing coverage
e2e: testExternalToPodFlows has four subtests — "Connection to source node", "Connection to destination node", "NodePortExternalTrafficPolicyLocal", "NodePortLocal". All four obtain their client through createExternalToPodConnection / createNPLConnection (test/e2e/flowaggregator_test.go:2069 and :2124), which create a privileged hostNetwork Pod only as a vehicle for getCommandInFakeExternalNetwork, then curl from inside that fake netns. The source IP is a randomly generated off-cluster address (randExternalSubnet) and the destination is always a NodePort on the Node IP. Nothing exercises a host-network process using the Node's or the gateway's own IP as the actual source toward a Pod.
unit: TestDestination_findFlowType in pkg/agent/flowexporter/destination_test.go does cover both branches as table entries — "Non-Pod source (e.g. Node host IP) to Pod - FromExternal" (:445) and "Source is gateway with destination Pod namespace - InterNode (destination-node from-external)" (:415). But these feed findFlowType a hand-built Connection, so they assert the branch logic and not which branch a real hostNetwork Pod flow actually lands in.
Proposed tests
Add subtests under testExternalToPodFlows (or a sibling function) using a plain hostNetwork Pod — NewPodBuilder(...).InHostNetwork() without the fake-netns wrapper:
- hostNetwork Pod → regular Pod, same Node, direct Pod IP.
- hostNetwork Pod → regular Pod, different Node, direct Pod IP.
- hostNetwork Pod → Service (ClusterIP and/or NodePort) → regular Pod. This is the only one of the three that reaches
FromExternalCorrelator, and the only one where the source might plausibly be the transport IP rather than the gateway IP.
Rather than assuming the source address, the test should discover it — e.g. run ip route get <podIP> inside the hostNetwork Pod and parse the src field, then use that when filtering collector output. That keeps the test correct across encap/noEncap/WireGuard instead of baking one routing assumption into an assertion.
What is not verified
- I have not run any of this on a live cluster. The source-address claim is inferred from the route/address configuration cited above, not observed.
- Only the encap and WireGuard paths in
AddRoutes were examined. noEncap/hybrid, and Windows, may select the transport IP instead and therefore may genuinely land on the FROM_EXTERNAL branch.
- I did not trace what the Flow Aggregator ultimately emits for the uncorrelated
INTER_NODE record after the correlation timeout, or with which fields populated.
Open question
If the analysis holds, what should these flows be? FROM_EXTERNAL matches the fact that the source is not a Pod the exporter can identify, but the traffic is not external to the cluster either, and the source Node is known even though the source Pod is not. Worth deciding before writing the assertions, since the tests will either pin current behavior or fail until findFlowType is changed.
Summary
Following up on #7884 (external-to-Pod flow support): there is no test coverage for flows whose source is a local hostNetwork Pod (i.e. a real process in the host network namespace using the Node's own addressing), as opposed to a simulated external client. Reading the code, I believe these flows do not take the
FROM_EXTERNALpath that #7884 added, and are instead classified asINTER_NODErecords that can never be correlated by the Flow Aggregator.I have not confirmed this on a live cluster — see "What is not verified" below. Filing this mainly so the behavior gets pinned down by tests one way or the other.
All code references are to
main@50e3fe8469a481fb205f6bceb3c31fd3707a2602.What #7884 added
findFlowTypeinpkg/agent/flowexporter/destination.go:318-363gained two branches:Before #7884 both of these returned
FlowTypeUnsupported, so neither shape was exported at all.The comment on the second branch explicitly mentions the host-network case, but is conditioned on the source being the Node's transport IP rather than antrea-gw0. That condition looks load-bearing, and I do not think it holds for the common hostNetwork-Pod-to-Pod cases.
The gap
srcIsGwis true for the local gateway, not just remote ones.LookupIPInPodSubnets(pkg/agent/controller/noderoute/node_route_controller.go:822-846) finds the Pod subnet containing the IP and compares it againstutil.GetGatewayIPForPodPrefix(prefix).podSubnetsis seeded with this Node's ownPodIPv4CIDR/PodIPv6CIDRat controller construction (node_route_controller.go:145-154), so the localantrea-gw0IP yieldssrcIsGw == trueexactly like a remote gateway IP does.antrea-gw0 carries the gateway IP with the Pod CIDR mask.
allocateGatewayAddressesbuildsgwIP := &net.IPNet{IP: ip.NextIP(subnetID), Mask: localSubnet.Mask}(pkg/agent/agent.go:1273) and assigns it to the gateway link, so there is a connected route for the local Pod CIDR onantrea-gw0. For remote Pod CIDRs in encap mode,AddRoutesinstalls the route withLinkIndex= gateway andGw= peer gateway IP and no explicitSrc(pkg/agent/route/route_linux.go:2028-2031); in WireGuard modeSrcis set explicitly to the local gateway IP (route_linux.go:2008-2016).If the kernel therefore selects the antrea-gw0 IP as the source address for host-originated traffic to a Pod IP (this is the part I have not verified empirically), then:
srcIsGwis true and the destination Pod is local, soDestinationPodNamespace != ""→INTER_NODE. The!srcIsPod/FROM_EXTERNALbranch is never reached.DestinationPodNamespace == ""→FlowTypeUnsupported, nothing exported. On the destination Node the source is the remote gateway IP, so again →INTER_NODE.In both cases the exported record has
DestinationPodNameset andSourcePodNameempty. In the Flow Aggregator that record isisRecordFromDst(pkg/flowaggregator/intermediate/aggregate.go:760-762) andisCorrelationRequiredreturns true forINTER_NODE(aggregate.go:806-813), so it waits for a source-Node half that is never produced.The different-Node case is the more interesting one: on the destination Node such a record is indistinguishable from the destination half of a genuine inter-Node external-to-Pod flow — which is precisely the record shape the
srcIsGwbranch was added to produce — but it has no peer to merge with.The source Pod is unidentifiable regardless of flow type. The Pod store filters out hostNetwork Pods (
pkg/util/objectstore/podstore.go:49):This is the same store used by the agent (
fillPodInfo) and by the Flow Aggregator (fillK8sMetadata), sosourcePodName/sourcePodNamespacewill be empty for these flows no matter which branch they take. Any test must assert on flow type and destination metadata, not on source Pod identity.Existing coverage
e2e:
testExternalToPodFlowshas four subtests — "Connection to source node", "Connection to destination node", "NodePortExternalTrafficPolicyLocal", "NodePortLocal". All four obtain their client throughcreateExternalToPodConnection/createNPLConnection(test/e2e/flowaggregator_test.go:2069and:2124), which create a privileged hostNetwork Pod only as a vehicle forgetCommandInFakeExternalNetwork, then curl from inside that fake netns. The source IP is a randomly generated off-cluster address (randExternalSubnet) and the destination is always a NodePort on the Node IP. Nothing exercises a host-network process using the Node's or the gateway's own IP as the actual source toward a Pod.unit:
TestDestination_findFlowTypeinpkg/agent/flowexporter/destination_test.godoes cover both branches as table entries —"Non-Pod source (e.g. Node host IP) to Pod - FromExternal"(:445) and"Source is gateway with destination Pod namespace - InterNode (destination-node from-external)"(:415). But these feedfindFlowTypea hand-builtConnection, so they assert the branch logic and not which branch a real hostNetwork Pod flow actually lands in.Proposed tests
Add subtests under
testExternalToPodFlows(or a sibling function) using a plain hostNetwork Pod —NewPodBuilder(...).InHostNetwork()without the fake-netns wrapper:FromExternalCorrelator, and the only one where the source might plausibly be the transport IP rather than the gateway IP.Rather than assuming the source address, the test should discover it — e.g. run
ip route get <podIP>inside the hostNetwork Pod and parse thesrcfield, then use that when filtering collector output. That keeps the test correct across encap/noEncap/WireGuard instead of baking one routing assumption into an assertion.What is not verified
AddRouteswere examined. noEncap/hybrid, and Windows, may select the transport IP instead and therefore may genuinely land on theFROM_EXTERNALbranch.INTER_NODErecord after the correlation timeout, or with which fields populated.Open question
If the analysis holds, what should these flows be?
FROM_EXTERNALmatches the fact that the source is not a Pod the exporter can identify, but the traffic is not external to the cluster either, and the source Node is known even though the source Pod is not. Worth deciding before writing the assertions, since the tests will either pin current behavior or fail untilfindFlowTypeis changed.