In a previous article I introduced Snabb, a toolkit for developing network functions. In this article I want to dive into some practical examples on how to use Snabb for network function programming.

The elements of a network function

A network function is any program that does something with traffic data. There’s a certain set of operations that can be done onto any packet. Operations such as reading, modifying (headers or payload), creating (new packets), dropping or forwarding. Any network function is a combination of these primitives. For instance, a NAT function consists of packet header modification and forwarding.

Some of the built-in network functions featured in Snabb are:

  • lwAFTR (NAT, encap/decap): Implementation of the lwAFTR network function as specified in RFC7596. lwAFTR is a NAT between IPv6 and IPv4 address+port.
  • IPSEC (processing): encryption of packet payloads using AES instructions.
  • Snabbwall (filtering): a L7 firewall that relies on libnDPI for Deep-Packet Inspection. It also allows L3/L4 filtering using tcpdump alike expressions.

Real-world scenarios

The downside of by-passing the kernel and taking full control of a NIC is that the NIC cannot be used by any other program. That means the network function run by Snabb acts as a black-box. Some traffic comes in, gets transformed and it’s pushed out through the same NIC (or any other NIC controlled by the network function). The advantage is clear, outstanding performance.

For this reason Snabb is mostly used to develop network functions that run within the ISP’s network, where traffic load is expected to be high. An ISP can spare one or several NICs to run a network function alone since the results pay off (lower hardware costs, custom network function development, good performance, etc).

Snabb might seem like a less attractive tool in other scenarios. However, that doesn’t mean it cannot be used to program network functions that run in a personal computer or in a less demanding network. Snabb has interfaces to Tap, Raw socket and Unix socket programming, which allows to use Snabb as a program managed by the kernel. In fact, using some of these interfaces is the best way to start with Snabb if you don’t count with native hardware support.

Building Snabb

In this tutorial I’ll cover two examples to help me illustrate how to use Snabb. But before proceeding with the examples, we need to download and build Snabb.

$ git clone https://github.com/snabbco/snabb
$ cd snabb
$ make

Now we can run the snabb executable, which will print out a list of all the subprograms available:

$ cd src/
$ sudo ./snabb
Usage: ./snabb <program> ...

This snabb executable has the following programs built in:
  config
  example_replay
  example_spray
  firehose
  ...
  snsh
  wall

For detailed usage of any program run:
  snabb <program> --help

If you rename (or copy or symlink) this executable with one of
the names above then that program will be chosen automatically.

Hello world!

One of the simplest network functions to build is something that reads packets from a source, filters some of them and forwards the rest to an output. In this case I want to capture traffic from my browser (packets to HTTP or HTTPS). Here is how our hello world! program looks like:

#!./snabb snsh

local pcap = require("apps.pcap.pcap")
local PcapFilter = require("apps.packet_filter.pcap_filter").PcapFilter
local RawSocket = require("apps.socket.raw").RawSocket

local args = main.parameters
local iface = assert(args[1], "No listening interface")
local fileout = args[2] or "output.pcap"

local c = config.new()
config.app(c, "nic", RawSocket, iface)
config.app(c, "filter", PcapFilter, {filter = "tcp dst port 80 or dst port 443"})
config.app(c, "writer", pcap.PcapWriter, fileout)

config.link(c, "nic.tx -> filter.input")
config.link(c, "filter.output -> writer.input")

engine.configure(c)
engine.main({duration=30})

main.exit(0)

Now save the script and run it:

$ chmod +x http-filter.snabb 
$ sudo ./http-filter.snabb wlp3s0

While the script is running I open a few websites in my browser. Hopefully some packets will be captured onto output.pcap:

$ sudo tcpdump -tr output.pcap
IP sagan.50062 > 54.239.17.7.http: Flags [P.], seq 0:926, ack 1, win 229, length 926: HTTP: GET / HTTP/1.1
IP sagan.50062 > 54.239.17.7.http: Flags [.], ack 189, win 237, length 0
IP sagan.50062 > 54.239.17.7.http: Flags [.], ack 368, win 245, length 0
IP sagan.37346 > 93.184.220.29.http: Flags [S], seq 370675941, win 29200, options [mss 1460,sackOK,TS val 1370741706 ecr 0,nop,wscale 7], length 0
IP sagan.37346 > 93.184.220.29.http: Flags [.], ack 2640726891, win 229, options [nop,nop,TS val 1370741710 ecr 2287287426], length 0
IP sagan.37346 > 93.184.220.29.http: Flags [P.], seq 0:439, ack 1, win 229, options [nop,nop,TS val 1370741729 ecr 2287287426], length 439: HTTP: POST / HTTP/1.1
IP sagan.37346 > 93.184.220.29.http: Flags [.], ack 789, win 251, options [nop,nop,TS val 1370741733 ecr 2287287449], length 0

