diff options
| author | Donald Sharp <sharpd@cumulusnetworks.com> | 2021-08-26 08:52:31 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-08-26 08:52:31 -0400 | 
| commit | 7ed45c9a1751f435357a24099ba788d58e99bdd6 (patch) | |
| tree | b4cf09996d0239b4d4127cc87e1160d999aa590f | |
| parent | 589a1e8cb580e1f26ac81e52a198ee4561be26a7 (diff) | |
| parent | e01cebf9ee07dbc1b41042cb0071e3566d19d91d (diff) | |
Merge pull request #9186 from FRRouting/mergify/bp/stable/8.0/pr-8637
Pim vrf acl fixes (backport #8637)
66 files changed, 2094 insertions, 49 deletions
diff --git a/lib/plist.c b/lib/plist.c index 0ee02f8a0b..1ee855a594 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;  	} 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 353f133001..04f1487099 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 d706fd8693..0f3d50f6ac 100644 --- a/pimd/pim_rp.c +++ b/pimd/pim_rp.c @@ -205,6 +205,26 @@ static struct rp_info *pim_rp_find_exact(struct pim_instance *pim,  }  /* + * 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   */  struct rp_info *pim_rp_find_match_group(struct pim_instance *pim, @@ -214,7 +234,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; @@ -222,19 +243,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/topotests/lib/mcast-tester.py b/tests/topotests/lib/mcast-tester.py new file mode 100755 index 0000000000..07e4ab8773 --- /dev/null +++ b/tests/topotests/lib/mcast-tester.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. + +""" +Subscribe to a multicast group so that the kernel sends an IGMP JOIN +for the multicast group we subscribed to. +""" + +import argparse +import os +import json +import socket +import subprocess +import struct +import sys +import time + +# +# Functions +# +def interface_name_to_index(name): +    "Gets the interface index using its name. Returns None on failure." +    interfaces = json.loads( +        subprocess.check_output('ip -j link show', shell=True)) + +    for interface in interfaces: +        if interface['ifname'] == name: +            return interface['ifindex'] + +    return None + + +def multicast_join(sock, ifindex, group, port): +    "Joins a multicast group." +    mreq = struct.pack( +        "=4sLL", socket.inet_aton(args.group), socket.INADDR_ANY, ifindex +    ) + +    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +    sock.bind((group, port)) +    sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + + +# +# Main code. +# +parser = argparse.ArgumentParser(description="Multicast RX utility") +parser.add_argument('socket', help='Point to topotest UNIX socket') +parser.add_argument('group', help='Multicast IP') +parser.add_argument('interface', help='Interface name') +parser.add_argument( +    '--send', +    help='Transmit instead of join with interval (defaults to 0.7 sec)', +    type=float, default=0) +args = parser.parse_args() + +ttl = 16 +port = 1000 + +# Get interface index/validate. +ifindex = interface_name_to_index(args.interface) +if ifindex is None: +    sys.stderr.write('Interface {} does not exists\n'.format(args.interface)) +    sys.exit(1) + +# We need root privileges to set up multicast. +if os.geteuid() != 0: +    sys.stderr.write("ERROR: You must have root privileges\n") +    sys.exit(1) + +# Wait for topotest to synchronize with us. +toposock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0) +while True: +    try: +        toposock.connect(args.socket) +        break +    except ConnectionRefusedError: +        time.sleep(1) +        continue + +msock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +if args.send > 0: +    # Prepare multicast bit in that interface. +    msock.setsockopt( +        socket.SOL_SOCKET, 25, +        struct.pack("%ds" % len(args.interface), +                    args.interface.encode('utf-8'))) +    # Set packets TTL. +    msock.setsockopt( +        socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, struct.pack("b", ttl)) +    # Block to ensure packet send. +    msock.setblocking(True) +    # Set topotest socket non blocking so we can multiplex the main loop. +    toposock.setblocking(False) +else: +    multicast_join(msock, ifindex, args.group, port) + +counter = 0 +while True: +    if args.send > 0: +        msock.sendto(b"test %d" % counter, (args.group, port)) +        counter += 1 +        time.sleep(args.send) + +    try: +        data = toposock.recv(1) +        if data == b'': +            print(' -> Connection closed') +            break +    except BlockingIOError: +        continue + +msock.close() + +sys.exit(0) diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 2a5bd17361..4f0b7b07a0 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -1223,25 +1223,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))  | 
