Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 92 additions & 48 deletions docs/module/bgp.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions netsim/ansible/templates/bgp/dellos10.j2
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ router bgp {{ bgp.as }}
{% if bgp.router_id|ipv4 %}
router-id {{ bgp.router_id }}
{% endif %}
{% if bgp.confederation is defined %}
confederation identifier {{ bgp.confederation.as }}
confederation peers {{ bgp.confederation.peers|join(' ') }}
{% endif %}
{% if bgp.rr|default(False) and bgp.rr_cluster_id|default(False) %}
cluster-id {{ bgp.rr_cluster_id }}
{% endif %}
Expand Down
16 changes: 6 additions & 10 deletions netsim/ansible/templates/bgp/dellos10.macro.j2
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,20 @@
{% if n._source_intf is defined %}
update-source {{ n._source_intf.ifname }}
{% endif %}
{% if 'ibgp' in n.type %}
{% if bgp.next_hop_self is defined and bgp.next_hop_self %}
{% if n.type in ['ibgp','localas_ibgp','confed_ebgp'] and bgp.next_hop_self|default(False) %}
{#
In Dell OS10, next-hop-self is configured under AF
#}
{% for af in ['ipv4','ipv6'] if n.activate[af]|default(False) and (af == t_af or unnumbered) %}
{% for af in ['ipv4','ipv6'] if n.activate[af]|default(False) and (af == t_af or unnumbered) %}
!
address-family {{ af }} unicast
next-hop-self
exit
{% endfor %}
{% endif -%}

{% if bgp.rr|default('') and not n.rr|default('') %}
{% endfor %}
{% endif %}
{% if 'ibgp' in n.type and bgp.rr|default('') and not n.rr|default('') %}
route-reflector-client
{% endif %}
{% endif -%}

{% endif %}
{% if n.type in bgp.community %}
{% for comm in ['standard','extended'] %}
{% if comm in bgp.community[n.type] %}
Expand Down
7 changes: 7 additions & 0 deletions netsim/ansible/templates/bgp/eos.j2
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ router bgp {{ bgp.as }}
bgp log-neighbor-changes
no bgp default ipv4-unicast
no bgp default ipv6-unicast
{% if bgp.confederation is defined %}
bgp confederation identifier {{ bgp.confederation.as }}
bgp confederation peers {{ bgp.confederation.peers|join(' ') }}
{% endif %}
{% if bgp.router_id|ipv4 %}
router-id {{ bgp.router_id }}
{% endif %}
Expand Down Expand Up @@ -79,6 +83,9 @@ router bgp {{ bgp.as }}
{% if n.type == 'ibgp' and bgp.next_hop_self|default(False) %}
neighbor {{ peer }} route-map next-hop-self-{{ af }} out
{% endif %}
{% if n.type == 'confed_ebgp' and bgp.next_hop_self|default(False) %}
neighbor {{ peer }} next-hop-self
{% endif %}
{% if n.type == 'localas_ibgp' %}
neighbor {{ peer }} next-hop-self
neighbor {{ peer }} route-reflector-client
Expand Down
13 changes: 7 additions & 6 deletions netsim/ansible/templates/bgp/frr.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ router bgp {{ bgp.as }}
no bgp default ipv4-unicast
bgp default show-hostname
bgp default show-nexthop-hostname

{% if bgp.confederation is defined %}
bgp confederation identifier {{ bgp.confederation.as }}
bgp confederation peers {{ bgp.confederation.peers|join(' ') }}
{% endif %}
! Consider AS paths of same length but with different AS as ECMP candidates
bgp bestpath as-path multipath-relax

Expand Down Expand Up @@ -65,13 +68,11 @@ router bgp {{ bgp.as }}
else False %}
{% if peer %}
neighbor {{ peer }} activate
{% if n.type == 'ibgp' %}
{% if bgp.next_hop_self is defined and bgp.next_hop_self %}
{% if n.type in ['ibgp','confed_ebgp'] and bgp.next_hop_self|default(False) %}
neighbor {{ peer }} next-hop-self
{% endif %}
{% if bgp.rr|default('') and not n.rr|default('') %}
{% endif %}
{% if n.type == 'ibgp' and bgp.rr|default('') and not n.rr|default('') %}
neighbor {{ peer }} route-reflector-client
{% endif %}
{% endif %}
{% if n.type == 'localas_ibgp' %}
neighbor {{ peer }} route-reflector-client
Expand Down
4 changes: 4 additions & 0 deletions netsim/ansible/templates/bgp/ios.j2
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ router bgp {{ bgp.as }}
bgp update-delay 5
bgp nopeerup-delay cold-boot 1
bgp nopeerup-delay user-initiated 1
{% if bgp.confederation is defined %}
bgp confederation identifier {{ bgp.confederation.as }}
bgp confederation peers {{ bgp.confederation.peers|join(' ') }}
{% endif %}
{% if bgp.router_id|ipv4 %}
bgp router-id {{ bgp.router_id }}
{% endif %}
Expand Down
8 changes: 3 additions & 5 deletions netsim/ansible/templates/bgp/ios.macro.j2
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@
{% macro neighbor_af(n,ip,bgp) %}
neighbor {{ ip }} activate
neighbor {{ ip }} advertisement-interval 0
{% if n.type == 'ibgp' %}
{% if bgp.next_hop_self is defined and bgp.next_hop_self %}
{% if n.type in ['ibgp','confed_ebgp'] and bgp.next_hop_self|default(False) %}
neighbor {{ ip }} next-hop-self
{% endif %}
{% if bgp.rr|default('') and (not n.rr|default('') or n.type == 'localas_ibgp') %}
{% endif %}
{% if n.type == 'ibgp' and bgp.rr|default('') and not n.rr|default('') %}
neighbor {{ ip }} route-reflector-client
{% endif %}
{% endif %}
{% if n.type == 'localas_ibgp' %}
neighbor {{ ip }} route-reflector-client
Expand Down
1 change: 1 addition & 0 deletions netsim/devices/dellos10.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ features:
local_as_ibgp: false # Device fails to send correct local AS (only)
vrf_local_as: true
import: [ ospf, connected, static, vrf ]
confederation: True
evpn:
asymmetrical_irb: true
irb: true
Expand Down
1 change: 1 addition & 0 deletions netsim/devices/eos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ features:
large: [ large ]
extended: [ extended ]
2octet: [ standard ]
confederation: True
dhcp:
client:
ipv4: true
Expand Down
1 change: 1 addition & 0 deletions netsim/devices/frr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ features:
large: [ large ]
extended: [ extended ]
2octet: [ standard ]
confederation: True
evpn:
irb: true
asymmetrical_irb: True
Expand Down
1 change: 1 addition & 0 deletions netsim/devices/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ features:
community:
standard: [ standard ]
extended: [ extended ]
confederation: True
dhcp:
client:
ipv4: true
Expand Down
1 change: 1 addition & 0 deletions netsim/devices/none.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ features:
tcp_ao: [ libvirt, virtualbox, external ]
timers: True
import: [ ospf, isis, ripv2, connected, static, vrf ]
confederation: True
dhcp:
client:
ipv4: true
Expand Down
88 changes: 81 additions & 7 deletions netsim/modules/bgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,24 @@
def setup_bgp_constants(topology: Box) -> None:
global BGP_DEFAULT_SESSIONS, BGP_VALID_SESSION_TYPE, BGP_INHERIT_COMMUNITY

