Skip to content

Commit 18d7d1c

Browse files
authored
Merge pull request #5093 from ricky-rav/SDN-5345
SDN-5345: allow localnet -> host network on the same node
2 parents 945c6c4 + b1525c3 commit 18d7d1c

File tree

3 files changed

+161
-58
lines changed

3 files changed

+161
-58
lines changed

go-controller/pkg/node/gateway_shared_intf.go

Lines changed: 66 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,9 +1850,10 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin
18501850
defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, config.Default.ConntrackZone,
18511851
netConfig.masqCTMark, ofPortPhys))
18521852

1853-
// Allow OVN->Host traffic on the same node
1853+
// Allow (a) OVN->host traffic on the same node
1854+
// (b) host->host traffic on the same node
18541855
if config.Gateway.Mode == config.GatewayModeShared || config.Gateway.Mode == config.GatewayModeLocal {
1855-
dftFlows = append(dftFlows, ovnToHostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, false)...)
1856+
dftFlows = append(dftFlows, hostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, false)...)
18561857
}
18571858
} else {
18581859
// for UDN we additionally SNAT the packet from masquerade IP -> node IP
@@ -1946,9 +1947,10 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin
19461947
"actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:%s",
19471948
defaultOpenFlowCookie, netConfig.ofPortPatch, bridgeMacAddress, config.Default.ConntrackZone, netConfig.masqCTMark, ofPortPhys))
19481949

