summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2025-08-28 14:46:48 +0200
committerChristian Breunig <christian@breunig.cc>2025-09-09 17:58:26 +0200
commitd871fe9c4c65de87232802ed54b263c9b2824391 (patch)
tree85d9e15d9fad7ddba7bde3eae357a7a051fa3ca2 /src
parenta4b72d3839f2ee05456b0677a8f2cc27c04a9423 (diff)
downloadvyos-1x-d871fe9c4c65de87232802ed54b263c9b2824391.tar.gz
vyos-1x-d871fe9c4c65de87232802ed54b263c9b2824391.zip
bgp: T7760: deprecate per bgp vrf instance system-as node
Originating from the bug in T7665. To avoid potential issues down the line - and given that there's no compelling technical reason to retain the system-as CLI node under per-VRF BGP configuration, which cannot be achieved through alternative means - the maintainers have collectively decided to deprecate the following command: set vrf name <name> protocols bgp system-as <asn> Starting with VyOS 1.4.4, this CLI command will be considered deprecated. While it will still be accepted, it will no longer have any operational effect. A deprecation warning will be displayed at commit time, indicating that the BGP ASN from the global BGP configuration is now used instead. A migration script will handle the transition and perform the following actions: * Ensure a global BGP configuration exists; if not, initialize one. * Iterate over all configured VRFs to determine whether a BGP instance exists * For any insance, update the configuration to use the global system-as and apply the local-as ASN no-prepend replace-as option on all affected neighbors to preserve existing behavior. * If a neighbor is already configured with a local-as directive, that neighbor will be excluded from the migration process, as it already follows a custom configuration. * Add allowas-in per neighbor option. Required to not deny prefix received updates due to as-path contains our own global ASN.
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py34
-rw-r--r--src/migration-scripts/bgp/6-to-7108
2 files changed, 132 insertions, 10 deletions
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index fe5740a18..ab0e7e44a 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -18,6 +18,7 @@ from sys import exit
from sys import argv
from vyos.base import Warning
+from vyos.base import DeprecationWarning
from vyos.config import Config
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_prefix_list
@@ -210,8 +211,27 @@ def verify(config_dict):
return None
- if 'system_as' not in bgp:
- raise ConfigError('BGP system-as number must be defined!')
+ ERR_MSG_GLOBAL_VRF_AS_MISSING = 'BGP "system-as" number must be defined! Use "set protocols ' \
+ 'bgp system-as <asn>" to define a global BGP instance AS number.'
+ system_as = None
+ if vrf:
+ system_as = dict_search('dependent_vrfs.default.protocols.bgp.system_as', bgp)
+ if not system_as:
+ raise ConfigError(ERR_MSG_GLOBAL_VRF_AS_MISSING)
+
+ if 'system_as' in bgp:
+ tmp_as = bgp['system_as']
+ DeprecationWarning(f'CLI command "vrf name {vrf} protocols bgp system-as ' \
+ f'{tmp_as}" is ignored and will be removed in VyOS 1.5! ' \
+ f'\n\nGlobal "protocols bgp system-as {system_as}" option ' \
+ 'applies, use per neighbor "local-as" option to override.')
+
+ elif 'system_as' not in bgp:
+ raise ConfigError(ERR_MSG_GLOBAL_VRF_AS_MISSING)
+
+ # Cache global defined system AS number used in further checks
+ if not system_as:
+ system_as = bgp['system_as']
# Verify BMP
if 'bmp' in bgp:
@@ -257,7 +277,7 @@ def verify(config_dict):
if 'remote_as' in peer_config:
is_ibgp = True
if peer_config['remote_as'] != 'internal' and \
- peer_config['remote_as'] != bgp['system_as']:
+ peer_config['remote_as'] != system_as:
is_ibgp = False
if peer_group not in peer_groups_context:
@@ -278,7 +298,7 @@ def verify(config_dict):
# Neighbor local-as override can not be the same as the local-as
# we use for this BGP instane!
asn = list(peer_config['local_as'].keys())[0]
- if asn == bgp['system_as']:
+ if asn == system_as:
raise ConfigError('Cannot have local-as same as system-as number')
# Neighbor AS specified for local-as and remote-as can not be the same
@@ -369,12 +389,6 @@ def verify(config_dict):
if 'source_interface' in peer_config['interface']:
raise ConfigError(f'"source-interface" option not allowed for neighbor "{peer}"')
- # Local-AS allowed only for EBGP peers
- if 'local_as' in peer_config:
- remote_as = verify_remote_as(peer_config, bgp)
- if remote_as == bgp['system_as']:
- raise ConfigError(f'local-as configured for "{peer}", allowed only for eBGP peers!')
-
for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec',
'ipv6_unicast', 'ipv6_multicast', 'ipv6_labeled_unicast', 'ipv6_flowspec',
'l2vpn_evpn']:
diff --git a/src/migration-scripts/bgp/6-to-7 b/src/migration-scripts/bgp/6-to-7
new file mode 100644
index 000000000..7a11bbf3a
--- /dev/null
+++ b/src/migration-scripts/bgp/6-to-7
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+#
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T7760: Remove per VRF setting for system-as option and replace it with
+# local-as if required.
+
+from vyos.configtree import ConfigTree
+
+def migrate(config: ConfigTree) -> None:
+ vrf_base = ['vrf', 'name']
+ bgp_base = ['protocols', 'bgp']
+
+ if not config.exists(vrf_base):
+ return
+
+ global_asn = None
+ if config.exists(bgp_base + ['system-as']):
+ global_asn = config.return_value(bgp_base + ['system-as'])
+
+ for vrf in config.list_nodes(vrf_base):
+ # bail out early if there is no per VRF BGP instance defined
+ vrf_bgp_base = vrf_base + [vrf] + bgp_base
+ if not config.exists(vrf_bgp_base):
+ continue
+
+ # This is a mandatory node in the old design but keep it optional
+ # if one want's to load a weird config
+ if config.exists(vrf_bgp_base + ['system-as']):
+ system_as = config.return_value(vrf_bgp_base + ['system-as'])
+ config.delete(vrf_bgp_base + ['system-as'])
+
+ # If there is no existing global BGP instance - start one
+ if not config.exists(bgp_base):
+ config.set(bgp_base + ['system-as'], value=system_as)
+ global_asn = system_as
+
+ vrf_neighbor_base = vrf_bgp_base + ['neighbor']
+ vrf_peer_group_base = vrf_bgp_base + ['peer-group']
+ if config.exists(vrf_neighbor_base):
+ for neighbor in config.list_nodes(vrf_neighbor_base):
+ # Neighbor already has local-as option set, do not touch it
+ if config.exists(vrf_neighbor_base + [neighbor, 'local-as']):
+ #print(f'VRF {vrf} BGP neighbor {neighbor} has local-as set - do not migrate!')
+ continue
+
+ # Check if the neighbor uses a peer-group which has the local-as
+ # option set, do not touch it either
+ peer_group = None
+ if config.exists(vrf_neighbor_base + [neighbor, 'peer-group']):
+ peer_group = config.return_value(vrf_neighbor_base + [neighbor, 'peer-group'])
+ # Check if the peer-group has a local-as option set
+ if config.exists(vrf_peer_group_base + [peer_group, 'local-as']):
+ #print(f'VRF {vrf} BGP neighbor {neighbor} uses peer-group {peer_group} which has local-as set - do not migrate!')
+ continue
+
+ # BGP local-as option is only allowed for eBGP speakers
+ if global_asn == system_as:
+ continue
+
+ config.set(vrf_neighbor_base + [neighbor, 'local-as', system_as, 'no-prepend', 'replace-as'])
+ config.set_tag(vrf_neighbor_base + [neighbor, 'local-as'])
+
+ # We do also need to take care about BGP internas. When using local-as routes with our own AS
+ # previously in the path will get rejected:
+ # bgpd: x.x.x.x(Unknown) rcvd UPDATE about 192.0.2.0/24 IPv4 unicast -- DENIED due to: as-path contains our own AS;
+ # Set allowas-in option
+ allowas_in_numer = ['allowas-in', 'number']
+ for afi in ['ipv4-labeled-unicast', 'ipv4-multicast', 'ipv4-unicast', 'ipv4-vpn',
+ 'ipv6-labeled-unicast', 'ipv6-multicast', 'ipv6-unicast', 'ipv6-vpn']:
+ afi_neighbor_base = vrf_neighbor_base + [neighbor, 'address-family', afi]
+ afi_peer_group_base = vrf_peer_group_base + [peer_group, 'address-family', afi]
+
+ # No need to change anything on an AFI not in use
+ if not config.exists(afi_neighbor_base) and not config.exists(afi_peer_group_base):
+ continue
+
+ allowas_in_value = 0
+ print(neighbor, afi)
+ # Check if there is any allowas-in definition for a peer-group
+ if peer_group and config.exists(afi_peer_group_base + allowas_in_numer):
+ allowas_in_value = int(config.return_value(afi_peer_group_base + allowas_in_numer))
+ #print(f'peer-group {peer_group} allowas-in for {afi} is {allowas_in_value}')
+
+ # Per neighbor "allowas-in" definition takes higher precendence
+ if config.exists(afi_neighbor_base + allowas_in_numer):
+ allowas_in_value = int(config.return_value(afi_neighbor_base + allowas_in_numer))
+ #print(f'neighbor {neighbor} allowas-in for {afi} is {allowas_in_value}')
+
+ # Increment allowas-in by 1 as we now have one more entry
+ allowas_in_value += 1
+ # Clip allowas-in to 10 - max supported by FRR platform
+ if allowas_in_value > 10: allowas_in_value = 10
+
+ # Set per neighbor allowas-in which always takes precedence over the peer-group definition
+ config.set(afi_neighbor_base + allowas_in_numer, value=allowas_in_value, replace=True)