Skip to content

Commit 7077765

Browse files
committed
Configure mgmtport-no-snat-subnets sets elements
Currently traffic gets SNATed at ovn-k8s-mp0 within the mgmtport-snat chain. Since OVNK has transitioned to nftables, this behavior can no longer be overridden. Previously, with iptables, SNAT could be avoided by adding a higher-priority rule in the POSTROUTING chain. However, with nftables, all rules are evaluated before making a final decision, making it impossible to skip SNAT. Some applications, like Submariner, need to preserve the source IP when traffic reaches the destination pod, as certain use cases depend on it. This PR Update mgmtport-no-snat-subnets-v4 and mgmtport-no-snat-subnets-v6 nftables set based on node's annotation values. Signed-off-by: Yossi Boaron <[email protected]>
1 parent 9554ba6 commit 7077765

File tree

4 files changed

+112
-6
lines changed

4 files changed

+112
-6
lines changed

go-controller/pkg/node/managementport/port_linux.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,57 @@ func setupManagementPortNFTChain(interfaceName string, cfg *managementPortConfig
485485
return nil
486486
}
487487

488+
func UpdateNoSNATSubnetsSets(node *corev1.Node, getSubnetsFn func(*corev1.Node) ([]string, error)) error {
489+
subnetsList, err := getSubnetsFn(node)
490+
if err != nil {
491+
return fmt.Errorf("error retrieving subnets list: %w", err)
492+
}
493+
494+
subNetV4 := make([]*knftables.Element, 0)
495+
subNetV6 := make([]*knftables.Element, 0)
496+
497+
for _, subnet := range subnetsList {
498+
if utilnet.IPFamilyOfCIDRString(subnet) == utilnet.IPv4 {
499+
subNetV4 = append(subNetV4,
500+
&knftables.Element{
501+
Set: types.NFTMgmtPortNoSNATSubnetsV4,
502+
Key: []string{subnet},
503+
},
504+
)
505+
}
506+
if utilnet.IPFamilyOfCIDRString(subnet) == utilnet.IPv6 {
507+
subNetV6 = append(subNetV6,
508+
&knftables.Element{
509+
Set: types.NFTMgmtPortNoSNATSubnetsV6,
510+
Key: []string{subnet},
511+
},
512+
)
513+
}
514+
515+
}
516+
nft, err := nodenft.GetNFTablesHelper()
517+
if err != nil {
518+
return fmt.Errorf("failed to get nftables: %v", err)
519+
}
520+
521+
tx := nft.NewTransaction()
522+
tx.Flush(&knftables.Set{
523+
Name: types.NFTMgmtPortNoSNATSubnetsV4,
524+
})
525+
tx.Flush(&knftables.Set{
526+
Name: types.NFTMgmtPortNoSNATSubnetsV6,
527+
})
528+
529+
for _, elem := range subNetV4 {
530+
tx.Add(elem)
531+
}
532+
for _, elem := range subNetV6 {
533+
tx.Add(elem)
534+
}
535+
536+
return nft.Run(context.TODO(), tx)
537+
}
538+
488539
// createPlatformManagementPort creates a management port attached to the node switch
489540
// that lets the node access its pods via their private IP address. This is used
490541
// for health checking and other management tasks.

