Repository: vyos/vyos-1x
Pull request #4463: router-advert: T7380: Implement auto-ignore-prefix syntax for router advertisements
Author: login: ryanzuwala
Labels: name: current
name: mirror-completed
Created: 2025-04-21T05:34:33Z | Merged: 2025-04-23T20:32:54Z

<!-- All PR should follow this template to allow a clean and transparent review -->
<!-- Text placed between these delimiters is considered a comment and is not rendered -->

## Change summary
<!--- Provide a general summary of your changes in the Title above -->
radvd released a new feature, "auto ignore prefixes", which will be critically important for enterprise IPv6 deployments, or anywhere where internal ULAs are in use.

You can read all about how this functionality works in this merged PR conversation: https://github.com/radvd-project/radvd/pull/189

But to prevent link rot, I'll also explain its use case here:

When router advertisements are configured on an interface with multiple IPv6 prefixes, it is recommended that you configure your interface with the special wildcard prefix "::/64". The wildcard prefix will generate RAs for all prefixes including dynamic ones obtained from the ISP via DHCPv6-PD.

The downside is that RAs for all prefixes will have the same configuration. That is, they'll have the same flags and preferred/valid lifetimes. If you attempt to override these configuration settings for a specific prefix, the RA will contain a duplicate of the prefix - one with the general settings from the wildcard, and the other with your changes. This results in unpredictable client behavior.

"Auto ignore prefixes" solves this problem by allowing you to exclude certain prefixes from being auto-generated from the wildcard. This is useful for when you want to set different configuration options for different prefixes, or when you want to prevent the router from advertising a specific prefix altogether.

## Types of changes
<!---
What types of changes does your code introduce? Put an 'x' in all the boxes that apply.
NOTE: Markdown requires no leading or trailing whitespace inside the [ ] for checking
the box, please use [x]
-->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes)
- [ ] Migration from an old Vyatta component to vyos-1x, please link to related PR inside obsoleted component
- [ ] Other (please describe):

## Related Task(s)
<!-- optional: Link to related other tasks on Phabricator. -->
* https://vyos.dev/T7380

## Related PR(s)
<!-- Link here any PRs in other repositories that are required by this PR -->

## How to test / Smoketest result
<!---
Please describe in detail how you tested your changes. Include details of your testing
environment, and the tests you ran. When pasting configs, logs, shell output, backtraces,
and other large chunks of text, surround this text with triple backtics
```
like this
```

Or provide the output of the smoketest

```
$ /usr/libexec/vyos/tests/smoke/cli/test_xxx_feature.py
test_01_simple_options (__main__.TestFeature.test_01_simple_options) ... ok
```
-->
**Demonstration**
On a clean install of VyOS rolling in VirtualBox, configure eth1 as a simulated LAN interface:

```
set interfaces ethernet eth1 address '2001:db8::1/64'
set interfaces ethernet eth1 address 'fd00::1/64'
set interfaces ethernet eth1 address 'fd01::1/64'
```

NOTE: `2001:db8::1` is a fake GUA for documentation purposes, but for this example, pretend it's a delegated ISP prefix.

Then configure router advertisements with the wildcard `::/64` prefix:

```
set service router-advert interface eth1 prefix ::/64
```

As expected, this generates an RA with all three prefixes:

