log the agent bridge's forwarded flows in the hive_egress table
A forward chain in the hive-owned nft table logs each new flow from the agent bridge to the uplink (v4 and v6) and counts every packet. It never blocks. The mesh interface is skipped. Logging is rate limited per source address; a flow that limit cannot log gets one line from a shared limit with its own prefix. services.hyperhive.network.egress.forward selects accept, log (default) or drop; drop is refused at evaluation until the interception it needs exists.
This commit is contained in:
parent
f5e7caa2eb
commit
bd79b58e78
1 changed files with 321 additions and 8 deletions
|
|
@ -70,6 +70,18 @@ let
|
|||
{ boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1; }
|
||||
] { network.enable = true; };
|
||||
|
||||
# The two settings of `egress.forward` the ordinary host (`log`) does not
|
||||
# render, for ../host-modules/hive-egress.nix: `accept` leaves the table
|
||||
# without a forward chain, `drop` is refused by an assertion.
|
||||
egressForwardAccept = hive {
|
||||
network.enable = true;
|
||||
network.egress.forward = "accept";
|
||||
};
|
||||
egressForwardDrop = hive {
|
||||
network.enable = true;
|
||||
network.egress.forward = "drop";
|
||||
};
|
||||
|
||||
# Every hive defaults the agents' queue address to the queue's name, so the
|
||||
# only hive without one is a hive told to have none. Also the host still
|
||||
# setting the four removed host options; no case on its queue reads them.
|
||||
|
|
@ -1521,6 +1533,68 @@ let
|
|||
&& bare.networking.firewall.extraInputRules == ""
|
||||
&& !(centralToggleOff.systemd.services ? hive-egress);
|
||||
}
|
||||
{
|
||||
# The forward chain is in the ordinary host's table and logs the bridge's
|
||||
# forwarded flows without a verdict that blocks. The ruleset is read as
|
||||
# text; the last rule of the chain is the accept that keeps the log from
|
||||
# changing any outcome.
|
||||
name = "the default egress table logs the bridge's forwarded flows and blocks none";
|
||||
ok =
|
||||
let
|
||||
text = bare.services.hyperhive.network.egress.ruleset;
|
||||
chain = lib.last (lib.splitString "chain forward" text);
|
||||
rules = lib.filter (l: l != "") (map lib.trim (lib.splitString "\n" chain));
|
||||
idx = pat: lib.lists.findFirstIndex (lib.hasInfix pat) 1000 rules;
|
||||
in
|
||||
lib.hasInfix "chain forward" text
|
||||
&& lib.hasInfix "type filter hook forward priority filter - 10; policy accept;" chain
|
||||
&& lib.hasInfix ''iifname "hive-br0" oifname != "hive-br0" ct state new update @log_v4 { ip saddr limit rate'' chain
|
||||
&& lib.hasInfix ''iifname "hive-br0" oifname != "hive-br0" ct state new update @log_v6 { ip6 saddr limit rate'' chain
|
||||
&& lib.hasInfix ''log prefix "hive-egress-would-drop "'' chain
|
||||
&& lib.hasInfix ''iifname "hive-br0" oifname "wg-hive" accept'' chain
|
||||
# The per-source rules accept after logging, and the unmetered rule
|
||||
# comes after both: a flow gets one line, and one the per-source rules
|
||||
# cannot log still gets the unmetered one.
|
||||
&& lib.all (l: lib.hasSuffix " accept" l) (lib.filter (lib.hasInfix "update @log_") rules)
|
||||
&& lib.hasInfix ''iifname "hive-br0" oifname != "hive-br0" ct state new limit rate 5/second burst 50 packets log prefix "hive-egress-would-drop-unmetered "'' chain
|
||||
&& idx "update @log_v4" < idx "update @log_v6"
|
||||
&& idx "update @log_v6" < idx "would-drop-unmetered"
|
||||
&& lib.last (lib.filter (l: l != "}") rules) == ''iifname "hive-br0" oifname != "hive-br0" accept''
|
||||
&& !(lib.any (l: lib.hasSuffix " drop" l || lib.hasSuffix " reject" l) rules)
|
||||
&& lib.hasInfix "chain input" text;
|
||||
}
|
||||
{
|
||||
# `accept` renders the table without the forward chain and without the
|
||||
# log prefix; the input chain is the presence arm that the text it
|
||||
# reads is the table.
|
||||
name = "egress.forward accept leaves the table without a forward chain";
|
||||
ok =
|
||||
let
|
||||
text = egressForwardAccept.services.hyperhive.network.egress.ruleset;
|
||||
in
|
||||
lib.hasInfix "chain input" text
|
||||
&& !(lib.hasInfix "chain forward" text)
|
||||
&& !(lib.hasInfix "hive-egress-would-drop" text)
|
||||
&& !(lib.any (a: !a.assertion) egressForwardAccept.assertions);
|
||||
}
|
||||
{
|
||||
# `drop` is refused by an assertion that names the option and says it is
|
||||
# not available; `bare` and the `accept` host, which set `log` and
|
||||
# `accept`, are the controls that the lookup finds nothing for them.
|
||||
name = "egress.forward drop is refused as not available yet";
|
||||
ok =
|
||||
let
|
||||
refused =
|
||||
m:
|
||||
lib.any (
|
||||
a:
|
||||
!a.assertion
|
||||
&& lib.hasInfix "services.hyperhive.network.egress.forward" a.message
|
||||
&& lib.hasInfix "not available yet" a.message
|
||||
) m.assertions;
|
||||
in
|
||||
refused egressForwardDrop && !(refused bare) && !(refused egressForwardAccept);
|
||||
}
|
||||
{
|
||||
# The host that turns `route_localnet` on is refused by an assertion
|
||||
# naming it; the ordinary host, which leaves it alone, is not.
|
||||
|
|
|
|||
Loading…
Reference in a new issue