Skip to content

Commit 1fbb95d

Browse files
committed
Implement dual-stack and VRF-aware bgp.originate attribute
1 parent ab57a18 commit 1fbb95d

File tree

11 files changed

+141
-61
lines changed

11 files changed

+141
-61
lines changed

netsim/devices/none.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ group_vars:
2323
features:
2424
bfd: True
2525
bgp:
26+
activate_af: True
27+
advertise: True
28+
aggregate: true
2629
local_as: True
2730
vrf_local_as: True
2831
local_as_ibgp: True
29-
activate_af: True
3032
ipv6_lla: True
3133
rfc8950: True
3234
bandwidth: True
@@ -46,7 +48,6 @@ features:
4648
timers: True
4749
import: [ ospf, isis, ripv2, connected, static, vrf ]
4850
confederation: True
49-
aggregate: true
5051
dhcp:
5152
client:
5253
ipv4: true

netsim/extra/bgp.originate/defaults.yml

Lines changed: 0 additions & 12 deletions
This file was deleted.

netsim/extra/bgp.originate/plugin.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
from box import Box
22

3-
from netsim.augment import links
3+
from netsim.augment import devices, links
44
from netsim.data import get_box
5+
from netsim.utils import log
56

67
_requires = [ 'bgp' ]
78

89
"""
910
Have to complete the fix of bgp.originate attribute (can't do everything through the settings)
1011
"""
1112
def init(topology: Box) -> None:
12-
o_dict = topology.defaults.bgp.attributes.node.originate
13-
for kw in ('use','named','type'):
14-
o_dict._subtype.pop(kw,None)
13+
log.warning(
14+
text='The functionality of the bgp.originate plugin is now available in the BGP module',
15+
module='bgp',
16+
flag='obsolete')
1517

1618
def post_node_transform(topology: Box) -> None:
1719
for n, ndata in topology.nodes.items():
@@ -22,6 +24,15 @@ def post_node_transform(topology: Box) -> None:
2224
if not o_list:
2325
continue
2426

