Skip to content
Open
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
2 changes: 0 additions & 2 deletions confd/etc/calico/confd/conf.d/bird6_ipam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ keys = [
"/bgpconfig",
"/v1/ipam/v6/pool",
"/bgp/v1/host//NODENAME",
"/bgp/v1/global/svc_loadbalancer_ips",
"/bgp/v1/global/program_cluster_routes",
"/staticroutesv6",
"/rejectcidrsv6",
]
Expand Down
2 changes: 0 additions & 2 deletions confd/etc/calico/confd/conf.d/bird_ipam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ keys = [
"/bgpconfig",
"/v1/ipam/v4/pool",
"/bgp/v1/host//NODENAME",
"/bgp/v1/global/svc_loadbalancer_ips",
"/bgp/v1/global/program_cluster_routes",
"/staticroutes",
"/rejectcidrs",
]
Expand Down
5 changes: 2 additions & 3 deletions confd/etc/calico/confd/templates/bird6_ipam.cfg.template
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@ function calico_export_to_bgp_peers(bool internal_peer) {
{{- end}}
{{- end}}
{{- $rr_cluster_id_key := printf "/bgp/v1/host/%s/rr_cluster_id" $config.NodeName}}
{{- $lb_ips := "/bgp/v1/global/svc_loadbalancer_ips"}}
{{- if exists $rr_cluster_id_key}}{{$rr_cluster_id := getv $rr_cluster_id_key}}
{{- if and (not (eq $rr_cluster_id "")) (exists $lb_ips)}}
{{- if and (not (eq $rr_cluster_id "")) $config.LoadBalancerIPs}}

# Configured as a RR - accept any routes within configured LB service IP ranges
{{- range split (getv $lb_ips) ","}}
{{- range $config.LoadBalancerIPs}}
{{- $cidr := .}}
{{- if contains $cidr ":"}}
if ( net ~ {{$cidr}} ) then { accept; }
Expand Down
5 changes: 2 additions & 3 deletions confd/etc/calico/confd/templates/bird_ipam.cfg.template
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@ function calico_export_to_bgp_peers(bool internal_peer) {
{{- end}}
{{- end}}
{{- $rr_cluster_id_key := printf "/bgp/v1/host/%s/rr_cluster_id" $config.NodeName}}
{{- $lb_ips := "/bgp/v1/global/svc_loadbalancer_ips"}}
{{- if exists $rr_cluster_id_key}}{{$rr_cluster_id := getv $rr_cluster_id_key}}
{{- if and (not (eq $rr_cluster_id "")) (exists $lb_ips)}}
{{- if and (not (eq $rr_cluster_id "")) $config.LoadBalancerIPs}}

# Configured as a RR - accept any routes within configured LB service IP ranges
{{- range split (getv $lb_ips) ","}}
{{- range $config.LoadBalancerIPs}}
{{- $cidr := .}}
{{- if not (contains $cidr ":")}}
if ( net ~ {{$cidr}} ) then { accept; }
Expand Down
51 changes: 21 additions & 30 deletions confd/pkg/backends/calico/bgp_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ func (c *client) GetBirdBGPConfig(ipVersion int) (*types.BirdBGPConfig, error) {
pc := c.getBGPProcessorContext()

config := &types.BirdBGPConfig{
NodeName: NodeName,
Peers: make([]types.BirdBGPPeer, 0),
Filters: make(map[string]string),
Communities: make([]types.CommunityRule, 0),
NodeName: NodeName,
Peers: make([]types.BirdBGPPeer, 0),
Filters: make(map[string]string),
Communities: make([]types.CommunityRule, 0),
LoadBalancerIPs: getServiceLoadBalancerIPs(pc.globalBGPConfig),
}

// Get basic node configuration
Expand All @@ -76,7 +77,7 @@ func (c *client) GetBirdBGPConfig(ipVersion int) (*types.BirdBGPConfig, error) {
logc.Debugf("Populated node configuration: node=%s, ip=%s, ipv6=%s, as=%s", config.NodeName, config.NodeIP, config.NodeIPv6, config.ASNumber)

// Process all peer types
if err := c.processPeers(config, ipVersion); err != nil {
if err := c.processPeers(pc, config, ipVersion); err != nil {
logc.WithError(err).Warn("Failed to process BGP peers")
return nil, err
}
Expand Down Expand Up @@ -198,23 +199,17 @@ func (c *client) populateNodeConfig(pc *processorContext, config *types.BirdBGPC
}

// Process ignored interfaces and build complete interface string
ignoredInterfaces, err := c.getNodeOrGlobalValue(NodeName, "ignored_interfaces")

// Build the complete interface pattern string
if err == nil && ignoredInterfaces != "" {
// Parse comma-separated list and build pattern
ifaceList := strings.Split(ignoredInterfaces, ",")
var patterns []string
for _, iface := range ifaceList {
patterns = append(patterns, fmt.Sprintf(`-"%s"`, iface))
}
// Add standard exclusions and wildcard
patterns = append(patterns, `-"cali*"`, `-"kube-ipvs*"`, `"*"`)
config.DirectInterfaces = strings.Join(patterns, ", ")
} else {
// Default pattern with explanatory comment
config.DirectInterfaces = `-"cali*", -"kube-ipvs*", "*"`
var ignoredInterfaces []string
if pc.globalBGPConfig != nil {
ignoredInterfaces = pc.globalBGPConfig.Spec.IgnoredInterfaces
}
var patterns []string
for _, iface := range ignoredInterfaces {
patterns = append(patterns, fmt.Sprintf(`-"%s"`, iface))
}
Copy link
Copy Markdown
Member

@mazdakn mazdakn Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think moving these processing logics to pc would make the code cleaner. For example:

func (pc *ProcessorContext) GetValueX() ...

Then we can move all values checks and defaulting to that method.

// Add standard exclusions and wildcard include.
patterns = append(patterns, `-"cali*"`, `-"kube-ipvs*"`, `"*"`)
config.DirectInterfaces = strings.Join(patterns, ", ")

// Set NormalRoutePriority from BGPConfiguration (default 1024).
config.NormalRoutePriority = getNormalRoutePriority(ipVersion, pc.globalBGPConfig)
Expand Down Expand Up @@ -247,12 +242,12 @@ func getNormalRoutePriority(ipVersion int, bgpConfig *v3.BGPConfiguration) (prio
}

// processPeers processes all BGP peers (mesh, global, and node-specific)
func (c *client) processPeers(config *types.BirdBGPConfig, ipVersion int) error {
func (c *client) processPeers(pc *processorContext, config *types.BirdBGPConfig, ipVersion int) error {
// Get node's route reflector cluster ID
nodeClusterID, _ := c.GetValue(fmt.Sprintf("/calico/bgp/v1/host/%s/rr_cluster_id", NodeName))

// Process node-to-node mesh peers
if err := c.processMeshPeers(config, nodeClusterID, ipVersion); err != nil {
if err := c.processMeshPeers(pc, config, nodeClusterID, ipVersion); err != nil {
return fmt.Errorf("failed to process mesh peers: %w", err)
}

Expand All @@ -270,7 +265,7 @@ func (c *client) processPeers(config *types.BirdBGPConfig, ipVersion int) error
}

// processMeshPeers processes node-to-node mesh BGP peers
func (c *client) processMeshPeers(config *types.BirdBGPConfig, nodeClusterID string, ipVersion int) error {
func (c *client) processMeshPeers(pc *processorContext, config *types.BirdBGPConfig, nodeClusterID string, ipVersion int) error {
logc := log.WithField("ipVersion", ipVersion)

// If this node is a route reflector, skip mesh processing
Expand Down Expand Up @@ -300,10 +295,6 @@ func (c *client) processMeshPeers(config *types.BirdBGPConfig, nodeClusterID str
return nil
}

// Get global mesh settings
meshPassword, _ := c.GetValue("/calico/bgp/v1/global/node_mesh_password")
meshRestartTime, _ := c.GetValue("/calico/bgp/v1/global/node_mesh_restart_time")

// Determine which IP address field to use
ipAddrSuffix := "ip_addr_v4"
if ipVersion == 6 {
Expand Down Expand Up @@ -382,8 +373,8 @@ func (c *client) processMeshPeers(config *types.BirdBGPConfig, nodeClusterID str
ASNumber: peerAS,
Type: "mesh",
SourceAddr: currentNodeIP,
Password: meshPassword,
GracefulRestart: meshRestartTime,
Password: c.getNodeMeshPassword(pc.globalBGPConfig),
GracefulRestart: getNodeMeshRestartTime(pc.globalBGPConfig),
}

// Make mesh unidirectional to avoid race conditions
Expand Down
78 changes: 36 additions & 42 deletions confd/pkg/backends/calico/bgp_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"

"github.com/projectcalico/calico/confd/pkg/backends/types"
Expand Down Expand Up @@ -594,10 +596,10 @@ func TestPopulateNodeConfig_IgnoredInterfaces_Custom(t *testing.T) {
cache := map[string]string{
"/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1",
"/calico/bgp/v1/global/as_num": "64512",
"/calico/bgp/v1/global/ignored_interfaces": "eth0,docker*",
}

c := newTestClient(cache, nil)
c.globalBGPConfig.Spec.IgnoredInterfaces = []string{"eth0", "docker*"}
config := &types.BirdBGPConfig{
NodeName: NodeName,
}
Expand All @@ -613,34 +615,6 @@ func TestPopulateNodeConfig_IgnoredInterfaces_Custom(t *testing.T) {
assert.Contains(t, config.DirectInterfaces, `"*"`)
}

func TestPopulateNodeConfig_NodeSpecificIgnoredInterfaces(t *testing.T) {
originalNodeName := NodeName
NodeName = "test-node"
defer func() { NodeName = originalNodeName }()

_ = os.Unsetenv("CALICO_ROUTER_ID")

cache := map[string]string{
"/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1",
"/calico/bgp/v1/global/as_num": "64512",
"/calico/bgp/v1/global/ignored_interfaces": "global-if", // Global
"/calico/bgp/v1/host/test-node/ignored_interfaces": "node-if", // Node-specific (should win)
}

c := newTestClient(cache, nil)
config := &types.BirdBGPConfig{
NodeName: NodeName,
}

err := c.populateNodeConfig(c.getBGPProcessorContext(), config, 4)
require.NoError(t, err)

// Node-specific interface should be present
assert.Contains(t, config.DirectInterfaces, `-"node-if"`)
// Global interface should NOT be present
assert.NotContains(t, config.DirectInterfaces, `-"global-if"`)
}

func TestPopulateNodeConfig_NoBindMode(t *testing.T) {
originalNodeName := NodeName
NodeName = "test-node"
Expand Down Expand Up @@ -1158,7 +1132,7 @@ func TestProcessMeshPeers_BasicMesh(t *testing.T) {
ASNumber: "64512",
}

err := c.processMeshPeers(config, "", 4)
err := c.processMeshPeers(c.getBGPProcessorContext(), config, "", 4)
require.NoError(t, err)

// Should have 2 mesh peers (node-2 and node-3, excluding ourselves)
Expand Down Expand Up @@ -1200,7 +1174,7 @@ func TestProcessMeshPeers_IPv6(t *testing.T) {
ASNumber: "64512",
}

err := c.processMeshPeers(config, "", 6)
err := c.processMeshPeers(c.getBGPProcessorContext(), config, "", 6)
require.NoError(t, err)

assert.Len(t, config.Peers, 1)
Expand Down Expand Up @@ -1232,7 +1206,7 @@ func TestProcessMeshPeers_MeshDisabled(t *testing.T) {
ASNumber: "64512",
}

err := c.processMeshPeers(config, "", 4)
err := c.processMeshPeers(c.getBGPProcessorContext(), config, "", 4)
require.NoError(t, err)

// No peers should be added when mesh is disabled
Expand Down Expand Up @@ -1263,7 +1237,7 @@ func TestProcessMeshPeers_RouteReflector(t *testing.T) {
}

// Node is a route reflector - should skip mesh
err := c.processMeshPeers(config, "rr-cluster-1", 4)
err := c.processMeshPeers(c.getBGPProcessorContext(), config, "rr-cluster-1", 4)
require.NoError(t, err)

assert.Len(t, config.Peers, 0)
Expand Down Expand Up @@ -1294,7 +1268,7 @@ func TestProcessMeshPeers_SkipRouteReflectorPeers(t *testing.T) {
ASNumber: "64512",
}

err := c.processMeshPeers(config, "", 4)
err := c.processMeshPeers(c.getBGPProcessorContext(), config, "", 4)
require.NoError(t, err)

// Should only have node-3, node-2 is a route reflector
Expand Down Expand Up @@ -1328,7 +1302,7 @@ func TestProcessMeshPeers_PassiveMode(t *testing.T) {
ASNumber: "64512",
}

err := c.processMeshPeers(config, "", 4)
err := c.processMeshPeers(c.getBGPProcessorContext(), config, "", 4)
require.NoError(t, err)

assert.Len(t, config.Peers, 2)
Expand Down Expand Up @@ -1356,28 +1330,48 @@ func TestProcessMeshPeers_WithPasswordAndRestartTime(t *testing.T) {
meshConfigJSON, _ := json.Marshal(meshConfig)

cache := map[string]string{
"/calico/bgp/v1/global/node_mesh": string(meshConfigJSON),
"/calico/bgp/v1/global/node_mesh_password": "secret123",
"/calico/bgp/v1/global/node_mesh_restart_time": "120",
"/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1",
"/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2",
"/calico/bgp/v1/global/node_mesh": string(meshConfigJSON),
"/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1",
"/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2",
}

c := newTestClient(cache, nil)
c.globalBGPConfig.Spec.NodeMeshMaxRestartTime = ptr.To(metav1.Duration{Duration: 120 * time.Second})
c.globalBGPConfig.Spec.NodeMeshPassword = &v3.BGPPassword{
SecretKeyRef: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "",
},
Key: "",
},
}
c.secretWatcher = &fakeSecret{secret: "secret123"}

config := &types.BirdBGPConfig{
NodeIP: "10.0.0.1",
ASNumber: "64512",
}

err := c.processMeshPeers(config, "", 4)
err := c.processMeshPeers(c.getBGPProcessorContext(), config, "", 4)
require.NoError(t, err)

require.Len(t, config.Peers, 1)
assert.Equal(t, "secret123", config.Peers[0].Password)
assert.Equal(t, "120", config.Peers[0].GracefulRestart)
}

type fakeSecret struct {
secret string
}

func (s *fakeSecret) GetSecret(name, key string) (string, error) {
return s.secret, nil
}

func (s *fakeSecret) MarkStale() {}

func (s *fakeSecret) SweepStale() {}

func TestProcessGlobalPeers_BasicPeer(t *testing.T) {
originalNodeName := NodeName
NodeName = "node-1"
Expand Down Expand Up @@ -1647,7 +1641,7 @@ func TestProcessPeers_CombinedMeshGlobalNode(t *testing.T) {
}

// Process all peer types
err := c.processPeers(config, 4)
err := c.processPeers(c.getBGPProcessorContext(), config, 4)
require.NoError(t, err)

// Should have: 1 mesh peer (node-2), 1 global peer, 1 node peer
Expand Down
Loading
Loading