Adding a new Switch class to Mininet.

This is a quick post about making Mininet support new network objects, like switches.

Depending on the amount of features that a software switch (or other program) supports, and the degree to which you want to access those features through Mininet, adding a custom node type to Mininet can be a relatively simple task. For something as simple as a classic learning switch, this may be a matter of of implementing just a handful of methods within a child class of Switch.

An example: the LinuxBridge class

A good example of such a custom node is the LinuxBridge, prepackaged with Mininet and found in nodelib.py. This node, which implements a non-OpenFlow learning switch using the classic Linux bridge(8) device, just implements five methods, ignoring its constructor:

  • connected( self ):
    Returns True if the switch is connected to a controller or False if not. For a non-SDN switch, a connected state is when it is fully started up and therefore, ready for interaction. For the LinuxBridge, this is when brctl(8) shows that it is in the “forwarding” state.

  • start( self, _controllers ):
    Initializes and configures the software switch underlying the Mininet Switch object. For our example, this method invokes a series of brctl commands to initialize the switch, add ports, and to apply the necessary configurations (e.g. enable STP) to get it to the connected state. An OpenFlow switch such as OVS would also be configured to connect with a set of controllers passed into this method.

  • stop( self, deleteIntfs=True ):
    Carries out the teardown and removal of the underlying software switch backing the Mininet object. This method is the complement to start.

  • dpctl( self, *args ):
    Invokes the configuration utility for the software switch, parameterized to point to the particular instance associated with the Mininet object. In our case, this is, again, brctl.

  • setup( cls ):
    Checks that all prerequisites are met in order to start up and configure the software switch. This may entail checking that certain kernel modules are loaded, packages like bridge-utils are installed, and certain features, such as the firewall, are configured to allow the switch to function.

A bit of detail to keep note of is that the Switch class does its own housekeeping in its __init__ and stop methods, so these should be called from your custom class as well:

class LinuxBridge( Switch ):

    def __init__( self, name, stp=False, prio=None, **kwargs ):
        # snipped out initialization things here
        Switch.__init__( self, name, **kwargs )

    # snip...

    def stop( self, deleteIntfs=True ):
        # snipped out cleanup things here
        super( LinuxBridge, self ).stop( deleteIntfs )

Specifically, __init__ would be tied to initializing any major lightweight virtualization functions and facilities for sending commands to the process(es) underlying the switch object, and stop, the teardown and cleanup of the interfaces on the switch.

This meant that, by implementing the above methods in a IfBridge class, I was able to quickly put together a learning switch using if_bridge(4) and ifconfig(8) for my experimental Mininet port – barring one easily circumvented but odd bit of behavior. As a convention, custom nodes usually end up in nodelib.py, so that is where I ended up adding my IfBridge node.

Integrating a custom element into `mn`

mn allows you to launch a Mininet topology from the shell, and is one of the first things that the Mininet quickstart has you run to sanity check an install. It’s also handy for launching a topology to test with, without having to write a Python script. Among the options that mn comes with is the class of various network elements (switches, links, etc) to use when building the topology:

$ mn --help
Usage: mn [options]
(type mn -h for details)

The mn utility creates Mininet network from the command line. It can create
parametrized topologies, invoke the Mininet CLI, and run tests.

Options:
  -h, --help            show this help message and exit
  --switch=SWITCH       default|ivs|lxbr|ovs|ovsbr|ovsk|user[,param=value...]
                        ovs=OVSSwitch default=OVSSwitch ovsk=OVSSwitch
                        lxbr=LinuxBridge user=UserSwitch ivs=IVSSwitch
                        ovsbr=OVSBridge
  --host=HOST           cfs|proc|rt[,param=value...]
                        rt=CPULimitedHost{'sched': 'rt'} proc=Host
                        cfs=CPULimitedHost{'sched': 'cfs'}
...

Adding your custom Switch to this list is a matter of a few changes in mn. mn keeps a mapping between the various Node and Link classes and their aliases (e.g. default for OVS, lxbr for LinuxBridge …). So in my case, I have my IfBridge custom switch, which I aliased to ‘ifbr’ in the SWITCHES map:

SWITCHES = { 'user': UserSwitch,
             'ovs': OVSSwitch,
             ...
             'ifbr': IfBridge }

At this point, your custom class should be displayed alongside the other choices in the help message:

  --switch=SWITCH       default|ifbr|ivs|lxbr|ovs|ovsbr|ovsk|user[,param=value
                        ...] ovs=OVSSwitch lxbr=LinuxBridge user=UserSwitch
                        ivs=IVSSwitch default=OVSSwitch ovsk=OVSSwitch
                        ovsbr=OVSBridge ifbr=IfBridge

The rest of the script more or less sanity-checks the combination of classes that had been chosen during invocation, before launching a Mininet object. The sanity checks done for each network element type depends on their features. For example, if --switch=default is specified, mn checks for an OpenFlow controller, since the OVS switch would need a controller to connect to. If a non-functional dummy controller (--controller=none) had been used in the invocation, it will fall back to using bridge-mode OVS (ovsbr) even if ‘default’ was chosen for the switch.

