Exemplo n.º 1
0
class Experiment(object):
    """ Experiment object to organize tests.
    """
    def __init__(self, opt, start):
        """ Initialize with opt and start time. """
        self.session = None
        # node list
        self.nodes = []
        # WLAN network
        self.net = None
        self.verbose = opt.verbose
        # dict from OptionParser
        self.opt = opt
        self.start = start
        self.numping = opt.numping
        self.numiperf = opt.numiperf
        self.nummgen = opt.nummgen
        self.logbegin()

    def info(self, msg):
        """ Utility method for writing output to stdout. """
        print(msg)
        sys.stdout.flush()
        self.log(msg)

    def warn(self, msg):
        """ Utility method for writing output to stderr. """
        sys.stderr.write(msg)
        sys.stderr.flush()
        self.log(msg)

    def logbegin(self):
        """ Start logging. """
        self.logfp = None
        if not self.opt.logfile:
            return
        self.logfp = open(self.opt.logfile, "w")
        self.log("%s begin: %s\n" % (sys.argv[0], self.start.ctime()))
        self.log("%s args: %s\n" % (sys.argv[0], sys.argv[1:]))
        (sysname, rel, ver, machine, nodename) = os.uname()
        self.log("%s %s %s %s on %s" % (sysname, rel, ver, machine, nodename))

    def logend(self):
        """ End logging. """
        if not self.logfp:
            return
        end = datetime.datetime.now()
        self.log("%s end: %s (%s)\n" % \
                 (sys.argv[0], end.ctime(), end - self.start))
        self.logfp.flush()
        self.logfp.close()
        self.logfp = None

    def log(self, msg):
        """ Write to the log file, if any. """
        if not self.logfp:
            return
        self.logfp.write(msg)

    def reset(self):
        """ Prepare for another experiment run.
        """
        if self.session:
            self.session.shutdown()
            del self.session
            self.session = None
        self.nodes = []
        self.net = None

    def createbridgedsession(self, numnodes, verbose=False):
        """ Build a topology consisting of the given number of LxcNodes
            connected to a WLAN.
        """
        # IP subnet
        prefix = ipaddress.Ipv4Prefix("10.0.0.0/16")
        self.session = Session(1)
        # emulated network
        self.net = self.session.create_node(cls=core.nodes.network.WlanNode,
                                            name="wlan1")
        prev = None
        for i in range(1, numnodes + 1):
            addr = "%s/%s" % (prefix.addr(i), 32)
            tmp = self.session.create_node(cls=core.nodes.base.CoreNode,
                                           _id=i,
                                           name="n%d" % i)
            tmp.newnetif(self.net, [addr])
            self.nodes.append(tmp)
            self.session.services.add_services(tmp, "router", "IPForward")
            self.session.services.boot_services(tmp)
            self.staticroutes(i, prefix, numnodes)

            # link each node in a chain, with the previous node
            if prev:
                self.net.link(prev.netif(0), tmp.netif(0))
            prev = tmp

    def createemanesession(self,
                           numnodes,
                           verbose=False,
                           cls=None,
                           values=None):
        """ Build a topology consisting of the given number of LxcNodes
            connected to an EMANE WLAN.
        """
        prefix = ipaddress.Ipv4Prefix("10.0.0.0/16")
        self.session = Session(2)
        self.session.node_count = str(numnodes + 1)
        self.session.master = True
        self.session.location.setrefgeo(47.57917, -122.13232, 2.00000)
        self.session.location.refscale = 150.0
        self.session.emane.loadmodels()
        self.net = self.session.create_node(cls=EmaneNode,
                                            _id=numnodes + 1,
                                            name="wlan1")
        self.net.verbose = verbose
        # self.session.emane.addobj(self.net)
        for i in range(1, numnodes + 1):
            addr = "%s/%s" % (prefix.addr(i), 32)
            tmp = self.session.create_node(cls=core.nodes.base.CoreNode,
                                           _id=i,
                                           name="n%d" % i)
            # tmp.setposition(i * 20, 50, None)
            tmp.setposition(50, 50, None)
            tmp.newnetif(self.net, [addr])
            self.nodes.append(tmp)
            self.session.services.add_services(tmp, "router", "IPForward")

        if values is None:
            values = cls.getdefaultvalues()
        self.session.emane.setconfig(self.net.id, cls.name, values)
        self.session.instantiate()

        self.info("waiting %s sec (TAP bring-up)" % 2)
        time.sleep(2)

        for i in range(1, numnodes + 1):
            tmp = self.nodes[i - 1]
            self.session.services.boot_services(tmp)
            self.staticroutes(i, prefix, numnodes)

    def setnodes(self):
        """ Set the sender and receiver nodes for use in this experiment,
            along with the address of the receiver to be used.
        """
        self.firstnode = self.nodes[0]
        self.lastnode = self.nodes[-1]
        self.lastaddr = self.lastnode.netif(0).addrlist[0].split("/")[0]

    def staticroutes(self, i, prefix, numnodes):
        """ Add static routes on node number i to the other nodes in the chain.
        """
        routecmd = ["/sbin/ip", "route", "add"]
        node = self.nodes[i - 1]
        neigh_left = ""
        neigh_right = ""
        # add direct interface routes first
        if i > 1:
            neigh_left = "%s" % prefix.addr(i - 1)
            cmd = routecmd + [neigh_left, "dev", node.netif(0).name]
            (status, result) = node.cmd_output(cmd)
            if status != 0:
                self.warn("failed to add interface route: %s" % cmd)
        if i < numnodes:
            neigh_right = "%s" % prefix.addr(i + 1)
            cmd = routecmd + [neigh_right, "dev", node.netif(0).name]
            (status, result) = node.cmd_output(cmd)
            if status != 0:
                self.warn("failed to add interface route: %s" % cmd)

        # add static routes to all other nodes via left/right neighbors
        for j in range(1, numnodes + 1):
            if abs(j - i) < 2:
                continue
            addr = "%s" % prefix.addr(j)
            if j < i:
                gw = neigh_left
            else:
                gw = neigh_right
            cmd = routecmd + [addr, "via", gw]
            (status, result) = node.cmd_output(cmd)
            if status != 0:
                self.warn("failed to add route: %s" % cmd)

    def setpathloss(self, numnodes):
        """ Send EMANE pathloss events to connect all NEMs in a chain.
        """
        if self.session.emane.version < self.session.emane.EMANE091:
            service = emaneeventservice.EventService()
            e = emaneeventpathloss.EventPathloss(1)
            old = True
        else:
            if self.session.emane.version == self.session.emane.EMANE091:
                dev = "lo"
            else:
                dev = self.session.obj("ctrlnet").brname
            service = EventService(eventchannel=("224.1.2.8", 45703, dev),
                                   otachannel=None)
            old = False

        for i in range(1, numnodes + 1):
            rxnem = i
            # inform rxnem that it can hear node to the left with 10dB noise
            txnem = rxnem - 1
            if txnem > 0:
                if old:
                    e.set(0, txnem, 10.0, 10.0)
                    service.publish(emaneeventpathloss.EVENT_ID,
                                    emaneeventservice.PLATFORMID_ANY, rxnem,
                                    emaneeventservice.COMPONENTID_ANY,
                                    e.export())
                else:
                    e = PathlossEvent()
                    e.append(txnem, forward=10.0, reverse=10.0)
                    service.publish(rxnem, e)
            # inform rxnem that it can hear node to the right with 10dB noise
            txnem = rxnem + 1
            if txnem > numnodes:
                continue
            if old:
                e.set(0, txnem, 10.0, 10.0)
                service.publish(emaneeventpathloss.EVENT_ID,
                                emaneeventservice.PLATFORMID_ANY, rxnem,
                                emaneeventservice.COMPONENTID_ANY, e.export())
            else:
                e = PathlossEvent()
                e.append(txnem, forward=10.0, reverse=10.0)
                service.publish(rxnem, e)

    def setneteffects(self, bw=None, delay=None):
        """ Set link effects for all interfaces attached to the network node.
        """
        if not self.net:
            self.warn("failed to set effects: no network node")
            return
        for netif in self.net.netifs():
            self.net.linkconfig(netif, bw=bw, delay=delay)

    def runalltests(self, title=""):
        """ Convenience helper to run all defined experiment tests.
            If tests are run multiple times, this returns the average of
            those runs.
        """
        duration = self.opt.duration
        rate = self.opt.rate
        if len(title) > 0:
            self.info("----- running %s tests (duration=%s, rate=%s) -----" % \
                      (title, duration, rate))
        (latency, mdev, throughput, cpu, loss) = (0, 0, 0, 0, 0)

        self.info("number of runs: ping=%d, iperf=%d, mgen=%d" % \
                  (self.numping, self.numiperf, self.nummgen))

        if self.numping > 0:
            (latency, mdev) = self.pingtest(count=self.numping)

        if self.numiperf > 0:
            throughputs = []
            for i in range(1, self.numiperf + 1):
                throughput = self.iperftest(time=duration)
                if self.numiperf > 1:
                    throughputs += throughput
                # iperf is very CPU intensive
                time.sleep(1)
            if self.numiperf > 1:
                throughput = sum(throughputs) / len(throughputs)
                self.info("throughputs=%s" % ["%.2f" % v for v in throughputs])

        if self.nummgen > 0:
            cpus = []
            losses = []
            for i in range(1, self.nummgen + 1):
                (cpu, loss) = self.cputest(time=duration, rate=rate)
                if self.nummgen > 1:
                    cpus += cpu,
                    losses += loss,
            if self.nummgen > 1:
                cpu = sum(cpus) / len(cpus)
                loss = sum(losses) / len(losses)
                self.info("cpus=%s" % ["%.2f" % v for v in cpus])
                self.info("losses=%s" % ["%.2f" % v for v in losses])

        return latency, mdev, throughput, cpu, loss

    def pingtest(self, count=50):
        """ Ping through a chain of nodes and report the average latency.
        """
        p = PingCmd(node=self.firstnode,
                    verbose=self.verbose,
                    addr=self.lastaddr,
                    count=count,
                    interval=0.1).run()
        (latency, mdev) = p
        self.info("latency (ms): %.03f, %.03f" % (latency, mdev))
        return p

    def iperftest(self, time=10):
        """ Run iperf through a chain of nodes and report the maximum
            throughput.
        """
        bps = IperfCmd(node=self.lastnode,
                       client_node=self.firstnode,
                       verbose=False,
                       addr=self.lastaddr,
                       time=time).run()
        self.info("throughput (bps): %s" % bps)
        return bps

    def cputest(self, time=10, rate=512):
        """ Run MGEN through a chain of nodes and report the CPU usage and
            percent of lost packets. Rate is in kbps.
        """
        if self.verbose:
            self.info("%s initial test ping (max 1 second)..." % \
                      self.firstnode.name)
        (status, result) = self.firstnode.cmd_output(
            ["ping", "-q", "-c", "1", "-w", "1", self.lastaddr])
        if status != 0:
            self.warn("initial ping from %s to %s failed! result:\n%s" % \
                      (self.firstnode.name, self.lastaddr, result))
            return 0.0, 0.0
        lines = readstat()
        cpustart = getcputimes(lines[0])
        loss = MgenCmd(node=self.lastnode,
                       client_node=self.firstnode,
                       verbose=False,
                       addr=self.lastaddr,
                       time=time,
                       rate=rate).run()
        lines = readstat()
        cpuend = getcputimes(lines[0])
        percent = calculatecpu(cpustart, cpuend)
        self.info("CPU usage (%%): %.02f, %.02f loss" % (percent, loss))
        return percent, loss