go-controller/pkg/node/obj_retry_node.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config"
1313
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory"
14+
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/managementport"
1415
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/retry"
1516
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"
1617
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
@@ -121,7 +122,7 @@ func (h *nodeEventHandler) AreResourcesEqual(obj1, obj2 interface{}) (bool, erro
121122
if !ok {
122123
return false, fmt.Errorf("could not cast obj2 of type %T to *kapi.Node", obj2)
123124
}
124-
return reflect.DeepEqual(node1.Status.Addresses, node2.Status.Addresses), nil
125+
return reflect.DeepEqual(node1.Status.Addresses, node2.Status.Addresses) && reflect.DeepEqual(node1.Annotations, node2.Annotations), nil
125126

126127
default:
127128
return false, fmt.Errorf("no object comparison for type %s", h.objType)
@@ -175,6 +176,13 @@ func (h *nodeEventHandler) AddResource(obj interface{}, _ bool) error {
175176
node := obj.(*corev1.Node)
176177
// if it's our node that is changing, then nothing to do as we dont add our own IP to the nftables rules
177178
if node.Name == h.nc.name {
179+
if util.NodeDontSNATSubnetAnnotationExist(node) {
180+
err := managementport.UpdateNoSNATSubnetsSets(node, util.ParseNodeDontSNATSubnetsList)
181+
if err != nil {
182+
return fmt.Errorf("error updating no snat subnets sets: %w", err)
183+
}
184+
}
185+
178186
return nil
179187
}
180188
return h.nc.addOrUpdateNode(node)
@@ -218,6 +226,15 @@ func (h *nodeEventHandler) UpdateResource(oldObj, newObj interface{}, _ bool) er
218226

219227
// if it's our node that is changing, then nothing to do as we dont add our own IP to the nftables rules
220228
if newNode.Name == h.nc.name {
229+
230+
// if node's dont SNAT subnet annotation changed sync nftables
231+
if !reflect.DeepEqual(oldNode.Annotations, newNode.Annotations) &&
232+
util.NodeDontSNATSubnetAnnotationChanged(oldNode, newNode) {
233+
err := managementport.UpdateNoSNATSubnetsSets(newNode, util.ParseNodeDontSNATSubnetsList)
234+
if err != nil {
235+
return fmt.Errorf("error updating no snat subnets sets: %w", err)
236+
}
237+
}
221238
return nil
222239
}
223240

@@ -273,6 +290,10 @@ func (h *nodeEventHandler) DeleteResource(obj, _ interface{}) error {
273290

274291
case factory.NodeType:
275292
h.nc.deleteNode(obj.(*corev1.Node))
293+
_ = managementport.UpdateNoSNATSubnetsSets(obj.(*corev1.Node), func(_ *corev1.Node) ([]string, error) {
294+
return []string{}, nil
295+
})
296+
276297
return nil
277298

278299
default:

go-controller/pkg/ovnwebhook/nodeadmission.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var commonNodeAnnotationChecks = map[string]checkNodeAnnot{
3434
util.OvnNodeMasqCIDR: nil,
3535
util.OvnNodeGatewayMtuSupport: nil,
3636
util.OvnNodeManagementPort: nil,
37+
util.OvnNodeDontSNATSubnets: nil,
3738
util.OvnNodeChassisID: func(v annotationChange, _ string) error {
3839
if v.action == removed {
3940
return fmt.Errorf("%s cannot be removed", util.OvnNodeChassisID)

go-controller/pkg/util/node_annotations.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ const (
154154

155155
// ovnNodeEncapIPs is used to indicate encap IPs set on the node
156156
OVNNodeEncapIPs = "k8s.ovn.org/node-encap-ips"
157+
158+
// OvnNodeDontSNATSubnets is a user assigned source subnets that should avoid SNAT at ovn-k8s-mp0 interface
159+
OvnNodeDontSNATSubnets = "k8s.ovn.org/node-ingress-snat-exclude-subnets"
157160
)
158161

159162
type L3GatewayConfig struct {
@@ -1115,15 +1118,45 @@ func ParseNodeHostCIDRsExcludeOVNNetworks(node *corev1.Node) ([]string, error) {
11151118
}
11161119

11171120
func ParseNodeHostCIDRsList(node *corev1.Node) ([]string, error) {
1118-
addrAnnotation, ok := node.Annotations[OVNNodeHostCIDRs]
1121+
return parseNodeAnnotationList(node, OVNNodeHostCIDRs)
1122+
}
1123+
1124+
func ParseNodeDontSNATSubnetsList(node *corev1.Node) ([]string, error) {
1125+
return parseNodeAnnotationList(node, OvnNodeDontSNATSubnets)
1126+
}
1127+
1128+
// NodeDontSNATSubnetAnnotationChanged returns true if the OvnNodeDontSNATSubnets in the corev1.Nodes doesn't match
1129+
func NodeDontSNATSubnetAnnotationChanged(oldNode, newNode *corev1.Node) bool {
1130+
oldVal, oldOk := oldNode.Annotations[OvnNodeDontSNATSubnets]
1131+
newVal, newOk := newNode.Annotations[OvnNodeDontSNATSubnets]
1132+
1133+
if oldOk != newOk {
1134+
return true
1135+
}
1136+
1137+
if oldOk && newOk && oldVal != newVal {
1138+
return true
1139+
}
1140+
1141+
return false
1142+
}
1143+
1144+
// NodeDontSNATSubnetAnnotationExist returns true OvnNodeDontSNATSubnets annotation key exists in node annotation
1145+
func NodeDontSNATSubnetAnnotationExist(node *corev1.Node) bool {
1146+
_, ok := node.Annotations[OvnNodeDontSNATSubnets]
1147+
return ok
1148+
}
1149+
1150+
func parseNodeAnnotationList(node *corev1.Node, annotationKey string) ([]string, error) {
1151+
annotationValue, ok := node.Annotations[annotationKey]
11191152
if !ok {
1120-
return nil, newAnnotationNotSetError("%s annotation not found for node %q", OVNNodeHostCIDRs, node.Name)
1153+
return []string{}, nil
11211154
}
11221155

11231156
var cfg []string
1124-
if err := json.Unmarshal([]byte(addrAnnotation), &cfg); err != nil {
1125-
return nil, fmt.Errorf("failed to unmarshal host cidrs annotation %s for node %q: %v",
1126-
addrAnnotation, node.Name, err)
1157+
if err := json.Unmarshal([]byte(annotationValue), &cfg); err != nil {
1158+
return nil, fmt.Errorf("failed to unmarshal %s annotation %s for node %q: %v",
1159+
annotationKey, annotationValue, node.Name, err)
11271160
}
11281161
return cfg, nil
11291162
}

0 commit comments

Comments
 (0)