From 0543c7a94a125f31ce15c631fe9c5256c4b1a36e Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:05:51 +0800 Subject: [PATCH] hm2_eth: add nftables firewall backend --- docs/src/man/man9/hm2_eth.9.adoc | 94 +++++-- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 353 ++++++++++++++++-------- 2 files changed, 310 insertions(+), 137 deletions(-) diff --git a/docs/src/man/man9/hm2_eth.9.adoc b/docs/src/man/man9/hm2_eth.9.adoc index 8fbadf1a165..0f84dccdde0 100644 --- a/docs/src/man/man9/hm2_eth.9.adoc +++ b/docs/src/man/man9/hm2_eth.9.adoc @@ -7,23 +7,35 @@ IO boards, with HostMot2 firmware. == SYNOPSIS -*loadrt hm2_eth* [**config=**"__str__[,__str__...]"] [**board_ip=**__ip__[,__ip__...] ] [**board_mac=**__mac__[,__mac__...] ] [**no_iptables=**__0|1__] +*loadrt hm2_eth* [**config=**"__str__[,__str__...]"] [**board_ip=**__ip__[,__ip__...] ] [**board_mac=**__mac__[,__mac__...] ] [**firewall=**__auto|iptables|nft|none__] *config* [default: ""]:: HostMot2 config strings, described in the hostmot2(9) manpage. *board_ip* [default: ""]:: The IP address of the board(s), separated by commas. As shipped, the board address is 192.168.1.121. -*no_iptables* [default: 0]:: - Explicit override that disables all iptables interaction. By default - hm2_eth installs *iptables* and *ip6tables* rules itself; rtapi_app - raises *cap_net_admin* into its ambient capability set at startup so - the calls succeed under both setuid-root and rootless (file-cap) - installs. If the cap is not held the probe fails and rule - installation is skipped with a warning; in that case configure the - rules manually using the recipe in the NOTES section below. Set - *no_iptables=1* when iptables is reachable but you prefer to manage - the firewall externally (nftables, firewalld, systemd units). +*firewall* [default: auto]:: + Selects the firewall backend used to isolate the dedicated interface. + By default hm2_eth installs the rules itself; rtapi_app raises + *cap_net_admin* into its ambient capability set at startup so the + calls succeed under both setuid-root and rootless (file-cap) installs. + If the cap is not held (or no backend binary is present) the probe + fails and rule installation is skipped with a warning; in that case + configure the rules manually using the recipe in the NOTES section + below. Accepted values: ++ +-- +*auto*::: Use *iptables* when it is usable, otherwise fall back to + *nft*. This preserves historical behaviour on iptables systems and + works out of the box on nftables-only systems that have no iptables + binary. +*iptables*::: Force the legacy *iptables* / *ip6tables* backend. +*nft*::: Force the *nftables* backend. Rules live in a dedicated + *inet* table named *hm2_eth*, so flushing or removing them never + touches your other firewall rules. +*none*::: Disable all firewall interaction; manage the firewall + externally (firewalld, systemd units, your own ruleset). +-- == DESCRIPTION @@ -140,19 +152,28 @@ Setting it too high can cause realtime delay errors. == NOTES -hm2_eth uses an iptables chain called "hm2-eth-rules-output". -That technology is common to control network access to (INPUT chain), -through (FORWARD chain) or from (OUTPUT chain) your computer. -Someone who has configured a firewall on Linux has encountered iptables is familiar with that technology. -This chain contains additional rules to control network interface while HAL is running. -The chain is created if it does not exist, -and a jump to it is inserted at the beginning of the OUTPUT chain if it is not there already. -If you have an existing iptables setup, -you can insert a direct jump from OUTPUT to hm2-eth-rules-output in an order appropriate to your local network. +hm2_eth installs firewall rules to control the network interface while +HAL is running. Two backends are supported and chosen with the +*firewall* parameter (see above): legacy *iptables* and *nftables*. + +With the iptables backend, hm2_eth uses a chain called +"hm2-eth-rules-output". That technology is common to control network +access to (INPUT chain), through (FORWARD chain) or from (OUTPUT chain) +your computer. The chain is created if it does not exist, and a jump to +it is inserted at the beginning of the OUTPUT chain if it is not there +already. If you have an existing iptables setup, you can insert a +direct jump from OUTPUT to hm2-eth-rules-output in an order appropriate +to your local network. + +With the nftables backend, hm2_eth uses a dedicated *inet* table named +"hm2_eth" with an *output*-hook chain. Because the table is private, +flushing or deleting it never affects your other nftables rules. At (normal) exit, hm2_eth will remove the rules. After a crash, you can -manually clear the rules with *sudo iptables -F hm2-eth-rules-output*; -the rules are also removed by a reboot. +manually clear the iptables rules with +*sudo iptables -F hm2-eth-rules-output* (or, for nftables, with +*sudo nft delete table inet hm2_eth*); the rules are also removed by a +reboot. === Manual iptables configuration @@ -201,6 +222,35 @@ ip6tables -D OUTPUT -j hm2-eth-rules-output ip6tables -X hm2-eth-rules-output ---- +=== Manual nftables configuration + +On nftables-only systems (no iptables binary), set the rules up as root +with *nft*. The *inet* family covers IPv4 and IPv6 in one chain. +Adjust the addresses, UDP destination port, and interface name: + +---- +HOST_IP=192.168.1.1 +BOARD_IP=192.168.1.121 +BOARD_DPORT=27181 +IFACE=eth1 + +nft add table inet hm2_eth +nft add chain inet hm2_eth output \ + '{ type filter hook output priority 0; policy accept; }' +nft add rule inet hm2_eth output \ + ip saddr $HOST_IP ip daddr $BOARD_IP udp dport $BOARD_DPORT accept +nft add rule inet hm2_eth output oifname $IFACE ip protocol icmp drop +nft add rule inet hm2_eth output oifname $IFACE \ + reject with icmp type admin-prohibited +nft add rule inet hm2_eth output oifname $IFACE ip6 version 6 drop +---- + +Tear the rules down by deleting the whole private table: + +---- +nft delete table inet hm2_eth +---- + "hardware-irq-coalesce-rx-usecs" decreases time waiting to receive a packet on most systems, but on at least some Marvel-chipset NICs it is harmful. If the line does not improve system performance, then remove it. diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 2b426f8e419..f7085b95eba 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -95,8 +95,8 @@ RTAPI_MP_ARRAY_STRING(config, MAX_ETH_BOARDS, "config string for the AnyIO board int debug = 0; RTAPI_MP_INT(debug, "Developer/debug use only! Enable debug logging."); -static int no_iptables = 0; -RTAPI_MP_INT(no_iptables, "Skip automatic iptables rule installation; firewall must be configured externally."); +static char *firewall = 0; +RTAPI_MP_STRING(firewall, "Firewall backend for traffic isolation: auto (default), iptables, nft, or none."); static int boards_count = 0; @@ -464,36 +464,34 @@ static hm2_eth_t boards[MAX_ETH_BOARDS]; static int eth_socket_send(int sockfd, const void *buffer, int len, int flags); static int eth_socket_recv(int sockfd, void *buffer, int len, int flags); -// hm2_eth installs iptables/ip6tables rules to isolate the dedicated Mesa +// hm2_eth installs firewall rules to isolate the dedicated Mesa // interface from non-realtime traffic. rtapi_app raises CAP_NET_ADMIN // into its ambient set at startup (see uspace_rtapi_main.cc), so the -// caps survive execve() into the iptables binaries even when we run +// caps survive execve() into the firewall binaries even when we run // under file caps instead of setuid root. +// +// Two backends are supported and selected with the `firewall` module +// parameter (default auto): legacy iptables/ip6tables, and nftables. +// Auto-detection prefers iptables when it is usable (preserving the +// historical behaviour) and falls back to nft, which is the only +// backend present on iptables-less systems. #define IPTABLES_BIN "/sbin/iptables" #define IP6TABLES_BIN "/sbin/ip6tables" +#define NFT_BIN "/sbin/nft" #define CHAIN "hm2-eth-rules-output" - -// run_iptables(): fork+exec the named iptables binary with a -// NULL-terminated argv list (sentinel == NULL, do not omit). Returns -// the child exit status, or -1 on spawn/wait failure. When quiet, the -// child's stdout+stderr are suppressed so probe-style "is this rule -// present?" calls do not spam the log on the (expected) failure path. -static int run_iptables(const char *bin, int quiet, ...) { - char *argv[24]; - int n = 0; - // argv[0] is the program name iptables expects, not the path. - argv[n++] = (char *)(strrchr(bin, '/') ? strrchr(bin, '/') + 1 : bin); - - va_list ap; - va_start(ap, quiet); - while(n < (int)(sizeof(argv)/sizeof(argv[0])) - 1) { - char *a = va_arg(ap, char *); - if(!a) break; - argv[n++] = a; - } - argv[n] = NULL; - va_end(ap); - +// nft keeps its own table so a flush/delete never touches user rules. +// inet covers IPv4 and IPv6 in a single chain. +#define NFT_TABLE "hm2_eth" +#define NFT_CHAIN "output" + +typedef enum { FW_NONE = 0, FW_IPTABLES, FW_NFTABLES } fw_backend_t; + +// run_cmd(): fork+exec bin with a caller-built, NULL-terminated argv +// (argv[0] is the program name). Returns the child exit status, or -1 on +// spawn/wait failure. When quiet, the child's stdout+stderr are +// suppressed so probe-style "is this present?" calls do not spam the log +// on the (expected) failure path. +static int run_cmd(const char *bin, int quiet, char *const argv[]) { posix_spawn_file_actions_t fa, *pfa = NULL; if(quiet && posix_spawn_file_actions_init(&fa) == 0) { if(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, @@ -515,64 +513,157 @@ static int run_iptables(const char *bin, int quiet, ...) { return -1; } -#define IPT(quiet, ...) run_iptables(IPTABLES_BIN, (quiet), __VA_ARGS__, NULL) -#define IP6T(quiet, ...) run_iptables(IP6TABLES_BIN, (quiet), __VA_ARGS__, NULL) - -static int iptables_state = -1; -static bool use_iptables() { - if(iptables_state == -1) { - if(no_iptables) { - LL_PRINT("Skipping iptables setup (no_iptables=1); " - "configure firewall externally.\n"); - return (iptables_state = 0); +// Each macro builds the argv as a compound literal: argv[0] is the program +// name, then the caller's tokens, then a NULL terminator. +#define IPT(quiet, ...) run_cmd(IPTABLES_BIN, (quiet), \ + (char *[]){ "iptables", __VA_ARGS__, NULL }) +#define IP6T(quiet, ...) run_cmd(IP6TABLES_BIN, (quiet), \ + (char *[]){ "ip6tables", __VA_ARGS__, NULL }) +#define NFT(quiet, ...) run_cmd(NFT_BIN, (quiet), \ + (char *[]){ "nft", __VA_ARGS__, NULL }) + +// Bring up the iptables chain plus its OUTPUT jump (idempotently) and +// mirror it for ip6tables. Returns true on success. +static bool setup_iptables() { + // Read-only probe: list the INPUT chain. Fails when the + // process lacks CAP_NET_ADMIN (rootless without setcap, or + // setcap applied but ambient raise failed in rtapi_app), or when + // the iptables binary is absent (pure-nftables systems). + if(IPT(1, "-n", "-L", "INPUT") != 0) + return false; + // Create chain only if absent; insert OUTPUT jump only if absent. + if(IPT(1, "-n", "-L", CHAIN) != 0) { + if(IPT(0, "-N", CHAIN) != 0) { + LL_PRINT("ERROR: failed to create iptables chain " CHAIN "\n"); + return false; } - // Read-only probe: list the INPUT chain. Fails when the - // process lacks CAP_NET_ADMIN (rootless without setcap, or - // setcap applied but ambient raise failed in rtapi_app). - if(IPT(1, "-n", "-L", "INPUT") != 0) { - LL_PRINT("iptables not available (missing CAP_NET_ADMIN?); " - "automatic firewall setup skipped. See hm2_eth(9) " - "NOTES for the manual rule recipe.\n"); - return (iptables_state = 0); - } - // Create chain only if absent; insert OUTPUT jump only if absent. - if(IPT(1, "-n", "-L", CHAIN) != 0) { - if(IPT(0, "-N", CHAIN) != 0) { - LL_PRINT("ERROR: failed to create iptables chain " CHAIN "\n"); - return (iptables_state = 0); - } - } - if(IPT(1, "-C", "OUTPUT", "-j", CHAIN) != 0) { - if(IPT(0, "-I", "OUTPUT", "1", "-j", CHAIN) != 0) { - LL_PRINT("ERROR: failed to insert OUTPUT jump\n"); - return (iptables_state = 0); - } + } + if(IPT(1, "-C", "OUTPUT", "-j", CHAIN) != 0) { + if(IPT(0, "-I", "OUTPUT", "1", "-j", CHAIN) != 0) { + LL_PRINT("ERROR: failed to insert OUTPUT jump\n"); + return false; } - // Mirror the chain for ip6tables so IPv6 isolation can hang off - // it. Best-effort: kernels without IPv6 support cause this to - // fail silently and the IPv6 rules are simply absent. - if(IP6T(1, "-n", "-L", CHAIN) != 0) - IP6T(1, "-N", CHAIN); - if(IP6T(1, "-C", "OUTPUT", "-j", CHAIN) != 0) - IP6T(1, "-I", "OUTPUT", "1", "-j", CHAIN); - - return (iptables_state = 1); } - return iptables_state; + // Mirror the chain for ip6tables so IPv6 isolation can hang off + // it. Best-effort: kernels without IPv6 support cause this to + // fail silently and the IPv6 rules are simply absent. + if(IP6T(1, "-n", "-L", CHAIN) != 0) + IP6T(1, "-N", CHAIN); + if(IP6T(1, "-C", "OUTPUT", "-j", CHAIN) != 0) + IP6T(1, "-I", "OUTPUT", "1", "-j", CHAIN); + return true; } -static void clear_iptables() { - IPT(1, "-F", CHAIN); - IP6T(1, "-F", CHAIN); +// Bring up the nft table and output-hook chain (idempotently). `nft +// add table/chain` is a no-op when the object already exists with the +// same spec, so re-init does not error. Returns true on success. +static bool setup_nftables() { + // Read-only probe: listing tables fails when nft is absent or the + // process lacks CAP_NET_ADMIN. + if(NFT(1, "list", "tables") != 0) + return false; + if(NFT(0, "add", "table", "inet", NFT_TABLE) != 0) { + LL_PRINT("ERROR: failed to create nft table inet " NFT_TABLE "\n"); + return false; + } + if(NFT(0, "add", "chain", "inet", NFT_TABLE, NFT_CHAIN, + "{ type filter hook output priority 0; policy accept; }") != 0) { + LL_PRINT("ERROR: failed to create nft chain " NFT_CHAIN "\n"); + return false; + } + return true; } -static void cleanup_iptables() { - IPT(1, "-F", CHAIN); - IPT(1, "-D", "OUTPUT", "-j", CHAIN); - IPT(1, "-X", CHAIN); - IP6T(1, "-F", CHAIN); - IP6T(1, "-D", "OUTPUT", "-j", CHAIN); - IP6T(1, "-X", CHAIN); +static fw_backend_t fw_backend = FW_NONE; + +// Tri-state cache for use_firewall(): not yet resolved, resolved to +// unavailable/disabled, or resolved to a ready backend. +typedef enum { FW_UNRESOLVED, FW_UNAVAILABLE, FW_READY } fw_state_t; +static fw_state_t fw_state = FW_UNRESOLVED; + +// Resolve and bring up the firewall backend on first call, caching the +// result. Returns true when a backend is ready, false when isolation +// is unavailable or disabled. +static bool use_firewall() { + if(fw_state != FW_UNRESOLVED) + return fw_state == FW_READY; + + const char *want = firewall ? firewall : "auto"; + if(!strcmp(want, "none")) { + LL_PRINT("Skipping firewall setup (firewall=none); " + "configure firewall externally.\n"); + fw_backend = FW_NONE; + fw_state = FW_UNAVAILABLE; + return false; + } + + bool try_ipt = !strcmp(want, "auto") || !strcmp(want, "iptables"); + bool try_nft = !strcmp(want, "auto") || !strcmp(want, "nft") + || !strcmp(want, "nftables"); + if(!try_ipt && !try_nft) { + LL_PRINT("ERROR: unknown firewall backend '%s'; " + "expected auto, iptables, nft, or none.\n", want); + fw_backend = FW_NONE; + fw_state = FW_UNAVAILABLE; + return false; + } + + // Prefer iptables when usable to preserve historical behaviour; fall + // back to nft, the only backend on iptables-less systems. + if(try_ipt && setup_iptables()) { + fw_backend = FW_IPTABLES; + fw_state = FW_READY; + return true; + } + if(try_nft && setup_nftables()) { + fw_backend = FW_NFTABLES; + fw_state = FW_READY; + return true; + } + + LL_PRINT("firewall not available (missing CAP_NET_ADMIN or backend " + "binary?); automatic firewall setup skipped. See hm2_eth(9) " + "NOTES for the manual rule recipe.\n"); + fw_backend = FW_NONE; + fw_state = FW_UNAVAILABLE; + return false; +} + +// Drop all rules from our chain/table but keep the chain in place, so a +// fresh set can be installed on (re-)init. +static void clear_firewall() { + if(!use_firewall()) return; + switch(fw_backend) { + case FW_IPTABLES: + IPT(1, "-F", CHAIN); + IP6T(1, "-F", CHAIN); + break; + case FW_NFTABLES: + NFT(1, "flush", "chain", "inet", NFT_TABLE, NFT_CHAIN); + break; + case FW_NONE: + break; + } +} + +// Remove everything we installed: chain, OUTPUT jump, and (nft) table. +static void cleanup_firewall() { + if(!use_firewall()) return; + switch(fw_backend) { + case FW_IPTABLES: + IPT(1, "-F", CHAIN); + IPT(1, "-D", "OUTPUT", "-j", CHAIN); + IPT(1, "-X", CHAIN); + IP6T(1, "-F", CHAIN); + IP6T(1, "-D", "OUTPUT", "-j", CHAIN); + IP6T(1, "-X", CHAIN); + break; + case FW_NFTABLES: + NFT(1, "delete", "table", "inet", NFT_TABLE); + break; + case FW_NONE: + break; + } } static char* inet_ntoa_buf(struct in_addr in, char *buf, size_t n) { @@ -608,11 +699,13 @@ static char* fetch_ifname(int sockfd, char *buf, size_t n) { return NULL; } -static int install_iptables_board(int sockfd) { +static int install_firewall_board(int sockfd) { struct sockaddr_in srcaddr, dstaddr; char srchost[16], dsthost[16]; // enough for 255.255.255.255\0 char dport_s[8], sport_s[8]; + if(!use_firewall()) return 0; + socklen_t addrlen = sizeof(srcaddr); int res = getsockname(sockfd, &srcaddr, &addrlen); if(res < 0) return -errno; @@ -621,49 +714,77 @@ static int install_iptables_board(int sockfd) { res = getpeername(sockfd, &dstaddr, &addrlen); if(res < 0) return -errno; - if(!use_iptables()) return 0; - inet_ntoa_buf(srcaddr.sin_addr, srchost, sizeof(srchost)); inet_ntoa_buf(dstaddr.sin_addr, dsthost, sizeof(dsthost)); snprintf(dport_s, sizeof(dport_s), "%d", ntohs(dstaddr.sin_port)); snprintf(sport_s, sizeof(sport_s), "%d", ntohs(srcaddr.sin_port)); - // --sport is safe here: cleanup_iptables() removes the chain on exit, - // so a stale rule from a previous run with a different ephemeral port - // cannot block the second invocation. - if(IPT(0, "-A", CHAIN, - "-p", "udp", "-m", "udp", - "-d", dsthost, "--dport", dport_s, - "-s", srchost, "--sport", sport_s, - "-j", "ACCEPT") != 0) - return -EINVAL; + // --sport / sport is safe here: cleanup_firewall() removes the chain + // on exit, so a stale rule from a previous run with a different + // ephemeral port cannot block the second invocation. + switch(fw_backend) { + case FW_IPTABLES: + if(IPT(0, "-A", CHAIN, + "-p", "udp", "-m", "udp", + "-d", dsthost, "--dport", dport_s, + "-s", srchost, "--sport", sport_s, + "-j", "ACCEPT") != 0) + return -EINVAL; + break; + case FW_NFTABLES: + if(NFT(0, "add", "rule", "inet", NFT_TABLE, NFT_CHAIN, + "ip", "saddr", srchost, "ip", "daddr", dsthost, + "udp", "sport", sport_s, "udp", "dport", dport_s, + "accept") != 0) + return -EINVAL; + break; + case FW_NONE: + break; + } return 0; } -static int install_iptables_perinterface(const char *ifbuf) { +static int install_firewall_perinterface(const char *ifbuf) { // Without these rules, 'ping' spews a lot of "Packet filtered" // messages. With them, ping prints 'ping: sendmsg: Operation not // permitted' once per second. // - // Outbound IPv6 on the dedicated interface is dropped via ip6tables - // rather than disable_ipv6 sysctl: writing the sysctl needs + // Outbound IPv6 on the dedicated interface is dropped at the firewall + // rather than via the disable_ipv6 sysctl: writing the sysctl needs // CAP_DAC_OVERRIDE (file is mode 644 root:root) and we'd rather not // grant it to rtapi_app. Users who want full IPv6 quiescence (no // router solicitations etc.) can additionally set // 'net.ipv6.conf..disable_ipv6=1' in /etc/sysctl.conf. - if(!use_iptables()) return 0; - - if(IPT(0, "-A", CHAIN, "-o", (char *)ifbuf, "-p", "icmp", "-j", "DROP") != 0) - return -EINVAL; - if(IPT(0, "-A", CHAIN, "-o", (char *)ifbuf, - "-j", "REJECT", "--reject-with", "icmp-admin-prohibited") != 0) - return -EINVAL; - - // ip6tables is best-effort: kernel may not have IPv6 support - // compiled in, in which case the chain creation in use_iptables() - // already failed and this rule is simply absent. - IP6T(1, "-A", CHAIN, "-o", (char *)ifbuf, "-j", "DROP"); - + if(!use_firewall()) return 0; + + switch(fw_backend) { + case FW_IPTABLES: + if(IPT(0, "-A", CHAIN, "-o", (char *)ifbuf, "-p", "icmp", "-j", "DROP") != 0) + return -EINVAL; + if(IPT(0, "-A", CHAIN, "-o", (char *)ifbuf, + "-j", "REJECT", "--reject-with", "icmp-admin-prohibited") != 0) + return -EINVAL; + // ip6tables is best-effort: kernel may not have IPv6 support + // compiled in, in which case the chain creation in setup_iptables() + // already failed and this rule is simply absent. + IP6T(1, "-A", CHAIN, "-o", (char *)ifbuf, "-j", "DROP"); + break; + case FW_NFTABLES: + // The inet chain covers both families, so a single set of rules + // handles IPv4 ICMP, the catch-all reject, and all IPv6. + if(NFT(0, "add", "rule", "inet", NFT_TABLE, NFT_CHAIN, + "oifname", (char *)ifbuf, "ip", "protocol", "icmp", "drop") != 0) + return -EINVAL; + if(NFT(0, "add", "rule", "inet", NFT_TABLE, NFT_CHAIN, + "oifname", (char *)ifbuf, + "reject", "with", "icmp", "type", "admin-prohibited") != 0) + return -EINVAL; + NFT(1, "add", "rule", "inet", NFT_TABLE, NFT_CHAIN, + "oifname", (char *)ifbuf, "ip6", "version", "6", "drop"); + break; + case FW_NONE: + break; + } return 0; } @@ -720,7 +841,7 @@ static int init_board(hm2_eth_t *board, const char *board_ip) { return -errno; } - if(!use_iptables()) { + if(!use_firewall()) { LL_PRINT(\ "WARNING: Unable to restrict other access to the hm2-eth device.\n" "This means that other software using the same network interface can violate\n" @@ -766,10 +887,10 @@ static int init_board(hm2_eth_t *board, const char *board_ip) { return -errno; } - // install_iptables_board() is a no-op when iptables is unavailable - // (rootless install without setcap on hm2_eth_iptables, or - // no_iptables=1), so it is safe to call unconditionally. - ret = install_iptables_board(board->sockfd); + // install_firewall_board() is a no-op when no firewall backend is + // available (rootless install without CAP_NET_ADMIN, or + // firewall=none), so it is safe to call unconditionally. + ret = install_firewall_board(board->sockfd); if(ret < 0) return ret; board->write_packet_ptr = board->write_packet; @@ -782,7 +903,7 @@ static int close_board(hm2_eth_t *board) { int ret; board->llio.reset(&board->llio); - if(use_iptables()) clear_iptables(); + clear_firewall(); if(board->req.arp_flags & ATF_PERM) { ret = ioctl_siocdarp(board); @@ -1614,7 +1735,7 @@ int rtapi_app_main(void) { return ret; comp_id = ret; - if(use_iptables()) clear_iptables(); + clear_firewall(); for(i = 0, ret = 0; ret == 0 && i