Skip to content

Fix IPv6 DNAT/SNAT rule formatting in iptables and nftables backends#11960

Merged
hjiawei merged 1 commit into
projectcalico:masterfrom
hjiawei:join-host-port-ipt-nft
Mar 2, 2026
Merged

Fix IPv6 DNAT/SNAT rule formatting in iptables and nftables backends#11960
hjiawei merged 1 commit into
projectcalico:masterfrom
hjiawei:join-host-port-ipt-nft

Conversation

@hjiawei
Copy link
Copy Markdown
Contributor

@hjiawei hjiawei commented Feb 28, 2026

Both ip6tables and nftables require bracketed IPv6 addresses when a port is present (e.g., [2001:db8::1]:80). The DNAT and SNAT actions were using plain string formatting which produces invalid rules for IPv6. Use net.JoinHostPort() which correctly brackets IPv6 addresses.

Release note:

TBD

Both ip6tables and nftables require bracketed IPv6 addresses when a
port is present (e.g., [2001:db8::1]:80). The DNAT and SNAT actions
were using plain string formatting which produces invalid rules for
IPv6. Use net.JoinHostPort() which correctly brackets IPv6 addresses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hjiawei hjiawei requested a review from a team as a code owner February 28, 2026 16:54
Copilot AI review requested due to automatic review settings February 28, 2026 16:54
@marvin-tigera marvin-tigera added this to the Calico v3.32.0 milestone Feb 28, 2026
@marvin-tigera marvin-tigera added release-note-required Change has user-facing impact (no matter how small) docs-pr-required Change is not yet documented labels Feb 28, 2026
@hjiawei hjiawei added docs-not-required Docs not required for this change release-note-not-required Change has no user-facing impact and removed release-note-required Change has user-facing impact (no matter how small) docs-pr-required Change is not yet documented labels Feb 28, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes NAT rule rendering so IPv6 addresses are correctly bracketed when combined with ports/port-ranges, which is required by ip6tables and nftables.

Changes:

  • Use net.JoinHostPort() when rendering SNAT target with a port range in NATOutgoingChain.
  • Use net.JoinHostPort() for DNAT rule fragments in both iptables and nftables backends.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
felix/rules/nat.go Formats SNAT address+port-range using net.JoinHostPort() to ensure valid IPv6 output.
felix/nftables/actions.go Formats DNAT addr:port using net.JoinHostPort() to ensure valid IPv6 output.
felix/iptables/actions.go Formats DNAT addr:port using net.JoinHostPort() to ensure valid IPv6 output.

Comment thread felix/rules/nat.go
Comment on lines 92 to 98
if r.NATPortRange.MaxPort > 0 {
toPorts := fmt.Sprintf("%d-%d", r.NATPortRange.MinPort, r.NATPortRange.MaxPort)
portRangeSnatRule := r.Masq(toPorts)
if r.NATOutgoingAddress != nil {
toAddress := fmt.Sprintf("%s:%s", r.NATOutgoingAddress.String(), toPorts)
toAddress := net.JoinHostPort(r.NATOutgoingAddress.String(), toPorts)
portRangeSnatRule = r.SNAT(toAddress)
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The new JoinHostPort-based formatting is specifically to fix IPv6 when a port range is present, but the existing NATOutgoingChain unit tests only cover IPv4. Please add an IPv6 test case that sets NATOutgoingAddress to an IPv6 literal and NATPortRange to a non-zero range, and assert the rendered SNAT address is bracketed (e.g., "[v6addr]:min-max").

Copilot uses AI. Check for mistakes.
Comment thread felix/iptables/actions.go
Comment on lines 275 to 280
func (g DNATAction) ToFragment(features *environment.Features) string {
if g.DestPort == 0 {
return fmt.Sprintf("--jump DNAT --to-destination %s", g.DestAddr)
} else {
return fmt.Sprintf("--jump DNAT --to-destination %s:%d", g.DestAddr, g.DestPort)
return fmt.Sprintf("--jump DNAT --to-destination %s", net.JoinHostPort(g.DestAddr, strconv.Itoa(int(g.DestPort))))
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

There are existing action rendering tests in this package, but none validate DNAT rendering for IPv6 addresses with a port. Please add a test case using an IPv6 DestAddr and non-zero DestPort to assert the fragment uses bracketed IPv6 hostport formatting ("[v6addr]:port").

Copilot uses AI. Check for mistakes.
Comment thread felix/nftables/actions.go
Comment on lines 293 to 298
func (g DNATAction) ToFragment(features *environment.Features) string {
if g.DestPort == 0 {
return fmt.Sprintf("dnat to %s", g.DestAddr)
} else {
return fmt.Sprintf("dnat to %s:%d", g.DestAddr, g.DestPort)
return fmt.Sprintf("dnat to %s", net.JoinHostPort(g.DestAddr, strconv.Itoa(int(g.DestPort))))
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

There are existing action rendering tests in this package, but none validate DNAT rendering for IPv6 addresses with a port. Please add a test case using an IPv6 DestAddr and non-zero DestPort to assert the fragment uses bracketed IPv6 hostport formatting ("[v6addr]:port").

Copilot uses AI. Check for mistakes.
@hjiawei hjiawei merged commit 443d5cd into projectcalico:master Mar 2, 2026
10 of 12 checks passed
@hjiawei hjiawei deleted the join-host-port-ipt-nft branch March 2, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-not-required Docs not required for this change release-note-not-required Change has no user-facing impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants