diff options
| author | Ruben Herold <ruben@puettmann.net> | 2026-10-29 15:22:24 +0100 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-11-01 19:58:49 +0100 |
| commit | 39507a51f9ec0b22796d1ddb591812fc4c6ac903 (patch) | |
| tree | 29a2fe824b37287fdef3e4c80abccf5665e70905 /src/migration-scripts | |
| parent | 1005b1ca772a40d2937e7add9707656802cc8a1a (diff) | |
| download | vyos-1x-39507a51f9ec0b22796d1ddb591812fc4c6ac903.tar.gz vyos-1x-39507a51f9ec0b22796d1ddb591812fc4c6ac903.zip | |
static: T9482: handle a blackhole next to a next-hop of the same distance
FRR staticd refuses a blackhole or reject route next to a next-hop or
interface route of the same prefix and distance. VyOS accepted the
combination, the commit failed later in frr-reload and on some FRR
versions the config manager stalled.
Add a disable node to blackhole and reject. Verification refuses an
enabled blackhole or reject route next to a next-hop, interface or
dhcp-interface route of the same distance, and blackhole together with
reject, in routes, tables and VRFs. The same applies to the default
route of a PPPoE interface and of a DHCP interface that has a gateway.
A commit that changes only such an interface does not run the check,
frr-reload refuses it and the commit is rolled back.
A config migration sets disable on the route FRR did not apply, so
existing configurations keep loading and route as before.
Diffstat (limited to 'src/migration-scripts')
| -rw-r--r-- | src/migration-scripts/quagga/12-to-13 | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/src/migration-scripts/quagga/12-to-13 b/src/migration-scripts/quagga/12-to-13 new file mode 100644 index 000000000..15025c899 --- /dev/null +++ b/src/migration-scripts/quagga/12-to-13 @@ -0,0 +1,92 @@ +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# T9482: +# FRR staticd refuses a blackhole or reject route next to a next-hop or +# interface route of the same prefix with the same distance. The route +# configured first is kept, VyOS renders the blackhole and reject route last, +# so it is the one that is dropped. Make this visible in the CLI by setting +# "disable" on that blackhole or reject route. +# A prefix with both a blackhole and a reject route rendered only the +# blackhole route, the reject route gets "disable" the same way. + +from vyos.configtree import ConfigTree + + +def distance(config: ConfigTree, path: list) -> int: + if config.exists(path + ['distance']): + return int(config.return_value(path + ['distance'])) + return 1 + + +def migrate_static(config: ConfigTree, base: list) -> None: + if not config.exists(base): + return + + sources = [base] + if config.exists(base + ['table']): + sources += [base + ['table', table] for table in config.list_nodes(base + ['table'])] + + for source in sources: + for route in ['route', 'route6']: + route_base = source + [route] + if not config.exists(route_base): + continue + for prefix in config.list_nodes(route_base): + prefix_base = route_base + [prefix] + # Both at once used to render only the blackhole route, the + # reject route was ignored. Routes in a table were not checked, + # so such a configuration can exist. + blackhole_base = prefix_base + ['blackhole'] + reject_base = prefix_base + ['reject'] + if ( + config.exists(blackhole_base) + and config.exists(reject_base) + and not config.exists(blackhole_base + ['disable']) + and not config.exists(reject_base + ['disable']) + ): + config.set(reject_base + ['disable']) + + for discard in ['blackhole', 'reject']: + discard_base = prefix_base + [discard] + if not config.exists(discard_base): + continue + if config.exists(discard_base + ['disable']): + continue + discard_distance = distance(config, discard_base) + + conflict = False + # dhcp-interface routes use the default distance + if discard_distance == 1 and config.exists(prefix_base + ['dhcp-interface']): + conflict = True + for node in ['interface', 'next-hop']: + node_base = prefix_base + [node] + if not config.exists(node_base): + continue + for name in config.list_nodes(node_base): + if config.exists(node_base + [name, 'disable']): + continue + if distance(config, node_base + [name]) == discard_distance: + conflict = True + if conflict: + config.set(discard_base + ['disable']) + + +def migrate(config: ConfigTree) -> None: + migrate_static(config, ['protocols', 'static']) + + if config.exists(['vrf', 'name']): + for vrf in config.list_nodes(['vrf', 'name']): + migrate_static(config, ['vrf', 'name', vrf, 'protocols', 'static']) |