![wireshark-initial-wildcard-cfg](https://github.com/user-attachments/assets/fbe0cc8e-e0ff-46e5-8207-fefd9d33fc7e)

Note, all three prefixes will have the same lifetimes and flags set. Let's attempt to override the settings for the `fd00::/64` prefix:

```
set service router-advert interface eth1 prefix fd00::/64 preferred-lifetime '5555'
set service router-advert interface eth1 prefix fd00::/64 valid-lifetime '9999'
```

As you can see below, this produces a problem. We now have duplicate advertisements for the same prefix, one with the general settings from the wildcard, and the other with our manual configuration. This will result in unpredictable client behavior:

![wireshark-before-aip-duplicate](https://github.com/user-attachments/assets/cb6ed33d-6503-460a-ab8b-562884c02a53)

Now let's remove the manual configuation and try out radvd's new "auto ignore prefixes" feature:

```
delete service router-advert interface eth1 prefix fd00::/64 preferred-lifetime '5555'
delete service router-advert interface eth1 prefix fd00::/64 valid-lifetime '9999'
delete service router-advert interface eth1 prefix fd00::/64

set service router-advert interface eth1 auto-ignore fd00::/64
set service router-advert interface eth1 auto-ignore fd01::/64
```

This produces the following in /run/radvd/radvd.conf:

```
### Autogenerated by service_router-advert.py ###

interface eth1 {
    IgnoreIfMissing on;
    AdvDefaultPreference medium;
    MaxRtrAdvInterval 600;
    AdvReachableTime 0;
    AdvIntervalOpt on;
    AdvSendAdvert on;
    AdvOtherConfigFlag off;
    AdvRetransTimer 0;
    AdvCurHopLimit 64;
    autoignoreprefixes {
        fd00::/64;
        fd01::/64;
    };
    prefix ::/64 {
        AdvAutonomous on;
        AdvValidLifetime 2592000;
        AdvOnLink on;
        AdvPreferredLifetime 14400;
        DeprecatePrefix off;
        DecrementLifetimes off;
    };
};
```

Now, only the dynamic ISP prefix is being advertised via the wildcard:

![wireshark-after-ignore-prefix](https://github.com/user-attachments/assets/f35afec6-5399-49bf-a975-7d7782716aeb)

If you still want to broadcast `fd00::/64` with your own settings, you can now do so manually:

```
set service router-advert interface eth1 prefix fd00::/64 preferred-lifetime '5555'
set service router-advert interface eth1 prefix fd00::/64 valid-lifetime '9999'
```

![wireshark-manual-config](https://github.com/user-attachments/assets/e88e05f9-87c3-4222-8c0a-c87716c6397e)

Now, `2001:db8::/64` and `fd00::/64` are both advertised once again. But this time, the `fd00::/64` prefix only appears once, and it contains our manual configuration. This achieves the desired result.

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
<!--- The entire development process is outlined here: https://docs.vyos.io/en/latest/contributing/development.html -->
- [x] I have read the [**CONTRIBUTING**](https://github.com/vyos/vyos-1x/blob/current/CONTRIBUTING.md) document
- [x] I have linked this PR to one or more Phabricator Task(s)
- [x] I have run the components [**SMOKETESTS**](https://github.com/vyos/vyos-1x/tree/current/smoketest/scripts/cli) if applicable
- [x] My commit headlines contain a valid Task id
- [x] My change requires a change to the documentation
- [x] I have updated the documentation accordingly



Diff:
diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2
index a83bd03ac9..e37cfde6cc 100644
--- a/data/templates/router-advert/radvd.conf.j2
+++ b/data/templates/router-advert/radvd.conf.j2
@@ -57,6 +57,13 @@ interface {{ iface }} {
     };
 {%             endfor %}
 {%         endif %}
+{%         if iface_config.auto_ignore is vyos_defined %}
+    autoignoreprefixes {
+{%             for auto_ignore_prefix in iface_config.auto_ignore %}
+        {{ auto_ignore_prefix }};
+{%             endfor %}
+    };
+{%         endif %}
 {%         if iface_config.prefix is vyos_defined %}
 {%             for prefix, prefix_options in iface_config.prefix.items() %}
     prefix {{ prefix }} {
diff --git a/debian/control b/debian/control
index ffa21f8407..ec3147820f 100644
--- a/debian/control
+++ b/debian/control
@@ -203,7 +203,7 @@ Depends:
   ndppd,
 # End "service ndp-proxy"
 # For "service router-advert"
-  radvd,
+  radvd (>= 2.20),
 # End "service route-advert"
 # For "load-balancing haproxy"
   haproxy,
diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in
index 3fd33540a7..7f96cdb19f 100644
--- a/interface-definitions/service_router-advert.xml.in
+++ b/interface-definitions/service_router-advert.xml.in
@@ -255,6 +255,19 @@
                   </leafNode>
                 </children>
               </tagNode>
+              <leafNode name="auto-ignore">
+                <properties>
+                  <help>IPv6 prefix to be excluded in Router Advertisements (RAs) - use in conjunction with the ::/64 wildcard prefix</help>
+                  <valueHelp>
+                    <format>ipv6net</format>
+                    <description>IPv6 prefix to be excluded</description>
+                  </valueHelp>
+                  <constraint>
+                    <validator name="ipv6-prefix"/>
+                  </constraint>
+                  <multi/>
+                </properties>
+              </leafNode>
               <tagNode name="prefix">
                 <properties>
                   <help>IPv6 prefix to be advertised in Router Advertisements (RAs)</help>
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index 6dbb6add48..83342bc729 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -252,6 +252,49 @@ def test_advsendadvert_advintervalopt(self):
         tmp = get_config_value('AdvIntervalOpt')
         self.assertEqual(tmp, 'off')
 
+    def test_auto_ignore(self):
+        isp_prefix = '2001:db8::/64'
+        ula_prefixes = ['fd00::/64', 'fd01::/64']
+
+        self.cli_set(base_path + ['auto-ignore', isp_prefix])
+
+        for ula_prefix in ula_prefixes:
+            self.cli_set(base_path + ['auto-ignore', ula_prefix])
+        
+        # commit changes
+        self.cli_commit()
+        config = read_file(RADVD_CONF)
+
+        # ensure autoignoreprefixes block is generated in config file
+        tmp = f'autoignoreprefixes' + ' {'
+        self.assertIn(tmp, config)
+
+        # ensure all three prefixes are contained in the block
+        self.assertIn(f'        {isp_prefix};', config)
+        for ula_prefix in ula_prefixes:
+            self.assertIn(f'        {ula_prefix};', config)
+        
+        # remove a prefix and verify it's gone
+        self.cli_delete(base_path + ['auto-ignore', ula_prefixes[1]])
+        
+        self.cli_commit()
+        config = read_file(RADVD_CONF)
+
+        self.assertNotIn(f'        {ula_prefixes[1]};', config)
+
+        # ensure remaining two prefixes are still present
+        self.assertIn(f'        {ula_prefixes[0]};', config)
+        self.assertIn(f'        {isp_prefix};', config)
+
+        # remove the remaining two prefixes and verify the config block is gone
+        self.cli_delete(base_path + ['auto-ignore', ula_prefixes[0]])
+        self.cli_delete(base_path + ['auto-ignore', isp_prefix])
+
+        self.cli_commit()
+        config = read_file(RADVD_CONF)
+
+        tmp = f'autoignoreprefixes' + ' {'
+        self.assertNotIn(tmp, config)
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)


Reviews:
user: login: c-po
body: Please extend the smoketests in https://github.com/vyos/vyos-1x/blob/current/smoketest/scripts/cli/test_service_router-advert.py to also cover this new feature.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-21T06:26:57Z
user: login: ryanzuwala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T06:27:42Z
user: login: sever-sever
body: The logic looks correct and we have updated smoketest
Approved 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-21T22:02:29Z
user: login: c-po
body: All requested changes addresses. Smoketests added and logic looks correct. Thank you.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-23T20:31:58Z

Review comments:
diff_hunk: @@ -255,6 +255,19 @@
                   </leafNode>
                 </children>
               </tagNode>
+              <leafNode name="auto-ignore-prefix">
path: interface-definitions/service_router-advert.xml.in
user: login: c-po
body: Can we change this to `auto-ignore` to keep the CLI short?
created_at: 2025-04-21T06:25:42Z
author_association: MEMBER
diff_hunk: @@ -255,6 +255,19 @@
                   </leafNode>
                 </children>
               </tagNode>
+              <leafNode name="auto-ignore-prefix">
path: interface-definitions/service_router-advert.xml.in
user: login: ryanzuwala
body: Will do!
created_at: 2025-04-21T06:27:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-21T05:34:53Z
body: <!-- add-pr-comment:TITLE_COMMIT_VALIDATION -->

❌
PR title does not match the required format
author_association: NONE
user: login: c-po
created_at: 2025-04-21T06:39:29Z
body: Please also lift the package dependency in https://github.com/vyos/vyos-1x/blob/2f54cbc0504a20424274e45b9210467d33acf919/debian/control#L206
to: `radvd (>= 2.20),`

author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-04-21T09:28:51Z
body: <!-- add-pr-comment:SMOKETEST_RESULTS -->

CI integration 👍 passed!

### Details

[CI logs](https://github.com/vyos/vyos-1x/actions/runs/14569493024)

* CLI Smoketests (no interfaces) 👍 passed
* CLI Smoketests (interfaces only) 👍 passed
* Config tests 👍 passed
* RAID1 tests 👍 passed
* TPM tests ❌ failed
author_association: NONE
user: login: ryanzuwala
created_at: 2025-04-21T17:34:35Z
body: Smoke test added and documentation PR: https://github.com/vyos/vyos-documentation/pull/1626
author_association: CONTRIBUTOR