From 5f334b9de23d30838d7c7f9786a32445013db0cb Mon Sep 17 00:00:00 2001 From: Francois Dumontet Date: Thu, 23 Nov 2023 10:45:24 +0100 Subject: [PATCH 1/2] bgpd: add multiple paths support for draft ietf bgp4v2 in nlriTable There is no support for dumping multiple paths for the same prefix. The current implementation only takes the first available entry. Fix this by walking over the list of available paths, ordered by peer. The nlri index is set gradually for each path. Signed-off-by: Francois Dumontet --- bgpd/bgp_snmp_bgp4v2.c | 90 ++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/bgpd/bgp_snmp_bgp4v2.c b/bgpd/bgp_snmp_bgp4v2.c index 3b499271c8ca..48db73a343ca 100644 --- a/bgpd/bgp_snmp_bgp4v2.c +++ b/bgpd/bgp_snmp_bgp4v2.c @@ -446,6 +446,8 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, size_t afi_len; long prefix_type = 0; long peer_addr_type = 0; + long nrli_index = 1; + long cur_index = 0; /* Bgp4V2AddressFamilyIdentifierTC limited to IPv6 */ if (name[namelen - 1] > IANA_AFI_IPV6) @@ -455,12 +457,17 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, assert(IS_VALID_AFI(afi)); #define BGP_NLRI_ENTRY_OFFSET namelen +#define BGP4V2_NLRI_V4_V4_OFFSET IN_ADDR_SIZE + IN_ADDR_SIZE + 5 +#define BGP4V2_NLRI_V4_V6_OFFSET IN_ADDR_SIZE + IN6_ADDR_SIZE + 5 +#define BGP4V2_NLRI_V6_V6_OFFSET IN6_ADDR_SIZE + IN6_ADDR_SIZE + 5 sockunion_init(&su); if (exact) { - if (*length - namelen != BGP_NLRI_ENTRY_OFFSET) + if (*length - namelen != BGP4V2_NLRI_V4_V4_OFFSET && + *length - namelen != BGP4V2_NLRI_V4_V6_OFFSET && + *length - namelen != BGP4V2_NLRI_V6_V6_OFFSET) return NULL; /* Set OID offset for prefix type */ @@ -504,12 +511,17 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, su.sin.sin_family = family; /* get bgp4V2PeerRemoteAddr*/ - if (family == AF_INET) + if (family == AF_INET) { oid2in_addr(offset, IN_ADDR_SIZE, &su.sin.sin_addr); - else + offset += IN_ADDR_SIZE; + } else { oid2in6_addr(offset, &su.sin6.sin6_addr); + offset += IN6_ADDR_SIZE; + } - /* bgp4V2NlriIndex currently ignored */ + /* bgp4V2NlriIndex */ + nrli_index = *offset; + offset++; /* Lookup node */ dest = bgp_node_lookup(bgp->rib[afi][safi], addr); @@ -517,8 +529,11 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, for (path = bgp_dest_get_bgp_path_info(dest); path; path = path->next) if (sockunion_same(&path->peer->connection->su, - &su)) - return path; + &su)) { + cur_index++; + if (cur_index == nrli_index) + return path; + } bgp_dest_unlock_node(dest); } @@ -573,7 +588,7 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, offsetlen--; /* get node */ - dest = bgp_node_get(bgp->rib[afi][safi], addr); + dest = bgp_node_lookup(bgp->rib[afi][safi], addr); } if (!dest) @@ -593,10 +608,17 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, family = AF_INET6; offset++; - if (family == AF_INET) + if (family == AF_INET) { oid2in_addr(offset, IN_ADDR_SIZE, &paddr.ip._v4_addr); - else + offset += IN_ADDR_SIZE; + } else { oid2in6_addr(offset, &paddr.ip._v6_addr); + offset += IN6_ADDR_SIZE; + } + /* get bgp4V2NlriIndex */ + nrli_index = *offset; + offset++; + } else { /* default case start with ipv4*/ if (afi == AFI_IP) @@ -604,11 +626,13 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, else family = AF_INET6; memset(&paddr.ip, 0, sizeof(paddr.ip)); + nrli_index = 1; } do { min = NULL; min_family = 0; + cur_index = 0; for (path = bgp_dest_get_bgp_path_info(dest); path; path = path->next) { @@ -618,19 +642,44 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, if (path_family < family) continue; - if (family == AF_INET - && IPV4_ADDR_CMP(&paddr.ip._v4_addr, - &path->peer->connection->su.sin.sin_addr) - >= 0) + if (family == AF_INET && + IPV4_ADDR_CMP(&paddr.ip._v4_addr, + &path->peer->connection->su.sin + .sin_addr) > 0) continue; - else if (family == AF_INET6 - && IPV6_ADDR_CMP( - &paddr.ip._v6_addr, - &path->peer->connection->su.sin6.sin6_addr) - >= 0) + else if (family == AF_INET6 && + IPV6_ADDR_CMP(&paddr.ip._v6_addr, + &path->peer->connection->su.sin6 + .sin6_addr) > 0) continue; - /* first valid path its the min*/ + if (family == AF_INET && + IPV4_ADDR_CMP(&paddr.ip._v4_addr, + &path->peer->connection->su.sin + .sin_addr) == 0) { + if (cur_index == nrli_index) { + min = path; + min_family = family; + nrli_index++; + break; + } + cur_index++; + continue; + } else if (family == AF_INET6 && + IPV6_ADDR_CMP(&paddr.ip._v6_addr, + &path->peer->connection->su + .sin6.sin6_addr) == 0) { + if (cur_index == nrli_index) { + min = path; + min_family = family; + nrli_index++; + break; + } + cur_index++; + continue; + } + + /* first valid path its the min peer addr*/ if (!min) { min = path; min_family = path_family; @@ -706,7 +755,7 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, /* Encode bgp4V2NlriIndex*/ - *offset = 1; + *offset = nrli_index; offset++; *length = offset - name; @@ -720,6 +769,7 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, } memset(&paddr.ip, 0, sizeof(paddr.ip)); + nrli_index = 1; } while ((dest = bgp_route_next(dest))); From 869047f150c40e1306002376f36381b0c22cdc21 Mon Sep 17 00:00:00 2001 From: Francois Dumontet Date: Wed, 4 Oct 2023 18:01:02 +0200 Subject: [PATCH 2/2] tests: improve test bgp_snmp_bgp4v2mib for snmp multi path support multi path support by snmp implies change in configuration and expected tests results. ipv6 trap test output is now ordered to avoid radom result due to timeline. Signed-off-by: Francois Dumontet Signed-off-by: Philippe Guibert --- .../topotests/bgp_snmp_bgp4v2mib/r1/bgpd.conf | 21 +- .../topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf | 18 +- .../topotests/bgp_snmp_bgp4v2mib/r3/bgpd.conf | 25 +++ .../bgp_snmp_bgp4v2mib/r3/zebra.conf | 5 + .../topotests/bgp_snmp_bgp4v2mib/rr/bgpd.conf | 67 +++++++ .../bgp_snmp_bgp4v2mib/rr/zebra.conf | 5 + .../test_bgp_snmp_bgp4v2mib.py | 179 ++++++++--------- tests/topotests/lib/snmptest.py | 186 +++++++++++++----- 8 files changed, 349 insertions(+), 157 deletions(-) create mode 100644 tests/topotests/bgp_snmp_bgp4v2mib/r3/bgpd.conf create mode 100644 tests/topotests/bgp_snmp_bgp4v2mib/r3/zebra.conf create mode 100644 tests/topotests/bgp_snmp_bgp4v2mib/rr/bgpd.conf create mode 100644 tests/topotests/bgp_snmp_bgp4v2mib/rr/zebra.conf diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r1/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r1/bgpd.conf index d82a21e1f9fe..144466e418da 100644 --- a/tests/topotests/bgp_snmp_bgp4v2mib/r1/bgpd.conf +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r1/bgpd.conf @@ -1,24 +1,28 @@ ! +!debug bgp updates +! router bgp 65001 no bgp ebgp-requires-policy no bgp network import-check no bgp default ipv4-unicast - neighbor 192.168.12.2 remote-as external - neighbor 192.168.12.2 timers 1 3 - neighbor 192.168.12.2 timers connect 1 - neighbor 2001:db8::12:2 remote-as external - neighbor 2001:db8::12:2 timers 1 3 - neighbor 2001:db8::12:2 timers connect 1 + neighbor 192.168.12.4 remote-as external + neighbor 192.168.12.4 timers 1 3 + neighbor 192.168.12.4 timers connect 1 + neighbor 2001:db8::12:4 remote-as external + neighbor 2001:db8::12:4 timers 1 3 + neighbor 2001:db8::12:4 timers connect 1 ! address-family ipv4 unicast network 10.0.0.0/31 route-map p1 network 10.0.0.2/32 route-map p2 - neighbor 192.168.12.2 activate + neighbor 192.168.12.4 activate + neighbor 192.168.12.4 addpath-tx-all-paths + network 10.10.10.10/32 exit-address-family address-family ipv6 unicast network 2001:db8::1/128 route-map p1 network 2001:db8:1::/56 route-map p2 - neighbor 2001:db8::12:2 activate + neighbor 2001:db8::12:4 activate exit-address-family ! route-map p1 permit 10 @@ -28,4 +32,3 @@ route-map p2 permit 10 set metric 2 set origin incomplete exit -! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf index cf0013e1b7a1..55686f407a7f 100644 --- a/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf @@ -5,18 +5,20 @@ router bgp 65002 no bgp ebgp-requires-policy no bgp network import-check no bgp default ipv4-unicast - neighbor 192.168.12.1 remote-as external - neighbor 192.168.12.1 timers 1 3 - neighbor 192.168.12.1 timers connect 1 - neighbor 2001:db8::12:1 remote-as external - neighbor 2001:db8::12:1 timers 1 3 - neighbor 2001:db8::12:1 timers connect 1 + neighbor 192.168.12.4 remote-as external + neighbor 192.168.12.4 timers 1 3 + neighbor 192.168.12.4 timers connect 1 + neighbor 2001:db8::12:4 remote-as external + neighbor 2001:db8::12:4 timers 1 3 + neighbor 2001:db8::12:4 timers connect 1 ! address-family ipv4 unicast - neighbor 192.168.12.1 activate + neighbor 192.168.12.4 activate + neighbor 192.168.12.4 addpath-tx-all-paths + exit-address-family address-family ipv6 unicast - neighbor 2001:db8::12:1 activate + neighbor 2001:db8::12:4 activate exit-address-family ! agentx diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r3/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r3/bgpd.conf new file mode 100644 index 000000000000..71dbda0bc137 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r3/bgpd.conf @@ -0,0 +1,25 @@ +! +!debug bgp updates +! +router bgp 65003 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + neighbor 192.168.12.4 remote-as external + neighbor 192.168.12.4 timers 1 3 + neighbor 192.168.12.4 timers connect 1 + neighbor 2001:db8::12:4 remote-as external + neighbor 2001:db8::12:4 timers 1 3 + neighbor 2001:db8::12:4 timers connect 1 + ! + address-family ipv4 unicast + neighbor 192.168.12.4 activate + neighbor 192.168.12.4 addpath-tx-all-paths + network 10.10.10.10/32 + exit-address-family + address-family ipv6 unicast + neighbor 2001:db8::12:4 activate + exit-address-family +! +agentx +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r3/zebra.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r3/zebra.conf new file mode 100644 index 000000000000..398af65ffe57 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r3/zebra.conf @@ -0,0 +1,5 @@ +! +interface r3-eth0 + ip address 192.168.12.3/24 + ipv6 address 2001:db8::12:3/64 +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/rr/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/rr/bgpd.conf new file mode 100644 index 000000000000..5ebbde6703fe --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/rr/bgpd.conf @@ -0,0 +1,67 @@ +! +! debug bgp updates +! +router bgp 65004 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + neighbor 192.168.12.1 remote-as external + neighbor 192.168.12.1 timers 1 3 + neighbor 192.168.12.1 timers connect 1 + neighbor 192.168.12.2 remote-as external + neighbor 192.168.12.2 timers 1 3 + neighbor 192.168.12.2 timers connect 1 + neighbor 192.168.12.3 remote-as external + neighbor 192.168.12.3 timers 1 3 + neighbor 192.168.12.3 timers connect 1 + neighbor 2001:db8::12:1 remote-as external + neighbor 2001:db8::12:1 timers 1 3 + neighbor 2001:db8::12:1 timers connect 1 + neighbor 2001:db8::12:2 remote-as external + neighbor 2001:db8::12:2 timers 1 3 + neighbor 2001:db8::12:2 timers connect 1 + neighbor 2001:db8::12:3 remote-as external + neighbor 2001:db8::12:3 timers 1 3 + neighbor 2001:db8::12:3 timers connect 1 + ! + address-family ipv4 unicast + network 10.0.0.0/31 route-map p1 + network 10.0.0.2/32 route-map p2 + neighbor 192.168.12.1 activate + neighbor 192.168.12.2 activate + neighbor 192.168.12.2 addpath-tx-all-paths + neighbor 192.168.12.2 route-map r2-import in + neighbor 192.168.12.2 route-map r2-export out +! neighbor 192.168.12.2 soft-reconfiguration inbound + neighbor 192.168.12.3 activate + exit-address-family + address-family ipv6 unicast + network 2001:db8::1/128 route-map p1 + network 2001:db8:1::/56 route-map p2 + neighbor 2001:db8::12:1 activate + neighbor 2001:db8::12:2 activate + neighbor 2001:db8::12:2 addpath-tx-all-paths + neighbor 2001:db8::12:3 activate + exit-address-family + + +ip prefix-list r2-toto permit any + +route-map r2-import permit 10 + match ip address prefix-list r2-toto + +route-map r2-export permit 10 + match ip address prefix-list r2-toto +! +route-map p1 permit 10 + set metric 1 +exit +route-map p2 permit 10 + set metric 2 + set origin incomplete +exit + + + +agentx +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/rr/zebra.conf b/tests/topotests/bgp_snmp_bgp4v2mib/rr/zebra.conf new file mode 100644 index 000000000000..092673b8a977 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/rr/zebra.conf @@ -0,0 +1,5 @@ +! +interface rr-eth0 + ip address 192.168.12.4/24 + ipv6 address 2001:db8::12:4/64 +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py b/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py index 14dadd45045b..8cd49e35483f 100755 --- a/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py +++ b/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py @@ -32,10 +32,14 @@ def build_topo(tgen): tgen.add_router("r1") tgen.add_router("r2") + tgen.add_router("r3") + tgen.add_router("rr") switch = tgen.add_switch("s1") switch.add_link(tgen.gears["r1"]) switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["rr"]) def setup_module(mod): @@ -57,14 +61,15 @@ def setup_module(mod): "-M snmp", ) - tgen.gears["r2"].load_config( + r2 = tgen.gears["r2"] + r2.load_config( TopoRouter.RD_SNMP, - os.path.join(CWD, "{}/snmpd.conf".format(rname)), + os.path.join(CWD, "{}/snmpd.conf".format(r2.name)), "-Le -Ivacm_conf,usmConf,iquery -V -DAgentX", ) - tgen.gears["r2"].load_config( + r2.load_config( TopoRouter.RD_TRAP, - os.path.join(CWD, "{}/snmptrapd.conf".format(rname)), + os.path.join(CWD, "{}/snmptrapd.conf".format(r2.name)), " -On -OQ ", ) @@ -81,27 +86,29 @@ def test_bgp_snmp_bgp4v2(): r1 = tgen.gears["r1"] r2 = tgen.gears["r2"] + rr = tgen.gears["rr"] def _bgp_converge_summary(): output = json.loads(r2.vtysh_cmd("show bgp summary json")) expected = { "ipv4Unicast": { "peers": { - "192.168.12.1": { + "192.168.12.4": { "state": "Established", - "pfxRcd": 2, + "pfxRcd": 6, } } }, "ipv6Unicast": { "peers": { - "2001:db8::12:1": { + "2001:db8::12:4": { "state": "Established", - "pfxRcd": 2, + "pfxRcd": 4, } } }, } + # tgen.mininet_cli() return topotest.json_cmp(output, expected) test_func = functools.partial(_bgp_converge_summary) @@ -144,6 +151,7 @@ def _bgp_converge_prefixes(): } }, } + # tgen.mininet_cli() return topotest.json_cmp(output, expected) test_func = functools.partial(_bgp_converge_prefixes) @@ -154,11 +162,12 @@ def _bgp_converge_prefixes(): def _snmpwalk_remote_addr(): expected = { - "1.3.6.1.3.5.1.1.2.1.5.1.1.192.168.12.1": "C0 A8 0C 01", - "1.3.6.1.3.5.1.1.2.1.5.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "20 01 0D B8 00 00 00 00 00 00 00 00 00 12 00 01", + "1.3.6.1.3.5.1.1.2.1.5.1.1.192.168.12.4": "C0 A8 0C 04", + "1.3.6.1.3.5.1.1.2.1.5.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4": "20 01 0D B8 00 00 00 00 00 00 00 00 00 12 00 04", } # bgp4V2PeerRemoteAddr + # tgen.mininet_cli() output, _ = snmp.walk(".1.3.6.1.3.5.1.1.2.1.5") return output == expected @@ -168,8 +177,8 @@ def _snmpwalk_remote_addr(): def _snmpwalk_peer_state(): expected = { - "1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.12.1": "6", - "1.3.6.1.3.5.1.1.2.1.13.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "6", + "1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.12.4": "6", + "1.3.6.1.3.5.1.1.2.1.13.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4": "6", } # bgp4V2PeerState @@ -182,8 +191,8 @@ def _snmpwalk_peer_state(): def _snmpwalk_peer_last_error_code_received(): expected = { - "1.3.6.1.3.5.1.1.3.1.1.1.1.192.168.12.1": "0", - "1.3.6.1.3.5.1.1.3.1.1.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "0", + "1.3.6.1.3.5.1.1.3.1.1.1.1.192.168.12.4": "0", + "1.3.6.1.3.5.1.1.3.1.1.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4": "0", } # bgp4V2PeerLastErrorCodeReceived @@ -198,14 +207,19 @@ def _snmpwalk_peer_last_error_code_received(): def _snmpwalk_origin(): expected = { - "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1": "1", - "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1": "3", - "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1": "1", - "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.10.10.10.32.1.192.168.12.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.10.10.10.32.1.192.168.12.4.2": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.0.31.1.192.168.12.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.0.31.1.192.168.12.4.2": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.2.32.1.192.168.12.4.1": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.2.32.1.192.168.12.4.2": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.2": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.1": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.2": "3", } # bgp4V2NlriOrigin - # tgen.mininet_cli() output, _ = snmp.walk(".1.3.6.1.3.5.1.1.9.1.9") return output == expected @@ -215,10 +229,16 @@ def _snmpwalk_origin(): def _snmpwalk_med(): expected = { - "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1": "1", - "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1": "2", - "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1": "1", - "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1": "2", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.10.10.10.32.1.192.168.12.4.1": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.10.10.10.32.1.192.168.12.4.2": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.0.31.1.192.168.12.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.0.31.1.192.168.12.4.2": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.2.32.1.192.168.12.4.1": "2", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.2.32.1.192.168.12.4.2": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.2": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.1": "2", + "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.2": "0", } # bgp4V2NlriMed @@ -230,96 +250,65 @@ def _snmpwalk_med(): assertmsg = "Can't fetch SNMP for bgp4V2NlriMed" assert result, assertmsg + # + # traps + # + + # + # bgp4 traps + # def _snmptrap_ipv4(): - expected = [ - ("1.3.6.1.2.1.15.3.1.7.192.168.12.1", "192.168.12.1"), - ("1.3.6.1.2.1.15.3.1.14.192.168.12.1", '"06 04 "'), - ("1.3.6.1.2.1.15.3.1.2.192.168.12.1", "7"), - ("1.3.6.1.2.1.15.3.1.7.192.168.12.1", "192.168.12.1"), - ("1.3.6.1.2.1.15.3.1.14.192.168.12.1", '"06 04 "'), - ("1.3.6.1.2.1.15.3.1.2.192.168.12.1", "6"), - ] - - # load trapd resulting file - # tgen.mininet_cli() + def __get_notif_bgp4_in_trap_file(router): + snmptrapfile = "{}/{}/snmptrapd.log".format(router.logdir, router.name) + outputfile = open(snmptrapfile).read() + output = snmp.get_notif_bgp4(outputfile) - snmptrapfile = "{}/{}/snmptrapd.log".format(r2.logdir, r2.name) - outputfile = open(snmptrapfile).read() - output = snmp.trap(outputfile) - return output == expected + return output + + output = __get_notif_bgp4_in_trap_file(r2) + logger.info("output bgp4") + logger.info(output) + return snmp.is_notif_bgp4_valid(output, "192.168.12.4") # skip tests is SNMP not installed if not os.path.isfile("/usr/sbin/snmptrapd"): error_msg = "SNMP not installed - skipping" pytest.skip(error_msg) - snmptrapfile = "{}/{}/snmptrapd.log".format(r2.logdir, r2.name) - trap_file = open(snmptrapfile, "w") - trap_file.truncate(0) - trap_file.close() - r1.vtysh_cmd("clear bgp *") + rr.vtysh_cmd("clear bgp *") _, result = topotest.run_and_expect(_snmptrap_ipv4, True, count=2, wait=10) assertmsg = "Can't fetch SNMP trap for ipv4" assert result, assertmsg + # + # bgp4v2 traps + # def _snmptrap_ipv6(): - expected = [ - ("1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.12.1", "7"), - ("1.3.6.1.3.5.1.1.2.1.6.1.1.192.168.12.1", "179"), - ("1.3.6.1.3.5.1.1.3.1.1.1.1.192.168.12.1", "6"), - ("1.3.6.1.3.5.1.1.3.1.2.1.1.192.168.12.1", "4"), - ("1.3.6.1.3.5.1.1.3.1.4.1.1.192.168.12.1", '"00 "'), - ("1.3.6.1.3.5.1.1.2.1.13.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "7"), - ("1.3.6.1.3.5.1.1.2.1.6.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "179"), - ("1.3.6.1.3.5.1.1.3.1.1.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "6"), - ("1.3.6.1.3.5.1.1.3.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "4"), - ( - "1.3.6.1.3.5.1.1.3.1.4.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", - '"00 "', - ), - ("1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.12.1", "6"), - ("1.3.6.1.3.5.1.1.2.1.6.1.1.192.168.12.1", "179"), - ("1.3.6.1.3.5.1.1.2.1.13.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "6"), - ("1.3.6.1.3.5.1.1.2.1.6.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "179"), - ] - - expected2 = [ - ("1.3.6.1.3.5.1.1.2.1.13.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "7"), - ("1.3.6.1.3.5.1.1.2.1.6.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "179"), - ("1.3.6.1.3.5.1.1.3.1.1.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "6"), - ("1.3.6.1.3.5.1.1.3.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "4"), - ( - "1.3.6.1.3.5.1.1.3.1.4.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", - '"00 "', - ), - ("1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.12.1", "7"), - ("1.3.6.1.3.5.1.1.2.1.6.1.1.192.168.12.1", "179"), - ("1.3.6.1.3.5.1.1.3.1.1.1.1.192.168.12.1", "6"), - ("1.3.6.1.3.5.1.1.3.1.2.1.1.192.168.12.1", "4"), - ("1.3.6.1.3.5.1.1.3.1.4.1.1.192.168.12.1", '"00 "'), - ("1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.12.1", "6"), - ("1.3.6.1.3.5.1.1.2.1.6.1.1.192.168.12.1", "179"), - ("1.3.6.1.3.5.1.1.2.1.13.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "6"), - ("1.3.6.1.3.5.1.1.2.1.6.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1", "179"), - ] - - # load trapd resulting file - # tgen.mininet_cli() + def __get_notif_bgp4v2_in_trap_file(router): + snmptrapfile = "{}/{}/snmptrapd.log".format(router.logdir, router.name) + outputfile = open(snmptrapfile).read() + output = snmp.get_notif_bgp4v2(outputfile) - snmptrapfile = "{}/{}/snmptrapd.log".format(r2.logdir, r2.name) - outputfile = open(snmptrapfile).read() - output = snmp.trap(outputfile) + return output + + # tgen.mininet_cli() + output = __get_notif_bgp4v2_in_trap_file(r2) + logger.info("output bgp4v2") logger.info(output) - output_cut = output[:14] - return output_cut == expected or output_cut == expected2 + p_ipv4_addr = "1.192.168.12.4" + p_ipv6_addr = "2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4" + return ( + snmp.is_notif_bgp4v2_valid(output, p_ipv4_addr, "Estab") + and snmp.is_notif_bgp4v2_valid(output, p_ipv6_addr, "Estab") + and snmp.is_notif_bgp4v2_valid(output, p_ipv4_addr, "Backward") + and snmp.is_notif_bgp4v2_valid(output, p_ipv6_addr, "Backward") + ) - snmptrapfile = "{}/{}/snmptrapd.log".format(r2.logdir, r2.name) - trap_file = open(snmptrapfile, "w") - trap_file.truncate(0) - trap_file.close() + sleep(10) r2.vtysh_cmd("conf\nbgp snmp traps bgp4-mibv2") r2.vtysh_cmd("conf\nno bgp snmp traps rfc4273") - r1.vtysh_cmd("clear bgp *") + rr.vtysh_cmd("clear bgp *") + sleep(30) _, result = topotest.run_and_expect(_snmptrap_ipv6, True, count=2, wait=10) assertmsg = "Can't fetch SNMP trap for ipv6" assert result, assertmsg diff --git a/tests/topotests/lib/snmptest.py b/tests/topotests/lib/snmptest.py index 598ad05f5897..5c4e97a5d245 100644 --- a/tests/topotests/lib/snmptest.py +++ b/tests/topotests/lib/snmptest.py @@ -73,39 +73,6 @@ def _get_snmp_oid(snmp_output): # third token onwards is the value of the object return tokens[0].split(".", 1)[1] - def _parse_notification_trap(self, snmp_out): - # we use the "=" as separator thus we will have - # element of list formated "value oid" - # value for index i is corresponding to index i-1 - results = snmp_out.strip().split("=") - - # remove the notification part date, notification OID - del results[0:2] - - index = 0 - oid_list = [] - next_oid = "" - oid = "" - while index < len(results): - result = results[index].strip().split() - if index < len(results) - 1: - raw_oid = result[-1] - # remove initial "." of oid - next_oid = raw_oid.split(".", 1)[1] - # remove oid from result to have only value - del result[-1] - if index > 0: - value = " ".join(result) - # ignore remote port oid 1.3.6.1.3.5.1.1.2.1.9 since - # it's value is variable - local_port = re.search("1.3.6.1.3.5.1.1.2.1.9", oid) - if not local_port: - oid_list.append((oid, value)) - - oid = next_oid - index += 1 - return oid_list - def _parse_multiline(self, snmp_output): results = snmp_output.strip().split("\n") @@ -117,15 +84,6 @@ def _parse_multiline(self, snmp_output): return out_dict, out_list - def _parse_multiline_trap(self, results): - out_list = [] - results = [elem for index, elem in enumerate(results) if index % 2 != 0] - - for response in results: - oid_list = self._parse_notification_trap(response) - out_list += oid_list - return out_list - def get(self, oid): cmd = "snmpget {0} {1}".format(self._snmp_config(), oid) @@ -149,10 +107,148 @@ def walk(self, oid): result = self.router.cmd(cmd) return self._parse_multiline(result) - def trap(self, outputfile): - whitecleanfile = re.sub("\t", " ", outputfile) + def parse_notif_ipv4(self, notif): + # normalise values + notif = re.sub(":", "", notif) + notif = re.sub('"([0-9]{2}) ([0-9]{2}) "', r"\1\2", notif) + notif = re.sub('"([0-9]{2}) "', r"\1", notif) + elems = re.findall("([0-9,\.]+) = ([0-9,\.]+)", notif) + + # remove common part + elems = elems[1:] + return elems + + def is_notif_bgp4_valid(self, output_list, address): + oid_notif_type = ".1.3.6.1.6.3.1.1.4.1.0" + peer_notif_established = ".1.3.6.1.2.1.15.0.1" + peer_notif_backward = ".1.3.6.1.2.1.15.0.2" + oid_peer_last_error = ".1.3.6.1.2.1.15.3.1.14" + oid_peer_remote_addr = ".1.3.6.1.2.1.15.3.1.7" + oid_peer_state = ".1.3.6.1.2.1.15.3.1.2" + + nb_notif = len(output_list) + for nb in range(0, nb_notif - 1): + # identify type of notification + # established or BackwardTransition + + if output_list[nb][0][0] != "{}".format(oid_notif_type): + return False + + if output_list[nb][0][1] == "{}".format(peer_notif_established): + logger.info("Established notification") + elif output_list[nb][0][1] == "{}".format(peer_notif_backward): + logger.info("Backward transition notification") + else: + return False + + # same behavior for 2 notification type in bgp4 + if output_list[nb][1][0] != "{}.{}".format(oid_peer_remote_addr, address): + return False + + if output_list[nb][2][0] != "{}.{}".format(oid_peer_last_error, address): + return False + if output_list[nb][3][0] != "{}.{}".format(oid_peer_state, address): + return False + + return True + + def is_notif_bgp4v2_valid(self, output_list, address, type_requested): + oid_notif_type = ".1.3.6.1.6.3.1.1.4.1.0" + peer_notif_established = ".1.3.6.1.3.5.1.0.1" + peer_notif_backward = ".1.3.6.1.3.5.1.0.2" + oid_peer_state = ".1.3.6.1.3.5.1.1.2.1.13" + oid_peer_local_port = ".1.3.6.1.3.5.1.1.2.1.6" + oid_peer_remote_port = ".1.3.6.1.3.5.1.1.2.1.9" + oid_peer_err_code_recv = ".1.3.6.1.3.5.1.1.3.1.1" + oid_peer_err_sub_code_recv = ".1.3.6.1.3.5.1.1.3.1.2" + oid_peer_err_recv_text = ".1.3.6.1.3.5.1.1.3.1.4" + + nb_notif = len(output_list) + for nb in range(nb_notif): + if output_list[nb][0][0] != "{}".format(oid_notif_type): + return False + + if output_list[nb][0][1] == "{}".format(peer_notif_established): + logger.info("Established notification") + notif_type = "Estab" + + elif output_list[nb][0][1] == "{}".format(peer_notif_backward): + logger.info("Backward transition notification") + notif_type = "Backward" + else: + return False + + if notif_type != type_requested: + continue + + if output_list[nb][1][0] != "{}.1.{}".format(oid_peer_state, address): + continue + + if output_list[nb][2][0] != "{}.1.{}".format(oid_peer_local_port, address): + return False + + if output_list[nb][3][0] != "{}.1.{}".format(oid_peer_remote_port, address): + return False + + if notif_type == "Estab": + return True + + if output_list[nb][4][0] != "{}.1.{}".format( + oid_peer_err_code_recv, address + ): + return False + + if output_list[nb][5][0] != "{}.1.{}".format( + oid_peer_err_sub_code_recv, address + ): + return False + + if output_list[nb][6][0] != "{}.1.{}".format( + oid_peer_err_recv_text, address + ): + return False + + return True + + return False + + def get_notif_bgp4(self, output_file): + notif_list = [] + whitecleanfile = re.sub("\t", " ", output_file) results = whitecleanfile.strip().split("\n") - return self._parse_multiline_trap(results) + + # don't consider SNMP additional messages + notifs_first = [elem for elem in results if not ("SNMP" in elem)] + # don't consider additional application messages + notifs = [elem for index, elem in enumerate(notifs_first) if index % 2 != 0] + + oid_v4 = "1\.3\.6\.1\.2\.1\.15" + for one_notif in notifs: + is_ipv4_notif = re.search(oid_v4, one_notif) + if is_ipv4_notif != None: + formated_notif = self.parse_notif_ipv4(one_notif) + notif_list.append(formated_notif) + + return notif_list + + def get_notif_bgp4v2(self, output_file): + notif_list = [] + whitecleanfile = re.sub("\t", " ", output_file) + results = whitecleanfile.strip().split("\n") + + # don't consider SNMP additional messages + notifs_first = [elem for elem in results if not ("SNMP" in elem)] + # don't consider additional application messages + notifs = [elem for index, elem in enumerate(results) if index % 2 != 0] + + oid_v6 = "1\.3\.6\.1\.3\.5\.1" + for one_notif in notifs: + is_ipv6_notif = re.search(oid_v6, one_notif) + if is_ipv6_notif != None: + formated_notif = self.parse_notif_ipv4(one_notif) + notif_list.append(formated_notif) + + return notif_list def test_oid(self, oid, value): print("oid: {}".format(self.get_next(oid)))