BGPSpeaker: Support to advertise PMSI Tunnel Attribute

This patch adds support to advertise the BGP PMSI Tunnel Attribute
for the Path attributes.

Signed-off-by: Shinpei Muraoka <shinpei.muraoka@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
This commit is contained in:
Shinpei Muraoka 2016-10-27 09:55:22 +09:00 committed by FUJITA Tomonori
parent 6e22fb4a05
commit 8d9ce87653
7 changed files with 174 additions and 4 deletions

View File

@ -50,6 +50,7 @@ IP_ADDR = 'ip_addr'
MPLS_LABELS = 'mpls_labels'
TUNNEL_TYPE = 'tunnel_type'
EVPN_VNI = 'vni'
PMSI_TUNNEL_TYPE = 'pmsi_tunnel_type'
# API call registry
_CALL_REGISTRY = {}

View File

@ -20,6 +20,7 @@ import logging
from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI
from ryu.lib.packet.bgp import EvpnInclusiveMulticastEthernetTagNLRI
from ryu.lib.packet.bgp import BGPPathAttributePmsiTunnel
from ryu.services.protocols.bgp.api.base import EVPN_ROUTE_TYPE
from ryu.services.protocols.bgp.api.base import EVPN_ESI
from ryu.services.protocols.bgp.api.base import EVPN_ETHERNET_TAG_ID
@ -33,6 +34,7 @@ from ryu.services.protocols.bgp.api.base import ROUTE_DISTINGUISHER
from ryu.services.protocols.bgp.api.base import VPN_LABEL
from ryu.services.protocols.bgp.api.base import EVPN_VNI
from ryu.services.protocols.bgp.api.base import TUNNEL_TYPE
from ryu.services.protocols.bgp.api.base import PMSI_TUNNEL_TYPE
from ryu.services.protocols.bgp.base import add_bgp_error_metadata
from ryu.services.protocols.bgp.base import PREFIX_ERROR_CODE
from ryu.services.protocols.bgp.base import validate
@ -69,6 +71,17 @@ SUPPORTED_TUNNEL_TYPES = [
TUNNEL_TYPE_MPLS_IN_GRE,
TUNNEL_TYPE_VXLAN_GRE,
]
# Constants for PMSI Tunnel Attribute
PMSI_TYPE_NO_TUNNEL_INFO = (
BGPPathAttributePmsiTunnel.TYPE_NO_TUNNEL_INFORMATION_PRESENT
)
PMSI_TYPE_INGRESS_REP = (
BGPPathAttributePmsiTunnel.TYPE_INGRESS_REPLICATION
)
SUPPORTED_PMSI_TUNNEL_TYPES = [
PMSI_TYPE_NO_TUNNEL_INFO,
PMSI_TYPE_INGRESS_REP,
]
@add_bgp_error_metadata(code=PREFIX_ERROR_CODE,
@ -152,6 +165,13 @@ def is_valid_tunnel_type(tunnel_type):
conf_value=tunnel_type)
@validate(name=PMSI_TUNNEL_TYPE)
def is_valid_pmsi_tunnel_type(pmsi_tunnel_type):
if pmsi_tunnel_type not in SUPPORTED_PMSI_TUNNEL_TYPES:
raise ConfigValueError(conf_name=PMSI_TUNNEL_TYPE,
conf_value=pmsi_tunnel_type)
@RegisterWithArgChecks(name='prefix.add_local',
req_args=[ROUTE_DISTINGUISHER, PREFIX, NEXT_HOP],
opt_args=[VRF_RF])
@ -201,7 +221,8 @@ def delete_local(route_dist, prefix, route_family=VRF_RF_IPV4):
req_args=[EVPN_ROUTE_TYPE, ROUTE_DISTINGUISHER,
NEXT_HOP],
opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR,
IP_ADDR, EVPN_VNI, TUNNEL_TYPE])
IP_ADDR, EVPN_VNI, TUNNEL_TYPE,
PMSI_TUNNEL_TYPE])
def add_evpn_local(route_type, route_dist, next_hop, **kwargs):
"""Adds EVPN route from VRF identified by *route_dist*.
"""

View File

