#!/usr/bin/env python # # ospf6-test1.py # Part of NetDEF Topology Tests # # Copyright (c) 2016 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. # """ ospf6-test1.py: -----\ SW1 - Stub Net 1 SW2 - Stub Net 2 \ fc00:1:1:1::/64 fc00:2:2:2::/64 \ \___________________/ \___________________/ | | | | | | | | ::1 | ::2 | +---------+---------+ +---------+---------+ | | R1 | | R2 | | | Quagga | | Quagga | | | Rtr-ID: 10.0.0.1 | | Rtr-ID: 10.0.0.2 | | +---------+---------+ +---------+---------+ | | ::1 | ::2 \ \______ ___________/ OSPFv3 \ / Area 0.0.0.0 \ / / ~~~~~~~~~~~~~~~~~~ | ~~ SW5 ~~ | ~~ Switch ~~ | ~~ fc00:A:A:A::/64 ~~ | ~~~~~~~~~~~~~~~~~~ | | /---- | | ::3 | SW3 - Stub Net 3 | +---------+---------+ /-+ fc00:3:3:3::/64 | | R3 | / | / | Quagga +--/ \---- / | Rtr-ID: 10.0.0.3 | ::3 ___________/ +---------+---------+ \ | ::3 \ | \ ~~~~~~~~~~~~~~~~~~ | ~~ SW6 ~~ | ~~ Switch ~~ | ~~ fc00:B:B:B::/64 ~~ \ ~~~~~~~~~~~~~~~~~~ OSPFv3 | Area 0.0.0.1 | ::4 / +---------+---------+ /---- | | R4 | | SW4 - Stub Net 4 | | Quagga +------+ fc00:4:4:4::/64 | | Rtr-ID: 10.0.0.4 | ::4 | / +-------------------+ \---- / -----/ """ import os import re import StringIO import sys import difflib from pprint import pprint from mininet.topo import Topo from mininet.net import Mininet from mininet.node import Node, OVSSwitch, Host from mininet.log import setLogLevel, info from mininet.cli import CLI from functools import partial from time import sleep def int2dpid(dpid): "Converting Integer to DPID" try: dpid = hex(dpid)[2:] dpid = '0'*(16-len(dpid))+dpid return dpid except IndexError: raise Exception('Unable to derive default datapath ID - ' 'please either specify a dpid or use a ' 'canonical switch name such as s23.') class LinuxRouter(Node): "A Node with IPv4/IPv6 forwarding enabled." def config(self, **params): super(LinuxRouter, self).config(**params) # Enable forwarding on the router self.cmd('sysctl net.ipv4.ip_forward=1') self.cmd('sysctl net.ipv6.conf.all.forwarding=1') def terminate(self): """ Terminate generic LinuxRouter Mininet instance """ self.cmd('sysctl net.ipv4.ip_forward=0') self.cmd('sysctl net.ipv6.conf.all.forwarding=0') super(LinuxRouter, self).terminate() class QuaggaRouter(Node): "A Node with IPv4/IPv6 forwarding enabled and Quagga as Routing Engine" def config(self, **params): super(QuaggaRouter, self).config(**params) # Enable forwarding on the router self.cmd('sysctl net.ipv4.ip_forward=1') self.cmd('sysctl net.ipv6.conf.all.forwarding=1') self.cmd('chown quagga:quaggavty /etc/quagga') self.daemons = {'zebra': 0, 'ripd': 0, 'ripngd': 0, 'ospfd': 0, 'ospf6d': 0, 'isisd': 0, 'bgpd': 0, 'pimd': 0} def terminate(self): # Delete Running Quagga Daemons rundaemons = self.cmd('ls -1 /var/run/quagga/*.pid') for d in StringIO.StringIO(rundaemons): self.cmd('kill -7 `cat %s`' % d.rstrip()) self.waitOutput() # Disable forwarding self.cmd('sysctl net.ipv4.ip_forward=0') self.cmd('sysctl net.ipv6.conf.all.forwarding=0') super(QuaggaRouter, self).terminate() def removeIPs(self): for interface in self.intfNames(): self.cmd('ip address flush', interface) def loadConf(self, daemon, source=None): # print "Daemons before:", self.daemons if daemon in self.daemons.keys(): self.daemons[daemon] = 1 if source is None: self.cmd('touch /etc/quagga/%s.conf' % daemon) self.waitOutput() else: self.cmd('cp %s /etc/quagga/%s.conf' % (source, daemon)) self.waitOutput() self.cmd('chmod 640 /etc/quagga/%s.conf' % daemon) self.waitOutput() self.cmd('chown quagga:quagga /etc/quagga/%s.conf' % daemon) self.waitOutput() else: print("No daemon %s known" % daemon) # print "Daemons after:", self.daemons def startQuagga(self): # Disable integrated-vtysh-config self.cmd('echo "no service integrated-vtysh-config" > /etc/quagga/vtysh.conf') with open("/etc/quagga/vtysh.conf", "w") as vtyshfile: vtyshfile.write('no service integrated-vtysh-config') self.cmd('chown quagga:quaggavty /etc/quagga/vtysh.conf') # Remove IP addresses from OS first - we have them in zebra.conf self.removeIPs() # Start Zebra first if self.daemons['zebra'] == 1: self.cmd('/usr/lib/quagga/zebra -d') self.waitOutput() print('%s: zebra started' % self) sleep(1) # Fix Link-Local Addresses # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this self.cmd('for i in `ls /sys/class/net/` ; do mac=`cat /sys/class/net/$i/address`; IFS=\':\'; set $mac; unset IFS; ip address add dev $i scope link fe80::$(printf %02x $((0x$1 ^ 2)))$2:${3}ff:fe$4:$5$6/64; done') # Now start all the other daemons for daemon in self.daemons: if (self.daemons[daemon] == 1) and (daemon != 'zebra'): self.cmd('/usr/lib/quagga/%s -d' % daemon) self.waitOutput() print('%s: %s started' % (self, daemon)) class LegacySwitch(OVSSwitch): "A Legacy Switch without OpenFlow" def __init__(self, name, **params): OVSSwitch.__init__(self, name, failMode='standalone', **params) self.switchIP = None class NetworkTopo(Topo): "A Quagga Topology with direct peering router and IXP connection" def build(self, **_opts): quaggaPrivateDirs = ['/etc/quagga', '/var/run/quagga', '/var/log'] # # Define Switches first # switch = {} for i in range(1, 7): switch[i] = self.addSwitch('SW%s' % i, dpid=int2dpid(i), cls=LegacySwitch) # # Define Quagga Routers # router = {} for i in range(1, 5): router[i] = self.addNode('r%s' % i, cls=QuaggaRouter, privateDirs=quaggaPrivateDirs) # # Wire up the switches and routers # # Stub nets for i in range(1, 5): self.addLink(switch[i], router[i], intfName2='r%s-stubnet' % i) # Switch 5 self.addLink(switch[5], router[1], intfName2='r1-sw5') self.addLink(switch[5], router[2], intfName2='r2-sw5') self.addLink(switch[5], router[3], intfName2='r3-sw5') # Switch 6 self.addLink(switch[6], router[3], intfName2='r3-sw6') self.addLink(switch[6], router[4], intfName2='r4-sw6') def run(): "ospf6-test1 Topology" thisDir = os.path.dirname(os.path.realpath(__file__)) topo = NetworkTopo() net = Mininet(controller=None, topo=topo) net.start() # Starting Routers for i in range(1, 5): net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i)) net['r%s' % i].loadConf('ospf6d', '%s/r%s/ospf6d.conf' % (thisDir, i)) net['r%s' % i].startQuagga() print('') # # Stop here for all manual checks # #CLI(net) # Wait for OSPF6 to converge (All Neighbors in either Full or TwoWay State) print("Waiting for OSPFv3 neighbors to establish (to Full)") timeout = 60 while timeout > 0: print("Timeout in %s: " % timeout), sys.stdout.flush() # Look for any node not yet converged for i in range(1, 5): notConverged = net['r%s' % i].cmd('vtysh -c "show ipv6 ospf neigh" 2> /dev/null | grep ^[0-9] | grep -v Full') if notConverged: print('Waiting for r%s' %i), sys.stdout.flush() break if notConverged: sleep(2) timeout -= 2 print('\r \r'), else: print('\rDone ') print(notConverged) break else: # Bail out with error if a router fails to converge sys.stderr.write("Router r%s failed to converge\n" % i) ospfStatus = net['r%s' % i].cmd('vtysh -c "show ipv6 ospf neigh"') sys.stderr.write("Status:\n%s" % ospfStatus) net.stop() sys.exit(1) print("OSPFv3 converged.") print("\nwaiting 15s for routes to populate") sleep(15) # Verify OSPFv3 Routing Table print("\nVerifing OSPFv3 Routing Table") failures = 0 for i in range(1, 5): refTableFile = '%s/r%s/show_ipv6_route.ref' % (thisDir, i) if os.path.isfile(refTableFile): # Read expected result from file expected = open(refTableFile).read().rstrip() # Fix newlines (make them all the same) expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1) # Actual output from router actual = net['r%s' % i].cmd('vtysh -c "show ipv6 route" 2> /dev/null | grep "^O"').rstrip() # Mask out Link-Local mac address portion. They are random... actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual) # Drop timers on end of line (older Quagga Versions) actual = re.sub(r", [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", "", actual) # Fix newlines (make them all the same) actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1) # Generate Diff diff=difflib.unified_diff(actual, expected) diff=''.join(diff) # Empty string if it matches, otherwise diff contains unified diff if diff: sys.stderr.write('r%s failed Routing Table Check:\n%s\n' % (i, diff)) failures += 1 else: print("r%s ok" % i) # # Stop here for manual checks after tests are executed # # CLI(net) # End - Shutdown network net.stop() # Set non-zero exit status in case of failures if failures > 0: sys.stderr.write("Verification failed\n") sys.exit(1) else: print("Verification Successful") if __name__ == '__main__': setLogLevel('info') run()