diff --git a/doc/user/filter.rst b/doc/user/filter.rst index 1fb9beccdc..cbbcd47dc3 100644 --- a/doc/user/filter.rst +++ b/doc/user/filter.rst @@ -137,6 +137,15 @@ Showing ip prefix-list .. clicmd:: show ip prefix-list detail .. clicmd:: show ip prefix-list detail NAME +.. clicmd:: debug prefix-list NAME match [address-mode] + + Execute the prefix list matching code for the specified list and prefix. + Shows which entry matched, if any. (``address-mode`` is used for + PIM RP lookups and skips prefix length checks.) + + The return value from this command is success only if the prefix-list + result is to permit the prefix, so the command can be used in scripting. + Clear counter of ip prefix-list ------------------------------- diff --git a/lib/plist.c b/lib/plist.c index 0ee02f8a0b..2b42c43764 100644 --- a/lib/plist.c +++ b/lib/plist.c @@ -750,7 +750,7 @@ static const char *prefix_list_type_str(struct prefix_list_entry *pentry) } static int prefix_list_entry_match(struct prefix_list_entry *pentry, - const struct prefix *p) + const struct prefix *p, bool address_mode) { int ret; @@ -761,6 +761,9 @@ static int prefix_list_entry_match(struct prefix_list_entry *pentry, if (!ret) return 0; + if (address_mode) + return 1; + /* In case of le nor ge is specified, exact match is performed. */ if (!pentry->le && !pentry->ge) { if (pentry->prefix.prefixlen != p->prefixlen) @@ -777,14 +780,15 @@ static int prefix_list_entry_match(struct prefix_list_entry *pentry, return 1; } -enum prefix_list_type prefix_list_apply_which_prefix( +enum prefix_list_type prefix_list_apply_ext( struct prefix_list *plist, - const struct prefix **which, - const void *object) + const struct prefix_list_entry **which, + union prefixconstptr object, + bool address_mode) { struct prefix_list_entry *pentry, *pbest = NULL; - const struct prefix *p = (const struct prefix *)object; + const struct prefix *p = object.p; const uint8_t *byte = p->u.val; size_t depth; size_t validbits = p->prefixlen; @@ -809,7 +813,7 @@ enum prefix_list_type prefix_list_apply_which_prefix( pentry = pentry->next_best) { if (pbest && pbest->seq < pentry->seq) continue; - if (prefix_list_entry_match(pentry, p)) + if (prefix_list_entry_match(pentry, p, address_mode)) pbest = pentry; } @@ -830,7 +834,7 @@ enum prefix_list_type prefix_list_apply_which_prefix( pentry = pentry->next_best) { if (pbest && pbest->seq < pentry->seq) continue; - if (prefix_list_entry_match(pentry, p)) + if (prefix_list_entry_match(pentry, p, address_mode)) pbest = pentry; } break; @@ -838,7 +842,7 @@ enum prefix_list_type prefix_list_apply_which_prefix( if (which) { if (pbest) - *which = &pbest->prefix; + *which = pbest; else *which = NULL; } @@ -1296,6 +1300,51 @@ DEFPY (clear_ipv6_prefix_list, return vty_clear_prefix_list(vty, AFI_IP6, prefix_list, prefix_str); } +DEFPY (debug_prefix_list_match, + debug_prefix_list_match_cmd, + "debug prefix-list WORD$prefix-list match " + " [address-mode$addr_mode]", + DEBUG_STR + "Prefix-list test access\n" + "Name of a prefix list\n" + "Test prefix for prefix list result\n" + "Prefix to test in ip prefix-list\n" + "Prefix to test in ipv6 prefix-list\n" + "Use address matching mode (PIM RP)\n") +{ + struct prefix_list *plist; + const struct prefix_list_entry *entry = NULL; + enum prefix_list_type ret; + + plist = prefix_list_lookup(family2afi(match->family), prefix_list); + if (!plist) { + vty_out(vty, "%% no prefix list named %s for AFI %s\n", + prefix_list, afi2str(family2afi(match->family))); + return CMD_WARNING; + } + + ret = prefix_list_apply_ext(plist, &entry, match, !!addr_mode); + + vty_out(vty, "%s prefix list %s yields %s for %pFX, ", + afi2str(family2afi(match->family)), prefix_list, + ret == PREFIX_DENY ? "DENY" : "PERMIT", match); + + if (!entry) + vty_out(vty, "no match found\n"); + else { + vty_out(vty, "matching entry #%"PRId64": %pFX", entry->seq, + &entry->prefix); + if (entry->ge) + vty_out(vty, " ge %d", entry->ge); + if (entry->le) + vty_out(vty, " le %d", entry->le); + vty_out(vty, "\n"); + } + + /* allow using this in scripts for quick prefix-list member tests */ + return (ret == PREFIX_PERMIT) ? CMD_SUCCESS : CMD_WARNING; +} + struct stream *prefix_bgp_orf_entry(struct stream *s, struct prefix_list *plist, uint8_t init_flag, uint8_t permit_flag, uint8_t deny_flag) @@ -1537,6 +1586,7 @@ static void prefix_list_init_ipv6(void) install_element(VIEW_NODE, &show_ipv6_prefix_list_prefix_cmd); install_element(VIEW_NODE, &show_ipv6_prefix_list_summary_cmd); install_element(VIEW_NODE, &show_ipv6_prefix_list_detail_cmd); + install_element(VIEW_NODE, &debug_prefix_list_match_cmd); install_element(ENABLE_NODE, &clear_ipv6_prefix_list_cmd); } diff --git a/lib/plist.h b/lib/plist.h index 57eb763a68..c9507df57c 100644 --- a/lib/plist.h +++ b/lib/plist.h @@ -37,6 +37,7 @@ enum prefix_list_type { }; struct prefix_list; +struct prefix_list_entry; struct orf_prefix { uint32_t seq; @@ -63,12 +64,18 @@ extern struct prefix_list *prefix_list_lookup(afi_t, const char *); * * If no pointer is sent in, do not return anything. * If it is a empty plist return a NULL pointer. + * + * address_mode = the "prefix" being passed in is really an address, match + * regardless of prefix length (i.e. ge/le are ignored.) prefix->prefixlen + * must be /32. */ extern enum prefix_list_type -prefix_list_apply_which_prefix(struct prefix_list *plist, - const struct prefix **which, - const void *object); -#define prefix_list_apply(A, B) prefix_list_apply_which_prefix((A), NULL, (B)) +prefix_list_apply_ext(struct prefix_list *plist, + const struct prefix_list_entry **matches, + union prefixconstptr prefix, + bool address_mode); +#define prefix_list_apply(A, B) \ + prefix_list_apply_ext((A), NULL, (B), false) extern struct prefix_list *prefix_bgp_orf_lookup(afi_t, const char *); extern struct stream *prefix_bgp_orf_entry(struct stream *, diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c index 48b019c8c8..0b28a3e84c 100644 --- a/pimd/pim_iface.c +++ b/pimd/pim_iface.c @@ -1512,10 +1512,15 @@ struct prefix *pim_if_connected_to_source(struct interface *ifp, struct in_addr p.prefixlen = IPV4_MAX_BITLEN; for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { - if ((c->address->family == AF_INET) - && prefix_match(CONNECTED_PREFIX(c), &p)) { - return CONNECTED_PREFIX(c); - } + if (c->address->family != AF_INET) + continue; + if (prefix_match(c->address, &p)) + return c->address; + if (CONNECTED_PEER(c) && prefix_match(c->destination, &p)) + /* this is not a typo, on PtP we need to return the + * *local* address that lines up with src. + */ + return c->address; } return NULL; diff --git a/pimd/pim_rp.c b/pimd/pim_rp.c index b6521132f7..56e1927528 100644 --- a/pimd/pim_rp.c +++ b/pimd/pim_rp.c @@ -203,6 +203,26 @@ static struct rp_info *pim_rp_find_exact(struct pim_instance *pim, return NULL; } +/* + * XXX: long-term issue: we don't actually have a good "ip address-list" + * implementation. ("access-list XYZ" is the closest but honestly it's + * kinda garbage.) + * + * So it's using a prefix-list to match an address here, which causes very + * unexpected results for the user since prefix-lists by default only match + * when the prefix length is an exact match too. i.e. you'd have to add the + * "le 32" and do "ip prefix-list foo permit 10.0.0.0/24 le 32" + * + * To avoid this pitfall, this code uses "address_mode = true" for the prefix + * list match (this is the only user for that.) + * + * In the long run, we need to add a "ip address-list", but that's a wholly + * separate bag of worms, and existing configs using ip prefix-list would + * drop into the UX pitfall. + */ + +#include "lib/plist_int.h" + /* * Given a group, return the rp_info for that group */ @@ -213,7 +233,8 @@ struct rp_info *pim_rp_find_match_group(struct pim_instance *pim, struct rp_info *best = NULL; struct rp_info *rp_info; struct prefix_list *plist; - const struct prefix *p, *bp; + const struct prefix *bp; + const struct prefix_list_entry *entry; struct route_node *rn; bp = NULL; @@ -221,19 +242,19 @@ struct rp_info *pim_rp_find_match_group(struct pim_instance *pim, if (rp_info->plist) { plist = prefix_list_lookup(AFI_IP, rp_info->plist); - if (prefix_list_apply_which_prefix(plist, &p, group) - == PREFIX_DENY) + if (prefix_list_apply_ext(plist, &entry, group, true) + == PREFIX_DENY || !entry) continue; if (!best) { best = rp_info; - bp = p; + bp = &entry->prefix; continue; } - if (bp && bp->prefixlen < p->prefixlen) { + if (bp && bp->prefixlen < entry->prefix.prefixlen) { best = rp_info; - bp = p; + bp = &entry->prefix; } } } diff --git a/pimd/pim_sock.c b/pimd/pim_sock.c index 504519c8a4..05b0f92a4b 100644 --- a/pimd/pim_sock.c +++ b/pimd/pim_sock.c @@ -112,17 +112,15 @@ int pim_socket_mcast(int protocol, struct in_addr ifaddr, struct interface *ifp, } #ifdef SO_BINDTODEVICE - if (protocol == IPPROTO_PIM) { - int ret; + int ret; - ret = pim_socket_bind(fd, ifp); - if (ret) { - close(fd); - zlog_warn( - "Could not set fd: %d for interface: %s to device", - fd, ifp->name); - return PIM_SOCK_ERR_BIND; - } + ret = pim_socket_bind(fd, ifp); + if (ret) { + close(fd); + zlog_warn( + "Could not set fd: %d for interface: %s to device", + fd, ifp->name); + return PIM_SOCK_ERR_BIND; } #else /* XXX: use IP_PKTINFO / IP_RECVIF to emulate behaviour? Or change to diff --git a/tests/.gitignore b/tests/.gitignore index 3fad1b0813..498d7dd0b7 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -36,6 +36,7 @@ /lib/test_nexthop /lib/test_nexthop_iter /lib/test_ntop +/lib/test_plist /lib/test_prefix2str /lib/test_printfrr /lib/test_privs diff --git a/tests/lib/cli/common_cli.c b/tests/lib/cli/common_cli.c index 49bc0f4fb2..8be81cc4cb 100644 --- a/tests/lib/cli/common_cli.c +++ b/tests/lib/cli/common_cli.c @@ -59,10 +59,13 @@ static void vty_do_exit(int isexit) exit(0); } +const struct frr_yang_module_info *const *test_yang_modules = NULL; + /* main routine. */ int main(int argc, char **argv) { struct thread thread; + size_t yangcount; /* Set umask before anything for security */ umask(0027); @@ -79,7 +82,11 @@ int main(int argc, char **argv) vty_init(master, false); lib_cmd_init(); - nb_init(master, NULL, 0, false); + + for (yangcount = 0; test_yang_modules && test_yang_modules[yangcount]; + yangcount++) + ; + nb_init(master, test_yang_modules, yangcount, false); test_init(argc, argv); diff --git a/tests/lib/cli/common_cli.h b/tests/lib/cli/common_cli.h index 15abe3b855..3042ff5b12 100644 --- a/tests/lib/cli/common_cli.h +++ b/tests/lib/cli/common_cli.h @@ -25,6 +25,9 @@ #include "zebra.h" #include "vty.h" #include "command.h" +#include "northbound.h" + +extern const struct frr_yang_module_info *const *test_yang_modules; /* function to be implemented by test */ extern void test_init(int argc, char **argv); diff --git a/tests/lib/test_plist.c b/tests/lib/test_plist.c new file mode 100644 index 0000000000..ee7a9ebf30 --- /dev/null +++ b/tests/lib/test_plist.c @@ -0,0 +1,48 @@ +/* + * Simple prefix list querying tool + * + * Copyright (C) 2021 by David Lamparter, + * for Open Source Routing / NetDEF, Inc. + * + * Quagga is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * Quagga is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "lib/plist.h" +#include "lib/filter.h" +#include "tests/lib/cli/common_cli.h" + +static const struct frr_yang_module_info *const my_yang_modules[] = { + &frr_filter_info, + NULL, +}; + +__attribute__((_CONSTRUCTOR(2000))) +static void test_yang_modules_set(void) +{ + test_yang_modules = my_yang_modules; +} + +void test_init(int argc, char **argv) +{ + prefix_list_init(); + filter_cli_init(); + + /* nothing else to do here, giving stand-alone access to the prefix + * list code's "debug prefix-list ..." command is the only purpose of + * this "test". + */ +} diff --git a/tests/subdir.am b/tests/subdir.am index c2153140f5..86c1aa4284 100644 --- a/tests/subdir.am +++ b/tests/subdir.am @@ -87,6 +87,7 @@ check_PROGRAMS = \ tests/lib/test_nexthop_iter \ tests/lib/test_nexthop \ tests/lib/test_ntop \ + tests/lib/test_plist \ tests/lib/test_prefix2str \ tests/lib/test_printfrr \ tests/lib/test_privs \ @@ -344,6 +345,10 @@ tests_lib_test_ntop_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_ntop_CPPFLAGS = $(CPPFLAGS_BASE) # no assert override tests_lib_test_ntop_LDADD = # none tests_lib_test_ntop_SOURCES = tests/lib/test_ntop.c tests/helpers/c/prng.c +tests_lib_test_plist_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_plist_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_plist_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_plist_SOURCES = tests/lib/test_plist.c tests/lib/cli/common_cli.c tests_lib_test_prefix2str_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_prefix2str_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_test_prefix2str_LDADD = $(ALL_TESTS_LDADD) diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 23dcced2bf..b516a67d5c 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -1235,25 +1235,28 @@ class Router(Node): dmns = rundaemons.split("\n") # Exclude empty string at end of list for d in dmns[:-1]: - daemonpid = self.cmd("cat %s" % d.rstrip()).rstrip() - if daemonpid.isdigit() and pid_exists(int(daemonpid)): - daemonname = os.path.basename(d.rstrip().rsplit(".", 1)[0]) - logger.info("{}: stopping {}".format(self.name, daemonname)) - try: - os.kill(int(daemonpid), signal.SIGTERM) - except OSError as err: - if err.errno == errno.ESRCH: - logger.error( - "{}: {} left a dead pidfile (pid={})".format( - self.name, daemonname, daemonpid + # Only check if daemonfilepath starts with / + # Avoids hang on "-> Connection closed" in above self.cmd() + if d[0] == '/': + daemonpid = self.cmd("cat %s" % d.rstrip()).rstrip() + if daemonpid.isdigit() and pid_exists(int(daemonpid)): + daemonname = os.path.basename(d.rstrip().rsplit(".", 1)[0]) + logger.info("{}: stopping {}".format(self.name, daemonname)) + try: + os.kill(int(daemonpid), signal.SIGTERM) + except OSError as err: + if err.errno == errno.ESRCH: + logger.error( + "{}: {} left a dead pidfile (pid={})".format( + self.name, daemonname, daemonpid + ) ) - ) - else: - logger.info( - "{}: {} could not kill pid {}: {}".format( - self.name, daemonname, daemonpid, str(err) + else: + logger.info( + "{}: {} could not kill pid {}: {}".format( + self.name, daemonname, daemonpid, str(err) + ) ) - ) if not wait: return errors diff --git a/tests/topotests/pim_acl/h1/zebra.conf b/tests/topotests/pim_acl/h1/zebra.conf new file mode 100644 index 0000000000..3d6540d40c --- /dev/null +++ b/tests/topotests/pim_acl/h1/zebra.conf @@ -0,0 +1,10 @@ +! +hostname h1 +log file zebra.log +! +interface h1-eth0 + description connection to r1 via sw1 + ip address 192.168.100.10/24 +! +ip route 0.0.0.0/0 192.168.100.1 +! diff --git a/tests/topotests/pim_acl/h2/zebra.conf b/tests/topotests/pim_acl/h2/zebra.conf new file mode 100644 index 0000000000..95342f9e8a --- /dev/null +++ b/tests/topotests/pim_acl/h2/zebra.conf @@ -0,0 +1,8 @@ +hostname h2 +! +interface h2-eth0 + description connection to r1 via sw2 + ip address 192.168.101.2/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_acl/r1/acl_1_pim_join.json b/tests/topotests/pim_acl/r1/acl_1_pim_join.json new file mode 100644 index 0000000000..1b44b2b5cf --- /dev/null +++ b/tests/topotests/pim_acl/r1/acl_1_pim_join.json @@ -0,0 +1,21 @@ +{ + "r1-eth0":{ + "name":"r1-eth0", + "state":"up", + "address":"192.168.100.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.1":{ + "*":{ + "source":"*", + "group":"239.100.0.1", + "upTime":"--:--:--", + "expire":"--:--", + "prune":"--:--", + "channelJoinName":"NOINFO", + "protocolIgmp":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r1/acl_2_pim_join.json b/tests/topotests/pim_acl/r1/acl_2_pim_join.json new file mode 100644 index 0000000000..c020a489a9 --- /dev/null +++ b/tests/topotests/pim_acl/r1/acl_2_pim_join.json @@ -0,0 +1,21 @@ +{ + "r1-eth0":{ + "name":"r1-eth0", + "state":"up", + "address":"192.168.100.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.17":{ + "*":{ + "source":"*", + "group":"239.100.0.17", + "upTime":"--:--:--", + "expire":"--:--", + "prune":"--:--", + "channelJoinName":"NOINFO", + "protocolIgmp":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r1/acl_3_pim_join.json b/tests/topotests/pim_acl/r1/acl_3_pim_join.json new file mode 100644 index 0000000000..6122f73992 --- /dev/null +++ b/tests/topotests/pim_acl/r1/acl_3_pim_join.json @@ -0,0 +1,21 @@ +{ + "r1-eth0":{ + "name":"r1-eth0", + "state":"up", + "address":"192.168.100.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.32":{ + "*":{ + "source":"*", + "group":"239.100.0.32", + "upTime":"--:--:--", + "expire":"--:--", + "prune":"--:--", + "channelJoinName":"NOINFO", + "protocolIgmp":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r1/acl_4_pim_join.json b/tests/topotests/pim_acl/r1/acl_4_pim_join.json new file mode 100644 index 0000000000..5f72256ba7 --- /dev/null +++ b/tests/topotests/pim_acl/r1/acl_4_pim_join.json @@ -0,0 +1,21 @@ +{ + "r1-eth0":{ + "name":"r1-eth0", + "state":"up", + "address":"192.168.100.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.255":{ + "*":{ + "source":"*", + "group":"239.100.0.255", + "upTime":"--:--:--", + "expire":"--:--", + "prune":"--:--", + "channelJoinName":"NOINFO", + "protocolIgmp":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r1/acl_5_pim_join.json b/tests/topotests/pim_acl/r1/acl_5_pim_join.json new file mode 100644 index 0000000000..70021bdbec --- /dev/null +++ b/tests/topotests/pim_acl/r1/acl_5_pim_join.json @@ -0,0 +1,21 @@ +{ + "r1-eth0":{ + "name":"r1-eth0", + "state":"up", + "address":"192.168.100.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.97":{ + "*":{ + "source":"*", + "group":"239.100.0.97", + "upTime":"--:--:--", + "expire":"--:--", + "prune":"--:--", + "channelJoinName":"NOINFO", + "protocolIgmp":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r1/acl_6_pim_join.json b/tests/topotests/pim_acl/r1/acl_6_pim_join.json new file mode 100644 index 0000000000..2baac6cb22 --- /dev/null +++ b/tests/topotests/pim_acl/r1/acl_6_pim_join.json @@ -0,0 +1,21 @@ +{ + "r1-eth0":{ + "name":"r1-eth0", + "state":"up", + "address":"192.168.100.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.70":{ + "*":{ + "source":"*", + "group":"239.100.0.70", + "upTime":"--:--:--", + "expire":"--:--", + "prune":"--:--", + "channelJoinName":"NOINFO", + "protocolIgmp":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r1/ospf_neighbor.json b/tests/topotests/pim_acl/r1/ospf_neighbor.json new file mode 100644 index 0000000000..a8fc093e90 --- /dev/null +++ b/tests/topotests/pim_acl/r1/ospf_neighbor.json @@ -0,0 +1,59 @@ +{ + "neighbors":{ + "192.168.0.11":[ + { + "priority":10, + "state":"Full\/Backup", + "address":"192.168.101.11", + "ifaceName":"r1-eth1:192.168.101.1", + "retransmitCounter":0, + "requestCounter":0, + "dbSummaryCounter":0 + } + ], + "192.168.0.12":[ + { + "priority":0, + "state":"Full\/DROther", + "address":"192.168.101.12", + "ifaceName":"r1-eth1:192.168.101.1", + "retransmitCounter":0, + "requestCounter":0, + "dbSummaryCounter":0 + } + ], + "192.168.0.13":[ + { + "priority":0, + "state":"Full\/DROther", + "address":"192.168.101.13", + "ifaceName":"r1-eth1:192.168.101.1", + "retransmitCounter":0, + "requestCounter":0, + "dbSummaryCounter":0 + } + ], + "192.168.0.14":[ + { + "priority":0, + "state":"Full\/DROther", + "address":"192.168.101.14", + "ifaceName":"r1-eth1:192.168.101.1", + "retransmitCounter":0, + "requestCounter":0, + "dbSummaryCounter":0 + } + ], + "192.168.0.15":[ + { + "priority":0, + "state":"Full\/DROther", + "address":"192.168.101.15", + "ifaceName":"r1-eth1:192.168.101.1", + "retransmitCounter":0, + "requestCounter":0, + "dbSummaryCounter":0 + } + ] + } +} diff --git a/tests/topotests/pim_acl/r1/ospfd.conf b/tests/topotests/pim_acl/r1/ospfd.conf new file mode 100644 index 0000000000..e1f47fb3b1 --- /dev/null +++ b/tests/topotests/pim_acl/r1/ospfd.conf @@ -0,0 +1,16 @@ +hostname r1 +! +debug ospf event +! +interface r1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 20 +! +router ospf + ospf router-id 192.168.0.1 + passive-interface r1-eth0 + network 192.168.0.1/32 area 0 + network 192.168.100.0/24 area 0 + network 192.168.101.0/24 area 0 + diff --git a/tests/topotests/pim_acl/r1/pim_neighbor.json b/tests/topotests/pim_acl/r1/pim_neighbor.json new file mode 100644 index 0000000000..ae95e8db14 --- /dev/null +++ b/tests/topotests/pim_acl/r1/pim_neighbor.json @@ -0,0 +1,31 @@ +{ + "r1-eth0":{ + }, + "r1-eth1":{ + "192.168.101.12":{ + "interface":"r1-eth1", + "neighbor":"192.168.101.12", + "drPriority":1 + }, + "192.168.101.15":{ + "interface":"r1-eth1", + "neighbor":"192.168.101.15", + "drPriority":1 + }, + "192.168.101.14":{ + "interface":"r1-eth1", + "neighbor":"192.168.101.14", + "drPriority":1 + }, + "192.168.101.11":{ + "interface":"r1-eth1", + "neighbor":"192.168.101.11", + "drPriority":1 + }, + "192.168.101.13":{ + "interface":"r1-eth1", + "neighbor":"192.168.101.13", + "drPriority":1 + } + } +} diff --git a/tests/topotests/pim_acl/r1/pimd.conf b/tests/topotests/pim_acl/r1/pimd.conf new file mode 100644 index 0000000000..72d28c9b02 --- /dev/null +++ b/tests/topotests/pim_acl/r1/pimd.conf @@ -0,0 +1,30 @@ +hostname r1 +! +debug igmp events +debug igmp packets +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +ip pim rp 192.168.0.11 prefix-list rp-pl-1 +ip pim rp 192.168.0.12 prefix-list rp-pl-2 +ip pim rp 192.168.0.13 prefix-list rp-pl-3 +ip pim rp 192.168.0.14 prefix-list rp-pl-4 +ip pim rp 192.168.0.15 prefix-list rp-pl-5 +! +interface r1-eth0 + ip igmp + ip igmp version 2 + ip pim +! +interface r1-eth1 + ip pim +! +ip prefix-list rp-pl-1 seq 10 permit 239.100.0.0/28 +ip prefix-list rp-pl-2 seq 10 permit 239.100.0.17/32 +ip prefix-list rp-pl-3 seq 10 permit 239.100.0.32/27 +ip prefix-list rp-pl-4 seq 10 permit 239.100.0.128/25 +ip prefix-list rp-pl-4 seq 20 permit 239.100.0.96/28 +ip prefix-list rp-pl-5 seq 10 permit 239.100.0.64/28 diff --git a/tests/topotests/pim_acl/r1/zebra.conf b/tests/topotests/pim_acl/r1/zebra.conf new file mode 100644 index 0000000000..74feb8f6a7 --- /dev/null +++ b/tests/topotests/pim_acl/r1/zebra.conf @@ -0,0 +1,18 @@ +! +hostname r1 +log file zebra.log +! +ip forwarding +ipv6 forwarding +! +interface lo + ip address 192.168.0.1/32 +! +interface r1-eth0 + description connection to h1 via sw1 + ip address 192.168.100.1/24 +! +interface r1-eth1 + description connection to r11/12/13/14/15 via sw2 + ip address 192.168.101.1/24 +! diff --git a/tests/topotests/pim_acl/r11/acl_1_pim_join.json b/tests/topotests/pim_acl/r11/acl_1_pim_join.json new file mode 100644 index 0000000000..289bf51e76 --- /dev/null +++ b/tests/topotests/pim_acl/r11/acl_1_pim_join.json @@ -0,0 +1,19 @@ +{ + "r11-eth0":{ + "name":"r11-eth0", + "state":"up", + "address":"192.168.101.11", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.1":{ + "*":{ + "source":"*", + "group":"239.100.0.1", + "prune":"--:--", + "channelJoinName":"JOIN", + "protocolPim":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r11/ospfd.conf b/tests/topotests/pim_acl/r11/ospfd.conf new file mode 100644 index 0000000000..e107220a4e --- /dev/null +++ b/tests/topotests/pim_acl/r11/ospfd.conf @@ -0,0 +1,14 @@ +hostname r11 +! +debug ospf event +! +interface r11-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 10 +! +router ospf + ospf router-id 192.168.0.11 + network 192.168.0.11/32 area 0 + network 192.168.101.0/24 area 0 +! diff --git a/tests/topotests/pim_acl/r11/pimd.conf b/tests/topotests/pim_acl/r11/pimd.conf new file mode 100644 index 0000000000..05cd5ac911 --- /dev/null +++ b/tests/topotests/pim_acl/r11/pimd.conf @@ -0,0 +1,16 @@ +hostname r11 +! +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +ip pim rp 192.168.0.11 239.100.0.0/28 +! +interface lo + ip pim +! +interface r11-eth0 + ip pim +! diff --git a/tests/topotests/pim_acl/r11/zebra.conf b/tests/topotests/pim_acl/r11/zebra.conf new file mode 100644 index 0000000000..137706d245 --- /dev/null +++ b/tests/topotests/pim_acl/r11/zebra.conf @@ -0,0 +1,13 @@ +! +hostname r11 +log file zebra.log +! +interface lo + ip address 192.168.0.11/32 +! +interface r11-eth0 + description connection to r1 via sw1 + ip address 192.168.101.11/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_acl/r12/acl_2_pim_join.json b/tests/topotests/pim_acl/r12/acl_2_pim_join.json new file mode 100644 index 0000000000..76ab7ee701 --- /dev/null +++ b/tests/topotests/pim_acl/r12/acl_2_pim_join.json @@ -0,0 +1,19 @@ +{ + "r12-eth0":{ + "name":"r12-eth0", + "state":"up", + "address":"192.168.101.12", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.17":{ + "*":{ + "source":"*", + "group":"239.100.0.17", + "prune":"--:--", + "channelJoinName":"JOIN", + "protocolPim":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r12/ospfd.conf b/tests/topotests/pim_acl/r12/ospfd.conf new file mode 100644 index 0000000000..f9203c78e4 --- /dev/null +++ b/tests/topotests/pim_acl/r12/ospfd.conf @@ -0,0 +1,14 @@ +hostname r12 +! +debug ospf event +! +interface r12-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 0 +! +router ospf + ospf router-id 192.168.0.12 + network 192.168.0.12/32 area 0 + network 192.168.101.0/24 area 0 +! diff --git a/tests/topotests/pim_acl/r12/pimd.conf b/tests/topotests/pim_acl/r12/pimd.conf new file mode 100644 index 0000000000..cedde73c59 --- /dev/null +++ b/tests/topotests/pim_acl/r12/pimd.conf @@ -0,0 +1,16 @@ +hostname r12 +! +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +ip pim rp 192.168.0.12 239.100.0.17/32 +! +interface lo + ip pim +! +interface r12-eth0 + ip pim +! diff --git a/tests/topotests/pim_acl/r12/zebra.conf b/tests/topotests/pim_acl/r12/zebra.conf new file mode 100644 index 0000000000..bede104906 --- /dev/null +++ b/tests/topotests/pim_acl/r12/zebra.conf @@ -0,0 +1,13 @@ +! +hostname r12 +log file zebra.log +! +interface lo + ip address 192.168.0.12/32 +! +interface r12-eth0 + description connection to r1 via sw1 + ip address 192.168.101.12/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_acl/r13/acl_3_pim_join.json b/tests/topotests/pim_acl/r13/acl_3_pim_join.json new file mode 100644 index 0000000000..48ad72cbe1 --- /dev/null +++ b/tests/topotests/pim_acl/r13/acl_3_pim_join.json @@ -0,0 +1,19 @@ +{ + "r13-eth0":{ + "name":"r13-eth0", + "state":"up", + "address":"192.168.101.13", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.32":{ + "*":{ + "source":"*", + "group":"239.100.0.32", + "prune":"--:--", + "channelJoinName":"JOIN", + "protocolPim":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r13/ospfd.conf b/tests/topotests/pim_acl/r13/ospfd.conf new file mode 100644 index 0000000000..830c5a14b6 --- /dev/null +++ b/tests/topotests/pim_acl/r13/ospfd.conf @@ -0,0 +1,14 @@ +hostname r13 +! +debug ospf event +! +interface r13-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 0 +! +router ospf + ospf router-id 192.168.0.13 + network 192.168.0.13/32 area 0 + network 192.168.101.0/24 area 0 +! diff --git a/tests/topotests/pim_acl/r13/pimd.conf b/tests/topotests/pim_acl/r13/pimd.conf new file mode 100644 index 0000000000..2dab0cabec --- /dev/null +++ b/tests/topotests/pim_acl/r13/pimd.conf @@ -0,0 +1,16 @@ +hostname r13 +! +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +ip pim rp 192.168.0.13 239.100.0.32/27 +! +interface lo + ip pim +! +interface r13-eth0 + ip pim +! diff --git a/tests/topotests/pim_acl/r13/zebra.conf b/tests/topotests/pim_acl/r13/zebra.conf new file mode 100644 index 0000000000..f9ff27abac --- /dev/null +++ b/tests/topotests/pim_acl/r13/zebra.conf @@ -0,0 +1,13 @@ +! +hostname r13 +log file zebra.log +! +interface lo + ip address 192.168.0.13/32 +! +interface r13-eth0 + description connection to r1 via sw1 + ip address 192.168.101.13/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_acl/r14/acl_4_pim_join.json b/tests/topotests/pim_acl/r14/acl_4_pim_join.json new file mode 100644 index 0000000000..46d86dd40d --- /dev/null +++ b/tests/topotests/pim_acl/r14/acl_4_pim_join.json @@ -0,0 +1,19 @@ +{ + "r14-eth0":{ + "name":"r14-eth0", + "state":"up", + "address":"192.168.101.14", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.255":{ + "*":{ + "source":"*", + "group":"239.100.0.255", + "prune":"--:--", + "channelJoinName":"JOIN", + "protocolPim":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r14/acl_5_pim_join.json b/tests/topotests/pim_acl/r14/acl_5_pim_join.json new file mode 100644 index 0000000000..2b291a8a0c --- /dev/null +++ b/tests/topotests/pim_acl/r14/acl_5_pim_join.json @@ -0,0 +1,19 @@ +{ + "r14-eth0":{ + "name":"r14-eth0", + "state":"up", + "address":"192.168.101.14", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.97":{ + "*":{ + "source":"*", + "group":"239.100.0.97", + "prune":"--:--", + "channelJoinName":"JOIN", + "protocolPim":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r14/ospfd.conf b/tests/topotests/pim_acl/r14/ospfd.conf new file mode 100644 index 0000000000..422e4c08b0 --- /dev/null +++ b/tests/topotests/pim_acl/r14/ospfd.conf @@ -0,0 +1,14 @@ +hostname r14 +! +debug ospf event +! +interface r14-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 0 +! +router ospf + ospf router-id 192.168.0.14 + network 192.168.0.14/32 area 0 + network 192.168.101.0/24 area 0 +! diff --git a/tests/topotests/pim_acl/r14/pimd.conf b/tests/topotests/pim_acl/r14/pimd.conf new file mode 100644 index 0000000000..c6b949af16 --- /dev/null +++ b/tests/topotests/pim_acl/r14/pimd.conf @@ -0,0 +1,17 @@ +hostname r14 +! +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +ip pim rp 192.168.0.14 239.100.0.96/28 +ip pim rp 192.168.0.14 239.100.0.128/25 +! +interface lo + ip pim +! +interface r14-eth0 + ip pim +! diff --git a/tests/topotests/pim_acl/r14/zebra.conf b/tests/topotests/pim_acl/r14/zebra.conf new file mode 100644 index 0000000000..8761b46206 --- /dev/null +++ b/tests/topotests/pim_acl/r14/zebra.conf @@ -0,0 +1,13 @@ +! +hostname r14 +log file zebra.log +! +interface lo + ip address 192.168.0.14/32 +! +interface r14-eth0 + description connection to r1 via sw1 + ip address 192.168.101.14/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_acl/r15/acl_6_pim_join.json b/tests/topotests/pim_acl/r15/acl_6_pim_join.json new file mode 100644 index 0000000000..05fed4ecc5 --- /dev/null +++ b/tests/topotests/pim_acl/r15/acl_6_pim_join.json @@ -0,0 +1,19 @@ +{ + "r15-eth0":{ + "name":"r15-eth0", + "state":"up", + "address":"192.168.101.15", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.70":{ + "*":{ + "source":"*", + "group":"239.100.0.70", + "prune":"--:--", + "channelJoinName":"JOIN", + "protocolPim":1 + } + } + } +} diff --git a/tests/topotests/pim_acl/r15/ospfd.conf b/tests/topotests/pim_acl/r15/ospfd.conf new file mode 100644 index 0000000000..cd4d7b3875 --- /dev/null +++ b/tests/topotests/pim_acl/r15/ospfd.conf @@ -0,0 +1,14 @@ +hostname r15 +! +debug ospf event +! +interface r15-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 0 +! +router ospf + ospf router-id 192.168.0.15 + network 192.168.0.15/32 area 0 + network 192.168.101.0/24 area 0 +! diff --git a/tests/topotests/pim_acl/r15/pimd.conf b/tests/topotests/pim_acl/r15/pimd.conf new file mode 100644 index 0000000000..85c9c51e1e --- /dev/null +++ b/tests/topotests/pim_acl/r15/pimd.conf @@ -0,0 +1,16 @@ +hostname r15 +! +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +ip pim rp 192.168.0.15 239.100.0.64/28 +! +interface lo + ip pim +! +interface r15-eth0 + ip pim +! diff --git a/tests/topotests/pim_acl/r15/zebra.conf b/tests/topotests/pim_acl/r15/zebra.conf new file mode 100644 index 0000000000..f6909dd020 --- /dev/null +++ b/tests/topotests/pim_acl/r15/zebra.conf @@ -0,0 +1,13 @@ +! +hostname r15 +log file zebra.log +! +interface lo + ip address 192.168.0.15/32 +! +interface r15-eth0 + description connection to r1 via sw1 + ip address 192.168.101.15/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_acl/test_pim_acl.py b/tests/topotests/pim_acl/test_pim_acl.py new file mode 100755 index 0000000000..848f7fa8ed --- /dev/null +++ b/tests/topotests/pim_acl/test_pim_acl.py @@ -0,0 +1,418 @@ +#!/usr/bin/env python + +# +# test_pim_acl.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# 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_pim_acl.py: Test PIM with RP selection using ACLs +""" + +# Test PIM RP selection with ACLs +# +# Testing RP selection with ACLs. R1 uses multiple ACLs +# to select desired RPs (R11 to R15) +# +# Test steps: +# - setup_module() +# Create topology. Hosts are only using zebra/staticd, +# no PIM, no OSPF (using IGMPv2 for multicast) +# - test_ospf_convergence() +# Wait for OSPF convergence in each VRF. OSPF is run on +# R1 and R11 - R15. +# - test_pim_convergence() +# Wait for PIM convergence on all routers. PIM is run on +# R1 and R11 - R15. +# - test_mcast_acl_1(): +# Test 1st ACL entry 239.100.0.0/28 with 239.100.0.1 which +# should use R11 as RP +# Stop multicast after verification +# - test_mcast_acl_2(): +# Test 2nd ACL entry 239.100.0.17/32 with 239.100.0.17 which +# should use R12 as RP +# Stop multicast after verification +# - test_mcast_acl_3(): +# Test 3rd ACL entry 239.100.0.32/27 with 239.100.0.32 which +# should use R13 as RP +# Stop multicast after verification +# - test_mcast_acl_4(): +# Test 4th ACL entry 239.100.0.128/25 with 239.100.0.255 which +# should use R14 as RP +# Stop multicast after verification +# - test_mcast_acl_5(): +# Test 5th ACL entry 239.100.0.96/28 with 239.100.0.97 which +# should use R14 as RP +# Stop multicast after verification +# - test_mcast_acl_6(): +# Test 6th ACL entry 239.100.0.64/28 with 239.100.0.70 which +# should use R15 as RP +# Stop multicast after verification +# - teardown_module() +# shutdown topology +# + + +TOPOLOGY = """ + +----------+ + | Host H2 | + | Source | + +----------+ + .2 | + +-----------+ | +----------+ + | | .1 | .11 | Host R11 | ++---------+ | R1 |---------+--------| PIM RP | +| Host H1 | 192.168.100.0/24 | | 192.168.101.0/24 +----------+ +| receive |------------------| uses ACLs | | +----------+ +|IGMP JOIN| .10 .1 | to pick | | .12 | Host R12 | ++---------+ | RP | +--------| PIM RP | + | | | +----------+ + +-----------+ | +----------+ + | .13 | Host R13 | + +--------| PIM RP | + | +----------+ + | +----------+ + | .14 | Host R14 | + +--------| PIM RP | + | +----------+ + | +----------+ + | .15 | Host R15 | + +--------| PIM RP | + +----------+ +""" + +import json +import functools +import os +import sys +import pytest +import re +import time +from time import sleep +import socket + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + +pytestmark = [pytest.mark.pimd] + + +# +# Test global variables: +# They are used to handle communicating with external application. +# +APP_SOCK_PATH = '/tmp/topotests/apps.sock' +HELPER_APP_PATH = os.path.join(CWD, "../lib/mcast-tester.py") +app_listener = None +app_clients = {} + +def listen_to_applications(): + "Start listening socket to connect with applications." + # Remove old socket. + try: + os.unlink(APP_SOCK_PATH) + except OSError: + pass + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0) + sock.bind(APP_SOCK_PATH) + sock.listen(10) + global app_listener + app_listener = sock + +def accept_host(host): + "Accept connection from application running in hosts." + global app_listener, app_clients + conn = app_listener.accept() + app_clients[host] = { + 'fd': conn[0], + 'address': conn[1] + } + +def close_applications(): + "Signal applications to stop and close all sockets." + global app_listener, app_clients + + if app_listener: + # Close listening socket. + app_listener.close() + + # Remove old socket. + try: + os.unlink(APP_SOCK_PATH) + except OSError: + pass + + # Close all host connections. + for host in ["h1", "h2"]: + if app_clients.get(host) is None: + continue + app_clients[host]["fd"].close() + + # Reset listener and clients data struct + app_listener = None + app_clients = {} + + +class PIMACLTopo(Topo): + "PIM ACL Test Topology" + + def build(self): + tgen = get_topogen(self) + + # Create the hosts + for hostNum in range(1,3): + tgen.add_router("h{}".format(hostNum)) + + # Create the main router + tgen.add_router("r1") + + # Create the PIM RP routers + for rtrNum in range(11, 16): + tgen.add_router("r{}".format(rtrNum)) + + # Setup Switches and connections + for swNum in range(1, 3): + tgen.add_switch("sw{}".format(swNum)) + + # Add connections H1 to R1 switch sw1 + tgen.gears["h1"].add_link(tgen.gears["sw1"]) + tgen.gears["r1"].add_link(tgen.gears["sw1"]) + + # Add connections R1 to R1x switch sw2 + tgen.gears["r1"].add_link(tgen.gears["sw2"]) + tgen.gears["h2"].add_link(tgen.gears["sw2"]) + tgen.gears["r11"].add_link(tgen.gears["sw2"]) + tgen.gears["r12"].add_link(tgen.gears["sw2"]) + tgen.gears["r13"].add_link(tgen.gears["sw2"]) + tgen.gears["r14"].add_link(tgen.gears["sw2"]) + tgen.gears["r15"].add_link(tgen.gears["sw2"]) + + +##################################################### +# +# Tests starting +# +##################################################### + +def setup_module(module): + logger.info("PIM RP ACL Topology: \n {}".format(TOPOLOGY)) + + tgen = Topogen(PIMACLTopo, module.__name__) + tgen.start_topology() + + # Starting Routers + router_list = tgen.routers() + + for rname, router in router_list.items(): + logger.info("Loading router %s" % rname) + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + if rname[0] != 'h': + # Only load ospf on routers, not on end hosts + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_PIM, os.path.join(CWD, "{}/pimd.conf".format(rname)) + ) + tgen.start_router() + + +def teardown_module(module): + tgen = get_topogen() + tgen.stop_topology() + close_applications() + + +def test_ospf_convergence(): + "Test for OSPFv2 convergence" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking OSPFv2 convergence on router r1") + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/ospf_neighbor.json") + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip ospf neighbor json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "OSPF router R1 did not converge" + assert res is None, assertmsg + + +def test_pim_convergence(): + "Test for PIM convergence" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking PIM convergence on router r1") + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/pim_neighbor.json") + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip pim neighbor json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "PIM router R1 did not converge" + assert res is None, assertmsg + + + +def check_mcast_entry(entry, mcastaddr, pimrp): + "Helper function to check RP" + tgen = get_topogen() + + logger.info("Testing PIM RP selection for ACL {} entry using {}".format(entry, mcastaddr)); + + # Start applications socket. + listen_to_applications() + + tgen.gears["h2"].run("{} --send='0.7' '{}' '{}' '{}' &".format( + HELPER_APP_PATH, APP_SOCK_PATH, mcastaddr, 'h2-eth0')) + accept_host("h2") + + tgen.gears["h1"].run("{} '{}' '{}' '{}' &".format( + HELPER_APP_PATH, APP_SOCK_PATH, mcastaddr, 'h1-eth0')) + accept_host("h1") + + logger.info("mcast join and source for {} started".format(mcastaddr)) + + # tgen.mininet_cli() + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/acl_{}_pim_join.json".format(entry)) + expected = json.loads(open(reffile).read()) + + logger.info("verifying pim join on r1 for {}".format(mcastaddr)) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip pim join json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "PIM router r1 did not show join status" + assert res is None, assertmsg + + logger.info("verifying pim join on PIM RP {} for {}".format(pimrp, mcastaddr)) + router = tgen.gears[pimrp] + reffile = os.path.join(CWD, "{}/acl_{}_pim_join.json".format(pimrp, entry)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip pim join json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "PIM router {} did not get selected as the PIM RP".format(pimrp) + assert res is None, assertmsg + + close_applications() + return + + +def test_mcast_acl_1(): + "Test 1st ACL entry 239.100.0.0/28 with 239.100.0.1" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_mcast_entry(1, '239.100.0.1', 'r11') + + +def test_mcast_acl_2(): + "Test 2nd ACL entry 239.100.0.17/32 with 239.100.0.17" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_mcast_entry(2, '239.100.0.17', 'r12') + + +def test_mcast_acl_3(): + "Test 3rd ACL entry 239.100.0.32/27 with 239.100.0.32" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_mcast_entry(3, '239.100.0.32', 'r13') + + +def test_mcast_acl_4(): + "Test 4th ACL entry 239.100.0.128/25 with 239.100.0.255" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_mcast_entry(4, '239.100.0.255', 'r14') + + +def test_mcast_acl_5(): + "Test 5th ACL entry 239.100.0.96/28 with 239.100.0.97" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_mcast_entry(5, '239.100.0.97', 'r14') + + +def test_mcast_acl_6(): + "Test 6th ACL entry 239.100.0.64/28 with 239.100.0.70" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_mcast_entry(6, '239.100.0.70', 'r15') + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/pim_igmp_vrf/h1/zebra.conf b/tests/topotests/pim_igmp_vrf/h1/zebra.conf new file mode 100644 index 0000000000..3d6540d40c --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/h1/zebra.conf @@ -0,0 +1,10 @@ +! +hostname h1 +log file zebra.log +! +interface h1-eth0 + description connection to r1 via sw1 + ip address 192.168.100.10/24 +! +ip route 0.0.0.0/0 192.168.100.1 +! diff --git a/tests/topotests/pim_igmp_vrf/h2/zebra.conf b/tests/topotests/pim_igmp_vrf/h2/zebra.conf new file mode 100644 index 0000000000..95342f9e8a --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/h2/zebra.conf @@ -0,0 +1,8 @@ +hostname h2 +! +interface h2-eth0 + description connection to r1 via sw2 + ip address 192.168.101.2/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_igmp_vrf/h3/zebra.conf b/tests/topotests/pim_igmp_vrf/h3/zebra.conf new file mode 100644 index 0000000000..ef99b1cd8f --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/h3/zebra.conf @@ -0,0 +1,10 @@ +! +hostname h3 +log file zebra.log +! +interface h3-eth0 + description connection to r1 via sw3 + ip address 192.168.100.20/24 +! +ip route 0.0.0.0/0 192.168.100.1 +! diff --git a/tests/topotests/pim_igmp_vrf/h4/zebra.conf b/tests/topotests/pim_igmp_vrf/h4/zebra.conf new file mode 100644 index 0000000000..6a2e466000 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/h4/zebra.conf @@ -0,0 +1,8 @@ +hostname h4 +! +interface h4-eth0 + description connection to r1 via sw4 + ip address 192.168.101.4/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_igmp_vrf/r1/ospf_blue_neighbor.json b/tests/topotests/pim_igmp_vrf/r1/ospf_blue_neighbor.json new file mode 100644 index 0000000000..604d25fac1 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/ospf_blue_neighbor.json @@ -0,0 +1,15 @@ +{ + "blue":{ + "vrfName":"blue", + "neighbors":{ + "192.168.0.11":[ + { + "priority":10, + "state":"Full\/Backup", + "address":"192.168.101.11", + "ifaceName":"r1-eth1:192.168.101.1" + } + ] + } + } +} diff --git a/tests/topotests/pim_igmp_vrf/r1/ospf_red_neighbor.json b/tests/topotests/pim_igmp_vrf/r1/ospf_red_neighbor.json new file mode 100644 index 0000000000..456bb87520 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/ospf_red_neighbor.json @@ -0,0 +1,16 @@ +{ + "red":{ + "vrfName":"red", + "neighbors":{ + "192.168.0.12":[ + { + "priority":10, + "state":"Full\/Backup", + "address":"192.168.101.12", + "ifaceName":"r1-eth3:192.168.101.1" + } + ] + } + } +} + diff --git a/tests/topotests/pim_igmp_vrf/r1/ospfd.conf b/tests/topotests/pim_igmp_vrf/r1/ospfd.conf new file mode 100644 index 0000000000..263b5867cc --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/ospfd.conf @@ -0,0 +1,26 @@ +hostname r1 +! +debug ospf event +! +! +interface r1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 20 +! +interface r1-eth3 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 20 +! +router ospf vrf blue + ospf router-id 192.168.0.1 + network 192.168.0.1/32 area 0 + network 192.168.100.0/24 area 0 + network 192.168.101.0/24 area 0 +router ospf vrf red + ospf router-id 192.168.0.1 + network 192.168.0.1/32 area 0 + network 192.168.100.0/24 area 0 + network 192.168.101.0/24 area 0 +! diff --git a/tests/topotests/pim_igmp_vrf/r1/pim_blue_join.json b/tests/topotests/pim_igmp_vrf/r1/pim_blue_join.json new file mode 100644 index 0000000000..8568bae2bc --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/pim_blue_join.json @@ -0,0 +1,22 @@ +{ + "r1-eth0":{ + "name":"r1-eth0", + "state":"up", + "address":"192.168.100.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.1":{ + "*":{ + "source":"*", + "group":"239.100.0.1", + "upTime":"--:--:--", + "expire":"--:--", + "prune":"--:--", + "channelJoinName":"NOINFO", + "protocolIgmp":1 + } + } + } +} + diff --git a/tests/topotests/pim_igmp_vrf/r1/pim_blue_neighbor.json b/tests/topotests/pim_igmp_vrf/r1/pim_blue_neighbor.json new file mode 100644 index 0000000000..ea7d4aca6f --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/pim_blue_neighbor.json @@ -0,0 +1,13 @@ +{ + "blue":{ + }, + "r1-eth0":{ + }, + "r1-eth1":{ + "192.168.101.11":{ + "interface":"r1-eth1", + "neighbor":"192.168.101.11", + "drPriority":1 + } + } +} diff --git a/tests/topotests/pim_igmp_vrf/r1/pim_blue_pimreg11.json b/tests/topotests/pim_igmp_vrf/r1/pim_blue_pimreg11.json new file mode 100644 index 0000000000..d3642f854a --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/pim_blue_pimreg11.json @@ -0,0 +1,14 @@ +{ + "pimreg11":{ + "name":"pimreg11", + "state":"up", + "address":"0.0.0.0", + "flagAllMulticast":true, + "lanDelayEnabled":true, + "drAddress":"*", + "drPriority":1, + "drUptime":"--:--:--", + "drElections":0, + "drChanges":0 + } +} diff --git a/tests/topotests/pim_igmp_vrf/r1/pim_red_join.json b/tests/topotests/pim_igmp_vrf/r1/pim_red_join.json new file mode 100644 index 0000000000..d0037ca4b0 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/pim_red_join.json @@ -0,0 +1,21 @@ +{ + "r1-eth2":{ + "name":"r1-eth2", + "state":"up", + "address":"192.168.100.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.1":{ + "*":{ + "source":"*", + "group":"239.100.0.1", + "upTime":"--:--:--", + "expire":"--:--", + "prune":"--:--", + "channelJoinName":"NOINFO", + "protocolIgmp":1 + } + } + } +} diff --git a/tests/topotests/pim_igmp_vrf/r1/pim_red_neighbor.json b/tests/topotests/pim_igmp_vrf/r1/pim_red_neighbor.json new file mode 100644 index 0000000000..e17b40854a --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/pim_red_neighbor.json @@ -0,0 +1,13 @@ +{ + "r1-eth2":{ + }, + "r1-eth3":{ + "192.168.101.12":{ + "interface":"r1-eth3", + "neighbor":"192.168.101.12", + "drPriority":1 + } + }, + "red":{ + } +} diff --git a/tests/topotests/pim_igmp_vrf/r1/pim_red_pimreg12.json b/tests/topotests/pim_igmp_vrf/r1/pim_red_pimreg12.json new file mode 100644 index 0000000000..45b6cd9645 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/pim_red_pimreg12.json @@ -0,0 +1,14 @@ +{ + "pimreg12":{ + "name":"pimreg12", + "state":"up", + "address":"0.0.0.0", + "flagAllMulticast":true, + "lanDelayEnabled":true, + "drAddress":"*", + "drPriority":1, + "drUptime":"--:--:--", + "drElections":0, + "drChanges":0 + } +} diff --git a/tests/topotests/pim_igmp_vrf/r1/pimd.conf b/tests/topotests/pim_igmp_vrf/r1/pimd.conf new file mode 100644 index 0000000000..6ee264d3d0 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/pimd.conf @@ -0,0 +1,26 @@ +hostname r1 +! +debug igmp events +debug igmp packets +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +interface r1-eth0 + ip igmp + ip igmp version 2 + ip pim +! +interface r1-eth1 + ip pim +! +interface r1-eth2 + ip igmp + ip igmp version 2 + ip pim +! +interface r1-eth3 + ip pim +! diff --git a/tests/topotests/pim_igmp_vrf/r1/zebra.conf b/tests/topotests/pim_igmp_vrf/r1/zebra.conf new file mode 100644 index 0000000000..9da9280945 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r1/zebra.conf @@ -0,0 +1,30 @@ +! +hostname r1 +log file zebra.log +! +ip forwarding +ipv6 forwarding +! +interface blue vrf blue + ip address 192.168.0.1/32 +! +interface red vrf red + ip address 192.168.0.1/32 +! +interface r1-eth0 vrf blue + description connection to h1 via sw1 + ip address 192.168.100.1/24 +! +interface r1-eth1 vrf blue + description connection to r11 via sw2 + ip address 192.168.101.1/24 +! +interface r1-eth2 vrf red + description connection to h1 via sw3 + ip address 192.168.100.1/24 +! +interface r1-eth3 vrf red + description connection to r12 via sw4 + ip address 192.168.101.1/24 +! + diff --git a/tests/topotests/pim_igmp_vrf/r11/ospfd.conf b/tests/topotests/pim_igmp_vrf/r11/ospfd.conf new file mode 100644 index 0000000000..e107220a4e --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r11/ospfd.conf @@ -0,0 +1,14 @@ +hostname r11 +! +debug ospf event +! +interface r11-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 10 +! +router ospf + ospf router-id 192.168.0.11 + network 192.168.0.11/32 area 0 + network 192.168.101.0/24 area 0 +! diff --git a/tests/topotests/pim_igmp_vrf/r11/pim_blue_join.json b/tests/topotests/pim_igmp_vrf/r11/pim_blue_join.json new file mode 100644 index 0000000000..289bf51e76 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r11/pim_blue_join.json @@ -0,0 +1,19 @@ +{ + "r11-eth0":{ + "name":"r11-eth0", + "state":"up", + "address":"192.168.101.11", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.1":{ + "*":{ + "source":"*", + "group":"239.100.0.1", + "prune":"--:--", + "channelJoinName":"JOIN", + "protocolPim":1 + } + } + } +} diff --git a/tests/topotests/pim_igmp_vrf/r11/pimd.conf b/tests/topotests/pim_igmp_vrf/r11/pimd.conf new file mode 100644 index 0000000000..05cd5ac911 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r11/pimd.conf @@ -0,0 +1,16 @@ +hostname r11 +! +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +ip pim rp 192.168.0.11 239.100.0.0/28 +! +interface lo + ip pim +! +interface r11-eth0 + ip pim +! diff --git a/tests/topotests/pim_igmp_vrf/r11/zebra.conf b/tests/topotests/pim_igmp_vrf/r11/zebra.conf new file mode 100644 index 0000000000..137706d245 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r11/zebra.conf @@ -0,0 +1,13 @@ +! +hostname r11 +log file zebra.log +! +interface lo + ip address 192.168.0.11/32 +! +interface r11-eth0 + description connection to r1 via sw1 + ip address 192.168.101.11/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_igmp_vrf/r12/ospfd.conf b/tests/topotests/pim_igmp_vrf/r12/ospfd.conf new file mode 100644 index 0000000000..03acc82c1d --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r12/ospfd.conf @@ -0,0 +1,14 @@ +hostname r12 +! +debug ospf event +! +interface r12-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 10 +! +router ospf + ospf router-id 192.168.0.12 + network 192.168.0.12/32 area 0 + network 192.168.101.0/24 area 0 +! diff --git a/tests/topotests/pim_igmp_vrf/r12/pim_red_join.json b/tests/topotests/pim_igmp_vrf/r12/pim_red_join.json new file mode 100644 index 0000000000..6926246568 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r12/pim_red_join.json @@ -0,0 +1,19 @@ +{ + "r12-eth0":{ + "name":"r12-eth0", + "state":"up", + "address":"192.168.101.12", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.100.0.1":{ + "*":{ + "source":"*", + "group":"239.100.0.1", + "prune":"--:--", + "channelJoinName":"JOIN", + "protocolPim":1 + } + } + } +} diff --git a/tests/topotests/pim_igmp_vrf/r12/pimd.conf b/tests/topotests/pim_igmp_vrf/r12/pimd.conf new file mode 100644 index 0000000000..531aec61ed --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r12/pimd.conf @@ -0,0 +1,16 @@ +hostname r12 +! +debug pim events +debug pim packets +debug pim trace +debug pim zebra +debug pim bsm +! +ip pim rp 192.168.0.12 239.100.0.0/28 +! +interface lo + ip pim +! +interface r12-eth0 + ip pim +! diff --git a/tests/topotests/pim_igmp_vrf/r12/zebra.conf b/tests/topotests/pim_igmp_vrf/r12/zebra.conf new file mode 100644 index 0000000000..bede104906 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/r12/zebra.conf @@ -0,0 +1,13 @@ +! +hostname r12 +log file zebra.log +! +interface lo + ip address 192.168.0.12/32 +! +interface r12-eth0 + description connection to r1 via sw1 + ip address 192.168.101.12/24 +! +ip route 0.0.0.0/0 192.168.101.1 +! diff --git a/tests/topotests/pim_igmp_vrf/test_pim_vrf.py b/tests/topotests/pim_igmp_vrf/test_pim_vrf.py new file mode 100755 index 0000000000..298adef9c6 --- /dev/null +++ b/tests/topotests/pim_igmp_vrf/test_pim_vrf.py @@ -0,0 +1,462 @@ +#!/usr/bin/env python + +# +# test_pim_vrf.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# 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_pim_vrf.py: Test PIM with VRFs. +""" + +# Tests PIM with VRF +# +# R1 is split into 2 VRF: Blue and Red, the others are normal +# routers and Hosts +# There are 2 similar topologies with overlapping IPs in each +# section. +# +# Test steps: +# - setup_module() +# Create topology. Hosts are only using zebra/staticd, +# no PIM, no OSPF (using IGMPv2 for multicast) +# - test_ospf_convergence() +# Wait for OSPF convergence in each VRF. OSPF is run on +# R1, R11 and R12. +# - test_pim_convergence() +# Wait for PIM convergence in each VRF. PIM is run on +# R1, R11 and R12. R11 is the RP for vrf blue, R12 is RP +# for vrf red. +# - test_vrf_pimreg_interfaces() +# Adding PIM RP in VRF information and verify pimreg +# interfaces in VRF blue and red +# - test_mcast_vrf_blue() +# Start multicast stream for group 239.100.0.1 from Host +# H2 and join from Host H1 on vrf blue +# Verify PIM JOIN status on R1 and R11 +# Stop multicast after verification +# - test_mcast_vrf_red() +# Start multicast stream for group 239.100.0.1 from Host +# H4 and join from Host H3 on vrf blue +# Verify PIM JOIN status on R1 and R12 +# Stop multicast after verification +# - teardown_module(module) +# shutdown topology +# + +TOPOLOGY = """ + +----------+ + | Host H2 | + | Source | + +----------+ + .2 | ++---------+ +------------+ | +---------+ +| Host H1 | 192.168.100.0/24 | | .1 | .11 | Host H2 | +| receive |------------------| VRF Blue |---------+--------| PIM RP | +|IGMP JOIN| .10 .1 | | 192.168.101.0/24 | | ++---------+ | | +---------+ + =| = = R1 = = |= ++---------+ | | +---------+ +| Host H3 | 192.168.100.0/24 | | 192.168.101.0/24 | Host H4 | +| receive |------------------| VRF Red |---------+--------| PIM RP | +|IGMP JOIN| .20 .1 | | .1 | .12 | | ++---------+ +------------+ | +---------+ + .4 | + +----------+ + | Host H4 | + | Source | + +----------+ +""" + +import json +import functools +import os +import sys +import pytest +import re +import time +from time import sleep +import socket + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.topotest import iproute2_is_vrf_capable +from lib.common_config import ( + required_linux_kernel_version) + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + +pytestmark = [pytest.mark.pimd] + + +# +# Test global variables: +# They are used to handle communicating with external application. +# +APP_SOCK_PATH = '/tmp/topotests/apps.sock' +HELPER_APP_PATH = os.path.join(CWD, "../lib/mcast-tester.py") +app_listener = None +app_clients = {} + +def listen_to_applications(): + "Start listening socket to connect with applications." + # Remove old socket. + try: + os.unlink(APP_SOCK_PATH) + except OSError: + pass + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0) + sock.bind(APP_SOCK_PATH) + sock.listen(10) + global app_listener + app_listener = sock + +def accept_host(host): + "Accept connection from application running in hosts." + global app_listener, app_clients + conn = app_listener.accept() + app_clients[host] = { + 'fd': conn[0], + 'address': conn[1] + } + +def close_applications(): + "Signal applications to stop and close all sockets." + global app_listener, app_clients + + if app_listener: + # Close listening socket. + app_listener.close() + + # Remove old socket. + try: + os.unlink(APP_SOCK_PATH) + except OSError: + pass + + # Close all host connections. + for host in ["h1", "h2"]: + if app_clients.get(host) is None: + continue + app_clients[host]["fd"].close() + + # Reset listener and clients data struct + app_listener = None + app_clients = {} + + +class PIMVRFTopo(Topo): + "PIM VRF Test Topology" + + def build(self): + tgen = get_topogen(self) + + # Create the hosts + for hostNum in range(1,5): + tgen.add_router("h{}".format(hostNum)) + + # Create the main router + tgen.add_router("r1") + + # Create the PIM RP routers + for rtrNum in range(11, 13): + tgen.add_router("r{}".format(rtrNum)) + + # Setup Switches and connections + for swNum in range(1, 5): + tgen.add_switch("sw{}".format(swNum)) + + ################ + # 1st set of connections to routers for VRF red + ################ + + # Add connections H1 to R1 switch sw1 + tgen.gears["h1"].add_link(tgen.gears["sw1"]) + tgen.gears["r1"].add_link(tgen.gears["sw1"]) + + # Add connections R1 to R1x switch sw2 + tgen.gears["r1"].add_link(tgen.gears["sw2"]) + tgen.gears["h2"].add_link(tgen.gears["sw2"]) + tgen.gears["r11"].add_link(tgen.gears["sw2"]) + + ################ + # 2nd set of connections to routers for vrf blue + ################ + + # Add connections H1 to R1 switch sw1 + tgen.gears["h3"].add_link(tgen.gears["sw3"]) + tgen.gears["r1"].add_link(tgen.gears["sw3"]) + + # Add connections R1 to R1x switch sw2 + tgen.gears["r1"].add_link(tgen.gears["sw4"]) + tgen.gears["h4"].add_link(tgen.gears["sw4"]) + tgen.gears["r12"].add_link(tgen.gears["sw4"]) + +##################################################### +# +# Tests starting +# +##################################################### + +def setup_module(module): + logger.info("PIM IGMP VRF Topology: \n {}".format(TOPOLOGY)) + + tgen = Topogen(PIMVRFTopo, module.__name__) + tgen.start_topology() + + vrf_setup_cmds = [ + "ip link add name blue type vrf table 11", + "ip link add name red type vrf table 12", + "ip link set dev blue up", + "ip link set dev red up", + "ip link set dev r1-eth0 vrf blue up", + "ip link set dev r1-eth1 vrf blue up", + "ip link set dev r1-eth2 vrf red up", + "ip link set dev r1-eth3 vrf red up", + ] + + # Starting Routers + router_list = tgen.routers() + + # Create VRF on r2 first and add it's interfaces + for cmd in vrf_setup_cmds: + tgen.net["r1"].cmd(cmd) + + for rname, router in router_list.items(): + logger.info("Loading router %s" % rname) + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + if rname[0] != 'h': + # Only load ospf on routers, not on end hosts + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_PIM, os.path.join(CWD, "{}/pimd.conf".format(rname)) + ) + tgen.start_router() + + +def teardown_module(module): + tgen = get_topogen() + tgen.stop_topology() + close_applications() + + +def test_ospf_convergence(): + "Test for OSPFv2 convergence" + tgen = get_topogen() + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + # iproute2 needs to support VRFs for this suite to run. + if not iproute2_is_vrf_capable(): + pytest.skip("Installed iproute2 version does not support VRFs") + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking OSPFv2 convergence on router r1 for VRF blue") + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/ospf_blue_neighbor.json") + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip ospf vrf blue neighbor json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "OSPF router R1 did not converge on VRF blue" + assert res is None, assertmsg + + logger.info("Checking OSPFv2 convergence on router r1 for VRF red") + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/ospf_red_neighbor.json") + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip ospf vrf red neighbor json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "OSPF router R1 did not converge on VRF red" + assert res is None, assertmsg + + +def test_pim_convergence(): + "Test for PIM convergence" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking PIM convergence on router r1 for VRF red") + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/pim_red_neighbor.json") + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip pim vrf red neighbor json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=2) + assertmsg = "PIM router R1 did not converge for VRF red" + assert res is None, assertmsg + + logger.info("Checking PIM convergence on router r1 for VRF blue") + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/pim_blue_neighbor.json") + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip pim vrf blue neighbor json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=2) + assertmsg = "PIM router R1 did not converge for VRF blue" + assert res is None, assertmsg + + +def test_vrf_pimreg_interfaces(): + "Adding PIM RP in VRF information and verify pimreg interfaces" + tgen = get_topogen() + + r1 = tgen.gears["r1"] + r1.vtysh_cmd("conf\ninterface blue\nip pim") + r1.vtysh_cmd("conf\nvrf blue\nip pim rp 192.168.0.11 239.100.0.1/32\nexit-vrf") + + # Check pimreg11 interface on R1, VRF blue + reffile = os.path.join(CWD, "r1/pim_blue_pimreg11.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, r1, "show ip pim vrf blue inter pimreg11 json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=2) + assertmsg = "PIM router R1, VRF blue (table 11) pimreg11 interface missing or incorrect status" + assert res is None, assertmsg + + r1.vtysh_cmd("conf\ninterface red\nip pim") + r1.vtysh_cmd("conf\nvrf red\nip pim rp 192.168.0.12 239.100.0.1/32\nexit-vrf") + + # Check pimreg12 interface on R1, VRF red + reffile = os.path.join(CWD, "r1/pim_red_pimreg12.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, r1, "show ip pim vrf red inter pimreg12 json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=2) + assertmsg = "PIM router R1, VRF red (table 12) pimreg12 interface missing or incorrect status" + assert res is None, assertmsg + + +################################## +### Test PIM / IGMP with VRF +################################## + +def check_mcast_entry(mcastaddr, pimrp, receiver, sender, vrf): + "Helper function to check RP" + tgen = get_topogen() + + logger.info("Testing PIM for VRF {} entry using {}".format(vrf, mcastaddr)); + + # Start applications socket. + listen_to_applications() + + tgen.gears[sender].run("{} --send='0.7' '{}' '{}' '{}' &".format( + HELPER_APP_PATH, APP_SOCK_PATH, mcastaddr, '{}-eth0'.format(sender))) + accept_host(sender) + + tgen.gears[receiver].run("{} '{}' '{}' '{}' &".format( + HELPER_APP_PATH, APP_SOCK_PATH, mcastaddr, '{}-eth0'.format(receiver))) + accept_host(receiver) + + logger.info("mcast join and source for {} started".format(mcastaddr)) + + # tgen.mininet_cli() + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/pim_{}_join.json".format(vrf)) + expected = json.loads(open(reffile).read()) + + logger.info("verifying pim join on r1 for {} on VRF {}".format(mcastaddr, vrf)) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip pim vrf {} join json".format(vrf), + expected + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=2) + assertmsg = "PIM router r1 did not show join status on VRF".format(vrf) + assert res is None, assertmsg + + logger.info("verifying pim join on PIM RP {} for {}".format(pimrp, mcastaddr)) + router = tgen.gears[pimrp] + reffile = os.path.join(CWD, "{}/pim_{}_join.json".format(pimrp, vrf)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip pim join json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=2) + assertmsg = "PIM router {} did not get selected as the PIM RP for VRF {}".format(pimrp, vrf) + assert res is None, assertmsg + + close_applications() + return + + +def test_mcast_vrf_blue(): + "Test vrf blue with 239.100.0.1" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_mcast_entry('239.100.0.1', 'r11', 'h1', 'h2', 'blue') + + +def test_mcast_vrf_red(): + "Test vrf red with 239.100.0.1" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_mcast_entry('239.100.0.1', 'r12', 'h3', 'h4', 'red') + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args))