diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index 7c15fd3711..860b2988be 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -2382,8 +2382,7 @@ static int bgp_attr_encap(uint8_t type, struct peer *peer, /* IN */ * Returns 0 if there was an error that needs to be passed up the stack */ static bgp_attr_parse_ret_t bgp_attr_psid_sub(uint8_t type, uint16_t length, - struct bgp_attr_parser_args *args, - struct bgp_nlri *mp_update) + struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; @@ -2421,15 +2420,6 @@ static bgp_attr_parse_ret_t bgp_attr_psid_sub(uint8_t type, uint16_t length, /* Store label index; subsequently, we'll check on * address-family */ attr->label_index = label_index; - - /* - * Ignore the Label index attribute unless received for - * labeled-unicast - * SAFI. - */ - if (!mp_update->length - || mp_update->safi != SAFI_LABELED_UNICAST) - attr->label_index = BGP_INVALID_LABEL_INDEX; } /* Placeholder code for the IPv6 SID type */ @@ -2628,8 +2618,7 @@ static bgp_attr_parse_ret_t bgp_attr_psid_sub(uint8_t type, uint16_t length, /* Prefix SID attribute * draft-ietf-idr-bgp-prefix-sid-05 */ -bgp_attr_parse_ret_t bgp_attr_prefix_sid(struct bgp_attr_parser_args *args, - struct bgp_nlri *mp_update) +bgp_attr_parse_ret_t bgp_attr_prefix_sid(struct bgp_attr_parser_args *args) { struct peer *const peer = args->peer; struct attr *const attr = args->attr; @@ -2640,8 +2629,10 @@ bgp_attr_parse_ret_t bgp_attr_prefix_sid(struct bgp_attr_parser_args *args, uint8_t type; uint16_t length; size_t headersz = sizeof(type) + sizeof(length); + size_t psid_parsed_length = 0; - while (STREAM_READABLE(peer->curr) > 0) { + while (STREAM_READABLE(peer->curr) > 0 + && psid_parsed_length < args->length) { if (STREAM_READABLE(peer->curr) < headersz) { flog_err( @@ -2667,10 +2658,23 @@ bgp_attr_parse_ret_t bgp_attr_prefix_sid(struct bgp_attr_parser_args *args, args->total); } - ret = bgp_attr_psid_sub(type, length, args, mp_update); + ret = bgp_attr_psid_sub(type, length, args); if (ret != BGP_ATTR_PARSE_PROCEED) return ret; + + psid_parsed_length += length + headersz; + + if (psid_parsed_length > args->length) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed Prefix SID attribute - TLV overflow by attribute (need %zu" + " for TLV length, have %zu overflowed in UPDATE)", + length + headersz, psid_parsed_length - (length + headersz)); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } } return BGP_ATTR_PARSE_PROCEED; @@ -3066,7 +3070,7 @@ bgp_attr_parse_ret_t bgp_attr_parse(struct peer *peer, struct attr *attr, startp); break; case BGP_ATTR_PREFIX_SID: - ret = bgp_attr_prefix_sid(&attr_args, mp_update); + ret = bgp_attr_prefix_sid(&attr_args); break; case BGP_ATTR_PMSI_TUNNEL: ret = bgp_attr_pmsi_tunnel(&attr_args); @@ -3113,6 +3117,17 @@ bgp_attr_parse_ret_t bgp_attr_parse(struct peer *peer, struct attr *attr, } } + /* + * draft-ietf-idr-bgp-prefix-sid-27#section-3: + * About Prefix-SID path attribute, + * Label-Index TLV(type1) and The Originator SRGB TLV(type-3) + * may only appear in a BGP Prefix-SID attribute attached to + * IPv4/IPv6 Labeled Unicast prefixes ([RFC8277]). + * It MUST be ignored when received for other BGP AFI/SAFI combinations. + */ + if (!attr->mp_nexthop_len || mp_update->safi != SAFI_LABELED_UNICAST) + attr->label_index = BGP_INVALID_LABEL_INDEX; + /* Check final read pointer is same as end pointer. */ if (BGP_INPUT_PNT(peer) != endp) { flog_warn(EC_BGP_ATTRIBUTES_MISMATCH, diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h index 2e91f56df5..1c519c67f3 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h @@ -351,8 +351,7 @@ extern int bgp_mp_reach_parse(struct bgp_attr_parser_args *args, extern int bgp_mp_unreach_parse(struct bgp_attr_parser_args *args, struct bgp_nlri *); extern bgp_attr_parse_ret_t -bgp_attr_prefix_sid(struct bgp_attr_parser_args *args, - struct bgp_nlri *mp_update); +bgp_attr_prefix_sid(struct bgp_attr_parser_args *args); extern struct bgp_attr_encap_subtlv * encap_tlv_dup(struct bgp_attr_encap_subtlv *orig); diff --git a/tests/bgpd/test_mp_attr.c b/tests/bgpd/test_mp_attr.c index c97ea57150..7fabaad7fa 100644 --- a/tests/bgpd/test_mp_attr.c +++ b/tests/bgpd/test_mp_attr.c @@ -951,12 +951,19 @@ static struct test_segment mp_prefix_sid[] = { "PREFIX-SID", "PREFIX-SID Test 1", { - 0x01, 0x00, 0x07, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, - 0x03, 0x00, 0x08, 0x00, - 0x00, 0x0a, 0x1b, 0xfe, - 0x00, 0x00, 0x0a + /* TLV[0] Latel-Index TLV */ + 0x01, /* Type 0x01:Label-Index */ + 0x00, 0x07, /* Length */ + 0x00, /* RESERVED */ + 0x00, 0x00, /* Flags */ + 0x00, 0x00, 0x00, 0x02, /* Label Index */ + + /* TLV[1] SRGB TLV */ + 0x03, /* Type 0x03:SRGB */ + 0x00, 0x08, /* Length */ + 0x00, 0x00, /* Flags */ + 0x0a, 0x1b, 0xfe, /* SRGB[0] first label */ + 0x00, 0x00, 0x0a /* SRBG[0] nb-labels in range */ }, .len = 21, .parses = SHOULD_PARSE, @@ -1027,7 +1034,7 @@ static void parse_test(struct peer *peer, struct test_segment *t, int type) parse_ret = bgp_mp_unreach_parse(&attr_args, &nlri); break; case BGP_ATTR_PREFIX_SID: - parse_ret = bgp_attr_prefix_sid(&attr_args, &nlri); + parse_ret = bgp_attr_prefix_sid(&attr_args); break; default: printf("unknown type"); diff --git a/tests/topotests/bgp_prefix_sid/__init__.py b/tests/topotests/bgp_prefix_sid/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bgp_prefix_sid/exabgp.env b/tests/topotests/bgp_prefix_sid/exabgp.env new file mode 100644 index 0000000000..6c554f5fa8 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/exabgp.env @@ -0,0 +1,53 @@ + +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_prefix_sid/peer1/exabgp.cfg b/tests/topotests/bgp_prefix_sid/peer1/exabgp.cfg new file mode 100644 index 0000000000..5b55366a0e --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/peer1/exabgp.cfg @@ -0,0 +1,103 @@ +group controller { + neighbor 10.0.0.1 { + router-id 10.0.0.101; + local-address 10.0.0.101; + local-as 2; + peer-as 1; + + family { + ipv4 nlri-mpls; + } + + static { + # ref: draft-ietf-idr-bgp-prefix-sid-27 + # + # IANA temporarily assigned the following: + # attribute code type (suggested value: 40) to + # the BGP Prefix-SID attribute + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type | Length | RESERVED | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Flags | Label Index | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Label Index | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # Figure. Label-Index TLV (Prefix-SID type-1) + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type | Length | Flags | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Flags | + # +-+-+-+-+-+-+-+-+ + # + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | SRGB 1 (6 octets) | + # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | SRGB n (6 octets) | + # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # Figure. Originator SRGB TLV (Prefix-SID type-3) + + # ExaBGP generic-attribute binary pattern: + # Attribute-type: 0x28 (40:BGP_PREFIX_SID) + # Attribute-flag: 0xc0 (Option, Transitive) + # Attribute-body: Label-Index TLV and Originator SRGB TLV + # Label-Index TLV: 0x01000700000000000001 + # Type (08bit): 0x01 + # Length (16bit): 0x0007 + # RESERVED (08bit): 0x00 + # Flags (16bit): 0x0000 + # Label Index (32bit): 0x00000001 + # Originator SRGB TLV: 0x03000800000c350000000a + # Type (08bit): 0x03 + # Length (16bit): 0x0008 (nb-SRGB is 1) + # Flags (16bit): 0x0000 + # SRGB1 (48bit): 0x0c3500:0x00000a (800000-800010 is SRGB1) + route 3.0.0.1/32 next-hop 10.0.0.101 label [800001] attribute [0x28 0xc0 0x0100070000000000000103000800000c350000000a]; + + # ExaBGP generic-attribute binary pattern: + # Attribute-type: 0x28 (40:BGP_PREFIX_SID) + # Attribute-flag: 0xc0 (Option, Transitive) + # Attribute-body: Label-Index TLV and Originator SRGB TLV + # Label-Index TLV: 0x01000700000000000001 + # Type (08bit): 0x01 + # Length (16bit): 0x0007 + # RESERVED (08bit): 0x00 + # Flags (16bit): 0x0000 + # Label Index (32bit): 0x00000002 + # Originator SRGB TLV: 0x03000800000c350000000a + # Type (08bit): 0x03 + # Length (16bit): 0x0008 (nb-SRGB is 1) + # Flags (16bit): 0x0000 + # SRGB1 (48bit): 0x0c3500:0x00000a (800000-800010 is SRGB1) + route 3.0.0.2/32 next-hop 10.0.0.101 label [800002] attribute [0x28 0xc0 0x0100070000000000000203000800000c350000000a]; + + # ExaBGP generic-attribute binary pattern: + # Attribute-type: 0x28 (40:BGP_PREFIX_SID) + # Attribute-flag: 0xc0 (Option, Transitive) + # Attribute-body: Label-Index TLV and Originator SRGB TLV + # Label-Index TLV: 0x01000700000000000001 + # Type (08bit): 0x01 + # Length (16bit): 0x0007 + # RESERVED (08bit): 0x00 + # Flags (16bit): 0x0000 + # Label Index (32bit): 0x00000003 + # Originator SRGB TLV: 0x03000800000c350000000a + # Type (08bit): 0x03 + # Length (16bit): 0x0008 (nb-SRGB is 1) + # Flags (16bit): 0x0000 + # SRGB1 (48bit): 0x0c3500:0x00000a (800000-800010 is SRGB1) + route 3.0.0.3/32 next-hop 10.0.0.101 label [800003] attribute [0x28 0xc0 0x0100070000000000000303000800000c350000000a]; + } + } +} diff --git a/tests/topotests/bgp_prefix_sid/peer2/exa-receive.py b/tests/topotests/bgp_prefix_sid/peer2/exa-receive.py new file mode 100755 index 0000000000..eaa6a67872 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/peer2/exa-receive.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +""" +exa-receive.py: Save received routes form ExaBGP into file +""" + +from sys import stdin,argv +from datetime import datetime + +# 1st arg is peer number +peer = int(argv[1]) + +# When the parent dies we are seeing continual newlines, so we only access so many before stopping +counter = 0 + +routesavefile = open('/tmp/peer%s-received.log' % peer, 'w') + +while True: + try: + line = stdin.readline() + routesavefile.write(line) + routesavefile.flush() + + if line == "": + counter += 1 + if counter > 100: + break + continue + + counter = 0 + except KeyboardInterrupt: + pass + except IOError: + # most likely a signal during readline + pass + +routesavefile.close() diff --git a/tests/topotests/bgp_prefix_sid/peer2/exabgp.cfg b/tests/topotests/bgp_prefix_sid/peer2/exabgp.cfg new file mode 100644 index 0000000000..dabd88e03d --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/peer2/exabgp.cfg @@ -0,0 +1,19 @@ +group controller { + + process receive-routes { + run "/etc/exabgp/exa-receive.py 2"; + receive-routes; + encoder json; + } + + neighbor 10.0.0.1 { + router-id 10.0.0.102; + local-address 10.0.0.102; + local-as 3; + peer-as 1; + + family { + ipv4 nlri-mpls; + } + } +} diff --git a/tests/topotests/bgp_prefix_sid/r1/bgpd.conf b/tests/topotests/bgp_prefix_sid/r1/bgpd.conf new file mode 100644 index 0000000000..7a38cc307f --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/r1/bgpd.conf @@ -0,0 +1,15 @@ +log stdout notifications +log monitor notifications +log commands +! +router bgp 1 + bgp router-id 10.0.0.1 + no bgp default ipv4-unicast + neighbor 10.0.0.101 remote-as 2 + neighbor 10.0.0.102 remote-as 3 + ! + address-family ipv4 labeled-unicast + neighbor 10.0.0.101 activate + neighbor 10.0.0.102 activate + exit-address-family +! diff --git a/tests/topotests/bgp_prefix_sid/r1/zebra.conf b/tests/topotests/bgp_prefix_sid/r1/zebra.conf new file mode 100644 index 0000000000..0cd26052f2 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/r1/zebra.conf @@ -0,0 +1,7 @@ +hostname r1 +! +interface r1-eth0 + ip address 10.0.0.1/24 + no shutdown +! +line vty diff --git a/tests/topotests/bgp_prefix_sid/test_bgp_prefix_sid.py b/tests/topotests/bgp_prefix_sid/test_bgp_prefix_sid.py new file mode 100755 index 0000000000..dc203cabc5 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/test_bgp_prefix_sid.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python + +# +# test_bgp_prefix_sid.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by LINE Corporation +# Copyright (c) 2020 by Hiroki Shirokura +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_bgp_prefix_sid.py: Test BGP topology with EBGP on prefix-sid +""" + +import json +import os +import sys +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, '../')) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from mininet.topo import Topo + + +class TemplateTopo(Topo): + def build(self, **_opts): + tgen = get_topogen(self) + router = tgen.add_router('r1') + switch = tgen.add_switch('s1') + switch.add_link(router) + + switch = tgen.gears['s1'] + peer1 = tgen.add_exabgp_peer('peer1', ip='10.0.0.101', defaultRoute='via 10.0.0.1') + peer2 = tgen.add_exabgp_peer('peer2', ip='10.0.0.102', defaultRoute='via 10.0.0.1') + switch.add_link(peer1) + switch.add_link(peer2) + + +def setup_module(module): + tgen = Topogen(TemplateTopo, module.__name__) + tgen.start_topology() + + router = tgen.gears['r1'] + router.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, '{}/zebra.conf'.format('r1'))) + router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, '{}/bgpd.conf'.format('r1'))) + router.start() + + logger.info('starting exaBGP on peer1') + peer_list = tgen.exabgp_peers() + for pname, peer in peer_list.iteritems(): + peer_dir = os.path.join(CWD, pname) + env_file = os.path.join(CWD, 'exabgp.env') + logger.info('Running ExaBGP peer') + peer.start(peer_dir, env_file) + logger.info(pname) + + +def teardown_module(module): + tgen = get_topogen() + tgen.stop_topology() + + +def test_r1_receive_and_advertise_prefix_sid_type1(): + tgen = get_topogen() + router = tgen.gears['r1'] + + def _check_type1_r1(router, prefix, remoteLabel, labelIndex): + output = router.vtysh_cmd('show bgp ipv4 labeled-unicast {} json'.format(prefix)) + output = json.loads(output) + expected = { + 'prefix': prefix, + 'advertisedTo': { '10.0.0.101':{}, '10.0.0.102':{} }, + 'paths': [{ + 'valid':True, + 'remoteLabel': remoteLabel, + 'labelIndex': labelIndex, + }] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_check_type1_r1, router, '3.0.0.1/32', 800001, 1) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, 'Failed _check_type1_r1 in "{}"'.format(router) + + test_func = functools.partial(_check_type1_r1, router, '3.0.0.2/32', 800002, 2) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, 'Failed _check_type1_r1 in "{}"'.format(router) + + +def exabgp_get_update_prefix(filename, afi, nexthop, prefix): + with open('/tmp/peer2-received.log') as f: + for line in f.readlines(): + output = json.loads(line) + ret = output.get('neighbor') + if ret is None: + continue + ret = ret.get('message') + if ret is None: + continue + ret = ret.get('update') + if ret is None: + continue + ret = ret.get('announce') + if ret is None: + continue + ret = ret.get(afi) + if ret is None: + continue + ret = ret.get(nexthop) + if ret is None: + continue + ret = ret.get(prefix) + if ret is None: + continue + return output + return "Not found" + + +def test_peer2_receive_prefix_sid_type1(): + tgen = get_topogen() + peer2 = tgen.gears['peer2'] + + def _check_type1_peer2(prefix, labelindex): + output = exabgp_get_update_prefix('/tmp/peer2-received.log', 'ipv4 nlri-mpls', '10.0.0.101', prefix) + expected = { + 'type': 'update', + 'neighbor': { + 'ip': '10.0.0.1', + 'message': { + 'update': { + 'attribute': { + 'attribute-0x28-0xE0': '0x010007000000{:08x}'.format(labelindex) + }, + 'announce': { 'ipv4 nlri-mpls': { '10.0.0.101': {} } } + } + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_check_type1_peer2, '3.0.0.1/32', labelindex=1) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, 'Failed _check_type1_peer2 in "{}"'.format('peer2') + + test_func = functools.partial(_check_type1_peer2, '3.0.0.2/32', labelindex=2) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, 'Failed _check_type1_peer2 in "{}"'.format('peer2') + + +if __name__ == '__main__': + args = ["-s"] + sys.argv[1:] + ret = pytest.main(args) + sys.exit(ret)