Skip to content

[client] Add IPv6 support to SSH server, client config, and netflow logger#5687

Merged
lixmal merged 5 commits intoclient-ipv6-dnsfrom
client-ipv6-ssh-netflow
Apr 7, 2026
Merged

[client] Add IPv6 support to SSH server, client config, and netflow logger#5687
lixmal merged 5 commits intoclient-ipv6-dnsfrom
client-ipv6-ssh-netflow

Conversation

@lixmal
Copy link
Copy Markdown
Collaborator

@lixmal lixmal commented Mar 25, 2026

Describe your changes

  • SSH server listens on both v4 and v6 overlay addresses, with DNAT port redirection (22→22022) for both families
  • SSH connection validator accepts connections from both overlay networks, rejects own v4/v6 IP
  • SSH client config includes v6 as host alias, GetPeerSSHKey matches by v6 address
  • Netflow conntrack and logger recognize ICMPv6 (proto 58), check both overlay networks for flow relevance and direction inference
  • Rosenpass peer endpoint uses net.JoinHostPort for correct v6 bracket formatting

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features
    • Added native IPv6 support for SSH access and peer connectivity.
    • Enhanced network flow monitoring to track both IPv4 and IPv6 traffic.
    • Improved protocol handling to properly support ICMP and ICMPv6.
    • Better compatibility with dual-stack (IPv4/IPv6) network environments.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

This PR adds comprehensive IPv6 support across SSH and network flow handling. Changes include IPv6 address extraction from peer configurations, dual IPv4/IPv6 SSH server listeners, enhanced network flow tracking with IPv6 ICMP support, updated connection validation for both address families, and migrations from string to netip.Addr types for type safety.

Changes

Cohort / File(s) Summary
SSH Engine IPv6 Support
client/internal/engine_ssh.go
Added IPv6 port redirection (22→22022) for WireGuard interface, IPv6 listener binding, and peer IP extraction parsing multiple AllowedIps entries to store both IP (IPv4) and new IPv6 fields in PeerSSHInfo; peer SSH key lookup now matches on IPv6 addresses.
SSH Configuration & Type Updates
client/ssh/config/manager.go, client/ssh/config/manager_test.go
Migrated PeerSSHInfo.IP from string to netip.Addr type, added IPv6 netip.Addr field, updated host-pattern generation to conditionally append both IPv4 and IPv6 addresses when valid; test fixtures now use netip.MustParseAddr() for address construction.
SSH Server Multi-Listener Support
client/ssh/server/server.go
Added extraListeners field and new AddListener(ctx, addr) method to bind additional SSH listeners (enabling IPv6); enhanced connectionValidator to check both IPv4 and IPv6 network membership separately and block connections from both IPv4 and IPv6 forms of configured address.
Network Flow IPv6 Integration
client/internal/netflow/logger/logger.go, client/internal/netflow/logger/logger_test.go, client/internal/netflow/manager.go
Extended netflow logger to track IPv6 overlay prefix alongside IPv4; updated New constructor signature to accept both prefixes; added isOverlayIP helper performing containment checks against both IPv4 and IPv6 prefixes.
Conntrack IPv6 Flow Handling
client/internal/netflow/conntrack/conntrack.go
Updated ICMP protocol handling to treat both ICMP and ICMPv6 as source fields; enhanced relevantFlow and inferDirection to perform dual network containment checks against both IPv4 and IPv6 networks when valid.
ICMPv6 Protocol Support
client/internal/netflow/types/types.go
Added ICMPv6 = Protocol(58) constant and updated Protocol.String() method to recognize and format ICMPv6 protocol values.
Rosenpass UDP Endpoint Formatting
client/internal/rosenpass/manager.go
Replaced manual host/port concatenation with net.JoinHostPort() for proper endpoint formatting in peer UDP address construction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • pappz
  • pascal-fischer

Poem

🐰 IPv6 hops through the tunnel so bright,
Both sixes now listen with dual-stack might,
From ports to addresses, no v4 alone,
Our SSH connections through both ways have grown! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding IPv6 support to SSH server, client config, and netflow logger, which aligns with all the file modifications in this changeset.
Description check ✅ Passed Pull request includes all required sections: change description, issue ticket with dependency stack, checklist selection, and documentation decision with explanation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch client-ipv6-ssh-netflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lixmal
Copy link
Copy Markdown
Collaborator Author

lixmal commented Mar 25, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@lixmal lixmal force-pushed the client-ipv6-ssh-netflow branch 2 times, most recently from ec971ef to 4350665 Compare March 25, 2026 06:01
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
client/internal/netflow/types/types.go (1)

22-22: Update getProtocolFromPacket() to distinguish ICMPv6 from ICMP.

The new ICMPv6 = Protocol(58) constant is correct, but client/firewall/uspfilter/filter.go:1124 currently returns nftypes.ICMP for both LayerTypeICMPv4 and LayerTypeICMPv6. This causes ICMPv6 traffic to be recorded as Protocol(1) when originating from the userspace filter, while the conntrack path returns Protocol(58), creating inconsistent protocol identification across the two paths.

