Skip to main content

Snabbwall's Firewall App: L7Fw

· 5 min read
Asumu Takikawa

Recently, I've been helping out with the development of the Snabbwall project at Igalia. Snabbwall is a project to develop a layer 7 firewall using the Snabb framework.

The project is graciously sponsored by the NLNet Foundation.

A layer 7 firewall, for those just tuning in, is a firewall that can track and filter packets based on their state/contents at the application-level (layer 7 in the OSI model). Comparatively, a more traditional firewall (think iptables-based firewalls like UFW) typically makes decisions at layer 3 or 4. For example, such firewalls may not be able to pinpoint and block arbitrary Bittorrent traffic since those packets may be hard to distinguish from other TCP traffic.

(see my colleague Adrián's blog post introducing Snabbwall for more background)

Most of the guts of Snabbwall were already built in 2016: a libndpi FFI binding for LuaJIT and a Snabb scanner app (L7Spy) that uses the FFI binding for deep packet inspection.

The remaining development milestone was to build a small app (that we call L7Fw) that would consult the packet scanner, apply some firewall rules, and take care of management duties such as logging.

The design of the firewall suite assumes that the Snabb app network will be set up so that an L7Spy instance will scan packets before L7Fw sees them. The scanner communicates packet data flow information to the firewall, which consists of a protocol name (e.g., HTTP or BITTORRENT) and how many packets are included in the flow.

Note that the set of detectable protocols will depend on the packet scanner that is used. L7Fw itself is agnostic about what kind of scanner is used by L7Spy (in practice only one is implemented so far---an nDPI scanner).

The point of the firewall app is to be able to make forwarding decisions based on this scanner information. For example, a user may wish to express a rule like "drop the packet if at least 5 packets are detected in a Bittorrent flow, and only if the destination IP is outside of this subnet".

To accommodate such rules, the app lets the user write the firewall rules using pflang (which I've conveniently already written about a fair amount on this blog). Specifically, the firewall rules can be written using the pfmatch DSL augmented with some firewall-specific information.

The example Bittorrent rule as a pfmatch expression is illustrated in the following firewall policy table:

{ -- pfmatch rule to block bittorrent traffic
BITTORRENT = [[match {
flow_count >= 5 and not dst net 192.168.1.0 => drop
otherwise => accept
}]],
-- catch-all rule
default = "accept" }

The table is indexed by the name of detected protocols. The mapped values are firewall policy strings, such as "accept" to forward the packet to the next app.

When a pfmatch expression is given, as in the first rule above, it is compiled into a matching function (that can call accept, reject, and drop methods) and applied to the relevant packets.

The L7Fw app is intended for use in custom Snabb deployments in which you may install the firewall in front of other Snabb apps (either off the shelf or your own bespoke ones) as needed.

For testing purposes, however, Snabbwall comes with a snabb wall filter program that lets you test out firewall rules to see how they will work. Like the snabb wall spy program, it can take input from a pcap file or a network interface.

For example, if we put the rules above in a file firewall-rules.lua, we can invoke the firewall on some test data from the Snabbwall repo like this:

$ sudo ./snabb wall filter -p -4 192.168.1.1 -m 12:34:56:78:9a:bc\
-f firewall-rules.lua pcap program/wall/tests/data/BITTORRENT.pcap

(assuming your $PWD is the src directory of the Snabb repo)

which will print out a report like the following:

apps report:
l7fw
Accepted packets: 0 (0%)
Rejected packets: 0 (0%)
Dropped packets: 53 (100%)

If you run it on a non-bittorrent data file like program/wall/tests/data/http.cap you'll see different results:

apps report:
l7fw
Accepted packets: 43 (100%)
Rejected packets: 0 (0%)
Dropped packets: 0 (0%)

Supplying the -l option will log blocked packets to the system log in a format similar to other Linux firewalls:

snabb[27740]: [Snabbwall DROP] PROTOCOL=BITTORRENT MAC=00:03:ff:3e:d0:dc SRC=10.10.10.22 DST=10.10.10.23

This could be useful if you are deploying your own firewall with L7Fw and want to monitor it.

Implementation

The app implementation itself is pretty straightforward. The most complicated code in the app is the method that constructs responses for "reject" rules. A reject rule (as in the corresponding iptables target) will drop a packet like the "drop" rule but will also send an error packet back to the sender. The error response is either an ICMP packet or a TCP RST packet, depending on the original input packet.

That means there's some fiddly code that has to deal with constructing various packets with the correct fields. Luckily, Snabb comes with some libraries for constructing protocol headers which simplifies the code considerably.

It's still easy to mess it up though. Before I wrote my unit test suite (I really should have followed the function design recipe and written my tests first), some of the ICMPv6 packets had an ethernet frame that indicated that they were IPv4 packets instead. I only caught the bug because my unit tests failed.

Future plans

With this milestone, the Snabbwall project is close to wrapping up the work funded by the NLNet folks. The next step is to write a user guide with details on how to set up your own firewall solution.

If you'd like to try the firewall out, the docs for the Snabb app are available on the Snabbwall website. The code is hosted on Github here.