Exemplo n.º 2
0
def main():
    usagestr = "usage: %prog [-h] [options] [args]"
    parser = optparse.OptionParser(usage=usagestr)
    parser.set_defaults(numnodes=5, slave=None)

    parser.add_option("-n",
                      "--numnodes",
                      dest="numnodes",
                      type=int,
                      help="number of nodes")
    parser.add_option("-s",
                      "--slave-server",
                      dest="slave",
                      type=str,
                      help="slave server IP address")

    def usage(msg=None, err=0):
        sys.stdout.write("\n")
        if msg:
            sys.stdout.write(msg + "\n\n")
        parser.print_help()
        sys.exit(err)

    # parse command line options
    (options, args) = parser.parse_args()

    if options.numnodes < 1:
        usage("invalid number of nodes: %s" % options.numnodes)
    if not options.slave:
        usage("slave server IP address (-s) is a required argument")

    for a in args:
        sys.stderr.write("ignoring command line argument: '%s'\n" % a)

    start = datetime.datetime.now()

    prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
    session = Session(1)
    server = globals().get("server")
    if server is not None:
        server.addsession(session)

    # distributed setup - connect to slave server
    slaveport = options.slave.split(":")
    slave = slaveport[0]
    if len(slaveport) > 1:
        port = int(slaveport[1])
    else:
        port = CORE_API_PORT
    print("connecting to slave at %s:%d" % (slave, port))
    session.broker.addserver(slave, slave, port)
    session.broker.setupserver(slave)
    session.set_state(EventTypes.CONFIGURATION_STATE)
    tlvdata = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value,
                                        EventTypes.CONFIGURATION_STATE.value)
    session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata))

    switch = session.create_node(cls=core.nodes.network.SwitchNode,
                                 name="switch")
    switch.setposition(x=80, y=50)
    num_local = options.numnodes / 2
    num_remote = options.numnodes / 2 + options.numnodes % 2
    print("creating %d (%d local / %d remote) nodes with addresses from %s" %
          (options.numnodes, num_local, num_remote, prefix))
    for i in range(1, num_local + 1):
        node = session.create_node(cls=core.nodes.base.CoreNode,
                                   name="n%d" % i,
                                   _id=i)
        node.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
        node.cmd(
            [constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"])
        node.setposition(x=150 * i, y=150)
        n.append(node)

    flags = MessageFlags.ADD.value
    session.broker.handlerawmsg(switch.tonodemsg(flags=flags))

    # create remote nodes via API
    for i in range(num_local + 1, options.numnodes + 1):
        node = core.nodes.base.CoreNode(session=session,
                                        _id=i,
                                        name="n%d" % i,
                                        start=False)
        node.setposition(x=150 * i, y=150)
        node.server = slave
        n.append(node)
        node_data = node.data(flags)
        node_message = dataconversion.convert_node(node_data)
        session.broker.handlerawmsg(node_message)

    # create remote links via API
    for i in range(num_local + 1, options.numnodes + 1):
        tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.id)
        tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i)
        tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value,
                                            LinkTypes.WIRED.value)
        tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value,
                                            0)
        tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4.value,
                                            prefix.addr(i))
        tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4_MASK.value,
                                            prefix.prefixlen)
        msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
        session.broker.handlerawmsg(msg)

    session.instantiate()
    tlvdata = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value,
                                        EventTypes.INSTANTIATION_STATE.value)
    msg = coreapi.CoreEventMessage.pack(0, tlvdata)
    session.broker.handlerawmsg(msg)

    # start a shell on node 1
    n[1].client.term("bash")

    print("elapsed time: %s" % (datetime.datetime.now() - start))
    print("To stop this session, use the 'core-cleanup' script on this server")
    print("and on the remote slave server.")
Exemplo n.º 3
0
def main():
    usagestr = "usage: %prog [-h] [options] [args]"
    parser = optparse.OptionParser(usage=usagestr)
    parser.set_defaults(waittime=0.2,
                        numnodes=0,
                        bridges=0,
                        retries=0,
                        logfile=None,
                        services=None)

    parser.add_option(
        "-w",
        "--waittime",
        dest="waittime",
        type=float,
        help="number of seconds to wait between node creation"
        " (default = %s)" % parser.defaults["waittime"],
    )
    parser.add_option(
        "-n",
        "--numnodes",
        dest="numnodes",
        type=int,
        help="number of nodes (default = unlimited)",
    )
    parser.add_option(
        "-b",
        "--bridges",
        dest="bridges",
        type=int,
        help="number of nodes per bridge; 0 = one bridge "
        "(def. = %s)" % parser.defaults["bridges"],
    )
    parser.add_option(
        "-r",
        "--retry",
        dest="retries",
        type=int,
        help="number of retries on error (default = %s)" %
        parser.defaults["retries"],
    )
    parser.add_option(
        "-l",
        "--log",
        dest="logfile",
        type=str,
        help="log memory usage to this file (default = %s)" %
        parser.defaults["logfile"],
    )
    parser.add_option(
        "-s",
        "--services",
        dest="services",
        type=str,
        help="pipe-delimited list of services added to each "
        "node (default = %s)\n(Example: zebra|OSPFv2|OSPFv3|"
        "IPForward)" % parser.defaults["services"],
    )

    def usage(msg=None, err=0):
        sys.stdout.write("\n")
        if msg:
            sys.stdout.write(msg + "\n\n")
        parser.print_help()
        sys.exit(err)

    options, args = parser.parse_args()

    for a in args:
        sys.stderr.write("ignoring command line argument: %s\n" % a)

    start = datetime.datetime.now()
    prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")

    print("Testing how many network namespace nodes this machine can create.")
    print(" - %s" % linuxversion())
    mem = memfree()
    print(" - %.02f GB total memory (%.02f GB swap)" %
          (mem["total"] / GBD, mem["stotal"] / GBD))
    print(" - using IPv4 network prefix %s" % prefix)
    print(" - using wait time of %s" % options.waittime)
    print(" - using %d nodes per bridge" % options.bridges)
    print(" - will retry %d times on failure" % options.retries)
    print(" - adding these services to each node: %s" % options.services)
    print(" ")

    lfp = None
    if options.logfile is not None:
        # initialize a csv log file header
        lfp = open(options.logfile, "a")
        lfp.write("# log from howmanynodes.py %s\n" % time.ctime())
        lfp.write("# options = %s\n#\n" % options)
        lfp.write("# numnodes,%s\n" % ",".join(MEMKEYS))
        lfp.flush()

    session = Session(1)
    switch = session.create_node(cls=core.nodes.network.SwitchNode)
    switchlist.append(switch)
    print("Added bridge %s (%d)." % (switch.brname, len(switchlist)))

    i = 0
    retry_count = options.retries
    while True:
        i += 1
        # optionally add a bridge (options.bridges nodes per bridge)
        try:
            if 0 < options.bridges <= switch.numnetif():
                switch = session.create_node(cls=core.nodes.network.SwitchNode)
                switchlist.append(switch)
                print("\nAdded bridge %s (%d) for node %d." %
                      (switch.brname, len(switchlist), i))
        except Exception as e:
            print("At %d bridges (%d nodes) caught exception:\n%s\n" %
                  (len(switchlist), i - 1, e))
            break

        # create a node
        try:
            n = session.create_node(cls=core.nodes.base.CoreNode,
                                    name="n%d" % i)
            n.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
            n.cmd([
                constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"
            ])
            if options.services is not None:
                session.services.add_services(n, "",
                                              options.services.split("|"))
                session.services.boot_services(n)
            nodelist.append(n)
            if i % 25 == 0:
                print("\n%s nodes created " % i)
                mem = memfree()
                free = mem["free"] + mem["buff"] + mem["cached"]
                swap = mem["stotal"] - mem["sfree"]
                print("(%.02f/%.02f GB free/swap)" % (free / GBD, swap / GBD))
                if lfp:
                    lfp.write("%d," % i)
                    lfp.write("%s\n" % ",".join(str(mem[x]) for x in MEMKEYS))
                    lfp.flush()
            else:
                sys.stdout.write(".")
            sys.stdout.flush()
            time.sleep(options.waittime)
        except Exception as e:
            print("At %d nodes caught exception:\n" % i, e)
            if retry_count > 0:
                print("\nWill retry creating node %d." % i)
                shutil.rmtree(n.nodedir, ignore_errors=True)
                retry_count -= 1
                i -= 1
                time.sleep(options.waittime)
                continue
            else:
                print("Stopping at %d nodes!" % i)
                break

        if i == options.numnodes:
            print("Stopping at %d nodes due to numnodes option." % i)
            break
        # node creation was successful at this point
        retry_count = options.retries

    if lfp:
        lfp.flush()
        lfp.close()

    print("elapsed time: %s" % (datetime.datetime.now() - start))
    print("Use the core-cleanup script to remove nodes and bridges.")