1949-
// Allow OVN->Host traffic on the same node
1950+
// Allow (a) OVN->host traffic on the same node
1951+
// (b) host->host traffic on the same node
19501952
if config.Gateway.Mode == config.GatewayModeShared || config.Gateway.Mode == config.GatewayModeLocal {
1951-
dftFlows = append(dftFlows, ovnToHostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, true)...)
1953+
dftFlows = append(dftFlows, hostNetworkNormalActionFlows(netConfig, bridgeMacAddress, hostSubnets, true)...)
19521954
}
19531955
} else {
19541956
// for UDN we additionally SNAT the packet from masquerade IP -> node IP
@@ -2128,23 +2130,15 @@ func commonFlows(hostSubnets []*net.IPNet, bridge *bridgeConfiguration) ([]strin
21282130
return dftFlows, nil
21292131
}
21302132

2131-
// ovnToHostNetworkNormalActionFlows returns the flows that allow IP{v4,v6} traffic from the OVN network to the host network
2132-
// when the destination is on the same node as the sender. This is necessary for pods in the default network to reach
2133-
// localnet pods on the same node, when the localnet is mapped to breth0. The expected srcMAC is the MAC address of breth0
2134-
// and the expected hostSubnets is the host subnets found on the node primary interface.
2135-
func ovnToHostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC string, hostSubnets []*net.IPNet, isV6 bool) []string {
2136-
var inPort, ctMark, ipFamily, ipFamilyDest string
2133+
// hostNetworkNormalActionFlows returns the flows that allow IP{v4,v6} traffic:
2134+
// a. from pods in the OVN network to pods in a localnet network, on the same node
2135+
// b. from pods on the host to pods in a localnet network, on the same node
2136+
// when the localnet is mapped to breth0.
2137+
// The expected srcMAC is the MAC address of breth0 and the expected hostSubnets is the host subnets found on the node
2138+
// primary interface.
2139+
func hostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC string, hostSubnets []*net.IPNet, isV6 bool) []string {
21372140
var flows []string
2138-
2139-
if config.Gateway.Mode == config.GatewayModeShared {
2140-
inPort = netConfig.ofPortPatch
2141-
ctMark = netConfig.masqCTMark
2142-
} else if config.Gateway.Mode == config.GatewayModeLocal {
2143-
inPort = "LOCAL"
2144-
ctMark = ctMarkHost
2145-
} else {
2146-
return nil
2147-
}
2141+
var ipFamily, ipFamilyDest string
21482142

21492143
if isV6 {
21502144
ipFamily = "ipv6"
@@ -2154,38 +2148,69 @@ func ovnToHostNetworkNormalActionFlows(netConfig *bridgeUDNConfiguration, srcMAC
21542148
ipFamilyDest = "nw_dst"
21552149
}
21562150

2151+
formatFlow := func(inPort, destIP, ctMark string) string {
2152+
// Matching IP traffic will be handled by the bridge instead of being output directly
2153+
// to the NIC by the existing flow at prio=100.
2154+
flowTemplate := "cookie=%s, priority=102, in_port=%s, dl_src=%s, %s, %s=%s, " +
2155+
"actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL"
2156+
return fmt.Sprintf(flowTemplate,
2157+
defaultOpenFlowCookie,
2158+
inPort,
2159+
srcMAC,
2160+
ipFamily,
2161+
ipFamilyDest,
2162+
destIP,
2163+
config.Default.ConntrackZone,
2164+
ctMark)
2165+
}
2166+
2167+
// Traffic path (a): OVN->localnet for shared gw mode
2168+
if config.Gateway.Mode == config.GatewayModeShared {
2169+
for _, hostSubnet := range hostSubnets {
2170+
if utilnet.IsIPv6(hostSubnet.IP) != isV6 {
2171+
continue
2172+
}
2173+
flows = append(flows, formatFlow(netConfig.ofPortPatch, hostSubnet.String(), netConfig.masqCTMark))
2174+
}
2175+
}
2176+
2177+
// Traffic path (a): OVN->localnet for local gw mode
2178+
// Traffic path (b): host->localnet for both gw modes
21572179
for _, hostSubnet := range hostSubnets {
2158-
if (hostSubnet.IP.To4() == nil) != isV6 {
2180+
if utilnet.IsIPv6(hostSubnet.IP) != isV6 {
21592181
continue
21602182
}
2161-
// IP traffic from the OVN network to the host network should be handled normally by the bridge instead of
2162-
// being output directly to the NIC by the existing flow at prio=100.
2163-
flows = append(flows,
2164-
fmt.Sprintf("cookie=%s, priority=102, in_port=%s, dl_src=%s, %s, %s=%s, "+
2165-
"actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL",
2183+
flows = append(flows, formatFlow("LOCAL", hostSubnet.String(), ctMarkHost))
2184+
}
2185+
2186+
if isV6 {
2187+
// IPv6 neighbor discovery uses ICMPv6 messages sent to a special destination (ff02::1:ff00:0/104)
2188+
// that is unrelated to the host subnets matched in the prio=102 flow above.
2189+
// Allow neighbor discovery by matching against ICMP type and ingress port.
2190+
formatICMPFlow := func(inPort, ctMark string, icmpType int) string {
2191+
icmpFlowTemplate := "cookie=%s, priority=102, in_port=%s, dl_src=%s, icmp6, icmpv6_type=%d, " +
2192+
"actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL"
2193+
return fmt.Sprintf(icmpFlowTemplate,
21662194
defaultOpenFlowCookie,
21672195
inPort,
21682196
srcMAC,
2169-
ipFamily,
2170-
ipFamilyDest,
2171-
hostSubnet.String(),
2197+
icmpType,
21722198
config.Default.ConntrackZone,
2173-
ctMark))
2174-
}
2199+
ctMark)
2200+
}
21752201

2176-
if isV6 {
2177-
// Neighbor discovery in IPv6 happens through ICMPv6 messages to a special destination (ff02::1:ff00:0/104),
2178-
// which has nothing to do with the host subnets we're matching against in the flow above at prio=102.
2179-
// Let's allow neighbor discovery by matching against icmp type and in_port.
21802202
for _, icmpType := range []int{types.NeighborSolicitationICMPType, types.NeighborAdvertisementICMPType} {
2181-
flows = append(flows,
2182-
fmt.Sprintf("cookie=%s, priority=102, in_port=%s, dl_src=%s, icmp6, icmpv6_type=%d, "+
2183-
"actions=ct(commit, zone=%d, exec(set_field:%s->ct_mark)), output:NORMAL",
2184-
defaultOpenFlowCookie, inPort, srcMAC, icmpType,
2185-
config.Default.ConntrackZone, ctMark))
2203+
// Traffic path (a) for ICMP: OVN-> localnet for shared gw mode
2204+
if config.Gateway.Mode == config.GatewayModeShared {
2205+
flows = append(flows,
2206+
formatICMPFlow(netConfig.ofPortPatch, netConfig.masqCTMark, icmpType))
2207+
}
2208+
2209+
// Traffic path (a) for ICMP: OVN->localnet for local gw mode
2210+
// Traffic path (b) for ICMP: host->localnet for both gw modes
2211+
flows = append(flows, formatICMPFlow("LOCAL", ctMarkHost, icmpType))
21862212
}
21872213
}
2188-
21892214
return flows
21902215
}
21912216

test/e2e/multihoming.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,17 +325,31 @@ var _ = Describe("Multi Homing", func() {
325325
kickstartPod(cs, clientPodConfig)
326326

327327
// Check that the client pod can reach the server pod on the server localnet interface
328-
serverIPs, err := podIPsForAttachment(cs, f.Namespace.Name, serverPod.GetName(), netConfig.name)
328+
var serverIPs []string
329+
if serverPodConfig.hostNetwork {
330+
serverIPs, err = podIPsFromStatus(cs, serverPodConfig.namespace, serverPodConfig.name)
331+
} else {
332+
serverIPs, err = podIPsForAttachment(cs, serverPod.Namespace, serverPod.Name, netConfig.name)
333+
334+
}
329335
Expect(err).NotTo(HaveOccurred())
336+
330337
for _, serverIP := range serverIPs {
331338
By(fmt.Sprintf("asserting the *client* can contact the server pod exposed endpoint: %q on port %q", serverIP, port))
339+
curlArgs := []string{}
340+
pingArgs := []string{}
341+
if clientPodConfig.attachments != nil {
342+
// When the client is attached to a localnet, send probes from the localnet interface
343+
curlArgs = []string{"--interface", "net1"}
344+
pingArgs = []string{"-I", "net1"}
345+
}
332346
Eventually(func() error {
333-
return reachServerPodFromClient(cs, serverPodConfig, clientPodConfig, serverIP, port)
347+
return reachServerPodFromClient(cs, serverPodConfig, clientPodConfig, serverIP, port, curlArgs...)
334348
}, 2*time.Minute, 6*time.Second).Should(Succeed())
335349

336350
By(fmt.Sprintf("asserting the *client* can ping the server pod exposed endpoint: %q", serverIP))
337351
Eventually(func() error {
338-
return pingServerPodFromClient(cs, serverPodConfig, clientPodConfig, serverIP)
352+
return pingServerPodFromClient(cs, serverPodConfig, clientPodConfig, serverIP, pingArgs...)
339353
}, 2*time.Minute, 6*time.Second).Should(Succeed())
340354
}
341355
},
@@ -383,6 +397,52 @@ var _ = Describe("Multi Homing", func() {
383397
},
384398
Label("BUG", "OCPBUGS-43004"),
385399
),
400+
ginkgo.Entry(
401+
"can reach a host-networked pod on a different node",
402+
networkAttachmentConfigParams{
403+
name: secondaryNetworkName,
404+
topology: "localnet",
405+
},
406+
podConfiguration{ // client on localnet
407+
attachments: []nadapi.NetworkSelectionElement{{
408+
Name: secondaryNetworkName,
409+
}},
410+
name: clientPodName,
411+
nodeSelector: map[string]string{nodeHostnameKey: workerOneNodeName},
412+
isPrivileged: true,
413+
needsIPRequestFromHostSubnet: true,
414+
},
415+
podConfiguration{ // server on default network, pod is host-networked
416+
name: podName,
417+
containerCmd: httpServerContainerCmd(port),
418+
nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName},
419+
hostNetwork: true,
420+
},
421+
Label("STORY", "SDN-5345"),
422+
),
423+
ginkgo.Entry(
424+
"can reach a host-networked pod on the same node",
425+
networkAttachmentConfigParams{
426+
name: secondaryNetworkName,
427+
topology: "localnet",
428+
},
429+
podConfiguration{ // client on localnet
430+
attachments: []nadapi.NetworkSelectionElement{{
431+
Name: secondaryNetworkName,
432+
}},
433+
name: clientPodName,
434+
nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName},
435+
isPrivileged: true,
436+
needsIPRequestFromHostSubnet: true,
437+
},
438+
podConfiguration{ // server on default network, pod is host-networked
439+
name: podName,
440+
containerCmd: httpServerContainerCmd(port),
441+
nodeSelector: map[string]string{nodeHostnameKey: workerTwoNodeName},
442+
hostNetwork: true,
443+
},
444+
Label("STORY", "SDN-5345"),
445+
),
386446
)
387447
})
388448

