I built an ansible-managed router and firewall solution called clammy-ng using only modern Linux tooling… and it’s pretty good! Did I mention it works on ARM?

Craving a new router solution

I was a Ubiquiti EdgeRouter fan for years. The series provided Amazing capability for the price, and the OS being based on a fork of Vyatta made it all that much more appealing.

However, in recent years my opinion began to sour as it’s become evident that priority on this product line is going way. Ubiquiti’s focus is clearly oriented around their USG platform. I don’t blame them for it. It’s a better market alignment for them. The EdgeRouter series isn’t a good fit for SMB’s and they don’t provide enough support for enterprise.

Despite them being amazing capability for the price, they’re still pretty pricey and their CPUs are slow. Most performance relies on hardware offload capabilities, so CPU oriented tasks like VPN solutions, and bonding can have an impact. (Disclosure: LACP performance when I upgraded to an ER-6P was fine.) A cheap ARM SBC like the NanoPi R5S could deliver the same performance for my use case.

My goal was retire my ER-6P and replace it with low-cost Armbian powered SBC. The core requirements were simple:

  • ansible managed
  • use mainline kernel
  • support zone a zone firewall configuration

Approach

Use modern components when possible

Basically I wanted to avoid getting bogged down in managing long orchestration scripts whenever possible. When Vyatta/VyOS was made, we didn’t have netplan, networkd, frrouting, nftables, etc. Consequently, it has a ton of code to directly manage interfaces, bird, and iptables. I wanted to offload those burdens as much as possible.

Another part of off-loading those burdens, was to try to find as many off-the-shelf ansible roles as possible to manage those modern services. Shout-out to mrlesmithjr as he had flexible roles for many of the components I needed.

Configuration

Since I’m using ansible (the point of this project), this means managing the configuration in a sane vars structure. The biggest part of this was creating a structure that could describe most aspects of an interface and its networks in a single place. I could then just reference components of that again later in the configuration as needed.

I decided to use my foomuuri_networks object as my main source of truth.

In the example snippets below you can see how I referenced other attributes of the network later in the configuration

foomuuri_networks:
  test1:
    interface: test1
    description: test network 1
    zone: test1z
    address: "192.168.101.1"
    netmask: "255.255.255.0"
    address_summarized: "192.168.101.1/24"
    dhcp:
      start: 192.168.101.100
      end: 192.168.101.150

lan_netplan_configuration:
  vlans:
    test1:
      id: 1001
      link: bond0
      addresses:
        - "{{ foomuuri_networks.test1.address_summarized }}"

dnsmasq_dhcp_scopes:
  - interface: "{{ foomuuri_networks.test1.interface }}"
    netmask: "{{ foomuuri_networks.test1.netmask }}"
    start: "{{ foomuuri_networks.test1.dhcp.start }}"
    end: "{{ foomuuri_networks.test1.dhcp.end }}"
    lease_time: "4h"

Challenges

The firewall

Solving the firewall the way I wanted it to be solved was the biggest part of the project.
Most existing tools and scripts in the wild were interacting with iptables, and sometimes even required legacy mode. I really wanted something strictly nftables native if possible.

To further my pickiness–I also really really really wanted a zone firewall.

Firewalld

I had originally assumed firewalld was going to be my path since it’s a “zone firewall” and has ansible modules to support it. The reality is, its a host firewall and not a router firewall. To be a router firewall I need firewall policies that are oriented around traffic traverse between 2 interfaces, rather than traffic between an interface and the host. It is possible to do it with firewalld, but it requires writing lots of custom rules in XML and managing that. No thanks.

Foomuuri

Foomuuri is the salvation of this project. If you take away anything from this blog, it’s please check out Foomuuri. Timing wise I was really lucky. Foomuuri is quite new, originating around Feb 2023… and I happened to come across it a few months later where it had just been added into debian unstable packaging. You don’t need a debian package in reality. Foomuuri is an extremely elegant python script with minimal dependencies.

Foomuuri can function as a host firewall, and most importantly for this project… It can function as a router firewall.

Its configuration language is flexible, and easy to template. Like a good *nix tool, the configuration files can be broken up and arranged in anyway the user sees fit.

I made a foomuuri ansible role for managing it’s installation and configuration.

The code

It consists of the clammy-ng ansible project reference implementation repo and the clammy-ng ansible collection containing a filter plugin, roles such as my foomuuri role to implement the solution.

Components used

My primary components right now include:

  • dnsmasq
  • foomuuri
  • frrouting
  • inadyn
  • netplan
  • networkd
  • nftables
  • wireguard

Check it out!

See the readme for demos, screenshots, and notes about other quality of life packages and scripts provided.

PRs Welcome

This was targeted for my use case, but I think it’s a flexible start for others. Feel free to fork or PR enhancements. Share your crazy configurations to inspire others!