Skip to content

Commit dcc403c

Browse files
committed
Fix UDN to alien ClusterIP looping issue
The isolation rules originally added here: d63887e redirect the traffic originated from a UDN back to the same UDNs patchport. This causes a following traffic loop for advertised L2 networks in LGW: 1. A UDN pod sends traffic to a service IP outside the UDN. 2. Traffic exits through the `ovn-mpX` port and is routed to `breth0`. 3. The following OpenFlow rules redirect it back to the UDN patch port: - `table=0,priority=550,ip,in_port=LOCAL,nw_src=<UDN_SUBNET>,nw_dst=<SVC_SUBNET>,actions=ct(commit,table=2,zone=64001)` - `table=2,priority=200,ip,nw_src=<UDN_SUBNET> actions=...,output:"<UDN_PATCH_PORT>"` 4. A route on the L2 gateway router sends the traffic back to `ovn-mpX`, restarting the loop. To fix this, the rule is changed to drop the traffic directly instead of redirecting it. Although currently this change is required only for advertised L2 networks in LGW the rule is changed for all scenarios to avoid introducing use-case specific behavior. Additionally, the priority of the packet marking flow is adjusted to remove any potential ambiguity. While this change could affect future support for host-networked UDN pods accessing ClusterIP services, it should be possible to re-use the existing per-UDN pkt marking approach. Signed-off-by: Patryk Diak <[email protected]>
1 parent 82850ed commit dcc403c

File tree

3 files changed

+28
-43
lines changed

3 files changed

+28
-43
lines changed