Update getProtocolFromPacket() to return nftypes.ICMPv6 for LayerTypeICMPv6 (aligning with the TODO comment at line 1200).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/internal/netflow/types/types.go` at line 22, getProtocolFromPacket
currently maps both LayerTypeICMPv4 and LayerTypeICMPv6 to nftypes.ICMP, causing
ICMPv6 packets to be misclassified; update the mapping in getProtocolFromPacket
(in client/firewall/uspfilter/filter.go) so that when pkt.LayerType() ==
gopacket.LayerTypeICMPv6 it returns nftypes.ICMPv6 (the new Protocol(58)) while
keeping LayerTypeICMPv4 mapped to nftypes.ICMP, and ensure the change aligns
with the existing TODO near the function.
client/ssh/config/manager_test.go (1)

29-60: Please cover the new IPv6 host alias in this test.

This fixture still only proves the IPv4/FQDN paths. The new PeerSSHInfo.IPv6 branch in Manager.buildHostPatterns() can regress without failing any test, so I'd add a dual-stack peer and assert the rendered config contains the IPv6 literal too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/ssh/config/manager_test.go` around lines 29 - 60, Test currently only
asserts IPv4/FQDN output so the new IPv6 branch in Manager.buildHostPatterns()
is untested; add a dual-stack PeerSSHInfo (set both IP and IPv6 fields) to the
peers slice passed to SetupSSHClientConfig and assert the generated config (read
from manager.sshConfigDir + manager.sshConfigFile) contains the IPv6 literal
string as well; ensure the added peer references the PeerSSHInfo.IPv6 field and
that SetupSSHClientConfig/Manager.buildHostPatterns() will render that address
so the test fails if IPv6 support regresses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/internal/engine_ssh.go`:
- Around line 44-50: Make the IPv6 setup atomic: when
e.wgInterface.Address().IPv6 is valid, first attempt to create the IPv6 listener
(the existing AddListener call) and only if that succeeds call
e.firewall.AddInboundDNAT(v6, ...). If AddInboundDNAT fails, immediately tear
down the previously created listener (call the listener cleanup/remove method
used where AddListener returns a handle, e.g., RemoveListener/Close) and log the
failure; conversely, only log the "SSH port redirection enabled" success message
after both the listener creation and AddInboundDNAT have succeeded. Also guard
the DNAT step behind whatever firewall capability check exists (e.g., a
SupportsIPv6 or similar) and skip/rollback when IPv6 DNAT is unsupported.
- Around line 163-176: extractPeerIPs currently accepts any parsed AllowedIps
prefix and picks the first per family, which can be a subnet (e.g. 10.0.0.0/8)
instead of a single-overlay host address; change it to only accept
single-address prefixes: when iterating peerConfig.GetAllowedIps(), parse the
prefix and then for IPv4 require prefix.Bits() == 32 and for IPv6 require
prefix.Bits() == 128 before using prefix.Addr().Unmap() to set v4/v6; update the
logic in extractPeerIPs (and the code that populates PeerSSHInfo) to ignore
non-/32 and non-/128 prefixes so only single-address overlay entries become
PeerSSHInfo addresses.

---

Nitpick comments:
In `@client/internal/netflow/types/types.go`:
- Line 22: getProtocolFromPacket currently maps both LayerTypeICMPv4 and
LayerTypeICMPv6 to nftypes.ICMP, causing ICMPv6 packets to be misclassified;
update the mapping in getProtocolFromPacket (in
client/firewall/uspfilter/filter.go) so that when pkt.LayerType() ==
gopacket.LayerTypeICMPv6 it returns nftypes.ICMPv6 (the new Protocol(58)) while
keeping LayerTypeICMPv4 mapped to nftypes.ICMP, and ensure the change aligns
with the existing TODO near the function.

In `@client/ssh/config/manager_test.go`:
- Around line 29-60: Test currently only asserts IPv4/FQDN output so the new
IPv6 branch in Manager.buildHostPatterns() is untested; add a dual-stack
PeerSSHInfo (set both IP and IPv6 fields) to the peers slice passed to
SetupSSHClientConfig and assert the generated config (read from
manager.sshConfigDir + manager.sshConfigFile) contains the IPv6 literal string
as well; ensure the added peer references the PeerSSHInfo.IPv6 field and that
SetupSSHClientConfig/Manager.buildHostPatterns() will render that address so the
test fails if IPv6 support regresses.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 87691edb-06d9-462d-9439-faecaf9c5285

📥 Commits

Reviewing files that changed from the base of the PR and between c3f4d21 and c6884ef.

📒 Files selected for processing (10)
  • client/internal/engine_ssh.go
  • client/internal/netflow/conntrack/conntrack.go
  • client/internal/netflow/logger/logger.go
  • client/internal/netflow/logger/logger_test.go
  • client/internal/netflow/manager.go
  • client/internal/netflow/types/types.go
  • client/internal/rosenpass/manager.go
  • client/ssh/config/manager.go
  • client/ssh/config/manager_test.go
  • client/ssh/server/server.go

Comment thread client/internal/engine_ssh.go
Comment thread client/internal/engine_ssh.go
@lixmal lixmal force-pushed the client-ipv6-ssh-netflow branch from 4350665 to e5e177e Compare March 25, 2026 06:26
@lixmal lixmal closed this Mar 25, 2026
@lixmal lixmal force-pushed the client-ipv6-ssh-netflow branch from e5e177e to 1a7e835 Compare March 25, 2026 08:56
@sonarqubecloud
Copy link
Copy Markdown

@lixmal lixmal merged commit daeb90c into client-ipv6-dns Apr 7, 2026
42 checks passed
@lixmal lixmal deleted the client-ipv6-ssh-netflow branch April 7, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants