Skip to content

flowexporter: hostNetwork Pod to Pod flows may be misclassified as INTER_NODE and are untested #8260

Description

@antoninbas

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:

  1. hostNetwork Pod → regular Pod, same Node, direct Pod IP.
  2. hostNetwork Pod → regular Pod, different Node, direct Pod IP.
  3. 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions