Skip to content

wip: systemvm: enable IPv6 link-local on the control network - #13795

Open
wido wants to merge 2 commits into
apache:mainfrom
wido:systemvm-control-ipv6
Open

wido wants to merge 2 commits into
apache:mainfrom
wido:systemvm-control-ipv6

Conversation

@wido

@wido wido commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

This PR enables IPv6 link-local addressing on the control (link-local) network of system VMs, alongside the existing 169.254.0.0/16 IPv4 addressing. With IPv6 explicitly enabled on the control network we can move our SSH commands (on at least KVM) to link local IPv6 and remove the 169.254.0.0/16 addressing in the future. That can significantly reduce the code-base inside CloudStack. That's not part of this PR yet.

The existing issue #9957 talks about this and this PR should be groundwork for that to be done in the future.

System VM (common.sh):

  • The control NIC gets an IPv6 link-local address generated with EUI-64 (addr_gen_mode=0), so the address is deterministic and can be calculated from the NIC's MAC address
  • Router Advertisements and SLAAC are disabled on the interface (accept_ra=0, autoconf=0); only the link-local address is configured
  • setup_sshd() waits for Duplicate Address Detection to complete and adds a ListenAddress fe80::...%ethX entry to sshd_config, so sshd listens on both the IPv4 and IPv6 link-local addresses. The sed expressions are idempotent across reboots
  • This works for all system VM types, as setup_sshd() is always handed the control interface (eth0 for CPVM/SSVM on KVM, eth1 for routers)

Management Server / API:

  • listSystemVms now returns linklocalip6: the EUI-64 IPv6 link-local address calculated from the control NIC's MAC address using the existing (unit-tested) NetUtils.ipv6LinkLocal(). This performs the same U/L-bit flip + ff:fe expansion the kernel does, so the reported address always matches what the system VM configures
  • New response field on SystemVmResponse (since 4.23.0)

KVM agent:

  • When the control network is set up, the agent now enables IPv6 (link-local only, no RA/SLAAC) on the control bridge (cloud0) via a shared enableBridgeIpv6LinkLocal() helper in VifDriverBase, called from BridgeVifDriver, OvsVifDriver and IvsVifDriver
  • This runs unconditionally on agent start, so hosts with a pre-existing cloud0 bridge also get IPv6 enabled on it
  • With this, the host can reach system VMs over the control network with e.g. ssh -6 -p 3922 fe80::...%cloud0

UI:

  • The System VMs list and detail views show the new "Link-local/Control IPv6 address" field

This is a first step towards using IPv6 link-local instead of 169.254.0.0/16 on the control network. The Management Server still provisions system VMs over IPv4; nothing changes for existing deployments.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • build/CI
  • test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

How Has This Been Tested?

  • api, server and plugins/hypervisors/kvm compile cleanly
  • bash -n on common.sh
  • NetUtils.ipv6LinkLocal() is covered by existing unit tests in NetUtilsTest (testIpv6LinkLocal), verifying the EUI-64 calculation matches the kernel's

Bring up an EUI-64 based IPv6 link-local address on the control NIC
of system VMs and make sshd listen on it, in addition to the
existing 169.254.0.0/16 IPv4 address.

Since the address is generated with EUI-64 it can be calculated from
the MAC address of the control NIC. listSystemVms now returns it as
'linklocalip6', calculated by the Management Server using
NetUtils.ipv6LinkLocal(), and the UI shows it in the System VMs view.

On KVM the agent enables IPv6 (link-local only, no RA/SLAAC) on the
control bridge (cloud0) when setting up the control network, so the
host can reach system VMs over IPv6 once this is used.
The nftables ip6_firewall and ip6_acl tables created on VRs with
IPv6 networking have an input hook chain with policy drop, which
applies to all interfaces including the control NIC. Accept TCP 3922
between link-local addresses so sshd remains reachable on the IPv6
link-local address of the control interface. Restricting both saddr
and daddr to fe80::/10 ensures no global address can reach sshd.
@boring-cyborg boring-cyborg Bot added the Python Warning... Python code Ahead! label Aug 5, 2026
@codecov

codecov Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 19.65%. Comparing base (4f11707) to head (137b3da).
⚠️ Report is 166 commits behind head on main.

Files with missing lines Patch % Lines
...ache/cloudstack/api/response/SystemVmResponse.java 0.00% 6 Missing ⚠️
...m/cloud/hypervisor/kvm/resource/VifDriverBase.java 0.00% 4 Missing ⚠️
...src/main/java/com/cloud/api/ApiResponseHelper.java 0.00% 2 Missing ⚠️
...cloud/hypervisor/kvm/resource/BridgeVifDriver.java 0.00% 1 Missing ⚠️
...om/cloud/hypervisor/kvm/resource/IvsVifDriver.java 0.00% 1 Missing ⚠️
...om/cloud/hypervisor/kvm/resource/OvsVifDriver.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##               main   #13795   +/-   ##
=========================================
  Coverage     19.65%   19.65%           
  Complexity    19792    19792           
=========================================
  Files          6368     6368           
  Lines        574881   574896   +15     
  Branches      70351    70352    +1     