In the case of my custom ‘ifbr’ switch, since it is a good-old-fashioned learning switch, the only extra work I needed to do was to add it to the list of non-OpenFlow switch types that are available for use with the ‘none’ controller:

    def begin( self ):
        "Create and run mininet."
        # snip...
        if not self.options.controller:
            if not CONTROLLERS[ 'default' ]:
            # snip...
                elif self.options.switch not in ( 'ovsbr', 'lxbr', 'ifbr' ):
                    raise Exception( "Could not find a default controller "
                                     "for switch %s" %
                                     self.options.switch )

At this point, I can run mn with my custom switch as so:

sudo ./mn --switch=ifbr --controller=none --test=pingall

A generic intro to switchwork.

This post introduces switchwork in a very general way; specifically, it’s aimed at people attempting to configure/administer their first switches. The takeaway from this post, hopefully, is a modus operandi for switch configuration, and a handful of keywords that might come in handy when searching for more information.

Background

I was asked the following question: “Given very little to begin with, how do I go about configuring/administering a switch? What is actually involved in switch work, and what are some keywords to look for?”

The scenario is this: You have just a basic knowledge of networking (e.g what OSI layers are), and the Linux command line. The switch staring back at you is the first of the (many makes and models of) “serious” network device you’re about to mess with. It is a layer 2/3 switch with a CLI, serial management interface, and at least a dozen ports. The switch has a wide range of features from STP to port mirroring and even a DHCP sever – Given that you know the features exist, and you know how to enable them. Which brings us to…

The Problem

You’ve found a manual, but it’s written under the assumption that the reader already knows a thing or two about administering networks, knows what needs to be done, and is just looking for the “how”. Manuals can be pretty useless until you have a knowledge base of what the options are and why they are there. Even when you have a high-level idea of what needs to be done, searching a manual can still be a pain when you aren’t aware of certain keywords or general procedures.

Bridging (some) gaps

After thinking a bit, I came up with this list of steps/notes on how to begin configuring a typical switch. Note, this answer was put together with the idea of exposing the various aspects of switchwork without focusing on just one make or model.

  1. Many switches don’t come with networked services enabled. You’ll likely have to connect to the switch using a serial (RS-232) cable and a client program supporting serial communication, such as HyperTerminal, Minicom, or C-Kermit. There is usually a labeled console port (RJ-45 or DB-9) on the front faceplate of a switch that you can hook a cable to.

  2. To actually connect via serial, expect to configure some settings on your client. Switches seem to favor:
    • baud rate: either 9600 or 115200 bps (e.g if one garbles output, try the other). 9600 seems to be more popular.
    • carrier detection off
    • no parity bits
    • 8 data 1 stop bit (8N1)

    You’ll likely have to specify a serial port, usually something like /dev/ttyS0 on Linux  or /dev/cuau0 on FreeBSD. Using C-kermit, the commands at your terminal prompt might amount to (for a Quanta LB9A):

        kermit -l /dev/ttyS0
        set carrier-watch off
        set baud 115200
        connect

  3. Some switches (e.g. some Netgear models) may not use a serial interface, but rather, auto-configure itself with a static IP. In this case, you can hook an Ethernet cable to your switch, and even likely, point a browser to its GUI.

  4. Once you’re connected, you’ll either have to enter a default username/password to get to, or be dropped straight into, the CLI. Sadly little can be said about the CLI that is generic:
    • There are usually various modes for various administration tasks. The default is a non-privelaged read-only mode that lets you see a limited set of system status and configurations. An ‘enabled’ mode with more privilege (similar to becoming root) may exist to allow you to configure a switch. From this enabled mode you can enter a configuration mode that actually lets you change things, such as enabling non-serial access methods, such as SSH or Telnet.
    • Considering the dozens, if not hundreds, of ports that these things can have, many CLIs (but not all) have a range syntax for configuring multiple ports at once. An example is the range keyword used by Cisco IOS and the firmware for NEC’s IP8800 series L2/L3 switches.
    • Often times, configuration changes have to be explicitly saved with a command before exiting “enabled” mode. Some CLIs will give you a visual hint (like an exclamation point) indicating there are changes that need saving.

    For example, on the IP8800/S3640, logging in, turning multiple ports into trunk ports, saving the settings, and logging off involves the following steps:

        login: operator

        > enable
        # configure
        (config)# interface range gi 0/45-48
        (config-if-range)# switchport mode trunk
        !(config-if-range)# save
        (config-if-range)# exit
        (config)# exit
        # disable
        > exit

  5. Management-wise, many switches and routers can be thought of as a *nix box with a bunch of network interfaces. For example, Quanta’s LB9A, NEC’s IP8800-series, and Juniper’s MX-80 run firmwares that are based on, or incorporate, Linux, NetBSD, and FreeBSD, respectively. Some may even give you the option to drop into a shell from the CLI or at boot up, or execute UNIX-flavored commands (The IP8800 even came with ed, a minimal text editor!).

  6. A part of configuration/maintenance will include updating or installing new firmware on (“flashing”) the switch. The procedures vary wildly with make and model. Some switches may be updated from the CLI (e.g. the IP8800, with its ‘ppupdate’ command), but some will require more invasive measures such as updating and configuring the boot loader. For example, enabling OpenFlow on the LB9A involves copying the new firmware to its CF Card (via FTP, from its Linux shell) and pointing its boot loader (in this case, U Boot) to the image location manually upon reboot.

  7. An improperly flashed/updated switch may potentially be rendered nonfunctional (“bricked”), so it is important to keep interruption of this process to an absolute minimum.