Skip to content

Commit 322c3b6

Browse files
authored
Fix: Mark phantom links used to create multihop EBGP sessions (#2502)
The ebgp.multihop plugin creates bogus ('phantom' sounds so much better) links to persuade the BGP module to establish BGP sessions between endpoints of multihop EBGP sessions. Unfortunately, other modules (in particular 'routing.static') find those links and want to use them. With this change, ebgp.multihop plugin marks the interfaces attached to those links with the '_phantom_link' flag, and the nexthop resolution for static routes avoids those interfaces. Likewise, the function copying interfaces into VRF routing protocol data structures skips the phantom links. At the moment, these seem to be the only undesired side effect of phantom links, but of course something else is bound to pop up eventually :( Fixes #2414
1 parent f98babe commit 322c3b6

File tree

4 files changed

+34
-39
lines changed

4 files changed

+34
-39
lines changed

netsim/extra/ebgp.multihop/plugin.py

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,6 @@
1212
_execute_after = [ 'ebgp.utils', 'bgp.session' ]
1313
_requires = [ 'bgp' ]
1414

15-
def pre_transform(topology: Box) -> None:
16-
global _config_name
17-
session_idx = data.find_in_list(['ebgp.utils','bgp.session'],topology.plugin)
18-
multihop_idx = data.find_in_list([ _config_name ],topology.plugin)
19-
20-
if session_idx is not None and multihop_idx is not None and session_idx > multihop_idx:
21-
log.error(
22-
'ebgp.multihop plugin must be included after bgp.session/ebgp.utils plugin',
23-
log.IncorrectValue,'ebgp.multihop')
24-
2515
def pre_link_transform(topology: Box) -> None:
2616
if not 'multihop' in topology.get('bgp',{}):
2717
return
@@ -31,6 +21,7 @@ def pre_link_transform(topology: Box) -> None:
3121

3222
sessions = links.adjust_link_list(topology.bgp.multihop.sessions,topology.nodes,'bgp.multihop[{link_cnt}]')
3323
topology.bgp.multihop.sessions = sessions
24+
next_linkindex = links.get_next_linkindex(topology)
3425
for s in sessions:
3526
for attr in list(s.keys()): # Change session attributes into BGP link attributes
3627
# Skip internal attributes and BGP/VRF attributes already within BGP namespace
@@ -40,8 +31,9 @@ def pre_link_transform(topology: Box) -> None:
4031
s.pop(attr,None)
4132

4233
s.type = 'tunnel'
43-
s.linkindex = links.get_next_linkindex(topology)
44-
s._bgp_session = True
34+
s.linkindex = next_linkindex
35+
next_linkindex += 1
36+
s._phantom_link = True
4537

4638
validate_attributes(
4739
data=s, # Validate pseudo-link data
@@ -82,7 +74,8 @@ def pre_link_transform(topology: Box) -> None:
8274
intf.bgp.multihop = 255
8375
if 'vrf' in intf:
8476
intf.bgp._vrf = intf.vrf
85-
intf._bgp_session = True
77+
intf._mh_bgp_session = True
78+
intf._phantom_link = True
8679
intf.ifname = f'_ebgp_multihop_{s.linkindex}'
8780

8881
topology.links.extend(sessions)
@@ -136,28 +129,17 @@ def augment_af_activation(ndata: Box, topology: Box) -> None:
136129
chg.pop(bgp_af,None) # Otherwise remove it
137130

138131
'''
139-
remove_fake_interfaces: remove all fake interfaces created to build BGP neighbor adjacencies
132+
Check whether a node has EBGP multihop sessions
140133
'''
141-
def remove_fake_interfaces(ndata: Box, topology: Box) -> bool:
142-
intf_count = len(ndata.interfaces)
143-
ndata.interfaces = [ intf for intf in ndata.interfaces if not intf.get('_bgp_session',None) ]
134+
def ebgp_multihop_sessions(ndata: Box, topology: Box) -> bool:
135+
mh_intf = [ intf for intf in ndata.interfaces if intf.get('_mh_bgp_session',False) ]
136+
if not mh_intf: # Do we have any fake interfaces for MH EBGP sessions?
137+
return False
144138

145-
return len(ndata.interfaces) != intf_count
146-
147-
'''
148-
cleanup_interfaces: remove fake interfaces from global interface list and VRF OSPF interface list
149-
'''
150-
def cleanup_interfaces(ndata: Box, topology: Box) -> None:
151-
changed = remove_fake_interfaces(ndata,topology) # Cleanup global interface list
152-
for vname,vdata in ndata.get('vrfs',{}).items(): # Iterate over VRFs
153-
if 'ospf' in vdata: # .. and cleanup OSPF interface list if needed
154-
v_chg = remove_fake_interfaces(vdata.ospf,topology)
155-
changed = changed or v_chg
156-
157-
if changed: # Did the interface count change?
158-
check_multihop_support(ndata,topology)
159-
api.node_config(ndata,_config_name) # We must have some multihop sessions, add extra config
160-
augment_af_activation(ndata,topology)
139+
check_multihop_support(ndata,topology) # Check that the node supports it
140+
api.node_config(ndata,_config_name) # We must have some multihop sessions, add extra config
141+
augment_af_activation(ndata,topology) # ... and activate relevant address families on these sessions
142+
return True
161143

162144
'''
163145
fix_vrf_loopbacks:
@@ -179,7 +161,7 @@ def fix_vrf_loopbacks(ndata: Box, topology: Box) -> None:
179161
if af in lb: # ... removing whatever might have come from the
180162
ngb[af] = str(ipaddress.ip_interface(lb[af]).ip) # ... global loopback
181163

182-
if '_src_vrf' in ngb: # Is out endpoint in a VRF?
164+
if '_src_vrf' in ngb: # Is our endpoint in a VRF?
183165
if not isinstance(features.bgp.multihop,Box) or features.bgp.multihop.vrf is not True:
184166
log.error(
185167
f'Device {ndata.device} does not support VRF EBGP multihop sessions (node {ndata.name}, VRF {ngb._src_vrf})',
@@ -211,7 +193,14 @@ def fix_vrf_loopbacks(ndata: Box, topology: Box) -> None:
211193
def post_transform(topology: Box) -> None:
212194
global _config_name
213195
for ndata in topology.nodes.values():
214-
fix_vrf_loopbacks(ndata,topology)
215-
cleanup_interfaces(ndata,topology)
196+
if ebgp_multihop_sessions(ndata,topology):
197+
fix_vrf_loopbacks(ndata,topology)
198+
199+
'''
200+
Cleanup: remove interfaces and links with _phantom_link flag
201+
'''
202+
def cleanup(topology: Box) -> None:
203+
for ndata in topology.nodes.values():
204+
ndata.interfaces = [ intf for intf in ndata.interfaces if not intf.get('_phantom_link',None) ]
216205

217-
topology.links = [ link for link in topology.links if not link.get('_bgp_session',None) ]
206+
topology.links = [ link for link in topology.links if not link.get('_phantom_link',None) ]

netsim/modules/_routing.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ def build_vrf_interface_list(
297297
continue
298298
if 'vrf' not in l: # Interface not in a VRF?
299299
continue
300+
if l.get('_phantom_link',False): # Is this an interface on a phantom link?
301+
continue # Skip it, the interface will be removed anyway
300302
if node.vrfs[l.vrf][proto] is True: # Handle 'force' the protocol by setting it to True
301303
node.vrfs[l.vrf][proto] = { 'active': True }
302304
elif node.vrfs[l.vrf][proto] is False: # Skip protocols disabled on VRF level

netsim/modules/routing.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,8 @@ def resolve_node_nexthop(sr_data: Box, node: Box, topology: Box) -> Box:
829829

830830
node_found = False
831831
for intf in node.interfaces:
832+
if intf.get('_phantom_link',False):
833+
continue
832834
for ngb in intf.neighbors:
833835
if ngb.node != sr_data.nexthop.node:
834836
continue

tests/topology/expected/ebgp.utils.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ bgp:
1212
bgp:
1313
password: Funny
1414
interfaces:
15-
- _bgp_session: true
15+
- _mh_bgp_session: true
16+
_phantom_link: true
1617
bgp:
1718
local_as: 123
1819
multihop: 255
1920
ifindex: 20000
2021
ifname: _ebgp_multihop_5
2122
ipv4: 10.0.0.2/32
2223
node: r2
23-
- _bgp_session: true
24+
- _mh_bgp_session: true
25+
_phantom_link: true
2426
bgp:
2527
local_as: 456
2628
multihop: 255

0 commit comments

Comments
 (0)