=========================================
+ Hits         112970   112975    +5     
- Misses       449639   449648    +9     
- Partials      12272    12273    +1     
Flag Coverage Δ
uitests 3.41% <ø> (ø)
unittests 20.92% <0.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@wido
wido requested a review from weizhouapache August 5, 2026 06:10
@DaanHoogland DaanHoogland linked an issue Aug 5, 2026 that may be closed by this pull request
@DaanHoogland DaanHoogland added this to the 4.24.0 milestone Aug 5, 2026
@DaanHoogland DaanHoogland moved this from Backlog to Ready in CloudStack Testing Aug 31, 2026
@Damans227

Copy link
Copy Markdown
Collaborator

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@Damans227 a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 19191

# Only a link-local address is wanted, no SLAAC/RA configuration
sysctl -w net.ipv6.conf.${eth}.accept_ra=0
sysctl -w net.ipv6.conf.${eth}.autoconf=0
sysctl -w net.ipv6.conf.${eth}.disable_ipv6=0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

tested this on a lab and it gets undone a few seconds later, so the feature doesnt survive a boot.

this sets the runtime value only. the template ships net.ipv6.conf.all.disable_ipv6 = 1 in /etc/sysctl.conf, and bootstrap.sh:104 runs sysctl -p after setup_sshd. boot order on the ssvm:

00:43:15  Configuring sshd
00:43:15  Enabling IPv6 link-local on interface eth0
00:43:17  Interface eth0 has IPv6 link-local address fe80::c00:a9ff:fefe:cadc
00:43:17  Configuring sshd to also listen on fe80::c00:a9ff:fefe:cadc%eth0
00:43:19  Executing cloud-early-config
00:43:20  Bootstrapping systemvm appliance      <- sysctl -p in here

end state, address gone and sshd only on v4:

$ ip -6 addr show dev eth0
(nothing)
$ sysctl -n net.ipv6.conf.eth0.disable_ipv6
1
$ ss -tln | grep 3922
LISTEN 0  128  169.254.202.220:3922  0.0.0.0:*

showed it directly on the vm:

$ sysctl -qw net.ipv6.conf.eth0.disable_ipv6=0
$ ip -6 addr show dev eth0 scope link
    inet6 fe80::c00:a9ff:fefe:cadc/64 scope link
$ sysctl -p
$ ip -6 addr show dev eth0 scope link
(gone)

so listSystemVms shows a linklocalip6 that nothing answers on. ping6 and ssh to it both time out from the host.

the design is fine by the way, once i left ipv6 on and restarted sshd it worked end to end over fe80::...%cloud0 on 3922.

can we clear the persistent one too? common.sh:126-129 already does that for net.ipv6.conf.all.disable_ipv6 including rewriting sysctl.conf.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I had a manual script checking a few things, didn't notice it was gone right after. Let me look into that, I now have a better lab to test this again.

My goal is to remove IPv4 entirely. We now have a complete allocation mechanism for IPv4 for this control cidr which is a lot of code and database entries which shouldn't be needed.

First step is adding IPv6, making sure its stable and then remove IPv4 in a future PR.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

sounds good, happy to test again once its updated

enable_ipv6_link_local $eth
if [ -n "$LINK_LOCAL_IP6" ]; then
log_it "Configuring sshd to also listen on ${LINK_LOCAL_IP6}%${eth}"
sed -i -e "/^ListenAddress $ip$/a ListenAddress ${LINK_LOCAL_IP6}%${eth}" /etc/ssh/sshd_config

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this writes the v6 ListenAddress but nothing rechecks the address is still there when sshd actually starts. on my lab it ended up pointing at an address that no longer existed:

$ grep -i ListenAddress /etc/ssh/sshd_config
ListenAddress 169.254.202.220
ListenAddress fe80::c00:a9ff:fefe:cadc%eth0

$ ss -tln | grep 3922
LISTEN 0  128  169.254.202.220:3922  0.0.0.0:*

sshd was fine with it and just bound v4, so no harm this time. but if it ever refused to start we'd have a systemvm with no way in.

worth only adding the line when the address is actually up at sshd start?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same goes for IPv4 ofcourse, we never check if the address exists, we assume it does. This needs fixing somewhere else.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fair enough, ok to fix that separately

vmResponse.setLinkLocalMacAddress(singleNicProfile.getMacAddress());
vmResponse.setLinkLocalNetmask(singleNicProfile.getIPv4Netmask());
if (singleNicProfile.getMacAddress() != null) {
vmResponse.setLinkLocalIp6(NetUtils.ipv6LinkLocal(singleNicProfile.getMacAddress()).toString());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is calculated from the mac, it never checks the vm actually has the address. so the field always shows something even when ipv6 is off on the systemvm.

on my lab both systemvms reported a linklocalip6 while nothing was listening on it.

is that the intent, a value you can always compute? if so maybe say so in the field description, otherwise someone will read it as "this address works".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The calculated address is always the same. It never changes or will be different. This is an RFC for IPv6 where the link-local is persistent. That's the great thing about it.

Calculate instead of store. Saves a lot of code.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

makes sense that it never changes. can we say in the field description that its calculated, so nobody reads it as the address being up?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, isn't this a basic IPv6 knowledge? Link Local IPv6 always exists and you don't need to do anything about it.

@Damans227 Damans227 moved this from Ready to In review in CloudStack Testing Sep 10, 2026
@wido wido changed the title systemvm: enable IPv6 link-local on the control network wip: systemvm: enable IPv6 link-local on the control network Sep 10, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

Replace control cidr for SSVM by IPv6 Link-Local

4 participants