Merge pull request #8637 from opensourcerouting/pim-vrf-acl-fixes

Pim vrf acl fixes
This commit is contained in:
Donatas Abraitis 2021-07-26 12:20:54 +03:00 committed by GitHub
commit 66aa87d03e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 2085 additions and 50 deletions

View File

@ -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 <A.B.C.D/M|X:X::X:X/M> [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
-------------------------------

View File

@ -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 <A.B.C.D/M|X:X::X:X/M>"
" [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);
}

View File

@ -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 *,

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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

1
tests/.gitignore vendored
View File

@ -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

View File

@ -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);

View File

@ -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);

48
tests/lib/test_plist.c Normal file
View File

@ -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 <zebra.h>
#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".
*/
}

View File

@ -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)

View File

@ -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

View File

@ -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
!

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
]
}
}

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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))

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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"
}
]
}
}
}

View File

@ -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"
}
]
}
}
}

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -0,0 +1,13 @@
{
"blue":{
},
"r1-eth0":{
},
"r1-eth1":{
"192.168.101.11":{
"interface":"r1-eth1",
"neighbor":"192.168.101.11",
"drPriority":1
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}
}

View File

@ -0,0 +1,13 @@
{
"r1-eth2":{
},
"r1-eth3":{
"192.168.101.12":{
"interface":"r1-eth3",
"neighbor":"192.168.101.12",
"drPriority":1
}
},
"red":{
}
}

View File

@ -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
}
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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
!

View File

@ -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
}
}
}
}

View File

@ -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
!

View File

@ -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
!

View File

@ -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))