diff --git a/yabgp/common/constants.py b/yabgp/common/constants.py index 1a742ac..043e6f1 100644 --- a/yabgp/common/constants.py +++ b/yabgp/common/constants.py @@ -312,10 +312,20 @@ NEW_ROUTE_REFRESH = False GRACEFUL_RESTART = False -# AFI_SAFI mapping +# AFI mapping +AFI_DICT = { + 1: 'ipv4', + 2: 'ipv6' +} +AFI_STR_DICT = { + 'ipv4': 1, + 'ipv6': 2 +} +# AFI_SAFI mapping AFI_SAFI_DICT = { (1, 1): 'ipv4', + (1, 2): 'ipv4_mcast', (2, 1): 'ipv6', (1, 4): 'ipv4_lu', (2, 4): 'ipv6_lu', @@ -328,8 +338,9 @@ (2, 133): 'ipv6_flowspec' } AFI_SAFI_STR_DICT = { - 'ipv6': (2, 1), 'ipv4': (1, 1), + 'ipv4_mcast': (1, 2), + 'ipv6': (2, 1), 'ipv4_lu': (1, 4), 'ipv6_lu': (2, 4), 'flowspec': (1, 133), diff --git a/yabgp/config.py b/yabgp/config.py index 9b48e4a..d198367 100644 --- a/yabgp/config.py +++ b/yabgp/config.py @@ -20,8 +20,10 @@ from oslo_config import cfg -CONF = cfg.CONF +from yabgp.common.constants import AFI_SAFI_STR_DICT +from yabgp.common.constants import AFI_STR_DICT +CONF = cfg.CONF BGP_CONFIG_OPTS = [ cfg.IntOpt('peer_start_interval', @@ -50,7 +52,10 @@ help='if support cisco multi session'), cfg.DictOpt('running_config', default={}, - help='The running configuration for BGP') + help='The running configuration for BGP'), + cfg.ListOpt('ext_nexthop', + default=[['ipv4', 'ipv6'], ['ipv4_mcast', 'ipv6'], ['vpnv4', 'ipv6']], + help='The ext_nexthop for BGP') ] CONF.register_opts(BGP_CONFIG_OPTS, group='bgp') @@ -129,10 +134,21 @@ def get_bgp_config(): 'enhanced_route_refresh': CONF.bgp.enhanced_route_refresh, 'graceful_restart': CONF.bgp.graceful_restart, 'cisco_multi_session': CONF.bgp.cisco_multi_session, - 'add_path': CONF.bgp.add_path}, + 'add_path': CONF.bgp.add_path + }, 'remote': {} } } + if ('vpnv4' in CONF.bgp.afi_safi) or ('vpnv6' in CONF.bgp.afi_safi): + ext_nexthop_from_cli = CONF.bgp.ext_nexthop + ext_nexthop = [] + for each in ext_nexthop_from_cli: + afi_safi, nexthop_afi = each + ext_nexthop.append({ + 'afi_safi': AFI_SAFI_STR_DICT[afi_safi], + 'nexthop_afi': AFI_STR_DICT[nexthop_afi] + }) + CONF.bgp.running_config['capability']['local']['ext_nexthop'] = ext_nexthop else: LOG.error('Please provide enough parameters!') sys.exit() diff --git a/yabgp/message/open.py b/yabgp/message/open.py index 21a236f..8c661f2 100644 --- a/yabgp/message/open.py +++ b/yabgp/message/open.py @@ -166,6 +166,7 @@ def parse(self, message): # (7) enhanced route refresh elif capability.capa_code == capability.ENHANCED_ROUTE_REFRESH: self.capa_dict['enhanced_route_refresh'] = True + # (8) add path elif capability.capa_code == capability.ADD_PATH: # could be more than one add path cap for different afi safi @@ -180,6 +181,7 @@ def parse(self, message): } ) capability.capa_value = capability.capa_value[4:] + # (9) Long-Lived Graceful Restart (LLGR) Capability elif capability.capa_code == capability.LLGR: self.capa_dict['LLGR'] = [] @@ -191,6 +193,8 @@ def parse(self, message): 'time': time }) capability.capa_value = capability.capa_value[7:] + + # (10) Extended Next Hop Encoding Capability elif capability.capa_code == capability.EXTENDED_NEXT_HOP: self.capa_dict['ext_nexthop'] = [] while len(capability.capa_value) > 0: @@ -225,9 +229,7 @@ def construct_header(msg): # Maker | Length | Type | msg | #---------------+--------+---------+------+ """ - return b'\xff'*16 + struct.pack('!HB', - len(msg) + 19, - 1) + msg + return b'\xff' * 16 + struct.pack('!HB', len(msg) + 19, 1) + msg def construct(self, my_capability): @@ -252,6 +254,11 @@ def construct(self, my_capability): else: if my_capability.get('four_bytes_as'): capas += Capability(capa_code=65, capa_length=4, capa_value=self.asn).construct(my_capability) + + if 'ext_nexthop' in my_capability: + # Extended Next Hop Encoding Capability + capas += Capability(capa_code=5, capa_length=0).construct(my_capability) + # for add path if my_capability.get('add_path'): capas += Capability(capa_code=69, capa_length=4, capa_value=my_capability['add_path']).construct() @@ -345,7 +352,7 @@ class Capability(object): MULTISESSION_BGP = 0x44 # [Appanna] ADD_PATH = 0x45 # [draft-ietf-idr-add-paths] ENHANCED_ROUTE_REFRESH = 0x46 - LLGR = 0x47 # [draft-uttaro-idr-bgp-persistence] + LLGR = 0x47 # [draft-uttaro-idr-bgp-persistence] # 70-127 Unassigned CISCO_ROUTE_REFRESH = 0x80 # I Can only find reference to this in the router logs # 128-255 Reserved for Private Use [RFC5492] @@ -418,6 +425,22 @@ def construct(self, my_capability=None): '!BBBBHBB', 2, 6, self.ADD_PATH, self.capa_length, afi_safi[0], afi_safi[1], value) return add_path + # (10) Extended Next Hop Encoding Capability + elif self.capa_code == self.EXTENDED_NEXT_HOP: + ext_nexthop_value = b'' + # 1. Value + for each_ext_nexthop in my_capability['ext_nexthop']: + afi, safi = each_ext_nexthop['afi_safi'] + nhafi = each_ext_nexthop['nexthop_afi'] + ext_nexthop_value += struct.pack('!HHH', afi, safi, nhafi) + + # 2. Header + ext_nexthop_value_length = len(ext_nexthop_value) + capability_and_ext_nexthtop_header = struct.pack('!BBBB', 2, ext_nexthop_value_length + 1 + 1, + self.EXTENDED_NEXT_HOP, ext_nexthop_value_length) + ext_nexthop = capability_and_ext_nexthtop_header + ext_nexthop_value + return ext_nexthop + elif self.capa_code == self.ENHANCED_ROUTE_REFRESH: return struct.pack('!BBBB', 2, 2, self.ENHANCED_ROUTE_REFRESH, 0) diff --git a/yabgp/tests/unit/message/test_open.py b/yabgp/tests/unit/message/test_open.py index 40a4086..fec3f6a 100644 --- a/yabgp/tests/unit/message/test_open.py +++ b/yabgp/tests/unit/message/test_open.py @@ -136,6 +136,70 @@ def test_parse_add_path_llgr(self): } self.assertEqual(results, self.open.parse(msg_hex[HDR_LEN:])) + def test_parse_ext_nexthop(self): + """ + + :return: + """ + real_msg_hex = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00S\x01' \ + b'\x04\xfd\xe8\x00\xb4\x01\x01\x01\x016\x02\x06\x01\x04\x00\x01\x00\x01\x02' \ + b'\x06\x01\x04\x00\x01\x00\x85\x02\x02\x80\x00\x02\x02\x02\x00\x02\x06A\x04' \ + b'\x00\x00\xfd\xe8\x02\x14\x05\x12\x00\x01\x00\x01\x00\x02\x00\x01\x00\x02' \ + b'\x00\x02\x00\x01\x00\x80\x00\x02' + open_msg = self.open.parse(real_msg_hex[HDR_LEN:]) + results = { + 'version': 4, + 'asn': 65000, + 'hold_time': 180, + 'bgp_id': '1.1.1.1', + 'capabilities': { + 'afi_safi': [ + (1, 1), + (1, 133) + ], + 'cisco_route_refresh': True, + 'route_refresh': True, + 'four_bytes_as': True, + 'ext_nexthop': [ + {'afi_safi': [1, 1], 'nexthop_afi': 2}, + {'afi_safi': [1, 2], 'nexthop_afi': 2}, + {'afi_safi': [1, 128], 'nexthop_afi': 2} + ] + } + } + self.assertEqual(results, open_msg) + + def test_construct_ext_nexthop(self): + """ + + :return: + """ + self.open.version = VERSION + self.open.asn = 65000 + self.open.hold_time = 180 + self.open.bgp_id = int(netaddr.IPAddress('1.1.1.1')) + my_capabilities = { + 'afi_safi': [ + (1, 1), + (1, 133) + ], + 'cisco_route_refresh': True, + 'route_refresh': True, + 'four_bytes_as': True, + 'ext_nexthop': [ + {'afi_safi': [1, 1], 'nexthop_afi': 2}, + {'afi_safi': [1, 2], 'nexthop_afi': 2}, + {'afi_safi': [1, 128], 'nexthop_afi': 2} + ] + } + msg_hex = self.open.construct(my_capabilities) + hope_hex = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00S\x01' \ + b'\x04\xfd\xe8\x00\xb4\x01\x01\x01\x016\x02\x06\x01\x04\x00\x01\x00\x01\x02' \ + b'\x06\x01\x04\x00\x01\x00\x85\x02\x02\x80\x00\x02\x02\x02\x00\x02\x06A\x04' \ + b'\x00\x00\xfd\xe8\x02\x14\x05\x12\x00\x01\x00\x01\x00\x02\x00\x01\x00\x02' \ + b'\x00\x02\x00\x01\x00\x80\x00\x02' + self.assertEqual(hope_hex, msg_hex) + if __name__ == '__main__': unittest.main()