Exemplo n.º 4
0
class ManetExperiment(object):
    """ A class for building an MDR network and checking and logging its state.
    """
    def __init__(self, options, start):
        """ Initialize with options and start time. """
        self.session = None
        # node list
        self.nodes = []
        # WLAN network
        self.net = None
        self.verbose = options.verbose
        # dict from OptionParser
        self.options = options
        self.start = start
        self.logbegin()

    def info(self, msg):
        ''' Utility method for writing output to stdout. '''
        print(msg)
        sys.stdout.flush()
        self.log(msg)

    def warn(self, msg):
        ''' Utility method for writing output to stderr. '''
        sys.stderr.write(msg)
        sys.stderr.flush()
        self.log(msg)

    def logbegin(self):
        """ Start logging. """
        self.logfp = None
        if not self.options.logfile:
            return
        self.logfp = open(self.options.logfile, "w")
        self.log("ospfmanetmdrtest begin: %s\n" % self.start.ctime())

    def logend(self):
        """ End logging. """
        if not self.logfp:
            return
        end = datetime.datetime.now()
        self.log("ospfmanetmdrtest end: %s (%s)\n" % \
                 (end.ctime(), end - self.start))
        self.logfp.flush()
        self.logfp.close()
        self.logfp = None

    def log(self, msg):
        """ Write to the log file, if any. """
        if not self.logfp:
            return
        self.logfp.write(msg)

    def logdata(self, nbrs, mdrs, lsdbs, krs, zrs):
        """ Dump experiment parameters and data to the log file. """
        self.log("ospfmantetmdrtest data:")
        self.log("----- parameters -----")
        self.log("%s" % self.options)
        self.log("----- neighbors -----")
        for rtrid in sorted(nbrs.keys()):
            self.log("%s: %s" % (rtrid, nbrs[rtrid]))
        self.log("----- mdr levels -----")
        self.log(mdrs)
        self.log("----- link state databases -----")
        for rtrid in sorted(lsdbs.keys()):
            self.log("%s lsdb:" % rtrid)
            for line in lsdbs[rtrid].split("\n"):
                self.log(line)
        self.log("----- kernel routes -----")
        for rtrid in sorted(krs.keys()):
            msg = rtrid + ": "
            for rt in krs[rtrid]:
                msg += "%s" % rt
            self.log(msg)
        self.log("----- zebra routes -----")
        for rtrid in sorted(zrs.keys()):
            msg = rtrid + ": "
            for rt in zrs[rtrid]:
                msg += "%s" % rt
            self.log(msg)

    def topology(self, numnodes, linkprob, verbose=False):
        """ Build a topology consisting of the given number of  ManetNodes
            connected to a WLAN and probabilty of links and set
            the session, WLAN, and node list objects.
        """
        # IP subnet
        prefix = ipaddress.Ipv4Prefix("10.14.0.0/16")
        self.session = Session(1)
        # emulated network
        self.net = self.session.create_node(cls=core.nodes.network.WlanNode)
        for i in range(1, numnodes + 1):
            addr = "%s/%s" % (prefix.addr(i), 32)
            tmp = self.session.create_node(cls=ManetNode,
                                           ipaddr=addr,
                                           _id="%d" % i,
                                           name="n%d" % i)
            tmp.newnetif(self.net, [addr])
            self.nodes.append(tmp)
        # connect nodes with probability linkprob
        for i in range(numnodes):
            for j in range(i + 1, numnodes):
                r = random.random()
                if r < linkprob:
                    if self.verbose:
                        self.info("linking (%d,%d)" % (i, j))
                    self.net.link(self.nodes[i].netif(0),
                                  self.nodes[j].netif(0))
            # force one link to avoid partitions (should check if this is needed)
            j = i
            while j == i:
                j = random.randint(0, numnodes - 1)
            if self.verbose:
                self.info("linking (%d,%d)" % (i, j))
            self.net.link(self.nodes[i].netif(0), self.nodes[j].netif(0))
            self.nodes[i].boot()
        # run the boot.sh script on all nodes to start Quagga
        for i in range(numnodes):
            self.nodes[i].cmd(["./%s" % self.nodes[i].bootsh])

    def compareroutes(self, node, kr, zr):
        """ Compare two lists of Route objects.
        """
        kr.sort(key=Route.key)
        zr.sort(key=Route.key)
        if kr != zr:
            self.warn("kernel and zebra routes differ")
            if self.verbose:
                msg = "kernel: "
                for r in kr:
                    msg += "%s " % r
                msg += "\nzebra: "
                for r in zr:
                    msg += "%s " % r
                self.warn(msg)
        else:
            self.info("  kernel and zebra routes match")

    def comparemdrlevels(self, nbrs, mdrs):
        """ Check that all routers form a connected dominating set, i.e. all
            routers are either MDR, BMDR, or adjacent to one.
        """
        msg = "All routers form a CDS"
        for n in self.nodes:
            if mdrs[n.routerid] != "OTHER":
                continue
            connected = False
            for nbr in nbrs[n.routerid]:
                if mdrs[nbr] == "MDR" or mdrs[nbr] == "BMDR":
                    connected = True
                    break
            if not connected:
                msg = "All routers do not form a CDS"
                self.warn("XXX %s: not in CDS; neighbors: %s" % \
                          (n.routerid, nbrs[n.routerid]))
        if self.verbose:
            self.info(msg)

    def comparelsdbs(self, lsdbs):
        """ Check LSDBs for consistency.
        """
        msg = "LSDBs of all routers are consistent"
        prev = self.nodes[0]
        for n in self.nodes:
            db = lsdbs[n.routerid]
            if lsdbs[prev.routerid] != db:
                msg = "LSDBs of all routers are not consistent"
                self.warn("XXX LSDBs inconsistent for %s and %s" % \
                          (n.routerid, prev.routerid))
                i = 0
                for entry in lsdbs[n.routerid].split("\n"):
                    preventries = lsdbs[prev.routerid].split("\n")
                    try:
                        preventry = preventries[i]
                    except IndexError:
                        preventry = None
                    if entry != preventry:
                        self.warn("%s: %s" % (n.routerid, entry))
                        self.warn("%s: %s" % (prev.routerid, preventry))
                    i += 1
            prev = n
        if self.verbose:
            self.info(msg)

    def checknodes(self):
        """ Check the neighbor state and routing tables of all nodes. """
        nbrs = {}
        mdrs = {}
        lsdbs = {}
        krs = {}
        zrs = {}
        v = self.verbose
        for n in self.nodes:
            self.info("checking %s" % n.name)
            nbrs[n.routerid] = Ospf6NeighState(n, verbose=v).run()
            krs[n.routerid] = KernelRoutes(n, verbose=v).run()
            zrs[n.routerid] = ZebraRoutes(n, verbose=v).run()
            self.compareroutes(n, krs[n.routerid], zrs[n.routerid])
            mdrs[n.routerid] = Ospf6MdrLevel(n, verbose=v).run()
            lsdbs[n.routerid] = Ospf6Database(n, verbose=v).run()
        self.comparemdrlevels(nbrs, mdrs)
        self.comparelsdbs(lsdbs)
        self.logdata(nbrs, mdrs, lsdbs, krs, zrs)