diff --git a/tests/topotests/bgp_bmp/r1/bgpd.conf b/tests/topotests/bgp_bmp/r1/bgpd.conf index 69acf6e75076..7504e00d03e8 100644 --- a/tests/topotests/bgp_bmp/r1/bgpd.conf +++ b/tests/topotests/bgp_bmp/r1/bgpd.conf @@ -4,6 +4,8 @@ router bgp 65501 no bgp ebgp-requires-policy neighbor 192.168.0.2 remote-as 65502 neighbor 192:168::2 remote-as 65502 + neighbor 192.168.0.3 remote-as 65502 + neighbor 192:168::3 remote-as 65502 ! bmp targets bmp1 bmp connect 192.0.178.10 port 1789 min-retry 100 max-retry 10000 @@ -13,10 +15,15 @@ router bgp 65501 neighbor 192.168.0.2 activate neighbor 192.168.0.2 soft-reconfiguration inbound no neighbor 192:168::2 activate + neighbor 192.168.0.3 activate + neighbor 192.168.0.3 soft-reconfiguration inbound + no neighbor 192:168::3 activate exit-address-family ! address-family ipv6 unicast neighbor 192:168::2 activate neighbor 192:168::2 soft-reconfiguration inbound + neighbor 192:168::3 activate + neighbor 192:168::3 soft-reconfiguration inbound exit-address-family ! diff --git a/tests/topotests/bgp_bmp/r3/bgpd.conf b/tests/topotests/bgp_bmp/r3/bgpd.conf new file mode 100644 index 000000000000..6f8247a14701 --- /dev/null +++ b/tests/topotests/bgp_bmp/r3/bgpd.conf @@ -0,0 +1,19 @@ +router bgp 65502 + bgp router-id 192.168.0.3 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.0.1 remote-as 65501 + neighbor 192:168::1 remote-as 65501 +! + address-family ipv4 unicast + neighbor 192.168.0.1 activate + no neighbor 192:168::1 activate + redistribute connected + exit-address-family +! + address-family ipv6 unicast + neighbor 192:168::1 activate + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_bmp/r3/zebra.conf b/tests/topotests/bgp_bmp/r3/zebra.conf new file mode 100644 index 000000000000..752a212411ca --- /dev/null +++ b/tests/topotests/bgp_bmp/r3/zebra.conf @@ -0,0 +1,8 @@ +interface r3-eth0 + ip address 192.168.0.3/24 + ipv6 address 192:168::3/64 +! +interface r3-eth1 + ip address 172.31.0.3/24 + ipv6 address 172:31::3/64 +! diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp.py b/tests/topotests/bgp_bmp/test_bgp_bmp.py index 250f1cb90d0e..4414bb266561 100644 --- a/tests/topotests/bgp_bmp/test_bgp_bmp.py +++ b/tests/topotests/bgp_bmp/test_bgp_bmp.py @@ -9,10 +9,16 @@ test_bgp_bmp.py: Test BGP BMP functionalities +------+ +------+ +------+ - | | | | | | - | BMP1 |------------| R1 |---------------| R2 | - | | | | | | - +------+ +------+ +------+ + | | | | eth1 eth0 | | + | BMP1 |------------| R1 |-------+-------| R2 | + | | | | | | | + +------+ +------+ | +------+ + | + | +------+ + | eth0 | | + +--------| R3 | + | | + +------+ Setup two routers R1 and R2 with one link configured with IPv4 and IPv6 addresses. @@ -47,21 +53,30 @@ # remember the last sequence number of the logging messages SEQ = 0 -PRE_POLICY = "pre-policy" -POST_POLICY = "post-policy" +ADJ_IN_PRE_POLICY = "rib-in pre-policy" +ADJ_IN_POST_POLICY = "rib-in post-policy" +ADJ_OUT_PRE_POLICY = "rib-out pre-policy" +ADJ_OUT_POST_POLICY = "rib-out post-policy" LOC_RIB = "loc-rib" +BMP_UPDATE = "update" +BMP_WITHDRAW = "withdraw" + def build_topo(tgen): tgen.add_router("r1") tgen.add_router("r2") + tgen.add_router("r3") tgen.add_bmp_server("bmp1", ip="192.0.178.10", defaultRoute="via 192.0.178.1") switch = tgen.add_switch("s1") switch.add_link(tgen.gears["r1"]) switch.add_link(tgen.gears["bmp1"]) - tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "r1-eth1", "r2-eth0") + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"], nodeif="r1-eth1") + switch.add_link(tgen.gears["r2"], nodeif="r2-eth0") + switch.add_link(tgen.gears["r3"], nodeif="r3-eth0") def setup_module(mod): @@ -203,17 +218,55 @@ def unicast_prefixes(policy): logger.info("checking for updated prefixes") # check - test_func = partial(check_for_prefixes, prefixes, "update", policy) + test_func = partial(check_for_prefixes, prefixes, BMP_UPDATE, policy) success, _ = topotest.run_and_expect(test_func, True, wait=0.5) assert success, "Checking the updated prefixes has been failed !." # withdraw prefixes configure_prefixes(tgen, "r2", 65502, "unicast", prefixes, update=False) - logger.info("checking for withdrawed prefxies") + logger.info("checking for withdrawn prefixes") # check - test_func = partial(check_for_prefixes, prefixes, "withdraw", policy) + test_func = partial(check_for_prefixes, prefixes, BMP_WITHDRAW, policy) success, _ = topotest.run_and_expect(test_func, True, wait=0.5) - assert success, "Checking the withdrawed prefixes has been failed !." + assert success, "Checking the withdrawn prefixes has been failed !." + + +def multipath_unicast_prefixes(policy): + """ + Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes. + Check if the previous actions are logged in the BMP server with the right + message type and the right policy. + + For multipath we just check if we receive an update multiple times. + We can't check for the peer address because RFC9069 does not include it in Local-RIB Peer Type. + """ + tgen = get_topogen() + set_bmp_policy(tgen, "r1", 65501, "bmp1", "unicast", policy) + + prefixes = ["172.31.0.15/32", "2111::1111/128"] + + def check_prefixes(node, asn, bmp_log_type): + past_participle = "updated" if bmp_log_type == BMP_UPDATE else "withdrawn" + + configure_prefixes( + tgen, node, asn, "unicast", prefixes, update=(bmp_log_type == BMP_UPDATE) + ) + logger.info(f"checking for {past_participle} prefixes") + + # check + test_func = partial(check_for_prefixes, prefixes, bmp_log_type, policy) + success, _ = topotest.run_and_expect(test_func, True, wait=0.5) + + logger.debug(f"Full BMP Logs: \n{get_bmp_messages()}") + assert success, f"Checking the {past_participle} prefixes has been failed !." + + # add prefixes + check_prefixes("r2", 65502, BMP_UPDATE) + check_prefixes("r3", 65502, BMP_UPDATE) + + # withdraw prefixes + check_prefixes("r2", 65502, BMP_WITHDRAW) + check_prefixes("r3", 65502, BMP_WITHDRAW) def test_bmp_server_logging(): @@ -232,16 +285,30 @@ def check_for_log_file(): assert success, "The BMP server is not logging" +def test_bmp_bgp_multipath(): + """ + Add/withdraw bgp unicast prefixes on two peers and check the bmp logs. + """ + + logger.info("*** Multipath unicast prefixes loc-rib logging ***") + multipath_unicast_prefixes(LOC_RIB) + + def test_bmp_bgp_unicast(): """ Add/withdraw bgp unicast prefixes and check the bmp logs. """ - logger.info("*** Unicast prefixes pre-policy logging ***") - unicast_prefixes(PRE_POLICY) - logger.info("*** Unicast prefixes post-policy logging ***") - unicast_prefixes(POST_POLICY) + + logger.info("*** Unicast prefixes adj-rib-in pre-policy logging ***") + unicast_prefixes(ADJ_IN_PRE_POLICY) + logger.info("*** Unicast prefixes adj-rib-in post-policy logging ***") + unicast_prefixes(ADJ_IN_POST_POLICY) logger.info("*** Unicast prefixes loc-rib logging ***") unicast_prefixes(LOC_RIB) + logger.info("*** Unicast prefixes adj-rib-out pre-policy logging ***") + unicast_prefixes(ADJ_OUT_PRE_POLICY) + logger.info("*** Unicast prefixes adj-rib-out post-policy logging ***") + unicast_prefixes(ADJ_OUT_POST_POLICY) if __name__ == "__main__": diff --git a/tests/topotests/lib/bmp_collector/bgp/update/nlri.py b/tests/topotests/lib/bmp_collector/bgp/update/nlri.py index c1720f126cc1..00da0c922a2f 100644 --- a/tests/topotests/lib/bmp_collector/bgp/update/nlri.py +++ b/tests/topotests/lib/bmp_collector/bgp/update/nlri.py @@ -13,7 +13,8 @@ def decode_label(label): # from frr # frr encode just one label - return (label[0] << 12) | (label[1] << 4) | (label[2] & 0xf0) >> 4 + return (label[0] << 12) | (label[1] << 4) | (label[2] & 0xF0) >> 4 + def padding(databin, len_): """ @@ -23,7 +24,8 @@ def padding(databin, len_): """ if len(databin) >= len_: return databin - return databin + b'\0' * (len_ - len(databin)) + return databin + b"\0" * (len_ - len(databin)) + def dissect_nlri(nlri_data, afi, safi): """ @@ -37,35 +39,126 @@ def dissect_nlri(nlri_data, afi, safi): elif addr_family == AF.IPv6_UNICAST: return NlriIPv6Unicast.parse(nlri_data) - return {'ip_prefix': 'Unknown'} + return {"ip_prefix": "Unknown"} -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriIPv4Unicast: - @staticmethod def parse(data): """parses prefixes from withdrawn_routes or nrli data""" - (prefix_len,) = struct.unpack_from('!B', data) - prefix = padding(data[1:], 4) - return {'ip_prefix': f'{ipaddress.IPv4Address(prefix)}/{prefix_len}'} + # we have an add-path id, this check is simpler than the unnecessary (i think?) + # detect_addpath_prefix_ipv46(data, max_bit_length=32) procedure + if len(data) > 5: + (addpath_id, prefix_len) = struct.unpack_from("!IB", data) + addpath_id = {"path_id": addpath_id} + prefix = padding(data[5:], 4) + else: + (prefix_len,) = struct.unpack_from("!B", data) + addpath_id = {} + prefix = padding(data[1:], 4) + + return { + "ip_prefix": f"{ipaddress.IPv4Address(prefix)}/{prefix_len}", + **addpath_id, + } + + +# ------------------------------------------------------------------------------ + +""" +this is the addpath detection from wireshark, not perfect but works in our use cases + +static int detect_add_path_prefix46(tvbuff_t *tvb, gint offset, gint end, gint max_bit_length) +in packet-bgp.c BGP dissector from Wireshark +""" + + +def detect_addpath_prefix_ipv46(data, max_bit_length): + end = len(data) + + # proof by contradiction + # assuming this a well-formatted add-path prefix + # if we find an error it means there was no path-id, or a badly formatted one + # prefix length would be right after path id + # (i don't understand why they loop this check in range(4, end, 4) in Wireshark) + offset = 4 + prefix_len = data[offset] + + # the prefix length is bigger than the maximum allowed size + if prefix_len > max_bit_length: + return False + + addr_len = (prefix_len + 7) // 8 + offset += 1 + addr_len + + # the prefix length announces a prefix bigger than what we have + if offset > end: + return False + + # the prefix length tells us that the last byte will have more some 0 padding bits + # and those bits are not set to 0 + if prefix_len % 8 > 0 and data[offset - 1] & (0xFF >> (prefix_len % 8)) > 0: + return False + + # proof by contradiction + # assuming there is not an add-path prefix, and this is well formatted + # if we find an error it may mean there was a path-id + # assuming there is no add-path path-id + offset = 0 + while offset < end: + # prefix length would be first + prefix_len = data[offset] + + # prefix length is zero and we have more than one byte of address so maybe this was a path-id + if prefix_len == 0 and end - offset > 1: + return True + + # invalid prefix length so maybe this was a path-id + if prefix_len > max_bit_length: + return True + + addr_len = (prefix_len + 7) // 8 + offset += 1 + addr_len + + # the prefix length announces a prefix bigger than what we have + if offset > end: + return True # maybe this was a path-id + + # the prefix length tells us that the last byte will have more some 0 padding bits + # and those bits are not set to 0 + if prefix_len % 8 > 0 and data[offset - 1] & (0xFF >> (prefix_len % 8)) > 0: + return True # maybe it was a path-id + + # we don't know if it's add-path so let's say no + return False -#------------------------------------------------------------------------------ class NlriIPv6Unicast: @staticmethod def parse(data): """parses prefixes from withdrawn_routes or nrli data""" - (prefix_len,) = struct.unpack_from('!B', data) - prefix = padding(data[1:], 16) - return {'ip_prefix': f'{ipaddress.IPv6Address(prefix)}/{prefix_len}'} + # we have an add-path id + if detect_addpath_prefix_ipv46(data, max_bit_length=128): + (addpath_id, prefix_len) = struct.unpack_from("!IB", data) + addpath_id = {"path_id": addpath_id} + prefix = padding(data[5:], 16) + else: + (prefix_len,) = struct.unpack_from("!B", data) + addpath_id = {} + prefix = padding(data[1:], 16) + + return { + "ip_prefix": f"{ipaddress.IPv6Address(prefix)}/{prefix_len}", + **addpath_id, + } -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriIPv4Vpn: - UNPACK_STR = '!B3s8s' + UNPACK_STR = "!B3s8s" @classmethod def parse(cls, data): @@ -74,17 +167,17 @@ def parse(cls, data): ipv4 = padding(data[offset:], 4) # prefix_len = total_bits_len - label_bits_len - rd_bits_len - prefix_len = bit_len - 3*8 - 8*8 + prefix_len = bit_len - 3 * 8 - 8 * 8 return { - 'label': decode_label(label), - 'rd': str(RouteDistinguisher(rd)), - 'ip_prefix': f'{ipaddress.IPv4Address(ipv4)}/{prefix_len}', + "label": decode_label(label), + "rd": str(RouteDistinguisher(rd)), + "ip_prefix": f"{ipaddress.IPv4Address(ipv4)}/{prefix_len}", } -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriIPv6Vpn: - UNPACK_STR = '!B3s8s' + UNPACK_STR = "!B3s8s" @classmethod def parse(cls, data): @@ -93,48 +186,49 @@ def parse(cls, data): offset = struct.calcsize(cls.UNPACK_STR) ipv6 = padding(data[offset:], 16) - prefix_len = bit_len - 3*8 - 8*8 + prefix_len = bit_len - 3 * 8 - 8 * 8 return { - 'label': decode_label(label), - 'rd': str(RouteDistinguisher(rd)), - 'ip_prefix': f'{ipaddress.IPv6Address(ipv6)}/{prefix_len}', + "label": decode_label(label), + "rd": str(RouteDistinguisher(rd)), + "ip_prefix": f"{ipaddress.IPv6Address(ipv6)}/{prefix_len}", } -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriIPv4Mpls: pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriIPv6Mpls: pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriIPv4FlowSpec: pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriIPv6FlowSpec: pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriVpn4FlowSpec: pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriVpn6FlowSpec: pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class NlriL2EVPN: pass -#------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ class NlriL2VPNFlowSpec: pass diff --git a/tests/topotests/lib/bmp_collector/bmp.py b/tests/topotests/lib/bmp_collector/bmp.py index 57f642aa0ef4..44c62996b4b2 100644 --- a/tests/topotests/lib/bmp_collector/bmp.py +++ b/tests/topotests/lib/bmp_collector/bmp.py @@ -33,30 +33,33 @@ if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR) + def bin2str_ipaddress(ip_bytes, is_ipv6=False): if is_ipv6: return str(ipaddress.IPv6Address(ip_bytes)) return str(ipaddress.IPv4Address(ip_bytes[-4:])) + def log2file(logs): """ XXX: extract the useful information and save it in a flat dictionnary """ - with open(LOG_FILE, 'a') as f: + with open(LOG_FILE, "a") as f: f.write(json.dumps(logs) + "\n") -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPCodes: """ XXX: complete the list, provide RFCs. """ + VERSION = 0x3 BMP_MSG_TYPE_ROUTE_MONITORING = 0x00 BMP_MSG_TYPE_STATISTICS_REPORT = 0x01 - BMP_MSG_TYPE_PEER_DOWN_NOTIFICATION = 0x02 - BMP_MSG_TYPE_PEER_UP_NOTIFICATION = 0x03 + BMP_MSG_TYPE_PEER_DOWN_NOTIFICATION = 0x02 + BMP_MSG_TYPE_PEER_UP_NOTIFICATION = 0x03 BMP_MSG_TYPE_INITIATION = 0x04 BMP_MSG_TYPE_TERMINATION = 0x05 BMP_MSG_TYPE_ROUTE_MIRRORING = 0x06 @@ -107,15 +110,15 @@ class BMPCodes: # peer down reason code BMP_PEER_DOWN_LOCAL_NOTIFY = 0x01 - BMP_PEER_DOWN_LOCAL_NO_NOTIFY = 0X02 - BMP_PEER_DOWN_REMOTE_NOTIFY = 0X03 - BMP_PEER_DOWN_REMOTE_NO_NOTIFY = 0X04 + BMP_PEER_DOWN_LOCAL_NO_NOTIFY = 0x02 + BMP_PEER_DOWN_REMOTE_NOTIFY = 0x03 + BMP_PEER_DOWN_REMOTE_NO_NOTIFY = 0x04 BMP_PEER_DOWN_INFO_NO_LONGER = 0x05 - BMP_PEER_DOWN_SYSTEM_CLOSED = 0X06 + BMP_PEER_DOWN_SYSTEM_CLOSED = 0x06 # termincation message types BMP_TERM_TYPE_STRING = 0x00 - BMP_TERM_TYPE_REASON = 0X01 + BMP_TERM_TYPE_REASON = 0x01 # termination reason code BMP_TERM_REASON_ADMIN_CLOSE = 0x00 @@ -126,31 +129,32 @@ class BMPCodes: # policy route tlv BMP_ROUTE_POLICY_TLV_VRF = 0x00 - BMP_ROUTE_POLICY_TLV_POLICY= 0x01 + BMP_ROUTE_POLICY_TLV_POLICY = 0x01 BMP_ROUTE_POLICY_TLV_PRE_POLICY = 0x02 BMP_ROUTE_POLICY_TLV_POST_POLICY = 0x03 BMP_ROUTE_POLICY_TLV_STRING = 0x04 -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPMsg: """ XXX: should we move register_msg_type and look_msg_type to generic Type class. """ + TYPES = {} UNKNOWN_TYPE = None - HDR_STR = '!BIB' + HDR_STR = "!BIB" MIN_LEN = struct.calcsize(HDR_STR) TYPES_STR = { - BMPCodes.BMP_MSG_TYPE_INITIATION: 'initiation', - BMPCodes.BMP_MSG_TYPE_PEER_DOWN_NOTIFICATION: 'peer down notification', - BMPCodes.BMP_MSG_TYPE_PEER_UP_NOTIFICATION: 'peer up notification', - BMPCodes.BMP_MSG_TYPE_ROUTE_MONITORING: 'route monitoring', - BMPCodes.BMP_MSG_TYPE_STATISTICS_REPORT: 'statistics report', - BMPCodes.BMP_MSG_TYPE_TERMINATION: 'termination', - BMPCodes.BMP_MSG_TYPE_ROUTE_MIRRORING: 'route mirroring', - BMPCodes.BMP_MSG_TYPE_ROUTE_POLICY: 'route policy', + BMPCodes.BMP_MSG_TYPE_INITIATION: "initiation", + BMPCodes.BMP_MSG_TYPE_PEER_DOWN_NOTIFICATION: "peer down notification", + BMPCodes.BMP_MSG_TYPE_PEER_UP_NOTIFICATION: "peer up notification", + BMPCodes.BMP_MSG_TYPE_ROUTE_MONITORING: "route monitoring", + BMPCodes.BMP_MSG_TYPE_STATISTICS_REPORT: "statistics report", + BMPCodes.BMP_MSG_TYPE_TERMINATION: "termination", + BMPCodes.BMP_MSG_TYPE_ROUTE_MIRRORING: "route mirroring", + BMPCodes.BMP_MSG_TYPE_ROUTE_POLICY: "route policy", } @classmethod @@ -158,6 +162,7 @@ def register_msg_type(cls, msgtype): def _register_type(subcls): cls.TYPES[msgtype] = subcls return subcls + return _register_type @classmethod @@ -179,7 +184,7 @@ def dissect_header(cls, data): if len(data) < cls.MIN_LEN: pass else: - _version, _len, _type = struct.unpack(cls.HDR_STR, data[0:cls.MIN_LEN]) + _version, _len, _type = struct.unpack(cls.HDR_STR, data[0 : cls.MIN_LEN]) return _version, _len, _type @classmethod @@ -187,7 +192,7 @@ def dissect(cls, data): global SEQ version, msglen, msgtype = cls.dissect_header(data) - msg_data = data[cls.MIN_LEN:msglen] + msg_data = data[cls.MIN_LEN : msglen] data = data[msglen:] if version != BMPCodes.VERSION: @@ -208,7 +213,7 @@ def dissect(cls, data): return data -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPPerPeerMessage: """ 0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 @@ -229,30 +234,33 @@ class BMPPerPeerMessage: | Timestamp (microseconds) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ - PEER_UNPACK_STR = '!BB8s16sI4sII' + + PEER_UNPACK_STR = "!BB8s16sI4sII" PEER_TYPE_STR = { - BMPCodes.BMP_PEER_GLOBAL_INSTANCE: 'global instance', - BMPCodes.BMP_PEER_RD_INSTANCE: 'route distinguisher instance', - BMPCodes.BMP_PEER_LOCAL_INSTANCE: 'local instance', - BMPCodes.BMP_PEER_LOC_RIB_INSTANCE: 'loc-rib instance', + BMPCodes.BMP_PEER_GLOBAL_INSTANCE: "global instance", + BMPCodes.BMP_PEER_RD_INSTANCE: "route distinguisher instance", + BMPCodes.BMP_PEER_LOCAL_INSTANCE: "local instance", + BMPCodes.BMP_PEER_LOC_RIB_INSTANCE: "loc-rib instance", } @classmethod def dissect(cls, data): - (peer_type, - peer_flags, - peer_distinguisher, - peer_address, - peer_asn, - peer_bgp_id, - timestamp_secs, - timestamp_microsecs) = struct.unpack_from(cls.PEER_UNPACK_STR, data) - - msg = {'peer_type': cls.PEER_TYPE_STR[peer_type]} + ( + peer_type, + peer_flags, + peer_distinguisher, + peer_address, + peer_asn, + peer_bgp_id, + timestamp_secs, + timestamp_microsecs, + ) = struct.unpack_from(cls.PEER_UNPACK_STR, data) + + msg = {"peer_type": cls.PEER_TYPE_STR[peer_type]} if peer_type == 0x03: - msg['is_filtered'] = bool(peer_flags & IS_FILTERED) - msg['policy'] = 'loc-rib' + msg["is_filtered"] = bool(peer_flags & IS_FILTERED) + msg["policy"] = "loc-rib" else: # peer_flags = 0x0000 0000 # ipv6, post-policy, as-path, adj-rib-out, reserverdx4 @@ -260,29 +268,33 @@ def dissect(cls, data): is_as_path = bool(peer_flags & IS_AS_PATH) is_post_policy = bool(peer_flags & IS_POST_POLICY) is_ipv6 = bool(peer_flags & IS_IPV6) - msg['policy'] = 'post-policy' if is_post_policy else 'pre-policy' - msg['ipv6'] = is_ipv6 - msg['peer_ip'] = bin2str_ipaddress(peer_address, is_ipv6) - + msg["policy"] = ( + ("rib-out" if is_adj_rib_out else "rib-in") + + " " + + ("post-policy" if is_post_policy else "pre-policy") + ) + msg["ipv6"] = is_ipv6 + msg["peer_ip"] = bin2str_ipaddress(peer_address, is_ipv6) peer_bgp_id = bin2str_ipaddress(peer_bgp_id) - timestamp = float(timestamp_secs) + timestamp_microsecs * (10 ** -6) - - data = data[struct.calcsize(cls.PEER_UNPACK_STR):] - msg.update({ - 'peer_distinguisher': str(RouteDistinguisher(peer_distinguisher)), - 'peer_asn': peer_asn, - 'peer_bgp_id': peer_bgp_id, - 'timestamp': str(datetime.datetime.fromtimestamp(timestamp)), - }) + timestamp = float(timestamp_secs) + timestamp_microsecs * (10**-6) + + data = data[struct.calcsize(cls.PEER_UNPACK_STR) :] + msg.update( + { + "peer_distinguisher": str(RouteDistinguisher(peer_distinguisher)), + "peer_asn": peer_asn, + "peer_bgp_id": peer_bgp_id, + "timestamp": str(datetime.datetime.fromtimestamp(timestamp)), + } + ) return data, msg -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ @BMPMsg.register_msg_type(BMPCodes.BMP_MSG_TYPE_ROUTE_MONITORING) class BMPRouteMonitoring(BMPPerPeerMessage): - @classmethod def dissect(cls, data): data, peer_msg = super().dissect(data) @@ -290,7 +302,7 @@ def dissect(cls, data): return {**peer_msg, **update_msg} -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPStatisticsReport: """ 0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 @@ -303,10 +315,11 @@ class BMPStatisticsReport: ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ + pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPPeerDownNotification: """ 0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 @@ -316,10 +329,11 @@ class BMPPeerDownNotification: | Data (present if Reason = 1, 2 or 3) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ + pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ @BMPMsg.register_msg_type(BMPCodes.BMP_MSG_TYPE_PEER_UP_NOTIFICATION) class BMPPeerUpNotification(BMPPerPeerMessage): """ @@ -336,7 +350,8 @@ class BMPPeerUpNotification(BMPPerPeerMessage): ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ - UNPACK_STR = '!16sHH' + + UNPACK_STR = "!16sHH" MIN_LEN = struct.calcsize(UNPACK_STR) MSG_LEN = None @@ -344,16 +359,14 @@ class BMPPeerUpNotification(BMPPerPeerMessage): def dissect(cls, data): data, peer_msg = super().dissect(data) - (local_addr, - local_port, - remote_port) = struct.unpack_from(cls.UNPACK_STR, data) + (local_addr, local_port, remote_port) = struct.unpack_from(cls.UNPACK_STR, data) msg = { **peer_msg, **{ - 'local_ip': bin2str_ipaddress(local_addr, peer_msg.get('ipv6')), - 'local_port': int(local_port), - 'remote_port': int(remote_port), + "local_ip": bin2str_ipaddress(local_addr, peer_msg.get("ipv6")), + "local_port": int(local_port), + "remote_port": int(remote_port), }, } @@ -362,7 +375,7 @@ def dissect(cls, data): return msg -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ @BMPMsg.register_msg_type(BMPCodes.BMP_MSG_TYPE_INITIATION) class BMPInitiation: """ @@ -374,30 +387,31 @@ class BMPInitiation: ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ - TLV_STR = '!HH' + + TLV_STR = "!HH" MIN_LEN = struct.calcsize(TLV_STR) FIELD_TO_STR = { - BMPCodes.BMP_INIT_INFO_STRING: 'information', - BMPCodes.BMP_INIT_ADMIN_LABEL: 'admin_label', - BMPCodes.BMP_INIT_SYSTEM_DESCRIPTION: 'system_description', - BMPCodes.BMP_INIT_SYSTEM_NAME: 'system_name', - BMPCodes.BMP_INIT_VRF_TABLE_NAME: 'vrf_table_name', + BMPCodes.BMP_INIT_INFO_STRING: "information", + BMPCodes.BMP_INIT_ADMIN_LABEL: "admin_label", + BMPCodes.BMP_INIT_SYSTEM_DESCRIPTION: "system_description", + BMPCodes.BMP_INIT_SYSTEM_NAME: "system_name", + BMPCodes.BMP_INIT_VRF_TABLE_NAME: "vrf_table_name", } @classmethod def dissect(cls, data): msg = {} while len(data) > cls.MIN_LEN: - _type, _len = struct.unpack_from(cls.TLV_STR, data[0:cls.MIN_LEN]) - _value = data[cls.MIN_LEN: cls.MIN_LEN + _len].decode() + _type, _len = struct.unpack_from(cls.TLV_STR, data[0 : cls.MIN_LEN]) + _value = data[cls.MIN_LEN : cls.MIN_LEN + _len].decode() msg[cls.FIELD_TO_STR[_type]] = _value - data = data[cls.MIN_LEN + _len:] + data = data[cls.MIN_LEN + _len :] return msg -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPTermination: """ 0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 @@ -408,14 +422,15 @@ class BMPTermination: ~ ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ """ + pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPRouteMirroring: pass -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class BMPRoutePolicy: pass diff --git a/tests/topotests/lib/bmp_collector/bmpserver b/tests/topotests/lib/bmp_collector/bmpserver index 25b4a52c5eb1..89a802f9d719 100755 --- a/tests/topotests/lib/bmp_collector/bmpserver +++ b/tests/topotests/lib/bmp_collector/bmpserver @@ -34,6 +34,7 @@ def main(): data = BMPMsg.dissect(data) except Exception as e: # XXX: do something + print(f"[bmp] Exception occured: {e}") pass except KeyboardInterrupt: # XXX: do something diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 48caf6f03a54..7adc5bd00d5f 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -1240,7 +1240,9 @@ def __str__(self): def start(self): self.run( - "{}/bmp_collector/bmpserver -a {} -p {}&".format(CWD, self.ip, self.port), + "{}/bmp_collector/bmpserver -a {} -p {} > /var/log/bmpserver.log &".format( + CWD, self.ip, self.port + ), stdout=None, )