Welcome to Kytos/Tutorials documentation

Creating an L3 learning switch - Part 1

Overview

This tutorial will show the first steps to create an L3 learning switch NApp using Kytos (Kytos). The average time to go through this is: 20 min

What you will learn

  • How to unpack the Packet-In data looking for L3 information;
  • How to install proactive flows in switches using Kytos.

What you will need

Introduction

Layer 3

Layer 3 refers to the Network layer, the third one in the OSI Model. This layer is responsible for defining logical addresses and identification for network devices.

The IP protocol, used in the Internet for example, is the most relevant implementation for this layer. IP addresses, like ‘192.168.10.1’, are essentialy Layer 3 addresses.

The L3 learning switch

The Layer 3 learning switch will operate similarly to the Layer 2 learning switch, but working with IP addresses instead of MAC addresses.

The l3ls NApp will learn at which port each IP address is, and install reactive flows in switches to handle traffic based on source IP and destination IP. ARP packets will be just flooded to the network, because they have different headers.

Is this a router?

No. A router should be capable of switching packets between different networks, and to actively communicate with hosts using protocols such as ARP and ICMP. We will cover the steps to implement routing in future tutorials.

For this implementation of l3ls, we will consider that hosts are in the same logical network.

Note

We are also considering the addresses to be IPv4 addresses.

Attention

This NApp was designed for instructional purposes. Running it in production environments may lead to unwanted behavior.

Creating your NApp

First, create your NApp using the kytos command. Use ‘tutorial’ as the username and ‘of_l3ls’ as the NApp name, as follows (don’t forget to create the ~/tutorials folder if it does not exist):

$ cd ~/tutorials
$ kytos napps create
--------------------------------------------------------------
Welcome to the bootstrap process of your NApp.
--------------------------------------------------------------
In order to answer both the username and the napp name,
You must follow this naming rules:
 - name starts with a letter
 - name contains only letters, numbers or underscores
 - at least three characters
--------------------------------------------------------------

Please, insert your NApps Server username: tutorial
Please, insert your NApp name: of_l3ls
Please, insert a brief description for your NApp [optional]: This NApp does packet switching using L3 information

Congratulations! Your NApp have been bootstrapped!
Now you can go to the directory tutorial/of_l3ls and begin to code your NApp.
Have fun!

Start copying the main.py file from the L2 Learning Switch tutorial: you’ll change it to use L3 information instead of L2. Then, open it in your preferred editor to start coding your NApp.

$ cp tutorial/of_l2ls/main.py tutorial/of_l3ls/main.py

Create an L3 switching table

First of all, you will create an L3 table for each switch that connects to the controller. When a new switch is connected, Kytos generates a core.switch.new KytosEvent. Your NApp will have a method to deal with those.

The L3 table will be a Python dictionary, mapping IP addresses to switch ports.

@listen_to('kytos/core.switch.new')
def initialize_switch(self, event):
    switch = event.content['switch']
    switch.l3_table = {}

    # (Continues...)

Deal with the ARP packets

When you perform packet switchin with L3 information, you have to deal separately with ARP. ARP packets will be sent in the network with a source MAC and a destination MAC, enabling the L2 learning switch to exchange them properly, but they have different L3 headers and do not carry a source IP or destination IP.

The naive solution is to just flood ARP packets, in a ‘hub-like’ behavior, without even sending them to the controller. While the L2 learning switch is only reactive, the L3 switch will have proactive flows matching ARP.

@listen_to('kytos/core.switch.new')
def initialize_switch(self, event):
    # (...)

    arp_flow_mod = FlowMod()
    arp_flow_mod.command = FlowModCommand.OFPFC_ADD
    arp_flow_mod.match = Match()
    arp_flow_mod.match.dl_type = EtherType.ARP
    arp_flow_mod.actions.append(ActionOutput(port=Port.OFPP_FLOOD))
    event_out = KytosEvent(name=('tutorial/of_l3ls.messages.out.'
                                 'ofpt_flow_mod'),
                           content={'destination': switch.connection,
                                    'message': arp_flow_mod})
    self.controller.buffers.msg_out.put(event_out)

Change handle_packet_in to use L3 information

Updating the L3 table

To update the switch’s L3 table, you need to get the IPV4 information of the PacketIn sent to the controller. You’ll do it with one more unpack on the data field of the Ethernet frame. It’s important to check if it is an IPV4 packet before the unpack, to avoid errors. Once unpacked, you can add the source IP to the L3 table. The needed changes are shown below:

@listen_to('kytos/of_core.v0x01.messages.in.ofpt_packet_in')
def handle_packet_in(self, event):
    #(...)
    ethernet.unpack(packet_in.data.value)

    # Add the unpack here
    if ethernet.ether_type = EtherType.IPV4
        ipv4 = IPv4()
        ipv4.unpack(ethernet.data.value)

        in_port = packet_in.in_port.value
        switch = event.source.switch
        switch.l3_table[ipv4.source] = in_port
        log.info('Packet received from %s to %s.', ipv4.source,
                 ipv4.destination)

        # To look for the port, we will use the dictionary's get() method.
        dest_port = switch.l3_table.get(ipv4.destination, None)

Note

All further code will be inside the above if statement. Check the final main.py file in case of doubt.

Warning

As dest_port is now a single port and not a list, remove the subscript (from dest_ports[0] to dest_port) in all the code.

Installing FlowMods with L3 information

To use L3 information on flows, you will change two lines used while constructing the FlowMod message.

# flow_mod.match.dl_src = ethernet.source.value
# flow_mod.match.dl_dst = ethernet.destination.value
# Remove the lines above, using instead:
flow_mod.match.nw_src = ipv4.source
flow_mod.match.nw_dst = ipv4.destination

You don’t need to change the PacketOut code: it shall work the same.

Final main.py file

Now your main.py file shall look like the one below. Here we have all the needed imports, and comments were removed to improve readability.

from kytos.core import KytosEvent, KytosNApp, log
from kytos.core.helpers import listen_to
from pyof.foundation.network_types import Ethernet, EtherType, IPv4
from pyof.v0x01.common.action import ActionOutput
from pyof.v0x01.common.flow_match import Match
from pyof.v0x01.common.phy_port import Port
from pyof.v0x01.controller2switch.flow_mod import FlowMod, FlowModCommand
from pyof.v0x01.controller2switch.packet_out import PacketOut

from napps.tutorial.of_l3ls import settings


class Main(KytosNApp):
    def setup(self):
        pass

    def execute(self):
        pass

    @listen_to('kytos/core.switch.new')
    def create_switching_table(self, event):
        switch = event.content['switch']
        switch.l3_table = {}

        arp_flow_mod = FlowMod()
        arp_flow_mod.command = FlowModCommand.OFPFC_ADD
        arp_flow_mod.match = Match()
        arp_flow_mod.match.dl_type = EtherType.ARP
        arp_flow_mod.actions.append(ActionOutput(port=Port.OFPP_FLOOD))
        event_out = KytosEvent(name=('tutorial/of_l3ls.messages.out.'
                               'ofpt_flow_mod'),
                         content={'destination': switch.connection,
                                  'message': arp_flow_mod})
        self.controller.buffers.msg_out.put(event_out)

    @listen_to('kytos/of_core.v0x01.messages.in.ofpt_packet_in')
    def handle_packet_in(self, event):
        packet_in = event.content['message']

        ethernet = Ethernet()
        ethernet.unpack(packet_in.data.value)

        if ethernet.ether_type.value == EtherType.IPV4:
            ipv4 = IPv4()
            ipv4.unpack(ethernet.data.value)

            in_port = packet_in.in_port.value
            switch = event.source.switch
            switch.l3_table[ipv4.source] = in_port
            log.info('Packet received from %s to %s.', ipv4.source,
                     ipv4.destination)

            dest_port = switch.l3_table.get(ipv4.destination, None)

            if dest_port is not None:
                log.info('%s is at port %d.', ipv4.destination, dest_port)
                flow_mod = FlowMod()
                flow_mod.command = FlowModCommand.OFPFC_ADD
                flow_mod.match = Match()
                flow_mod.match.nw_src = ipv4.source
                flow_mod.match.nw_dst = ipv4.destination
                flow_mod.match.dl_type = ethernet.ether_type
                flow_mod.actions.append(ActionOutput(port=dest_port))
                event_out = KytosEvent(name=('tutorial/of_l3ls.messages.out.'
                                             'ofpt_flow_mod'),
                                       content={'destination': event.source,
                                                'message': flow_mod})
                self.controller.buffers.msg_out.put(event_out)
                log.info('Flow installed! Subsequent packets will be sent directly.')

            packet_out = PacketOut()
            packet_out.buffer_id = packet_in.buffer_id
            packet_out.in_port = packet_in.in_port
            packet_out.data = packet_in.data

            port = dest_port if dest_port is not None else Port.OFPP_FLOOD
            packet_out.actions.append(ActionOutput(port=port))
            event_out = KytosEvent(name=('tutorial/of_l3ls.messages.out.'
                                         'ofpt_packet_out'),
                                   content={'destination': event.source,
                                            'message': packet_out})

            self.controller.buffers.msg_out.put(event_out)

    def shutdown(self):
        pass

Running and testing your NApp

To run your NApp, you need to run Kytos first to enable NApp management. In another terminal window, make sure to activate your Development Environment and run:

$ kytosd -f
2017-07-25 14:45:35,135 - INFO [kytos.core.logs] (MainThread) Logging config file "/home/user/test42/etc/kytos/logging.ini" loaded successfully.
2017-07-25 14:45:35,137 - INFO [kytos.core.controller] (MainThread) /home/user/test42/var/run/kytos
2017-07-25 14:45:35,137 - INFO [kytos.core.controller] (MainThread) Starting Kytos - Kytos Controller
2017-07-25 14:45:35,139 - INFO [kytos.core.tcp_server] (TCP server) Kytos listening at 0.0.0.0:6633
2017-07-25 14:45:35,142 - INFO [kytos.core.controller] (RawEvent Handler) Raw Event Handler started
2017-07-25 14:45:35,144 - INFO [kytos.core.controller] (MsgInEvent Handler) Message In Event Handler started
2017-07-25 14:45:35,148 - INFO [kytos.core.controller] (MsgOutEvent Handler) Message Out Event Handler started
2017-07-25 14:45:35,148 - INFO [kytos.core.controller] (AppEvent Handler) App Event Handler started
2017-07-25 14:45:35,148 - INFO [kytos.core.controller] (MainThread) Loading Kytos NApps...
2017-07-25 14:45:35,153 - INFO [kytos.core.napps.napp_dir_listener] (MainThread) NAppDirListener Started...
2017-07-25 14:45:35,155 - INFO [kytos.core.controller] (MainThread) Loading NApp kytos/of_core
2017-07-25 14:45:35,612 - INFO [root] (kytos/of_core) Running NApp: <Main(kytos/of_core, started 140029615662848)>

(...)

kytos $>

As you can see, there is a log line indicating that kytos/of_core is running. You need the OpenFlow core NApp installed and enabled. It is possible to check it by running, in the previous terminal window:

$ kytos napps list

Status |          NApp ID          |                     Description
=======+===========================+======================================================
 [ie]  | kytos/of_core             | OpenFlow Core of Kytos Controller, responsible for...
 [i-]  | kytos/flow_manager        | Manage switches' flows through a REST API.
 [i-]  | kytos/of_l2ls             | An L2 learning switch application for OpenFlow swi...
 [i-]  | kytos/of_lldp             | Discovers switches and hosts in the network using ...
 [i-]  | kytos/of_stats            | Provide statistics of openflow switches.
 [i-]  | kytos/topology            | Keeps track of links between hosts and switches. R...

If the NApp is installed but not enabled, you can enable it by running:

$ kytos napps enable kytos/of_core

Now, install and run the of_l3ls NApp:

$ cd ~/tutorials
$ kytos napps install tutorial/of_l3ls
INFO  NApp tutorial/of_l3ls:
INFO    Searching local NApp...
INFO    Found and installed.
INFO    Enabling...
INFO    Enabled.

With the NApp installed and enabled, you can run Mininet to see it in action:

$ sudo mn -c ; sudo mn --controller remote --switch ovsk,protocols=OpenFlow10

Important

As no specific topology configuration was passed to Mininet, it will generate a virtual network with a switch connecting two hosts, 10.0.0.1 and 10.0.0.2.

Attention

Just like the l2ls implementation, this NApp will NOT work in topologies containing loops. However, it works with linear and tree topologies.

Now, in the Mininet console, run:

mininet> h1 ping h2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=83.3 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=66.6 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.495 ms
64 bytes from 10.0.0.2: icmp_seq=4 ttl=64 time=0.117 ms
64 bytes from 10.0.0.2: icmp_seq=5 ttl=64 time=0.114 ms

The pings are sucessful! Communication between the hosts is possible because the of_l3ls NApp has dealt with the Flows. You can check it by looking at the controller logs:

2017-07-25 16:04:07,150 - INFO [tutorial/of_l3ls] (Thread-88) Packet received from 10.0.0.1 to 10.0.0.2.
2017-07-25 16:04:07,165 - INFO [tutorial/of_l3ls] (Thread-90) Packet received from 10.0.0.2 to 10.0.0.1.
2017-07-25 16:04:07,166 - INFO [tutorial/of_l3ls] (Thread-90) 10.0.0.1 is at port 1.
2017-07-25 16:04:07,177 - INFO [tutorial/of_l3ls] (Thread-90) Flow installed! Subsequent packets will be sent directly.
2017-07-25 16:04:08,148 - INFO [tutorial/of_l3ls] (Thread-94) Packet received from 10.0.0.1 to 10.0.0.2.
2017-07-25 16:04:08,150 - INFO [tutorial/of_l3ls] (Thread-94) 10.0.0.2 is at port 2.
2017-07-25 16:04:08,163 - INFO [tutorial/of_l3ls] (Thread-94) Flow installed! Subsequent packets will be sent directly.

Once the flows are set in both directions, the switch sends the packets direclty. Good job!

Well done! What’s Next?

Congratulations for finishing this Kytos Tutorial.

If you want to see other tutorials, click here to go to our Tutorials page.