Playing with the Erlang Linc Switch

Published 12-19-2014 00:00:00

Introduction

Linc is a software switch in Erlang, which support Openflow 1.4.

Here we will try to install it and make it run in a simple scenario. Later we will try to use it with an openflow compatible Controller (maybe Opendaylight or FlowER).

Installation

$ git clone https://github.com/FlowForwarding/LINC-Switch.git
$ sudo apt-get install libpcap0.8-dev
$ cd LINC-Switch
$ cp rel/files/sys.config.orig rel/files/sys.config
$ make

Note that at some point during installation, root privileges are required to use the setcap utility. This is needed to give the admin_net capability to the erlang binary.

We can notice that Erlang dependencies are:

  • enetconf: enetconf is a NETCONF 1.0/1.1 server implemented in Erlang.. NETCONF is a new XML-based protocol aimed at replacing the old SNMP protocol
  • of_protocol: This is an OpenFlow Protocol library implemented in Erlang.
  • pkt: An Erlang network protocol library.

Configuration

Edit the rel/linc/releases/1.0/sys.config file to configure 2 ports.

{capable_switch_ports,
  [
    {port, 1, [{interface, "tap0"}, {type, tap}]},
    {port, 2, [{interface, "tap1"}, {type, tap}]}
  ]}

Enable connection to the simple controller and assign port to the logical switch:

{controllers,
  [
         {"Switch0-DefaultController", "localhost", 6633, tcp}
  ]}

:::erlang
{ports, [
         {port, 1, [{queues, []}]},
         {port, 2, [{queues, []}]}
        ]}

First launch

First, launch a simple OpenFlow controller:

$ erl -pa apps/*/ebin deps/*/ebin

Compile it

1> c("scripts/of_controller_v4.erl").
2> rr(of_protocol), rr(ofp_v4).
[...]
3> {ok, CtrlPid} = of_controller_v4:start(6633).

We now have a simple OpenFlow controller running.

Now, we can launch the switch

$ ./rel/linc/bin/linc console

You should see messages between the switch and the controller.

Even if the cap_net_admin capability is set on the beam.smp binary, on my current installation, I have to launch the switch as root to create TAP interfaces.

Clean Flow tables

By default, the simple controller send multiple message to the switch. For example a flow table called flow_table_0 is populated with some entries:

To see the rules on the switch:

> ets:tab2list(linc:lookup(0, flow_table_0)).

From the controller, create an OpenFlow message to delete all entries from all tables.

%% Get the switch connection
> {ok, [Conn|_]} = of_controller_v4:get_connections(CtrlPid).
> RemoveAllMsg = of_controller_v4:remove_all_flows().
> of_controller_v4:send(CtrlPid, Conn, RemoveAllMsg).

Let use scapy to send packets on TAP interfaces

Installation:

$ sudo apt-get install scapy

Here we create a new ethernet frame encapsulating our new protocol (type 0xAAAA). The new protocol has a fixed size of 10 bytes enclosed by brackets for the destination.

>>> etherType = 0x4141

>>> eAlice = Ether()
>>> eAlice.type = etherType
>>> eAlice.payload = "[BOB     ]Hello BOB"
>>> sendp(eAlice, iface="tap0")

Use tcpdump to check that this ethernet frame is sent to the tap0 interface:

$ sudo tcpdump -i tap0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tap0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:20:41.154103 00:00:00:00:00:00 (oui Ethernet) > Broadcast, ethertype Unknown (0x4141), length 33: 
    0x0000:  5b42 4f42 2020 2020 205d 4865 6c6c 6f20  [BOB.....]Hello.
    0x0010:  424f 42                                  BOB

First OpenFlow rule

Alice is on the tap0 interface side and Bob on tap1 interface side. We want the switch to forward ethernet frames from Alice to Bob based on the identifier of the destination.

We want the rule to be: * If a packet is on tap0 and first 10 bytes are [BOB ] then forward this packet to tap1 * If a packet is on tap1 and first 10 bytes are [ALICE ] then forward this packet to tap0

To create a rule, we need to create a FlowMod message.

The #ofp_* records are defined in ofp_v4.hrl file of the of_protocol project.

%% Match on the ethernet_type field
EtherTypeMatch = #ofp_field{
    class = openflow_basic,
    name = eth_type,
    value = <<16#41,16#41>>,
    mask = undefined
}.

%% Match on packet coming from port 1
SrcMatchPort1 = #ofp_field{
    class = openflow_basic,
    name = in_port,
    value = <<1>>,
    mask = <<"">>
}.

%% Send packet to port 2
SendToPort2 = #ofp_action_output{
    port = 2,
    max_len = 64
}

%% Add rule to table 0
%% Send all packets with ethernet type 0x4141 to port 2
Rule1 = #ofp_flow_mod{
    cookie = <<0:64>>,
    cookie_mask = <<0:64>>,
    table_id = 0,
    command = add,
    idle_timeout = 30000,
    hard_timeout = 60000,
    match = #ofp_match{fields = [SrcMatchPort1, EtherTypeMatch]},
    instructions = [#ofp_instruction_write_actions{actions = [SendToPort2]}]
}.

Message = #ofp_message{
    version = 4,
    xid = 100,
    body = Rule1
}.

of_controller_v4:send(CtrlPid, Conn, Message).

Here, we create and send a message from the controller to the switch:

  • the message is of type flow_mod which modifies the state of the switch,
  • the command is add, to add a new rule to the table,
  • there is 2 match rules, one to match on packets coming on port 1, and the other is to match on ethernet frames with type 0x4141 both rules have to match for this rule to apply,
  • there is one action to forward matching packet to port 2.

We are now ready to make our first check:

$ sudo tcpdump -i tap1 # Listen on Bob's side

Then send the packet with scapy:

>>> sendp(eAlice, iface="tap0")

The message is coming on the tap1 interface:

17:04:01.834366 00:00:00:00:00:00 (oui Ethernet) > Broadcast, ethertype Unknown (0x4141), length 33: 
    0x0000:  5b42 4f42 2020 2020 205d 4865 6c6c 6f20  [BOB.....]Hello.
    0x0010:  424f 42                                  BOB

Now we need to find a way to match on the first ten bytes to get the destination.

By looking at the OpenFlow 1.3.4 Specification, section 7.2.3.8 Headers Match Fields, we can read that we can match only on a set of pre-defined headers. For example, on a VLAN ID, an IPv6 destination address or a label in a MPLS header.

So, the journey ends here. To achieve our use case (matching bytes on a non standard protocol) maybe we need to look at P4