go-controller/pkg/node/gateway_shared_intf.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,9 @@ func (npw *nodePortWatcher) updateServiceFlowCache(service *corev1.Service, netI
406406

407407
// Add flows for default network services that are accessible from UDN networks
408408
if util.IsNetworkSegmentationSupportEnabled() {
409-
// The flow added below has a higher priority than the per UDN service flow:
410-
// priority=200, table=2, ip, ip_src=169.254.0.<UDN>, actions=set_field:<bridge-mac>->eth_dst,output:<UDN-patch-port>
411-
// This ordering ensures that traffic to UDN allowed default services goes to the the default patch port.
409+
// The flow added below has a higher priority than the per UDN service isolation flow:
410+
// priority=200, table=2, ip, ip_src=169.254.0.<UDN>, actions=drop
411+
// This ordering ensures that traffic to UDN allowed default services goes to the default patch port.
412412

413413
if util.IsUDNEnabledService(ktypes.NamespacedName{Namespace: service.Namespace, Name: service.Name}.String()) {
414414
key = strings.Join([]string{"UDNAllowedSVC", service.Namespace, service.Name}, "_")
@@ -1788,13 +1788,17 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st
17881788
// Use the filtered subnets for the flow compute instead of the masqueradeIP
17891789
srcIPOrSubnet = matchingIPFamilySubnet.String()
17901790
}
1791+
1792+
// Drop traffic coming from the masquerade IP or the UDN subnet(for advertised UDNs) to ensure that
1793+
// isolation between networks is enforced. This handles the case where a pod on the UDN subnet is sending traffic to
1794+
// a service in another UDN.
17911795
dftFlows = append(dftFlows,
17921796
fmt.Sprintf("cookie=%s, priority=200, table=2, ip, ip_src=%s, "+
1793-
"actions=set_field:%s->eth_dst,output:%s",
1794-
defaultOpenFlowCookie, srcIPOrSubnet,
1795-
bridgeMacAddress, netConfig.ofPortPatch))
1797+
"actions=drop",
1798+
defaultOpenFlowCookie, srcIPOrSubnet))
1799+
17961800
dftFlows = append(dftFlows,
1797-
fmt.Sprintf("cookie=%s, priority=200, table=2, ip, pkt_mark=%s, "+
1801+
fmt.Sprintf("cookie=%s, priority=250, table=2, ip, pkt_mark=%s, "+
17981802
"actions=set_field:%s->eth_dst,output:%s",
17991803
defaultOpenFlowCookie, netConfig.pktMark,
18001804
bridgeMacAddress, netConfig.ofPortPatch))
@@ -1825,11 +1829,10 @@ func flowsForDefaultBridge(bridge *bridgeConfiguration, extraIPs []net.IP) ([]st
18251829
}
18261830
dftFlows = append(dftFlows,
18271831
fmt.Sprintf("cookie=%s, priority=200, table=2, ip6, ipv6_src=%s, "+
1828-
"actions=set_field:%s->eth_dst,output:%s",
1829-
defaultOpenFlowCookie, srcIPOrSubnet,
1830-
bridgeMacAddress, netConfig.ofPortPatch))
1832+
"actions=drop",
1833+
defaultOpenFlowCookie, srcIPOrSubnet))
18311834
dftFlows = append(dftFlows,
1832-
fmt.Sprintf("cookie=%s, priority=200, table=2, ip6, pkt_mark=%s, "+
1835+
fmt.Sprintf("cookie=%s, priority=250, table=2, ip6, pkt_mark=%s, "+
18331836
"actions=set_field:%s->eth_dst,output:%s",
18341837
defaultOpenFlowCookie, netConfig.pktMark,
18351838
bridgeMacAddress, netConfig.ofPortPatch))

go-controller/pkg/node/gateway_udn_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func checkDefaultSvcIsolationOVSFlows(flows []string, defaultConfig *bridgeUDNCo
280280
Expect(nTable2Flows).To(Equal(1))
281281
}
282282

283-
func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName, bridgeMAC string, svcCIDR *net.IPNet, expectedNFlows int) {
283+
func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) {
284284
By(fmt.Sprintf("Checking advertsised UDN %s service isolation flows for %s; expected %d flows",
285285
netName, svcCIDR.String(), expectedNFlows))
286286

@@ -303,8 +303,8 @@ func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDN
303303

304304
var nFlows int
305305
for _, flow := range flows {
306-
if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=set_field:%s->eth_dst,output:%s",
307-
protoPrefix, protoPrefix, matchingIPFamilySubnet, bridgeMAC, netConfig.ofPortPatch)) {
306+
if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=drop",
307+
protoPrefix, protoPrefix, matchingIPFamilySubnet)) {
308308
nFlows++
309309
}
310310
if strings.Contains(flow, fmt.Sprintf("priority=550, in_port=LOCAL, %s, %s_src=%s, %s_dst=%s, actions=ct(commit,zone=64001,table=2)",
@@ -316,7 +316,7 @@ func checkAdvertisedUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDN
316316
Expect(nFlows).To(Equal(expectedNFlows))
317317
}
318318

319-
func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName, bridgeMAC string, svcCIDR *net.IPNet, expectedNFlows int) {
319+
func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfiguration, netName string, svcCIDR *net.IPNet, expectedNFlows int) {
320320
By(fmt.Sprintf("Checking UDN %s service isolation flows for %s; expected %d flows",
321321
netName, svcCIDR.String(), expectedNFlows))
322322

@@ -332,8 +332,8 @@ func checkUDNSvcIsolationOVSFlows(flows []string, netConfig *bridgeUDNConfigurat
332332

333333
var nFlows int
334334
for _, flow := range flows {
335-
if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=set_field:%s->eth_dst,output:%s",
336-
protoPrefix, protoPrefix, mgmtMasqIP, bridgeMAC, netConfig.ofPortPatch)) {
335+
if strings.Contains(flow, fmt.Sprintf("priority=200, table=2, %s, %s_src=%s, actions=drop",
336+
protoPrefix, protoPrefix, mgmtMasqIP)) {
337337
nFlows++
338338
}
339339
}
@@ -797,7 +797,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() {
797797
checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR)
798798

799799
// Expect exactly one flow per UDN for table 2 for service isolation.
800-
checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 1)
800+
checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 1)
801801
}
802802