@ -33,10 +33,14 @@ from ryu.services.protocols.bgp.api.base import ROUTE_DISTINGUISHER
from ryu.services.protocols.bgp.api.base import ROUTE_FAMILY
from ryu.services.protocols.bgp.api.base import EVPN_VNI
from ryu.services.protocols.bgp.api.base import TUNNEL_TYPE
from ryu.services.protocols.bgp.api.base import PMSI_TUNNEL_TYPE
from ryu.services.protocols.bgp.api.prefix import EVPN_MAC_IP_ADV_ROUTE
from ryu.services.protocols.bgp.api.prefix import EVPN_MULTICAST_ETAG_ROUTE
from ryu.services.protocols.bgp.api.prefix import TUNNEL_TYPE_VXLAN
from ryu.services.protocols.bgp.api.prefix import TUNNEL_TYPE_NVGRE
from ryu.services.protocols.bgp.api.prefix import (
PMSI_TYPE_NO_TUNNEL_INFO,
PMSI_TYPE_INGRESS_REP)
from ryu.services.protocols.bgp.operator import ssh
from ryu.services.protocols.bgp.rtconf.common import LOCAL_AS
from ryu.services.protocols.bgp.rtconf.common import ROUTER_ID
@ -533,7 +537,8 @@ class BGPSpeaker(object):
def evpn_prefix_add(self, route_type, route_dist, esi=0,
ethernet_tag_id=None, mac_addr=None, ip_addr=None,
vni=None, next_hop=None, tunnel_type=None):
vni=None, next_hop=None, tunnel_type=None,
pmsi_tunnel_type=None):
""" This method adds a new EVPN route to be advertised.
``route_type`` specifies one of the EVPN route type name. The
@ -560,6 +565,11 @@ class BGPSpeaker(object):
``tunnel_type`` specifies the data plane encapsulation type
to advertise. By the default, this encapsulation attribute is
not advertised.
```pmsi_tunnel_type`` specifies the type of the PMSI tunnel attribute
used to encode the multicast tunnel identifier.
This field is advertised only if route_type is
EVPN_MULTICAST_ETAG_ROUTE.
"""
func_name = 'evpn_prefix.add_local'
@ -592,6 +602,15 @@ class BGPSpeaker(object):
EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
IP_ADDR: ip_addr,
})
# Set PMSI Tunnel Attribute arguments
if pmsi_tunnel_type in [
PMSI_TYPE_NO_TUNNEL_INFO,
PMSI_TYPE_INGRESS_REP]:
kwargs[PMSI_TUNNEL_TYPE] = pmsi_tunnel_type
elif pmsi_tunnel_type is not None:
raise ValueError('Unsupported PMSI tunnel type: %s' %
pmsi_tunnel_type)
else:
raise ValueError('Unsupported EVPN route type: %s' % route_type)

View File

