-
Housecarl v0.2.0-rc.4+git81-9b680ee
Pre-releaseAll checks were successfulrelease / release (push) Successful in 1m5sreleased this
2026-10-25 18:52:07 +00:00 | -81 commits to main since this releaseAdded
-
Sets from Jarl (docs/jarl.md). A set
can hold the addresses of names in Jarl instead of listing them:
every A and AAAA record of each name, CNAMEs followed (within Jarl's
zones, in the same view), one view per name (NAME@VIEW; a name more
than one view covers must say which). housecarld reads them through
Jarl's API everyjarl.refresh(a minute) and after each change, and
when they change applies the ruleset again, without a history entry
(audited asset.jarl). A set uses only the addresseswithinthe
zones (their connected networks, which grow with the zone) and
networks it names; others are left out and warned of (jarl_outside).
While Jarl can't be read, sets keep the addresses they had, saved in
the database for restarts and in the boot ruleset (jarl_unreachable).
Names that give nothing say why (jarl_name: no record, a wildcard
only, a CNAME out of Jarl or in a loop, below a delegation, a secondary
zone, more than one view).hc set add --jarl NAME@VIEW --within ZONE,
hc set jarl SET [--refresh]; the kind "from Jarl" and Addresses… on
the Sets page;GET /sets/{name}/jarl,POST /sets/{name}/jarl/refresh.
Housecarl's Jarl token needsrecords:readfor the zones. -
An administrator can make an API token for another user in the web
UI: New token on the user's row under Users (the CLI'shc token create --userand the API could already). -
Ban lists (docs/bans.md). A program (Guard, which
reads logs, or fail2ban) bans an address, of every port or of some,
for a time; the ban list says what the address is kept from: the
firewall itself (firewall), some hosts behind it (hosts, port
forwards to them included), oreverything. Bans are dropped ahead of
every rule, established connections too, and the banned address's
connections are closed. IPv6 addresses are banned by /64 (group_v6).
Never banned: inside networks, the firewall's own addresses, whoever
asks, and the list'snever. Bans end by themselves, are cut to the
list's longest time, survive restarts and reboots, and are audited, but
aren't configuration (no history, probation or changesets).hc banlist,hc bans,hc ban,hc unban; Traffic → Bans in the web UI;
/banlistsand/banlists/{ref}/bansin the API; abansgrant that
can name lists (bans:write:web), in theviewerandoperator
roles. -
A fail2ban action,
contrib/fail2ban/action.d/housecarl.conf
(installed under/usr/share/doc/housecarl/contrib/): fail2ban's bans
go into a ban list, with the jail's port and protocol. -
Failed logins in the journal. A failed login and a refused API
token are logged (msg="login failed" ip=… user=…), for Guard and
fail2ban to act on. -
Quota usage as a share. What a quota used shows its percentage
(12.3 GB of 50.0 GB used (24%), each address's in the quota
dialog), on the Rules page, the dashboard and inhc quotas. -
Reset one address's quota, or give some back.
hc rule reset-quota ID --address ADDRstarts only that device's allowance
again;--give 5gbytestakes that much off what was used instead (one
address's, or everyone's): extra allowance until the next reset,
without changing the rule. In the web UI, a rule's quota is one
line (what it used, how it resets) that opens a dialog with the rest:
usage per address, last and next reset, and Reset and Give back for
everyone or one address.POST /rules/{ref}/reset-quotatakesaddressandgive. -
Names beside inside addresses. Connections, the log, quota usage,
the dashboard's quotas and the statistics' devices and blocked
sources show each inside address's name, found by a reverse lookup:
laptop 192.168.1.20in the web UI,192.168.1.20 (laptop)inhc.
Only inside addresses are looked up (an inside zone's networks, and
private ones outside the external zones'). Names are cached for 10
minutes and never stored.names.enabled,names.resolverand
names.cachein housecarl.yaml;hc --no-names;POST /names. -
Quotas on the dashboard. With any rule holding a quota, the
dashboard lists them all: the allowance, what is used (for a
per-address quota, how many addresses and the busiest) with a bar, and
the next reset. Each opens its rule. -
Links to a rule open it. A rule named in the matrix, the log, the
tracer, statistics or the dashboard's busiest rules opens the Rules
page on its chain, scrolled to it and highlighted (#/rules?rule=ID).
The Rules page shows each rule's ID (#410, ashcand labels name
it) beside its order. -
A clearer zone matrix. A cell reads allow, block, or partly
(some new connections let through, the rest not), with the exceptions
counted by what they do (+3 -1instead of+4), and what they let
through: services with the addresses they're limited to (ssh from trusted,5 port forwards), under the grid inhc matrix, in each
cell on the Matrix page. A zone to itself is marked (only traffic routed
between its interfaces passes the firewall). The details list each
rule's hits. API:summary,allowed,blocked,open,
same_zone,note,hits, andshortandhitsper exception.
Raw rules that are a match and then accept, drop or reject (without
interfaces) are read instead of shown as unknown. -
Shadow mode (
shadow.enabledin housecarl.yaml; docs/shadow.md):
Housecarl's ruleset is loaded beside the firewall you have but blocks,
rejects and translates nothing. Drops and rejects are counted and accept
instead (an accept only ends Housecarl's chain), policies accept and
always log what they would drop, NAT only counts, MSS clamping and the
flowtable are left out, and raw rules that do more than accept, drop or
reject are left out with a warning (shadow_raw_left_out). Comments
mark what would happen ([would drop]).shadow.position: Housecarl's
filter chains run after the other firewall's (filter + 10, the
default: what it would drop is what switching would break) or before
(- 10); its NAT chains always first. Every change is checked by
nftables in both forms, so leaving shadow mode can't fail. No
probation; takeovers and closing connections are refused (409). The
boot ruleset is the shadow one, and its fallback filters nothing. When
a reload (flush ruleset) deletes Housecarl's table, housecarld puts it
back (it listens to nftables' netlink notifications).modein
/status,/healthzand/auth/whoami; a banner in the web UI;
would-be drops marked in the log (shadow) and the tracer
(would_drop);hc ruleset --enforcing(GET /ruleset?enforcing=1)
shows the ruleset for after.shadow_unprotectedwarns when no other
table filters the input hook;nftables_serviceandtakeover_pending
aren't raised in shadow mode. The e2e test imports in shadow mode
before taking over. -
Zone names at import:
hc import --zone enp2s0=lan(repeat;
interfaces given one name share a zone; Zone names on the Import
page), and a note listing each zone and where its name came from.
Defines holding a VLAN sub-interface (enp2s0.20) name their zone,
and_ifname,_deviceand_nicare taken off define names too
(docs/import.md, "How things are named"). -
Import: sets with
flags timeoutbecome dynamic sets; a DNAT of
several ports becomes one port forward per port.
Changed
- Moving a rule several places is one change. The Rules page has a
Move… button that puts a rule before any other of its chain (or at
the end) at once, and in a changeset a move right after a move of the
same rule takes its place, so ↑ three times stages one operation (and
moving it back where it was takes the operation out). - Statistics' "Busiest rules" (the Statistics page,
hc stats,
GET /stats/summary) lists only rules and NAT objects. Built-ins and
policies have their own list ("Built-ins and policies",builtinsin
the API): the established built-in passes most of the traffic, so it
topped the list every time and made every rule's bar look empty. hc user deleteasks first, as every other delete does, and takes
-y/--yesnot to; it deleted the user and their tokens at once.- The ruleset's diff (Changes,
hc changeset diff,--dry-run) names
the chain each part is in on its@@line (@@ -40,6 +40,8 @@ chain forward), asgit diffnames the function, so a rule added deep in a
long chain says whether it is in input, forward or output. - History shows the ruleset's diff. An entry's page (and
hc history diff ID,GET /history/{id}/diff) shows what the change did to the
ruleset, for entries made before this version too: the configuration
before and after it is got back from the current one and rendered by
this version. - Negating a match is said first: the zone, From and To fields (rules
and NAT) start with "any of" / "any but" instead of a checkbox under
the list, shown once the list has an entry, and an "any but" list is
marked with a red bar. An empty list is never saved as negated (it
matched everything anyway, but would have negated what was added to
it later). - docs/rules.md: "Matching everything but a few", on leaving some
addresses out with a zone and "any but", and what to do when the range
isn't a zone. - The rule dialog shows only the zone fields its chain can use: no
"Going out on" for input and early, no "Coming in on" for output
(they could only be refused). A zone left in a field the chain hides
isn't sent. - History lists a changed setting by its own fields
(builtins.drop_invalid: false → true), on the web page and inhc history show, instead of the wholebuiltinsbefore and after. - Set names starting with
ban_are now reserved (set_name_reserved):
ban lists' sets use them. - Rules read as a direction. A match's zones are written
lan → wan,
any but wan → any, or with the firewall itself as the other end
(lan → firewallin input,firewall → wanin output), instead of
in lan · out wanandin not wan; exclusions readfrom any but X.
On the Rules and NAT pages and inhc; the flags and JSON stay as
they were.
Fixed
- Adding an entry that is already in a dynamic set gives it the new
timeout, counted from now; it kept its old one (nftables keeps an
element's timeout when it is added again). Steward's captive portal
renews its clients' entries this way. - housecarld used a lot of CPU while it logged many packets (a rule with
logging on, hit by a flood or a scan; more so in shadow mode): once
the in-memory log held its 2000 packets, each new one copied all of
them, twice (about 0.2 ms and 420 KB per packet). The log is now a
ring buffer (about 35 ns per packet). - Statistics link source NAT and redirects (and ban lists) to their page,
as they did rules and port forwards; a NAT link highlights its row. - Link-local addresses (
fe80::/10) are refused in the IPv6 antispoof
list and in your prefixes (antispoof_link_local). Dropping them on
external zones dropped the ISP's router adverts and DHCPv6 replies
before the built-ins could accept them: the IPv6 default route expired
and, later, the delegated prefix would have. A configuration saved
with one keeps being applied, but the next change is refused until it
is taken out.hc import nftleaves them out, with a note. - An IPv4-mapped prefix (
::ffff:0:0/96) is refused with an error that
says so; it was refused as "has host bits set; the prefix is invalid
Prefix". hc forward showshows the forward's hits and comment, and its
hairpin asno,yesoryes, from …(it printedno -).- A history entry's ruleset diff showed the object it changed as moved:
stepping back put the old form at the end of its list, so updating
one port forward (turning hairpin off) also showed its forward and
DNAT lines moving down. The diff is now only what the change did, and
its sides are namedbefore Nandafter Ninstead ofnow. - Ruleset diffs numbered the hunks after one with a moved line too early
on the new side (@@ -76,9 +74,9 @@for+76,9), so the chain named
after@@could be the one before (chain outputfor a hunk in
chain dstnat). go-udiff miscounts them; Housecarl now numbers them
itself. - The dashboard's bar for a per-address quota showed only the busiest
address, so one address that used up its allowance filled it (100%).
The bar is now all the addresses together, and the dashboard and the
rule's quota line say how many addresses used theirs up. - Changing how a quota counts lost or muddled what it used: per address
to a total started at nothing, per source to per destination kept the
sources' use as if it were the destinations', and other groups left
the old entries behind. Now a total starts at the addresses' sum,
wider groups add up their addresses, and what can't be carried starts
again with a warning (quota_restarts) on the change. - What the quotas used is saved after every change, not only every five
minutes: a reboot soon after a change that counts a quota differently
could bring back its usage counted the old way. - Statistics' Quotas card linked every entry to the Rules page, not to
its rule; each now opens its rule, named. Per-address quotas were
missing from it (and from thequota:rNseries): they count their
addresses' sum. hc stats top deviceslisted each device twice (once for what it
received, once for what it sent); it is one row with both again.hc rule add --before/--after/--firstmade two changes (create at the
end, then move), so two history entries, two probations, and the rule
briefly in force at the end of its chain; staged in a changeset, the
place was lost. The create now takes the place (POST /rules?before=ID,after,first) and makes one change.hc matrixin a terminal: the colours' escape codes threw the columns
out of line.hc counters(GET /counters) kept listing the objects of a
configuration replaced since housecarld started (hc import --replacegives every object a new ID), beside the current ones; the
totals of objects that no longer exist are now forgotten.- Import: two base chains on one hook had their rules imported twice,
and the second chain's leading built-ins were moved above the first
chain's rules; they now follow each other by priority (read in a
fixed order), with the stricter policy, and a note. - Import: a rule inside a jumped-to chain that stays raw kept only its
own text and so applied to everything; the jump's condition now goes
in front. A jump and a rule matching the same field are combined only
when one's addresses lie within the other's; else the rule stays raw.
A jump's ports, ICMP types, connection status and hop limit were
dropped; a limit or log on the jump is no longer silently lost. - Import: a second match on the same field (
ip saddr A ip saddr != B)
replaced the first;!=on the hop limit and TCP flags was read as
==. Such rules now stay raw, as written. - Import: a forward-chain accept to a forwarded host and port without
ct status dnatwas folded into the port forward, which only lets
forwarded connections through; it stays a rule now. An IPv6 accept
limited to an interface no longer becomes a pinhole open to all. - Import: raw rules naming a set of the old table made nftables refuse
the ruleset; address sets are now renamedNAME_v4/NAME_v6, other
sets written out with their elements, and what can't be is left out
with a note. Rules naming a set that isn't imported stay raw instead
of failing validation. - Import: a DNAT to an IPv6 address, or with matches a port forward
can't hold (an interface excluded, a limit, a log), is left out with a
note instead of being imported without them.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-