Changelog what changed, release by release.

All notable changes are listed here. The project follows semantic versioning; the language version (version 1 in rule files) is separate from the tool version.

  1. v0.3.0 latest 17 October 2026 6 changes
    • Debian and Ubuntu packages are published to an apt repository: apt install moatfw after adding apt.moatfw.dev (see the install instructions). Every release lands there.
    • Packages name moat maintainers maintainers@moatfw.dev as their maintainer.
    • moat status notices when the pinned counters, events or variables map was removed by hand, and names the missing map, as it already did for sets and the configuration.
    • vlan.id matches with rx VLAN offload on, when the kernel is 6.8 or newer, the program runs in native mode and the driver reports the tags it strips (veth, mlx5, ice): moat load then asks the driver for the tag and loads the program bound to the interface. In the other cases load still warns, and moat doctor --iface says which case applies.
    • moat now needs Go 1.24 to build from source (cilium/ebpf v0.21).
    • stats, status, var list and the set listings wait while a load, reload or set change is running on the interface, so they never show one program's rule names with another program's counters.
  2. v0.2.0 16 October 2026 25 changes
    • moat import iptables also reads the PREROUTING chains of the raw and mangle tables, where anti-DDoS rulesets usually live, in the order the kernel runs them. -m string becomes payload contains, and chains it does not read are listed.
    • moat import iptables --ipsets FILE reads ipset save output and writes the ipsets the rules use as set files, loaded with the rules.
    • moat import nftables translates the JSON of nft -j list ruleset: the filter base chains on the ingress, prerouting and input hooks, named sets, meters, limits and raw payload matches.
    • moat test --model runs case files and captures in a model of the program written in Go: no kernel, no root, any system. Off Linux, moat test uses it on its own. The differential tests check it against the kernel packet by packet.
    • moat doctor --iface says whether the interface already runs an XDP program, and whether moat attached it. moat load explains a program another tool attached instead of failing with "device or resource busy". In a namespace entered with nsenter, doctor finds the namespace's interfaces.
    • moat explain says, rule by rule, why each case's packet got its verdict: the test that failed and what the packet had instead.
    • moat fmt --check lists the files that are not in canonical form and exits with 1, for CI.
    • Fixed: moat load after the interface was removed and came back (what the unit moat enable installs does) failed with "already loaded"; it now attaches afresh and keeps the sets, as reload does too.
    • Fixed: a failure after the program was attached (writing the manifest) removed the pins of the maps the running program uses.
    • Fixed: a reload that failed could leave the running program with the new file's set contents; shared maps now change only once the new program runs.
    • Fixed: a variable kept across a reload could hold a value the new rules cannot use; it goes back to the file's value, with a note.
    • Fixed: set sync into a set too small to hold the old and new entries at once failed; it now removes the leaving entries first in that case.
    • Fixed: set add without --ttl could shorten a permanent entry to the set's ttl; like a rule's add, it never shortens an entry now.
    • Fixed: moat trace running across a reload waited forever; it ends and says why. stats --watch follows moat audit changes.
    • Fixed in moat import: a non-terminating iptables target such as SET or MARK counted as an accept, so a final DROP after it was left out; ! --icmp-type any lost its negation; -p udp alone (and ! -p tcp) now becomes ip.proto, which holds for fragments as iptables does; an ipset with a netmask now writes networks; an nft match after a limit or set statement stays a comment; input the importers refuse exits with 2.
    • Fixed: ::ffff:0:0/96 in a set file became 0.0.0.0/0 and matched every IPv4 packet; the whole IPv4-mapped range now stays IPv6, as it does in a rule.
    • Fixed: a limit with a variable rate accepted a burst of 0 or one so large that the burst in nanoseconds overflowed, and dropped every packet; both are refused now, and so is a packet rate above 1e9/s next to a variable burst.
    • Fixed: on IPv6, ip.len wrapped to a small number for the largest payload lengths; it is now held at 65535.
    • Fixed: moat test --pcap ignored the case file's [vars] and audit, and its [[case]] entries; it applies the first two now and refuses the cases. In case files, a 0 written for a port, TTL or window is 0.
    • moat load warns, and moat doctor --iface notes, when the interface strips VLAN tags before XDP (rx VLAN offload), which makes vlan.id rules never match.
    • Fixed: moat import nftables left meter NAME { ip saddr limit rate over R } as a comment, the form nft 1.0.9 prints; it becomes a limit per source.
    • Fixed: the C backend could pass garbage arguments to a block of rules that clang proved can never match, and the kernel refused the program.
    • moat lsp completes: declaration keywords, std modules, fields, protocols, sets, constants, variables, TCP flags and actions, each where it can go.
    • moat lsp offers quick fixes: a misspelled field or name becomes the one moat check suggests, and a prefix with host bits set its network.
    • moat lsp finds the references to a set, constant, variable or rule, and renames one everywhere in the file.
  3. v0.1.0 15 October 2026 13 changes

    First public version.

    • The moat language: protocol tests, header fields for Ethernet, VLAN, IPv4, IPv6, TCP, UDP and ICMP, payload slices, constants, lists and ranges, prefix and address sets, pass, drop, limit and add.
    • moat check with static warnings (unused names, conditions that are never true, rules that never match, misleading not, small bursts) and moat fmt.
    • moat build compiles to a BPF object with no compiler involved: moat generates the instructions and writes the object file itself. --backend c compiles through C and clang instead, and --emit-c writes the C for reading. Random packets through both builds of random rule files must agree on every verdict.
    • moat test runs the program in the kernel against case files with simulated time, or against pcap and pcapng captures.
    • moat load, reload, unload, status, audit, set, stats, enable and disable attach rules to an interface and manage them.
    • Runtime variables (var, moat var set) change rates and switch rules on and off without reloading.
    • Standard modules: use std.bogons, std.private_sources, std.amplification, std.tcp_sanity.
    • moat trace shows or captures the packets a rule decides, and moat bench times the rules per packet.
    • moat metrics prints or serves counters, sets and variables in the Prometheus format.
    • moat import iptables translates the INPUT chain of iptables-save output and keeps what it cannot translate as comments.
    • moat lsp, a VS Code extension and a tree-sitter grammar.
    • payload contains "text" (and not contains) finds text or bytes anywhere in a window of the payload.
    • moat doctor checks that a machine can run moat, and releases include Debian and RPM packages named moatfw.

The same list is in CHANGELOG.md in the repository.