@ -483,7 +483,7 @@ class TableCoreManager(object):
def update_vrf_table(self, route_dist, prefix=None, next_hop=None,
route_family=None, route_type=None, tunnel_type=None,
is_withdraw=False, **kwargs):
is_withdraw=False, pmsi_tunnel_type=None, **kwargs):
"""Update a BGP route in the VRF table identified by `route_dist`
with the given `next_hop`.
@ -496,6 +496,11 @@ class TableCoreManager(object):
If `route_family` is VRF_RF_L2_EVPN, `route_type` and `kwargs`
are required to construct EVPN NLRI and `prefix` is ignored.
` `pmsi_tunnel_type` specifies the type of the PMSI tunnel attribute
used to encode the multicast tunnel identifier.
This field is advertised only if route_type is
EVPN_MULTICAST_ETAG_ROUTE.
Returns assigned VPN label.
"""
from ryu.services.protocols.bgp.core import BgpCoreError
@ -554,7 +559,8 @@ class TableCoreManager(object):
# withdrawal. Hence multiple withdrawals have not side effect.
return vrf_table.insert_vrf_path(
nlri=prefix, next_hop=next_hop, gen_lbl=gen_lbl,
is_withdraw=is_withdraw, tunnel_type=tunnel_type)
is_withdraw=is_withdraw, tunnel_type=tunnel_type,
pmsi_tunnel_type=pmsi_tunnel_type)
def update_global_table(self, prefix, next_hop=None, is_withdraw=False):
"""Update a BGP route in the Global table for the given `prefix`

View File

@ -24,6 +24,7 @@ import six
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_EXTENDED_COMMUNITIES
from ryu.lib.packet.bgp import BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC
from ryu.lib.packet.bgp import BGPPathAttributeOrigin
from ryu.lib.packet.bgp import BGPPathAttributeAsPath
@ -31,6 +32,8 @@ from ryu.lib.packet.bgp import BGPPathAttributeExtendedCommunities
from ryu.lib.packet.bgp import BGPTwoOctetAsSpecificExtendedCommunity
from ryu.lib.packet.bgp import BGPPathAttributeMultiExitDisc
from ryu.lib.packet.bgp import BGPEncapsulationExtendedCommunity
from ryu.lib.packet.bgp import BGPPathAttributePmsiTunnel
from ryu.lib.packet.bgp import PmsiTunnelIdIngressReplication
from ryu.lib.packet.bgp import RF_L2_EVPN
from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI
@ -271,6 +274,21 @@ class VrfTable(Table):
# If we do not have next_hop, get a new label.
label_list.append(table_manager.get_next_vpnv4_label())
# Set PMSI Tunnel Attribute
pmsi_tunnel_type = kwargs.get('pmsi_tunnel_type', None)
if pmsi_tunnel_type is not None:
from ryu.services.protocols.bgp.api.prefix import (
PMSI_TYPE_INGRESS_REP)
if pmsi_tunnel_type == PMSI_TYPE_INGRESS_REP:
tunnel_id = PmsiTunnelIdIngressReplication(
tunnel_endpoint_ip=self._core_service.router_id)
else: # pmsi_tunnel_type == PMSI_TYPE_NO_TUNNEL_INFO
tunnel_id = None
pattrs[BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE] = \
BGPPathAttributePmsiTunnel(pmsi_flags=0,
tunnel_type=pmsi_tunnel_type,
tunnel_id=tunnel_id)
# Set MPLS labels with the generated labels
if gen_lbl and isinstance(nlri, EvpnMacIPAdvertisementNLRI):
nlri.mpls_labels = label_list[:2]

View File

@ -91,6 +91,7 @@ from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MP_UNREACH_NLRI
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_COMMUNITIES
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_EXTENDED_COMMUNITIES
from ryu.lib.packet.bgp import BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE
from ryu.lib.packet.bgp import BGPTwoOctetAsSpecificExtendedCommunity
from ryu.lib.packet.bgp import BGPIPv4AddressSpecificExtendedCommunity
@ -988,6 +989,7 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
extcomm_attr = None
community_attr = None
localpref_attr = None
pmsi_tunnel_attr = None
unknown_opttrans_attrs = None
nlri_list = [path.nlri]
@ -1164,6 +1166,10 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
communities=communities
)
pmsi_tunnel_attr = pathattr_map.get(
BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE
)
# UNKNOWN Attributes.
# Get optional transitive path attributes
unknown_opttrans_attrs = bgp_utils.get_unknown_opttrans_attr(path)
@ -1192,6 +1198,8 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
new_pathattr.append(community_attr)
if extcomm_attr:
new_pathattr.append(extcomm_attr)
if pmsi_tunnel_attr:
new_pathattr.append(pmsi_tunnel_attr)
if unknown_opttrans_attrs:
new_pathattr.extend(unknown_opttrans_attrs.values())

View File

@ -318,3 +318,100 @@ class Test_BGPSpeaker(unittest.TestCase):
# Check
mock_call.assert_called_with(
'evpn_prefix.delete_local', 'Invalid arguments detected')
@mock.patch('ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
mock.MagicMock(return_value=None))
@mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
def test_evpn_prefix_add_pmsi_no_tunnel_info(self, mock_call):
# Prepare test data
route_type = bgpspeaker.EVPN_MULTICAST_ETAG_ROUTE
route_dist = '65000:100'
ethernet_tag_id = 200
next_hop = '0.0.0.0'
ip_addr = '192.168.0.1'
pmsi_tunnel_type = bgpspeaker.PMSI_TYPE_NO_TUNNEL_INFO
expected_kwargs = {
'route_type': route_type,
'route_dist': route_dist,
'ethernet_tag_id': ethernet_tag_id,
'next_hop': next_hop,
'ip_addr': ip_addr,
'pmsi_tunnel_type': pmsi_tunnel_type,
}
# Test
speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1')
speaker.evpn_prefix_add(
route_type=route_type,
route_dist=route_dist,
ethernet_tag_id=ethernet_tag_id,
ip_addr=ip_addr,
pmsi_tunnel_type=pmsi_tunnel_type,
)
# Check
mock_call.assert_called_with(
'evpn_prefix.add_local', **expected_kwargs)
@mock.patch(
'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
mock.MagicMock(return_value=None))
@mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
def test_evpn_prefix_add_pmsi_ingress_rep(self, mock_call):
# Prepare test data
route_type = bgpspeaker.EVPN_MULTICAST_ETAG_ROUTE
route_dist = '65000:100'
ethernet_tag_id = 200
next_hop = '0.0.0.0'
ip_addr = '192.168.0.1'
pmsi_tunnel_type = bgpspeaker.PMSI_TYPE_INGRESS_REP
expected_kwargs = {
'route_type': route_type,
'route_dist': route_dist,
'ethernet_tag_id': ethernet_tag_id,
'next_hop': next_hop,
'ip_addr': ip_addr,
'pmsi_tunnel_type': pmsi_tunnel_type,
}
# Test
speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1')
speaker.evpn_prefix_add(
route_type=route_type,
route_dist=route_dist,
ethernet_tag_id=ethernet_tag_id,
ip_addr=ip_addr,
pmsi_tunnel_type=pmsi_tunnel_type,
)
# Check
mock_call.assert_called_with(
'evpn_prefix.add_local', **expected_kwargs)
@raises(ValueError)
@mock.patch(
'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
mock.MagicMock(return_value=None))
@mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
def test_evpn_prefix_add_invalid_pmsi_tunnel_type(self, mock_call):
# Prepare test data
route_type = bgpspeaker.EVPN_MULTICAST_ETAG_ROUTE
route_dist = '65000:100'
ethernet_tag_id = 200
next_hop = '0.0.0.0'
ip_addr = '192.168.0.1'
pmsi_tunnel_type = 1
# Test
speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1')
speaker.evpn_prefix_add(
route_type=route_type,
route_dist=route_dist,
ethernet_tag_id=ethernet_tag_id,
ip_addr=ip_addr,
pmsi_tunnel_type=pmsi_tunnel_type,
)
# Check
mock_call.assert_called_with(
'evpn_prefix.add_local', 'Invalid arguments detected')