Skip to content

fix: BGP speaker reads IPs from pod annotations instead of pod status#6344

Merged
oilbeater merged 1 commit intomasterfrom
fix/bgp-speaker-annotation-ip
Mar 2, 2026
Merged

fix: BGP speaker reads IPs from pod annotations instead of pod status#6344
oilbeater merged 1 commit intomasterfrom
fix/bgp-speaker-annotation-ip

Conversation

@oilbeater
Copy link
Copy Markdown
Collaborator

Summary

  • BGP Speaker's syncSubnetRoutes() previously read pod IPs from pod.Status.PodIPs (kubelet-managed, primary CNI only), causing incorrect BGP advertisements when Kube-OVN runs as a secondary CNI or with attachment networks
  • Replaced with pod annotation-based IP discovery ({provider}.kubernetes.io/ip_address), which is the unified source for all Kube-OVN managed IPs across primary and attachment networks
  • Pod-level ovn.kubernetes.io/bgp annotation takes priority over subnet-level BGP policy; subnet CIDR-level broadcasting remains unchanged

Fixes #6270

Test plan

  • Added 11 unit tests covering: primary/attachment network BGP announcements, local/cluster policies, dual-stack, multi-network pods, non-primary CNI mode, dead/unannotated pod filtering
  • make lint passes with 0 issues
  • All pkg/speaker tests pass
  • E2E: verify BGP route advertisements with Kube-OVN as secondary CNI
  • E2E: verify attachment network IPs are correctly announced

🤖 Generated with Claude Code

@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working go Pull requests that update Go code test automation tests labels Feb 26, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @oilbeater, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where the BGP speaker failed to correctly advertise pod IPs in certain Kube-OVN configurations, specifically when Kube-OVN operated as a secondary CNI or utilized attachment networks. The core change involves shifting the source of pod IP discovery from Kubernetes' native pod status to Kube-OVN's custom pod annotations, which provide a unified and accurate representation of all managed IPs. This enhancement ensures reliable BGP route advertisements across diverse networking setups, improving the stability and functionality of the BGP speaker component.

Highlights

  • IP Source Change: The BGP speaker now reads pod IPs from pod annotations ({provider}.kubernetes.io/ip_address) instead of pod.Status.PodIPs. This ensures correct BGP advertisements for Kube-OVN when used as a secondary CNI or with attachment networks, as pod.Status.PodIPs only includes primary CNI IPs.
  • BGP Policy Precedence: Pod-level BGP annotations (ovn.kubernetes.io/bgp) now take precedence over subnet-level BGP policies, allowing for more granular control over IP advertisement.
  • Refactored IP Collection Logic: The logic for collecting expected BGP prefixes from pods has been extracted into a new, dedicated function collectPodExpectedPrefixes for improved modularity and testability.
  • Comprehensive Unit Tests: Eleven new unit tests have been added to cover various scenarios for IP collection, including primary/attachment networks, local/cluster policies, dual-stack, multi-network pods, and filtering of dead or unannotated pods.
Changelog
  • pkg/speaker/subnet.go
    • Updated imports to include fmt, corev1, and kubeovnv1.
    • Modified syncSubnetRoutes to remove localSubnets and introduce subnetByName map for efficient subnet lookup.
    • Refactored pod IP collection logic into a new helper function collectPodExpectedPrefixes.
    • Adjusted BGP policy evaluation to prioritize pod-level annotations over subnet-level annotations.
    • Changed IP retrieval from pod.Status.PodIPs to pod annotations ({provider}.kubernetes.io/ip_address) to support secondary CNI and attachment networks.
    • Added handling for comma-separated IP addresses within annotations for dual-stack or multiple IPs.
  • pkg/speaker/subnet_test.go
    • Added a new test file subnet_test.go.
    • Implemented TestCollectPodExpectedPrefixes with 11 distinct test cases.
    • Included tests for primary and attachment network BGP announcements, local and cluster policies, dual-stack configurations, multi-network pods, non-primary CNI mode, and filtering of dead or unannotated pods.
Activity
  • The author has added 11 new unit tests to cover various scenarios for IP collection.
  • The author has confirmed that make lint passes with 0 issues.
  • All existing pkg/speaker tests have passed.
  • E2E tests are planned to verify BGP route advertisements with Kube-OVN as a secondary CNI and for attachment network IPs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the BGP speaker logic to read pod IPs from annotations instead of pod.Status.PodIPs to support secondary CNI and attachment network scenarios. However, this introduces a critical security vulnerability: trusting unvalidated pod annotations for BGP route announcements allows an attacker to inject arbitrary IP addresses and hijack network traffic. Additionally, a non-standard function will cause compilation failure, and invalid BGP policy values need proper handling. The suggested fix re-introduces validation to ensure announced IPs belong to the pod's assigned subnet CIDR and addresses the compilation issue and policy handling.