bgp_session_values = topology.defaults.attributes.bgp_session_type.valid_values
# Add confed_ebgp to valid values if the topology is using confederations
if 'bgp.confederation' in topology:
bgp_session_values.confed_ebgp = None

# Valid session types are reconstructed from the valid values of the bgp.sessions.ipv4 global attribute
# We're assuming there's no difference between IPv4 and IPv6
BGP_VALID_SESSION_TYPE = [ v for v in topology.defaults.attributes.bgp_session_type.valid_values ]
BGP_VALID_SESSION_TYPE = list(bgp_session_values)

# We're assuming all address families are active on all session types
for af in log.AF_LIST:
BGP_DEFAULT_SESSIONS[af] = BGP_VALID_SESSION_TYPE

# Finally, copy the pointer to BGP community inheritance
# Finally, copy the pointer to BGP community inheritance and limit it to valid session types
BGP_INHERIT_COMMUNITY = topology.defaults.bgp.attributes._inherit_community
for cst in list(BGP_INHERIT_COMMUNITY):
if cst not in BGP_VALID_SESSION_TYPE:
BGP_INHERIT_COMMUNITY.pop(cst)

def check_bgp_parameters(node: Box, topology: Box) -> None:
if not "bgp" in node: # pragma: no cover (should have been tested and reported by the caller)
Expand Down Expand Up @@ -279,6 +287,10 @@ def build_ebgp_sessions(node: Box, sessions: Box, topology: Box) -> None:
ngb_name = ngb_ifdata.node
neighbor = topology.nodes[ngb_name]
neighbor_real_as = neighbor.get('bgp.as',None)
neighbor_c_as = neighbor.get('bgp.confederation.as',None)
neighbor_c_peers = neighbor.get('bgp.confederation.peers',[])
if neighbor_c_as and node_local_as not in neighbor_c_peers:
neighbor_real_as = neighbor_c_as
try: # Try to get neighbor local_as
neighbor_local_as = ( ngb_ifdata.get('bgp.local_as',None) or
neighbor.get('bgp.local_as',None) or
Expand Down Expand Up @@ -357,7 +369,13 @@ def build_ebgp_sessions(node: Box, sessions: Box, topology: Box) -> None:
if not local_as_data is None:
extra_data[k] = local_as_data # ... and copy it into neighbor data

session_type = 'localas_ibgp' if neighbor_local_as == node_local_as else 'ebgp'
if neighbor_local_as == node_local_as:
session_type = 'localas_ibgp'
elif neighbor_local_as in node.get('bgp.confederation.peers',[]):
session_type = 'confed_ebgp'
else:
session_type = 'ebgp'

if session_type == 'localas_ibgp':
if not features.bgp.local_as_ibgp:
log.error(
Expand Down Expand Up @@ -597,6 +615,61 @@ def process_as_list(topology: Box) -> None:

node.bgp = node_data[name] + node.bgp

"""
Check the confederation data (if it exists) against node AS numbers
"""
def check_confederation_data(topology: Box) -> None:
if 'bgp.confederation' not in topology:
return

rev_map: dict = {}
bgp_confed = topology.bgp.confederation
for c_asn,c_data in bgp_confed.items():
if not c_data.members:
log.error(
f'Confederation AS {c_asn} needs at least one member ASN',
category=log.MissingValue,
module='bgp')
for cm_asn in c_data.members:
if cm_asn in rev_map:
log.error(
f'Confederation member {cm_asn} is in confederations {c_asn} and {rev_map[cm_asn]}',
more_hints="While that's a valid BGP design, netlab does not support it",
category=log.IncorrectValue,
module='bgp')
rev_map[cm_asn] = c_asn
if cm_asn in bgp_confed:
log.error(
f'Confederation member AS {cm_asn} (part of AS {c_asn}) is itself also a confederation AS',
category=log.IncorrectValue,
module='bgp')

for ndata in topology.nodes.values():
if 'bgp.as' not in ndata:
continue
n_as = ndata.bgp['as']
if n_as in bgp_confed:
log.error(
f'Node {ndata.name} is using confederation ASN {n_as}',
more_hints='BGP speakers can use only confederation member ASNs, not the confederation ASN',
category=log.IncorrectValue,
module='bgp')
if n_as not in rev_map:
continue

c_as = rev_map[n_as]
d_features = devices.get_device_features(ndata,topology.defaults)
if not d_features.get('bgp.confederation',False):
log.error(
f'Node {ndata.name} (device {ndata.device}) uses member ASN {n_as} of confederation AS {c_as}',
more_hints=f'Device {ndata.device} does not support BGP confederations',
category=log.IncorrectType,
module='bgp')
continue

ndata.bgp.confederation['as'] = c_as
ndata.bgp.confederation.peers = [ asn for asn in bgp_confed[c_as].members if asn != n_as ]

"""
bgp_transform_community_list: transform _netlab_ community keywords into device keywords
"""
Expand Down Expand Up @@ -737,14 +810,14 @@ def sanitize_bgp_data(node: Box) -> None:
b_data.neighbors = []

class BGP(_Module):
def module_normalize(self, topology: Box) -> None:
setup_bgp_constants(topology)

"""
Node pre-transform: set bgp.rr node attribute to _true_ if the node name is in the
global bgp.rr attribute. Also, delete the global bgp.rr attribute so it's not propagated
down to nodes
"""
def module_pre_transform(self, topology: Box) -> None:
setup_bgp_constants(topology)

def node_pre_transform(self, node: Box, topology: Box) -> None:
if "rr_list" in topology.get("bgp",{}):
if node.name in topology.bgp.rr_list:
Expand Down Expand Up @@ -772,14 +845,15 @@ def module_pre_link_transform(self, topology: Box) -> None:
vlan_ebgp_role_set(topology,EBGP_ROLE)

#
# Have to set BGP router IDs and cluster IDs before going into node_post_transform
# Have to set BGP router IDs, cluster IDs, and confederation data before going into node_post_transform
#
def module_post_transform(self, topology: Box) -> None:
for n in topology.nodes.values():
if 'bgp' in n:
_routing.router_id(n,'bgp',topology.pools)

build_bgp_rr_clusters(topology)
check_confederation_data(topology)

#
# Execute the rest of node post-transform code, including setting up the BGP session table
Expand Down
11 changes: 11 additions & 0 deletions netsim/modules/bgp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ no_propagate:
advertise_roles:
rr_list:
as_list:
confederation:
transform_after: [ vlan ]
config_after: [ routing, ospf, isis, eigrp, ripv2 ]
next_hop_self: true
hooks: [ normalize ]

_top: # Define custom data types used by the BGP module
attributes:
Expand Down Expand Up @@ -47,9 +49,17 @@ attributes:
ebgp: [ standard, extended, large, 2octet ]
_alt_types: [ str, BoxList ]
replace_global_as: bool
confederation:
type: dict
_keytype: asn
_subtype:
members:
type: list
_subtype: asn

_inherit_community: # This is a lookup table for rare session types so you don't
localas_ibgp: ibgp # have to specify them in bgp.community attribute
confed_ebgp: ibgp

node:
as:
Expand Down Expand Up @@ -97,6 +107,7 @@ features:
ipv6_lla: Can run EBGP sessions over IPv6 link-local addresses
rfc8950: Can run IPv4 AF over regular IPv6 EBGP session
community: Granular BGP community propagation
confederation: BGP confederations
import: Import routes from other routing protocols
warnings:
missing_igp: True
Expand Down
9 changes: 9 additions & 0 deletions tests/errors/bgp-confed.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
IncorrectValue in bgp: Confederation member AS 65001 (part of AS 65000) is itself also a confederation AS
IncorrectValue in bgp: Confederation member 65002 is in confederations 65001 and 65000
... While that's a valid BGP design, netlab does not support it
MissingValue in bgp: Confederation AS 65100 needs at least one member ASN
IncorrectValue in bgp: Node r1 is using confederation ASN 65000
... BGP speakers can use only confederation member ASNs, not the confederation ASN
IncorrectType in bgp: Node r3 (device frr) uses member ASN 65003 of confederation AS 65000
... Device frr does not support BGP confederations
Fatal error in netlab: Cannot proceed beyond this point due to errors, exiting
25 changes: 25 additions & 0 deletions tests/errors/bgp-confed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
message: |
Invalid BGP confederations setup

module: [ bgp ]

defaults.device: none
defaults.devices.frr.features.bgp.confederation: False

bgp.confederation:
65000:
members: [ 65001, 65002, 65003 ]
65001:
members: [ 65002 ]
65100:
members: []

nodes:
r1:
bgp.as: 65000
r2:
bgp.as: 65003
r3:
device: frr
bgp.as: 65003
Loading