@@ -842,7 +902,6 @@ var _ = Describe("Multi Homing", func() {
842902
Context("localnet OVN-K secondary network", func() {
843903
const (
844904
clientPodName = "client-pod"
845-
nodeHostnameKey = "kubernetes.io/hostname"
846905
servicePort = 9000
847906
dockerNetworkName = "underlay"
848907
underlayServiceIP = "60.128.0.1"

test/e2e/multihoming_utils.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ type podConfiguration struct {
161161
isPrivileged bool
162162
labels map[string]string
163163
requiresExtraNamespace bool
164+
hostNetwork bool
164165
needsIPRequestFromHostSubnet bool
165166
}
166167

@@ -171,6 +172,7 @@ func generatePodSpec(config podConfiguration) *v1.Pod {
171172
}
172173
podSpec.Spec.NodeSelector = config.nodeSelector
173174
podSpec.Labels = config.labels
175+
podSpec.Spec.HostNetwork = config.hostNetwork
174176
if config.isPrivileged {
175177
podSpec.Spec.Containers[0].SecurityContext.Privileged = ptr.To(true)
176178
} else {
@@ -253,17 +255,19 @@ func inRange(cidr string, ip string) error {
253255
return fmt.Errorf("ip [%s] is NOT in range %s", ip, cidr)
254256
}
255257

256-
func connectToServer(clientPodConfig podConfiguration, serverIP string, port int) error {
257-
_, err := e2ekubectl.RunKubectl(
258-
clientPodConfig.namespace,
258+
func connectToServer(clientPodConfig podConfiguration, serverIP string, port int, args ...string) error {
259+
target := net.JoinHostPort(serverIP, fmt.Sprintf("%d", port))
260+
baseArgs := []string{
259261
"exec",
260262
clientPodConfig.name,
261263
"--",
262264
"curl",
263265
"--connect-timeout",
264266
"2",
265-
net.JoinHostPort(serverIP, fmt.Sprintf("%d", port)),
266-
)
267+
}
268+
baseArgs = append(baseArgs, args...)
269+
270+
_, err := e2ekubectl.RunKubectl(clientPodConfig.namespace, append(baseArgs, target)...)
267271
return err
268272
}
269273

@@ -308,16 +312,19 @@ func getSecondaryInterfaceMTU(clientPodConfig podConfiguration) (int, error) {
308312
return mtu, nil
309313
}
310314

311-
func pingServer(clientPodConfig podConfiguration, serverIP string) error {
312-
_, err := e2ekubectl.RunKubectl(
313-
clientPodConfig.namespace,
315+
func pingServer(clientPodConfig podConfiguration, serverIP string, args ...string) error {
316+
baseArgs := []string{
314317
"exec",
315318
clientPodConfig.name,
316319
"--",
317320
"ping",
318321
"-c", "1", // send one ICMP echo request
319322
"-W", "2", // timeout after 2 seconds if no response
320-
serverIP)
323+
}
324+
baseArgs = append(baseArgs, args...)
325+
326+
_, err := e2ekubectl.RunKubectl(clientPodConfig.namespace, append(baseArgs, serverIP)...)
327+
321328
return err
322329
}
323330

@@ -381,6 +388,18 @@ func podIPForAttachment(k8sClient clientset.Interface, podNamespace string, podN
381388
return ips[ipIndex], nil
382389
}
383390

391+
func podIPsFromStatus(k8sClient clientset.Interface, podNamespace string, podName string) ([]string, error) {
392+
pod, err := k8sClient.CoreV1().Pods(podNamespace).Get(context.Background(), podName, metav1.GetOptions{})
393+
if err != nil {
394+
return nil, err
395+
}
396+
podIPs := make([]string, 0, len(pod.Status.PodIPs))
397+
for _, podIP := range pod.Status.PodIPs {
398+
podIPs = append(podIPs, podIP.IP)
399+
}
400+
return podIPs, nil
401+
}
402+
384403
func allowedClient(podName string) string {
385404
return "allowed-" + podName
386405
}
@@ -610,27 +629,27 @@ func allowedTCPPortsForPolicy(allowPorts ...int) []mnpapi.MultiNetworkPolicyPort
610629
return portAllowlist
611630
}
612631

613-
func reachServerPodFromClient(cs clientset.Interface, serverConfig podConfiguration, clientConfig podConfiguration, serverIP string, serverPort int) error {
632+
func reachServerPodFromClient(cs clientset.Interface, serverConfig podConfiguration, clientConfig podConfiguration, serverIP string, serverPort int, args ...string) error {
614633
updatedPod, err := cs.CoreV1().Pods(serverConfig.namespace).Get(context.Background(), serverConfig.name, metav1.GetOptions{})
615634
if err != nil {
616635
return err
617636
}
618637

619638
if updatedPod.Status.Phase == v1.PodRunning {
620-
return connectToServer(clientConfig, serverIP, serverPort)
639+
return connectToServer(clientConfig, serverIP, serverPort, args...)
621640
}
622641

623642
return fmt.Errorf("pod not running. /me is sad")
624643
}
625644

626-
func pingServerPodFromClient(cs clientset.Interface, serverConfig podConfiguration, clientConfig podConfiguration, serverIP string) error {
645+
func pingServerPodFromClient(cs clientset.Interface, serverConfig podConfiguration, clientConfig podConfiguration, serverIP string, args ...string) error {
627646
updatedPod, err := cs.CoreV1().Pods(serverConfig.namespace).Get(context.Background(), serverConfig.name, metav1.GetOptions{})
628647
if err != nil {
629648
return err
630649
}
631650

632651
if updatedPod.Status.Phase == v1.PodRunning {
633-
return pingServer(clientConfig, serverIP)
652+
return pingServer(clientConfig, serverIP, args...)
634653
}
635654

636655
return fmt.Errorf("pod not running. /me is sad")

0 commit comments

Comments
 (0)