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