Skip to content

Instantly share code, notes, and snippets.

@0xjac
Last active December 7, 2016 14:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xjac/91e84acd381cbc8ea645f13366c39c8e to your computer and use it in GitHub Desktop.
Save 0xjac/91e84acd381cbc8ea645f13366c39c8e to your computer and use it in GitHub Desktop.
Help for the Assignment 2 of Advance Networking.

Adv-Ntw: Assignment 2 Help

Hello, I found that this assignment had quite a bit of overhead to setup everything properly. The information below should facilitate your work with vagrant, Mininet and Ryu. I however will not provide any help on how to do the actual assignment.

If anything is unclear or if something does not work, don't hesitate to comment below.

Disclaimer

If you find the information below helpful and use part or all of it for your assignment, I would appreciate if you could mention it in your submission. Either just by indicating my name or the link to this gist.

Vagrant

Project folder

If your project is located in a folder outside the sdn-course-vm git repo, you can easily make your project folder in the vm by editing the Vagrantfile as follow:

Append at the end of the file:

config.vm.synced_folder "<host/path/to/project>", "<absolute/path/on/vm>"

For example my project is named vlbexperiment so I added the following:

config.vm.synced_folder "~/usi/an/advanced-networking-exercises/ex2/vlbexperiment", "/vlbexperiment"

SSH as root

Your program will need to run as root for Mininet to work. You can easily authorize root to ssh in the machine with the same key by copying the authorized_keys from vagrant to root.

This can be done by adding the following to your 'Vagrantfile' (I added it after config.ssh.forward_x11 = true):

 config.vm.provision "shell",
    inline: "sudo cp /home/vagrant/.ssh/authorized_keys /root/.ssh/"

You will need to run vagrant reload for the changes to take effect

You can then connect as root from the command line:

vagrant ssh -- -l root

More importantly if you use PyCharm you can add a remote interpreter. When you add the Vagrant interpreter (Make sure you have the Vagrant plugin enabled): you should see the Vagrant Host URL as ssh://vagrant@127.0.0.1:2222.

You can use this information to add a second remote interpreter by going to the PyCharm preferences -> Project: <your_project_name> -> Project Interpreter

  • Click on the little cogs on the right to Add Remote.
  • Select SSH Credentials and fill in:
    • Host: 127.0.0.1
    • Port: 2222
    • Username: root
    • Auth type: Key pair
    • Private key file: path to the vagrant IdentityFile (this can be found using the vagrant ssh-config command)
    • Passphrase: Leave blank
    • Python interpreter: Leave as is

Once the interpreter is added, don't forget to add the path mapping. You will need to explicitly add the path for the vagrnat folder:

  • Local Path: path/to/sdn-course-vm
  • Remote Path: /vagrant

As well as the path to your project:

  • Local Path: <host/path/to/project>
  • Remote Path: <absolute/path/on/vm>

In my case:

  • Local Path: ~/usi/an/advanced-networking-exercises/ex2/vlbexperiment
  • Remote Path: /vlbexperiment

You can then go to your run configurations and change the interpreter.

Mininet

Always ensure Mininet is stopped

If you start Mininet (net.start()) but fail to stop it (net.stop()), for example because an exception was thrown before it could be stopped, you could get errors such as RTNETLINK answers: File exists.

To avoid such errors, use Mininet within a context manager which always stops the network in the end, even if an exception is raised. This is somewhat similar to Java's autoclosable.

Just put the code for the context manager in some util module:

from contextlib import contextmanager

from mininet.net import Mininet


@contextmanager
def mininet_network(*args, **kwargs):
    net = None
    try:
        net = Mininet(*args, **kwargs)
        yield net
    finally:
        if net is not None:
            net.stop()

Then you can call Mininet as follow:

from mininet.cli import CLI

from vlbexperiment.util import mininet_network


with mininet_network(topo=topo, switch=switch, controller=controller) as net:
    net.start()
    CLI(net)

Try to keep your code within the context as small as possible, just to run the pings. You can see that I create the topology and the controller before.

In case the interpreter crashes, net.stop() may not actually be called. In this case, simply run sudo mn --clear to clear remaining networks.

Building the topology

Switch datapath id

Each switch will need a datapath id which is generated by default from the first number found in the canonical name of
the switch. Because the Abilene switch names do not contain numbers, this will not work.

Instead you can keep the Abilene names as canonical names for the switch and specify a custom datapath id for each switch. This datapath id needs to be string containing a hexadecimal digits without the 0x prefix.

I've open a issue about this strange behaviour. In the meantime, make sure you pass the datapath id as a string of an hexadecimal number without the 0x prefix.

This can be done as follow:

topo = Topo()

for router in network_graph.routers_data:
    topo.addSwitch(router.name, dpid=hex(router.id)[2:])

where router.id is a simple integer.

You can use the router name to add the links, as follow:

for link in network_graph.links:
    topo.addLink(link.x, link.y, port1=link.port_x, port2=link.port_y)

where link.x and link.y are the names of two routers.

In Ryu however, the controller only knows the datapath id, so make sure you know which datapath id belong to which router.

Host IP