803803
// The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was
@@ -827,7 +827,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() {
827827
checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR)
828828

829829
// Expect no more flows per UDN for table 2 for service isolation.
830-
checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 0)
830+
checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0)
831831
}
832832
return nil
833833
})
@@ -1028,7 +1028,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() {
10281028
checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR)
10291029

10301030
// Expect exactly one flow per UDN for tables 0 and 2 for service isolation.
1031-
checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 1)
1031+
checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 1)
10321032
}
10331033

10341034
// The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was
@@ -1058,7 +1058,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() {
10581058
checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR)
10591059

10601060
// Expect no more flows per UDN for tables 0 and 2 for service isolation.
1061-
checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 0)
1061+
checkUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0)
10621062
}
10631063
return nil
10641064
})
@@ -1269,7 +1269,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() {
12691269
checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR)
12701270

12711271
// Expect exactly one flow per advertised UDN for table 2 and table 0 for service isolation.
1272-
checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 2)
1272+
checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 2)
12731273
}
12741274

12751275
// The second call to checkPorts() will return no ofPort for the UDN - simulating a deletion that already was
@@ -1299,7 +1299,7 @@ var _ = Describe("UserDefinedNetworkGateway", func() {
12991299
checkDefaultSvcIsolationOVSFlows(flowMap["DEFAULT"], defaultUdnConfig, ofPortHost, bridgeMAC, svcCIDR)
13001300

13011301
// Expect no more flows per UDN for table 2 and table0 for service isolation.
1302-
checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", bridgeMAC, svcCIDR, 0)
1302+
checkAdvertisedUDNSvcIsolationOVSFlows(flowMap["DEFAULT"], bridgeUdnConfig, "bluenet", svcCIDR, 0)
13031303
}
13041304
return nil
13051305
})

test/e2e/route_advertisements.go

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -882,33 +882,15 @@ var _ = ginkgo.DescribeTableSubtree("BGP: isolation between advertised networks"
882882
}),
883883
ginkgo.Entry("pod in the UDN should not be able to access a default network service",
884884
func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) {
885-
err := true
886-
out := curlConnectionTimeoutCode
887-
if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 {
888-
// FIXME: prevent looping of traffic in L2 UDNs
889-
// bad behaviour: packet is looping from management port -> breth0 -> GR -> management port -> breth0 and so on
890-
// which is a never ending loop
891-
// this causes curl timeout with code 7 host unreachable instead of code 28
892-
out = ""
893-
}
894-
return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetDefault.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", out, err
885+
return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetDefault.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", curlConnectionTimeoutCode, true
895886
}),
896887
ginkgo.Entry("pod in the UDN should be able to access kapi in default network service",
897888
func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) {
898889
return podsNetA[0].Name, podsNetA[0].Namespace, "https://kubernetes.default/healthz", "", false
899890
}),
900891
ginkgo.Entry("pod in the UDN should not be able to access a service in a different UDN",
901892
func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) {
902-
err := true
903-
out := curlConnectionTimeoutCode
904-
if cudnATemplate.Spec.Network.Topology == udnv1.NetworkTopologyLayer2 && isLocalGWModeEnabled() {
905-
// FIXME: prevent looping of traffic in L2 UDNs
906-
// bad behaviour: packet is looping from management port -> breth0 -> GR -> management port -> breth0 and so on
907-
// which is a never ending loop
908-
// this causes curl timeout with code 7 host unreachable instead of code 28
909-
out = ""
910-
}
911-
return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetB.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", out, err
893+
return podsNetA[0].Name, podsNetA[0].Namespace, net.JoinHostPort(svcNetB.Spec.ClusterIPs[ipFamilyIndex], "8080") + "/clientip", curlConnectionTimeoutCode, true
912894
}),
913895
ginkgo.Entry("host to a local UDN pod should not work",
914896
func(ipFamilyIndex int) (clientName string, clientNamespace string, dst string, expectedOutput string, expectErr bool) {

0 commit comments

Comments
 (0)