A firewall you review like code.

Songbird is an open-source network firewall for Linux. Policy is YAML in a Git repository, the data plane is eBPF on the NIC, and a change reaches production the way code does: as a validated, reviewed commit.

Status: design phase, no release yet. The requirements, the architecture, eighteen decision records and the configuration schema are public; the code is written as a consequence of those documents.

rulesets/internal-to-untrusted.yaml +7 −0
apiVersion: "config.songbird-fw.com/v1alpha1"kind: "RuleSet"metadata:  name: "internal-to-untrusted"spec:  from: "internal"  to: "untrusted"  rules:    - name: "block-printers-internet"      source: ["printers"]      action: "drop"      log: "all"    - name: "lan-web-dns"      comment: "Only web, DNS and ping out"      source: ["grp-lan"]      destination: ["any"]      service: ["web", "dns", "ping"]      action: "accept"      nat: { type: "snat" }
schema: 43 objects, 0 errors 2 approvals merge applies the RuleSet atomically

Why another firewall

Community firewalls ask you to click through a web interface and trust that the last backup is recent. Enterprise platforms give you clean operations and charge per seat, per feature, per year. Songbird keeps the operational model of the enterprise appliance (zones, named objects, one RuleSet per zone pair, first match wins) and puts it in files you can diff, review, and revert.

Default deny, written down
Traffic between two zones is dropped unless a RuleSet says otherwise. An empty RuleSet is a valid, explicit statement (rules: []), and the schema ships a negative corpus of 49 configurations that must fail validation. schema/examples/invalid
eBPF on the interface
XDP decides, the kernel forwards, TC rewrites. A dropped packet never becomes a socket buffer; NAT happens on the hooks that can see both directions. Connection tracking is Songbird's own, so a policy change can decide what happens to flows that are already open. ADR-0002 · ADR-0003 · ADR-0004
Rollback is git revert
The applied configuration is a commit. The schema is versioned (config.songbird-fw.com/v1alpha1) with conversion rules, so a previous commit is always loadable, and the appliance image is A/B updatable. ADR-0001 · ADR-0008 · ADR-0017

Where the packet goes

Every IPv4 frame on a physical interface is parsed, pre-filtered, matched against connection tracking and classified in XDP, before the kernel allocates anything for it. Only accepted packets continue into the stack; NAT is applied by TC programs that read the decision XDP left in packet metadata, so nothing is looked up twice.

NIC in → XDP ingress (parse, prefilter, conntrack, policy) → XDP_PASS with metadata → TC ingress (DNAT, reverse SNAT) → kernel forwarding → TC egress (SNAT) → NIC out. XDP_DROP leaves the pipeline with an event. NIC ingress XDP ingress · physical interface parse prefilter conntrack policy VLAN · PPPoE · IPv4 only established → pass · new → RuleSet(from, to) first match wins · decision + NAT flags → XDP metadata XDP_PASS + metadata TC ingress DNAT · reverse SNAT kernel routing · forward TC egress SNAT NIC egress XDP_DROP + event → ring buffer · ipv6, no RuleSet, policy drop

Kernel 6.6 or newer. TC programs attach with TCX (link-managed, ordered, detachable without qdisc surgery); XDP native where the driver supports it, generic as the fallback. ADR-0013

Atomic apply. A new configuration is compiled into a fresh generation of maps and switched in one step; flows that no longer match a rule are handled according to the policy-change decision, not left to time out silently. ADR-0004

Honest scope. IPv6 is deferred to a later release and is dropped, with an event, rather than passed through unfiltered. FQDN objects are accepted by the schema but cannot yet be enabled in a rule. Data plane design

Documents first, then code

The repository ships its reasoning before its binaries. Every artefact below exists today, is versioned, and changes through pull requests like everything else will.

songbird-fw/songbird-fw · state as of October 2026
ArtefactWhat it fixesState
Requirements Analysis Stakeholders, glossary, constraints, 90-odd requirements each with a verification criterion, explicit exclusions, traceability. 2.0.6-draft in team review
Project Plan Release roadmap, scope of MVP 0.1, milestones, work breakdown, risks, process rules. 0.2.2-draft for team decision
Architecture Domain model, component view, reconciliation loop, eBPF data plane with its pipeline, conntrack and NAT design. data plane 3.3-draft for review
Decision records 18 ADRs, one file per decision, never deleted: YAML in Git as source of truth, forwarding model, own conntrack, kernel floor, Go control plane, cilium/ebpf, Hadron as appliance OS, CRD-style objects. 7 accepted 11 proposed
Configuration schema JSON Schema 2020-12 for config.songbird-fw.com/v1alpha1: Address, AddressGroup, Service, Interface, Zone, RuleSet, Color, System. Example configuration and a negative corpus. 43 valid objects 49 cases that must fail
Test Plan How each requirement is verified: unit, eBPF, integration, benchmark; the coverage matrix from requirement to test. 0.1.1-draft milestone M0

Where help is useful right now

No code to compile yet means the most valuable contributions are the ones that are usually hardest to get: a second pair of eyes on a decision, a configuration that should not validate, a NIC that does not behave.

  • Review a proposed ADR

    Eleven decisions are still proposed. The kernel floor (ADR-0013), the DHCP and DNS engines (ADR-0014) and the capacity limits (ADR-0006) want opinions from people who run networks.

  • Break the schema

    Write a configuration that should be rejected and is not. Each one becomes a numbered case in schema/examples/invalid and a test the validator must keep failing.

  • Report your hardware

    Native XDP depends on the NIC driver. Which cards and kernels you run, and what ethtool says about them, is the evidence behind the reference platform (ADR-0007).

  • Edit the documents

    Right now the documents are the product. An ambiguous requirement or a diagram that does not match the text is a real bug; file it like one.