27+
features = devices.get_device_features(ndata,topology.defaults)
28+
has_advertise = features.get('bgp.advertise',False)
29+
originate_v2 = has_advertise and features.get('routing.static.discard',False)
30+
if originate_v2:
31+
continue
32+
33+
# The old bgp.plugin processing is still done for the devices that cannot handle bgp.advertise
34+
# or discard static routes
35+
#
2536
for o_idx,o_entry in enumerate(o_list): # Iterate over entries in originate list
2637
o_link = get_box({ # Create a loopback link for each originate entry
2738
'interfaces': [ { 'node': n }],

netsim/modules/bgp.py

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -601,11 +601,73 @@ def bgp_build_advertise_list(node: Box) -> None:
601601
list_name = 'bgp.advertise' if 'vrf' not in intf else f'vrfs.{intf.vrf}.bgp.advertise'
602602
data.append_to_list(node,list_name,af_data) # And append prefix data to the list
603603

604-
if 'originate' in node.bgp: # Next, append originate data to the advertise list
605-
for o_pfx in node.bgp.originate:
606-
# Convert old-style data (IPv4 string) into prefix
607-
af_data = o_pfx if isinstance(o_pfx,Box) else {'ipv4': o_pfx }
608-
data.append_to_list(node,'bgp.advertise',af_data)
604+
"""
605+
bgp_process_originate:
606+
607+
* Check the device support for dual-stack and VRF 'bgp.originate' attribute
608+
* Append prefixes from 'bgp.originate' list to 'bgp.advertise' list
609+
* Convert 'bgp.originate' list to a list of IPv4-only prefixes for legacy devices
610+
"""
611+
DISCARD_NH = data.get_box({'nexthop.discard': True})
612+
613+
def bgp_process_originate(node: Box, topology: Box) -> None:
614+
global DISCARD_NH
615+
features = devices.get_device_features(node,topology.defaults)
616+
617+
# Second-generation BGP route origination needs advertisement of routing table prefixes
618+
# and discard static routes
619+
#
620+
# VRF BGP route origination needs VRF static routes
621+
#
622+
has_advertise = features.get('bgp.advertise',False)
623+
originate_v2 = has_advertise and features.get('routing.static.discard',False)
624+
originate_vrf = originate_v2 and features.get('routing.static.vrf',False)
625+
has_routing = 'routing' in node.module
626+
627+
for (bgp_data,_,vname) in _rp_utils.rp_data(node,'bgp'):
628+
if 'originate' not in bgp_data: # No prefix origination? Nice, move on.
629+
continue
630+
631+
if vname and not originate_vrf:
632+
log.error(
633+
f'device {node.device} (node {node.name}) does not support VRF bgp.originate attribute',
634+
category=log.IncorrectAttr,
635+
module='bgp')
636+
continue
637+
638+
if originate_v2: # Handle next-generation BGP route origination
639+
#
640+
# The second-generation BGP origination append bgp.originate attribute to bgp.advertise
641+
# list and creates corresponding discard static routes
642+
#
643+
for pfx in bgp_data.originate:
644+
data.append_to_list(bgp_data,'advertise',pfx) # Append originate prefix to advertise list
645+
sr_data = DISCARD_NH + pfx # This will create a new object with discard NH
646+
if vname: # If needed, set the VRF name in static route
647+
sr_data.vrf = vname
648+
data.append_to_list(node,'routing.static',sr_data) # Add discard static route to node data
649+
if not has_routing: # If needed, add routing module
650+
data.append_to_list(node,'module','routing')
651+
has_routing = True
652+
653+
bgp_data.pop('originate',None) # The bgp.originate attribute has been processed
654+
655+
else: # The device can do only old-style BGP route origination
656+
originate_list = []
657+
for pfx in bgp_data.originate: # Rebuild bgp.originate into old format
658+
# bgp.advertise attribute could be ignored, but doing this does not hurt anyone
659+
# plus we need it for devices that support bgp.advertise but not discard routes
660+
#
661+
data.append_to_list(bgp_data,'advertise',pfx)
662+
if 'ipv6' in pfx: # Old bgp.originate implementations were IPv4 only
663+
log.error(
664+
f'device {node.device} (node {node.name}) cannot use bgp.originate with IPv6 BGP prefixes',
665+
category=log.IncorrectValue,
666+
module='bgp')
667+
return
668+
elif 'ipv4' in pfx: # Save IPv4 prefix (if it exists) into the old-style
669+
originate_list.append(pfx['ipv4']) # originate list
670+
bgp_data.originate = originate_list # ... and replace bgp.originate attribute
609671

610672
"""
611673
bgp_set_originate_af: set bgp[af] flags based on prefixes that should be originated
@@ -615,33 +677,12 @@ def bgp_set_originate_af(node: Box, topology: Box) -> None:
615677
if 'bgp' not in node: # Safeguard: skip non-BGP nodes
616678
return
617679

618-
if node.get('bgp.originate',[]): # bgp.originate attribute implies IPv4 is active
619-
node.bgp.ipv4 = True
620-
pfxs = topology.get('prefix',{})
621-
for o_idx,o_value in enumerate(node.bgp.originate): # Also, replace named prefixes with IPv4 values
622-
if o_value in pfxs:
623-
if 'ipv4' not in pfxs[o_value]:
624-
log.error(
625-
f'Named prefix {o_value} used in bgp.originate in node {node.name} must have IPv4 component',
626-
category=log.MissingValue,
627-
module='bgp')
628-
continue
629-
node.bgp.originate[o_idx] = pfxs[o_value].ipv4
630-
680+
advertise_list = node.get('bgp.advertise',[]) # Get the list of advertised prefixes
631681
for af in ['ipv4','ipv6']:
632682
if node.get(f'bgp.{af}',False): # No need for further checks if the AF flag is already set
633683
continue
634-
635-
if node.get('bgp.advertise_loopback',True): # If the router advertises its loopback prefix
636-
if af in node.get('loopback',{}): # ... do the AF checks on loopback interface
637-
node.bgp[af] = True
638-
continue
639-
640-
for intf in node.get("interfaces",[]): # No decision yet, iterate over all interfaces
641-
if af in intf and intf[af]: # Is the address family active on the interface?
642-
if intf.get('bgp.advertise',False): # Is the interface prefix advertised in BGP?
643-
if not 'vrf' in intf: # The AF fix is only required for global interfaces
644-
node.bgp[af] = True # ... the stars have aligned, set the BGP AF flag
684+
if any([ pfx for pfx in advertise_list if af in pfx]): # Do we advertise any prefix with that AF?
685+
node.bgp[af] = True
645686

646687
"""
647688
process_as_list:
@@ -967,12 +1008,13 @@ def node_post_transform(self, node: Box, topology: Box) -> None:
9671008
log.fatal(f"Internal error: node {node.name} has BGP module enabled but no BGP parameters","bgp")
9681009
return
9691010
build_bgp_sessions(node,topology)
1011+
check_advertise_data(node,topology)
9701012
bgp_set_advertise(node,topology)
971-
bgp_set_originate_af(node,topology)
1013+
bgp_process_originate(node,topology)
9721014
_routing.remove_vrf_routing_blocks(node,'bgp')
9731015
bgp_transform_community_list(node,topology)
9741016
_routing.check_vrf_protocol_support(node,'bgp',None,'bgp',topology)
9751017
_routing.process_imports(node,'bgp',topology,global_vars.get_const('vrf_igp_protocols',['connected']))
976-
check_advertise_data(node,topology)
9771018
sanitize_bgp_data(node)
9781019
bgp_build_advertise_list(node)
1020+
bgp_set_originate_af(node,topology)

netsim/modules/bgp.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ attributes:
8989
rr_cluster_id: { copy: global }
9090
rr_mesh: { copy: global }
9191
advertise: bgp_prefix_list
92-
originate:
93-
type: list
94-
_subtype: { type: ipv4, use: subnet_prefix, named: True }
92+
originate: bgp_prefix_list
9593
advertise_loopback: { copy: global }
9694
sessions: { copy: global }
9795
activate: { copy: global }
@@ -103,6 +101,7 @@ attributes:
103101
vrf:
104102
router_id: { type: ipv4, use: id }
105103
advertise: bgp_prefix_list
104+
originate: bgp_prefix_list
106105
import: _r_import
107106
node_copy: [ local_as, replace_global_as ]
108107
link:

netsim/utils/routing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ def clear_bgp_session(node: Box, ngb: Box) -> None:
8787
'''
8888
def rp_data(node: Box, proto: str, select: list = ['global','vrf']) -> typing.Generator:
8989
if 'global' in select:
90-
if proto in node:
90+
if node.get(proto,None):
9191
yield(node[proto],[ intf for intf in node.interfaces if proto in intf ],None)
9292

9393
if 'vrf' in select:
9494
for vname,vdata in node.get('vrfs',{}).items():
95-
if proto in vdata:
95+
if vdata.get(proto,None):
9696
yield(vdata[proto],vdata[proto].get('interfaces',[]),vname)
9797

9898
'''

tests/topology/expected/bgp-af-rt-929.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,6 @@ nodes:
199199
ipv4: true
200200
neighbors: []
201201
next_hop_self: true
202-
originate:
203-
- 10.1.0.0/24
204-
- 0.0.0.0/0
205202
router_id: 10.0.0.3
206203
box: arista/veos
207204
device: eos
@@ -219,9 +216,20 @@ nodes:
219216
ipv4: 192.168.121.103
220217
mac: ca:fe:00:03:00:00
221218
module:
219+
- routing
222220
- bgp
223221
name: nl1
224222
role: router
223+
routing:
224+
static:
225+
- ipv4: 10.1.0.0/24
226+
nexthop:
227+
discard: true
228+
idx: 0
229+
- ipv4: 0.0.0.0/0
230+
nexthop:
231+
discard: true
232+
idx: 0
225233
nl2:
226234
af:
227235
ipv4: true

tests/topology/expected/bgp-ibgp-localas.yml

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ nodes:
116116
ipv4: true
117117
bgp:
118118
advertise:
119-
- ipv4: 10.0.0.1/32
120119
- ipv4: 172.42.42.0/24
120+
- ipv4: 10.0.0.1/32
121121
advertise_loopback: true
122122
as: 65000
123123
community:
@@ -164,8 +164,6 @@ nodes:
164164
name: x1
165165
type: ebgp
166166
next_hop_self: true
167-
originate:
168-
- 172.42.42.0/24
169167
router_id: 10.0.0.42
170168
box: none
171169
device: none
@@ -250,6 +248,7 @@ nodes:
250248
mac: ca:fe:00:01:00:00
251249
module:
252250
- vlan
251+
- routing
253252
- ospf
254253
- bgp
255254
mtu: 1500
@@ -264,6 +263,12 @@ nodes:
264263
kind: regular
265264
router_id: 10.0.0.42
266265
router_id: 10.0.0.42
266+
routing:
267+
static:
268+
- ipv4: 172.42.42.0/24
269+
nexthop:
270+
discard: true
271+
idx: 0
267272
vlan:
268273
max_bridge_group: 1
269274
vlans:
@@ -344,10 +349,15 @@ nodes:
344349
mtu: 1500
345350
name: x1
346351
x2:
352+
_features:
353+
routing:
354+
static:
355+
discard: false
347356
af:
348357
ipv4: true
349358
bgp:
350359
advertise:
360+
- ipv4: 172.42.4.0/24
351361
- ipv4: 172.42.2.0/24
352362
advertise_loopback: true
353363
as: 65101
@@ -386,6 +396,8 @@ nodes:
386396
rr_client: true
387397
type: localas_ibgp
388398
next_hop_self: true
399+
originate:
400+
- 172.42.4.0/24
389401
router_id: 172.42.2.1
390402
box: none
391403
device: none
@@ -451,12 +463,15 @@ nodes:
451463
kind: regular
452464
router_id: 172.42.2.1
453465
x3:
466+
_features:
467+
bgp:
468+
advertise: false
454469
af:
455470
ipv4: true
456471
bgp:
457472
advertise:
458-
- ipv4: 10.0.0.4/32
459473
- ipv4: 172.42.3.0/24
474+
- ipv4: 10.0.0.4/32
460475
advertise_loopback: true
461476
as: 65000
462477
community:

tests/topology/expected/bgp-unnumbered-dual-stack.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ nodes:
142142
ipv6: true
143143
bgp:
144144
advertise:
145+
- ipv4: 10.42.42.0/24
146+
ipv6: 2001:db8:cafe:1::/64
145147
- ipv4: 10.0.0.1/32
146148
ipv6: 2001:db8:0:1::/64
147149
advertise_loopback: true
@@ -323,8 +325,16 @@ nodes:
323325
ipv4: 192.168.121.101
324326
mac: ca:fe:00:01:00:00
325327
module:
328+
- routing
326329
- bgp
327330
name: test
331+
routing:
332+
static:
333+
- ipv4: 10.42.42.0/24
334+
ipv6: 2001:db8:cafe:1::/64
335+
nexthop:
336+
discard: true
337+
idx: 0
328338
x1:
329339
af:
330340
ipv4: true

tests/topology/input/bgp-ibgp-localas.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ nodes:
2727
x2:
2828
bgp.as: 65101
2929
loopback.ipv4: 172.42.2.1/24
30+
bgp.originate: 172.42.4.0/24
31+
_features.routing.static.discard: False
3032
x3:
3133
bgp.originate: 172.42.3.0/24
34+
_features.bgp.advertise: False
3235
x4:
3336
bgp.as: 65101
3437
loopback.ipv4: 172.42.4.1/24

0 commit comments

Comments
 (0)