Comment on lines 137 to 148
switch policy {
case "true":
fallthrough
case announcePolicyCluster:
for _, podIP := range pod.Status.PodIPs {
ips[util.CheckProtocol(podIP.IP)] = podIP.IP
case "true", announcePolicyCluster:
for ip := range strings.SplitSeq(ipStr, ",") {
addExpectedPrefix(strings.TrimSpace(ip), bgpExpected)
}
case announcePolicyLocal:
if pod.Spec.NodeName == c.config.NodeName {
for _, podIP := range pod.Status.PodIPs {
ips[util.CheckProtocol(podIP.IP)] = podIP.IP
}
}
default:
klog.Warningf("invalid pod annotation %s=%s", util.BgpAnnotation, policy)
}
} else if pod.Spec.NodeName == c.config.NodeName {
cidrBlock := localSubnets[pod.Annotations[util.LogicalSwitchAnnotation]]
if cidrBlock != "" {
for _, podIP := range pod.Status.PodIPs {
if util.CIDRContainIP(cidrBlock, podIP.IP) {
ips[util.CheckProtocol(podIP.IP)] = podIP.IP
if pod.Spec.NodeName == nodeName {
for ip := range strings.SplitSeq(ipStr, ",") {
addExpectedPrefix(strings.TrimSpace(ip), bgpExpected)
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-high high

This switch block in collectPodExpectedPrefixes has a critical security vulnerability: it reads IP addresses from pod annotations ({provider}.kubernetes.io/ip_address) without validating them against the subnet's CIDR block. An attacker with patch pod permissions can exploit this to announce arbitrary IP addresses, leading to BGP hijacking. This is a regression from the previous implementation.

Additionally, there are functional issues:

  1. The function strings.SplitSeq is non-standard and will cause a compilation error. Use strings.Split.
  2. The loop for ip := range ... is incorrect for iterating slice values; use for _, ip := range ....
  3. A default case is missing from the switch statement to handle and log unknown BGP policies, which is crucial for debugging.
switch policy {
			case "true", announcePolicyCluster:
				for ip := range strings.SplitSeq(ipStr, ",") {
					ip = strings.TrimSpace(ip)
					if !util.CIDRContainIP(subnet.Spec.CIDRBlock, ip) {
						klog.Warningf("pod %s/%s IP %s is not in subnet %s CIDR %s", pod.Namespace, pod.Name, ip, subnet.Name, subnet.Spec.CIDRBlock)
						continue
					}
					addExpectedPrefix(ip, bgpExpected)
				}
			case announcePolicyLocal:
				if pod.Spec.NodeName == nodeName {
					for ip := range strings.SplitSeq(ipStr, ",") {
						ip = strings.TrimSpace(ip)
						if !util.CIDRContainIP(subnet.Spec.CIDRBlock, ip) {
							klog.Warningf("pod %s/%s IP %s is not in subnet %s CIDR %s", pod.Namespace, pod.Name, ip, subnet.Name, subnet.Spec.CIDRBlock)
							continue
						}
						addExpectedPrefix(ip, bgpExpected)
					}
				}
			}

@oilbeater oilbeater force-pushed the fix/bgp-speaker-annotation-ip branch from 37c4628 to f5e038b Compare February 26, 2026 05:37
BGP Speaker's syncSubnetRoutes() previously read pod IPs from
pod.Status.PodIPs, which is managed by kubelet and only contains
primary CNI IPs. This caused incorrect behavior when Kube-OVN runs
as a secondary CNI (non-primary mode) or when attachment networks
are configured — the speaker would either advertise the wrong IPs
or miss attachment network IPs entirely.

Replace pod.Status.PodIPs with pod annotation-based IP discovery
({provider}.kubernetes.io/ip_address), which is the unified and
reliable source for all Kube-OVN managed IPs across both primary
and attachment networks.

Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com>
@oilbeater oilbeater force-pushed the fix/bgp-speaker-annotation-ip branch from f5e038b to c333b1c Compare March 2, 2026 08:20
@coveralls
Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 22567353295

Details

  • 25 of 39 (64.1%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.1%) to 23.181%

Changes Missing Coverage Covered Lines Changed/Added Lines %
pkg/speaker/subnet.go 25 39 64.1%
Totals Coverage Status
Change from base Build 22566768185: 0.1%
Covered Lines: 12618
Relevant Lines: 54433

💛 - Coveralls

@oilbeater oilbeater merged commit 6d6c558 into master Mar 2, 2026
77 checks passed
@oilbeater oilbeater deleted the fix/bgp-speaker-annotation-ip branch March 2, 2026 09:09
@Zeratyl06
Copy link
Copy Markdown

@oilbeater when can we expect this functionality to appear in the next 1.15 release?

oilbeater added a commit that referenced this pull request Mar 17, 2026
…#6344)

BGP Speaker's syncSubnetRoutes() previously read pod IPs from
pod.Status.PodIPs, which is managed by kubelet and only contains
primary CNI IPs. This caused incorrect behavior when Kube-OVN runs
as a secondary CNI (non-primary mode) or when attachment networks
are configured — the speaker would either advertise the wrong IPs
or miss attachment network IPs entirely.

Replace pod.Status.PodIPs with pod annotation-based IP discovery
({provider}.kubernetes.io/ip_address), which is the unified and
reliable source for all Kube-OVN managed IPs across both primary
and attachment networks.

Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
zbb88888 pushed a commit to qiniu/kube-ovn that referenced this pull request Apr 7, 2026
…kubeovn#6344)

BGP Speaker's syncSubnetRoutes() previously read pod IPs from
pod.Status.PodIPs, which is managed by kubelet and only contains
primary CNI IPs. This caused incorrect behavior when Kube-OVN runs
as a secondary CNI (non-primary mode) or when attachment networks
are configured — the speaker would either advertise the wrong IPs
or miss attachment network IPs entirely.

Replace pod.Status.PodIPs with pod annotation-based IP discovery
({provider}.kubernetes.io/ip_address), which is the unified and
reliable source for all Kube-OVN managed IPs across both primary
and attachment networks.

Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit f3dca4b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working go Pull requests that update Go code size:L This PR changes 100-499 lines, ignoring generated files. test automation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] BGP does not work in the Non-Primary CNI Mode configuration

3 participants