Some highlights in this script:

  • The shebang line (#./snabb snsh) refers to the Snabb’s shell (snsh), one of the many subprograms available in Snabb. It allows us to run Snabb scripts, that is Lua programs that have access to the Snabb environment (engine, apps, libraries, etc).
  • There’s a series of libraries that where not loaded: config, engine, main, etc. These libraries are part of the Snabb environment and are automatically loaded in every program.
  • The network function instantiates 3 apps: RawSocket, PcapFilter and PcapWriter, initializes them and pipes them together through links forming a graph. This graph is passed to the engine that executes it for 30 seconds.

Martian packets

Let’s continue with another example: a network function that manages a more complex set of rules to filter out traffic. Since there are more rules I will encapsulate the filtering logic into a custom app.

The data we’re going to filter are martian packets. According to Wikipedia, a martian packet is “an IP packet seen on the public internet that contains a source or destination address that is reserved for special-use by Internet Assigned Numbers Authority (IANA)”. For instance, packets with RFC1918 addresses or multicast addresses seen on the public internet are martian packets.

Unlike the previous example, I decided not to code this network function as an script, but as a program instead. The network function lives at src/program/martian. I’ve pushed the final code to a branch in my Snabb repository:

$ git remote add https://github.com/dpino/snabb.git dpino
$ git fetch dpino
$ git checkout -b dpino/martian-packets

To run the app:

$ sudo ./snabb martian program/martian/test/sample.pcap
link report:
   3 sent on filter.output -> writer.input (loss rate: 0%)
   5 sent on reader.output -> filter.input (loss rate: 0%)

The functions lets pass 3 out of 5 packets from sample.pcap.

$ sudo tcpdump -qns 0 -t -e -r program/martian/test/sample.pcap
reading from file program/martian/test/sample.pcap, link-type EN10MB (Ethernet)
00:00:01:00:00:00 > fe:ff:20:00:01:00, IPv4, length 62: 145.254.160.237.3372 > 65.208.228.223.80: tcp 0
fe:ff:20:00:01:00 > 00:00:01:00:00:00, IPv4, length 62: 65.208.228.223.80 > 145.254.160.237.3372: tcp 0
00:00:01:00:00:00 > fe:ff:20:00:01:00, IPv4, length 54: 145.254.160.237.3372 > 65.208.228.223.80: tcp 0
90:e2:ba:94:2a:bc > 02:cf:69:15:81:01, IPv4, length 242: 10.0.1.100 > 10.10.0.0: ICMP echo reply, id 1024, seq 0, length 208
90:e2:ba:94:2a:bc > 02:cf:69:15:81:01, IPv4, length 242: 10.0.1.100 > 10.10.0.0: ICMP echo reply, id 53, seq 0, length 208

The last two packets are martian packets. They cannot occur in a public network since their source or destination addresses are private addresses.

Some highlights about this network function:

  • Instead of a filtering app, I’ve coded my own filtering app, called MartianFiltering. This new app is the responsible for determining whether a packet is a martian packet or not. This operation has to be done in the push method of the app.
  • I’ve coded some utility functions to parse CIDR addresses (such as 100.64.0.0/10) and to check whether an IP address belongs to a network. Instead I could have used Snabb’s filtering library that allows to filter packets using tcpdump like expressions. For instance, “net 100.64.0.0 mask 255.192.0.0”.
  • The network function doesn’t use a network interface to read packets from, instead it reads packets out of a .pcap file.
  • Every Snabb program has a run function, that is the program’s entry point. A Snabb program or library can also add a selftest function, which is used to unit test the module ($ sudo ./snabb snsh -t program.martian). On the other hand, Snabb apps must implement a new method and optionally a push or pull method (or both, but at least one of them).

Here’s the app’s graph:

config.app(c, "reader", pcap.PcapReader, filein)
config.app(c, "filter", MartianFilter)
config.app(c, "writer", pcap.PcapWriter, fileout)

config.link(c, "reader.output -> filter.input")
config.link(c, "filter.output -> writer.input")

And here is how MartianPacket:pull method looks like:

function MartianFilter:push ()
   local input, output = assert(self.input.input), assert(self.output.output)

   while not link.empty(input) do
      local pkt = link.receive(input)
      local ip_hdr = ipv4:new_from_mem(pkt.data + IPV4_OFFSET, IPV4_SIZE)
      if self:is_martian(ip_hdr:src()) or self:is_martian(ip_hdr:dst()) then
         packet.free(pkt)
      else
         link.transmit(output, pkt)
      end
   end
end

As a rule of thumb, in every Snabb program there’s always one app only that feeds packets into the graph, in this case the PcapReader app. Such applications have to override the method pull. Apps that would like to manipulate packets will have a chance to do it in their push method.

Summary

Snabb is a very useful tool for coding network functions that need to run at very high speed. For this reason, it’s usually deployed as part of an ISP network infrastructure. However, the toolkit is versatile enough to allow us code any type of application that has to manipulate network traffic.

In this tutorial I introduced how to start using Snabb to code network functions. In a first example I showed how to download and build Snabb plus a very simple application that filters HTTP or HTTPS traffic from a network interface. On a second example, I introduced how to code a Snabb program and an app, MartianFiltering. This app exemplifies how to filter out packets based on a set of rules and forward or drop packets based on those conditions. Other more sophisticated network functions, such as firewalling, packet-rate limiting or DDoS prevention attack, behave in a similar manner.

That’s all for now. I left out another example that consisted of sending and receiving Multicast DNS packets. Likely I’ll cover it in a followup article.