Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions go-controller/pkg/node/bridgeconfig/bridgeflows.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,10 @@ func (b *BridgeConfiguration) commonFlows(hostSubnets []*net.IPNet) ([]string, e
nodetypes.DefaultOpenFlowCookie, netConfig.OfPortPatch))
}

if util.IsNetworkSegmentationSupportEnabled() {
dftFlows = append(dftFlows, b.arpFilterFlows(bridgeMacAddress)...)
}

if config.IPv4Mode {
physicalIP, err := util.MatchFirstIPNetFamily(false, bridgeIPs)
if err != nil {
Expand Down Expand Up @@ -1017,6 +1021,43 @@ func (b *BridgeConfiguration) allowNodeIPGARPFlows(nodeIPs []net.IP) []string {
return flows
}

// arpFilterFlows ensures only the default network GR replies to ARP requests
// for the local node IP. When an ARP request arrives on br-ex, the priority-10
// fan-out rule replicates it to every patch port. All CUDN GRs share the same
// node IP on their external interface, so every one of them generates an ARP
// reply. These duplicate replies flood the physical network and cause remote
// nodes to passively learn lots of MAC_Bindings, driving ovs-vswitchd CPU up.
//
// Two flows per node IP (IPv4 only, ARP is IPv4):
// - priority 12: allow ARP replies from the default network patch port
// - priority 11: drop ARP replies from any other patch port
//
// Must be called with bridge.mutex held.
func (b *BridgeConfiguration) arpFilterFlows(bridgeMacAddress string) []string {
defaultNetConfig, found := b.netConfig[types.DefaultNetworkName]
if !found || defaultNetConfig.OfPortPatch == "" {
return nil
}

var flows []string
for _, ip := range b.ips {
if ip.IP.To4() == nil {
continue
}
// allow ARP replies for the node IP from the default network patch port
flows = append(flows,
fmt.Sprintf("cookie=%s, priority=12, table=0, in_port=%s, dl_src=%s, arp, arp_op=2, arp_spa=%s, "+
"actions=output:NORMAL",
nodetypes.DefaultOpenFlowCookie, defaultNetConfig.OfPortPatch, bridgeMacAddress, ip.IP))
// drop ARP replies for the node IP from all other patch ports
flows = append(flows,
fmt.Sprintf("cookie=%s, priority=11, table=0, dl_src=%s, arp, arp_op=2, arp_spa=%s, "+
"actions=drop",
nodetypes.DefaultOpenFlowCookie, bridgeMacAddress, ip.IP))
}
return flows
}

func getIPv(ipnet *net.IPNet) string {
prefix := protoPrefixV4
if utilnet.IsIPv6CIDR(ipnet) {
Expand Down
42 changes: 23 additions & 19 deletions go-controller/pkg/ovn/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -1168,26 +1168,30 @@ func (gw *GatewayManager) addExternalSwitch(prefix, interfaceID, gatewayRouter,

// Also add the port to connect the external_switch to the router.
externalSwitchPortToRouter := prefix + types.EXTSwitchToGWRouterPrefix + gatewayRouter
etorOptions := map[string]string{
libovsdbops.RouterPort: externalRouterPort,
}
if !gw.netInfo.IsUserDefinedNetwork() {
// nat-addresses=router programs OVN to send GARPs for all external IPs
// that the logical switch port has been configured to use. This is
// necessary for egress IP because if an egress IP is moved between two
// nodes, the nodes need to actively update the ARP cache of all neighbors
// as to notify them the change.
// For UDNs this is not set: UDN GRs only carry masquerade IPs which are
// link-local and identical across nodes, so GARPs for them cause an ARP
// storm on the physical network. EgressIP for UDNs has no SNATs on the
// GR, so nat-addresses=router is not needed.
etorOptions["nat-addresses"] = "router"

// Setting nat-addresses to router will send out GARPs for all externalIPs and LB VIPs
// hosted on the GR. Setting exclude-lb-vips-from-garp to true will make sure GARPs for
// LB VIPs are not sent, thereby preventing GARP overload.
etorOptions["exclude-lb-vips-from-garp"] = "true"
}
externalLogicalSwitchPortToRouter := nbdb.LogicalSwitchPort{
Name: externalSwitchPortToRouter,
Type: "router",
Options: map[string]string{
libovsdbops.RouterPort: externalRouterPort,

// This option will program OVN to start sending GARPs for all external IPS
// that the logical switch port has been configured to use. This is
// necessary for egress IP because if an egress IP is moved between two
// nodes, the nodes need to actively update the ARP cache of all neighbors
// as to notify them the change. If this is not the case: packets will
// continue to be routed to the old node which hosted the egress IP before
// it was moved, and the connections will fail.
"nat-addresses": "router",

// Setting nat-addresses to router will send out GARPs for all externalIPs and LB VIPs
// hosted on the GR. Setting exclude-lb-vips-from-garp to true will make sure GARPs for
// LB VIPs are not sent, thereby preventing GARP overload.
"exclude-lb-vips-from-garp": "true",
},
Name: externalSwitchPortToRouter,
Type: "router",
Options: etorOptions,
Addresses: []string{macAddress},
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,14 @@ func (oc *Layer2UserDefinedNetworkController) nodeGatewayConfig(node *corev1.Nod
return nil, fmt.Errorf("failed to get masquerade IPs, network %s (%d): %v", networkName, networkID, err)
}

l3GatewayConfig.IPAddresses = append(l3GatewayConfig.IPAddresses, masqIPs...)
for _, masqIP := range masqIPs {
if masqIP == nil {
continue
}
hostMask := util.GetIPFullMask(masqIP.IP)
l3GatewayConfig.IPAddresses = append(l3GatewayConfig.IPAddresses,
&net.IPNet{IP: masqIP.IP, Mask: hostMask})
}

// Always SNAT to the per network masquerade IP.
var externalIPs []net.IP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,14 @@ func (oc *Layer3UserDefinedNetworkController) nodeGatewayConfig(node *corev1.Nod
return nil, fmt.Errorf("failed to get masquerade IPs, network %s (%d): %v", networkName, networkID, err)
}

l3GatewayConfig.IPAddresses = append(l3GatewayConfig.IPAddresses, masqIPs...)
for _, masqIP := range masqIPs {
if masqIP == nil {
continue
}
hostMask := util.GetIPFullMask(masqIP.IP)
l3GatewayConfig.IPAddresses = append(l3GatewayConfig.IPAddresses,
&net.IPNet{IP: masqIP.IP, Mask: hostMask})
}

// Always SNAT to the per network masquerade IP.
var externalIPs []net.IP
Expand Down