To facilitate the routing, I assign custom IP addresses to all my hosts (I add one host per switch). You can specify the ip of a host as follow:

topo.addHost(router.name + '_h', ip=router.host_ip)  # router.host_ip = '192.168.1.12'

Note that hosts don't have datapath ids, so no need to worry about that.

Controller

I am using a Ryu controller as shown in class. Mininet as a specific Ryu controller which will autmatically start said controller with ryu-manager for you.

Just import the Ryu controller for mininet and pass it the name and the arguments, which is at least the path to the python module containing your Ryu controller (in my case: vlbexperiment/controller.py). Then simply pass the controller to Mininet:

from mininet.node import Ryu


controller = Ryu('ryu_c1', 'vlbexperiment/controller.py')
with mininet_network(topo=topo, switch=switch, controller=controller) as net:
    # ...

Static ARP

Full credit to Giacomo for this one.

To simplify your controller and avoid handling ARP resolution, you can statically set the ARP tables. This can be done by calling the staticARP method on your network:

net.staticArp()

Parsing ping

Mininet has a method _parsePingFull to parse the outpout of a ping command and return a tuple containing the min, avg, max and mdev values as floats. This is the same method use internally by the pingFull, pingPairFull and pingAllFull methods of mininet.net.Mininet.

This is a "private" method for some reason but you can use it without problem as it is a simple static method.

Ryu

Passing information to the Ryu controller

Your Ryu controller will run inside a separate process, it's own Ryu app which is a wrapper around Python.

This makes it a bit more difficult to communicate with the rest of your application, for example to pass the flow modifications from the application to the controller.

One solution is to use a config file to pass options to the controller. For that purpose, Ryu uses oslo.config to parse config files using the ini syntax.

The way the parse works however is that the structure of the file needs to be specified beforehand. This makes it difficult to pass configuration related to unknown network topologies. For this reason I use this file to pass the path to another file containing my flow modifications. This works as follow:

Main application

In the main application we need to first create the config file:

from ConfigParser import RawConfigParser


config = RawConfigParser()
config.add_section('switches')
config.set('switches', 'rules_file', os.path.abspath(rules_filename))

with open(config_filename, 'wb') as config_file:
    config.write(config_file)

This will create the following file:

[switches]
rules_file = <path/to/rules/file>

Then we need to pass it to our Ryu controller using the --config-file argument (see doc).

If you the mininet.node.Ryu controller as explained above, you can pass the config file by modifying your call to the controller as follow:

from mininet.node import Ryu


controller = Ryu('ryu_c1',
                 '--config-file', os.path.abspath(config_filename),
                 'vlbexperiment/controller.py')

The Ryu controller may cd into another directory so just to be safe, make sure you provide the absolute path to your config file.

Ryu Controller

In the controller we will read the config file. First we need to specify the structure of the file using oslo.config.

For my config file:

[switches]
rules_file = <path/to/rules/file>

I defined it as such just above my controller class definition:

from oslo_config import cfg

switches_group = cfg.OptGroup(name='switches', title="Switches configuration")
switches_opts = [cfg.StrOpt(name='rules_file', default="rules_json_file",
                            help='JSON files with switches rules')]

If you have other options in your config file, have a look at the doc of oslo.config on how to define them. It's quite straightforward.

I then register the group and options with Ryu's instance of oslo.config:

from ryu.cfg import CONF as RYU_CONF


RYU_CONF.register_group(switches_group)
RYU_CONF.register_opts(switches_opts, switches_group)

Then within the constructor of my controller, I can simply read my file containing the flow modifications as follow:

with open(RYU_CONF.switches.rules_file) as json_file:
    self.rules = json.load(json_file)

I just use a simple JSON file and dump the content to a custom attribute of my controller.

Matching packets

Credits to Giacomo for explaining this to me.

If you want to match an ip address in your controller. Specifying the IP is not enough, you also need to specify the type of the packet (to match an IP packet), this can be done as:

from ryu.lib.packet.ether_types import ETH_TYPE_IP

OFPMatch(ipv4_dst='<ip_address>', eth_type=ETH_TYPE_IP)

or if you use OpenFlow 1.0:

from ryu.lib.packet.ether_types import ETH_TYPE_IP

OFPMatch(nw_dst='<ip_address>', dl_type=ETH_TYPE_IP)

Depending on what you want to match, have a look here to know which fields to use.

Debugging the Ryu controller

Because the Ryu Controller runs in its own process, you may not notice any output to stdout, stderr or if the controller crashes.

If you run the controller from Mininet as mentionned before:

from mininet.node import Ryu


controller = Ryu('ryu_c1',
                 '--config-file', os.path.abspath(config_filename),
                 'vlbexperiment/controller.py')

Then a log file with all the output will be created automatically. The log are available on the VM at: /tmp/<controller_name>.log, in this case: /tmp/ryu_c1.log.

Plotting the results

I am not there yet so I will update this at a later point but I am thinking of using plotly to generate my chart. It has nice python bindings. (Credit to Jesper for showing me plotly.)

@giacomo-dr
Copy link

In order to avoid to deal with ARP resolution I used the following code:

net.start()
net.staticArp()

that sets ARP tables statically in each host.

It simplifies everything a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment