💐 Sync 2026-09-30 01:19:34

This commit is contained in:
github-actions[bot] committed 2026-09-30 01:19:34 +08:00
1 parent 67427d9030
commit a64d55f7c5
50 files changed
+2837 -589

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
#
# Copyright (C) 2021 Jo-Philipp Wich <jo@mein.io>
#
include $(TOPDIR)/rules.mk
PKG_NAME:=firewall4
PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL=$(PROJECT_GIT)/project/firewall4.git
PKG_SOURCE_DATE:=2025-03-17
PKG_SOURCE_VERSION:=b6e5157527d361f99ad52eaa6da273cb0f2dfd59
PKG_MIRROR_HASH:=skip
PKG_MAINTAINER:=Jo-Philipp Wich <jo@mein.io>
PKG_LICENSE:=ISC
include $(INCLUDE_DIR)/package.mk
define Package/firewall4
SECTION:=net
CATEGORY:=Base system
TITLE:=OpenWrt 4th gen firewall
DEPENDS:= \
+kmod-nft-core +kmod-nft-fib +kmod-nft-offload \
+kmod-nft-nat +kmod-nft-fullcone \
+nftables-json \
+ucode +ucode-mod-fs +ucode-mod-ubus +ucode-mod-uci \
+iptables +ip6tables +kmod-nft-fullcone +kmod-nft-socket +kmod-nft-tproxy
EXTRA_DEPENDS:=ucode (>=2022.03.22)
PROVIDES:=uci-firewall
DEFAULT_VARIANT:=1
endef
define Package/firewall4/description
This package provides an nftables-based implementation of the UCI firewall
sharing the same configuration format.
endef
define Package/firewall4/conffiles
/etc/config/firewall
/etc/nftables.d/
/etc/firewall.user
endef
define Package/firewall4/install
$(CP) -a $(PKG_BUILD_DIR)/root/* $(1)/
endef
define Build/Compile
endef
$(eval $(call BuildPackage,firewall4))
@@ -0,0 +1,216 @@
From aa3b56e289fba7425e649a608c333622ffd9c367 Mon Sep 17 00:00:00 2001
From: Syrone Wong <wong.syrone@gmail.com>
Date: Sat, 9 Apr 2022 13:24:19 +0800
Subject: [PATCH] firewall4: add fullcone support
fullcone is drop-in replacement of masq for non-udp traffic
add runtime fullcone rule check, disable it globally if fullcone expr is
invalid
defaults.fullcone and defaults.fullcone6 are switches for IPv4 and IPv6
respectively, most IPv6 traffic do NOT need this FullCone NAT functionality.
Renew: ZiMing Mo <msylgj@immortalwrt.org>
---
root/etc/config/firewall | 2 ++
root/usr/share/firewall4/templates/ruleset.uc | 16 ++++++++++++++--
.../firewall4/templates/zone-fullcone.uc | 4 ++++
root/usr/share/ucode/fw4.uc | 69 ++++++++++++++++++-
4 files changed, 89 insertions(+), 4 deletions(-)
create mode 100644 root/usr/share/firewall4/templates/zone-fullcone.uc
--- a/root/etc/config/firewall
+++ b/root/etc/config/firewall
@@ -5,6 +5,10 @@ config defaults
option forward REJECT
# Uncomment this line to disable ipv6 rules
# option disable_ipv6 1
+ option flow_offloading 1
+ option flow_offloading_hw 1
+ option fullcone 1
+ option fullcone6 0
config zone
option name lan
--- a/root/usr/share/firewall4/templates/ruleset.uc
+++ b/root/usr/share/firewall4/templates/ruleset.uc
@@ -327,6 +327,12 @@ table inet fw4 {
{% for (let redirect in fw4.redirects(`dstnat_${zone.name}`)): %}
{%+ include("redirect.uc", { fw4, zone, redirect }) %}
{% endfor %}
+{% if (zone.masq && fw4.default_option("fullcone")): %}
+ {%+ include("zone-fullcone.uc", { fw4, zone, family: 4, direction: "dstnat" }) %}
+{% endif %}
+{% if (zone.masq6 && fw4.default_option("fullcone6")): %}
+ {%+ include("zone-fullcone.uc", { fw4, zone, family: 6, direction: "dstnat" }) %}
+{% endif %}
{% fw4.includes('chain-append', `dstnat_${zone.name}`) %}
}
@@ -337,20 +343,26 @@ table inet fw4 {
{% for (let redirect in fw4.redirects(`srcnat_${zone.name}`)): %}
{%+ include("redirect.uc", { fw4, zone, redirect }) %}
{% endfor %}
-{% if (zone.masq): %}
+{% if (zone.masq && !fw4.default_option("fullcone")): %}
{% for (let saddrs in zone.masq4_src_subnets): %}
{% for (let daddrs in zone.masq4_dest_subnets): %}
{%+ include("zone-masq.uc", { fw4, zone, family: 4, saddrs, daddrs }) %}
{% endfor %}
{% endfor %}
{% endif %}
-{% if (zone.masq6): %}
+{% if (zone.masq6 && !fw4.default_option("fullcone6")): %}
{% for (let saddrs in zone.masq6_src_subnets): %}
{% for (let daddrs in zone.masq6_dest_subnets): %}
{%+ include("zone-masq.uc", { fw4, zone, family: 6, saddrs, daddrs }) %}
{% endfor %}
{% endfor %}
{% endif %}
+{% if (zone.masq && fw4.default_option("fullcone")): %}
+ {%+ include("zone-fullcone.uc", { fw4, zone, family: 4, direction: "srcnat" }) %}
+{% endif %}
+{% if (zone.masq6 && fw4.default_option("fullcone6")): %}
+ {%+ include("zone-fullcone.uc", { fw4, zone, family: 6, direction: "srcnat" }) %}
+{% endif %}
{% fw4.includes('chain-append', `srcnat_${zone.name}`) %}
}
--- /dev/null
+++ b/root/usr/share/firewall4/templates/zone-fullcone.uc
@@ -0,0 +1,4 @@
+{# /usr/share/firewall4/templates/zone-fullcone.uc #}
+ meta nfproto {{ fw4.nfproto(family) }} fullcone comment "!fw4: Handle {{
+ zone.name
+}} {{ fw4.nfproto(family, true) }} fullcone NAT {{ direction }} traffic"
--- a/root/usr/share/ucode/fw4.uc
+++ b/root/usr/share/ucode/fw4.uc
@@ -1,3 +1,5 @@
+// /usr/share/ucode/fw4.uc
+
const fs = require("fs");
const uci = require("uci");
const ubus = require("ubus");
@@ -489,6 +491,25 @@ function nft_try_hw_offload(devices) {
return (rc == 0);
}
+function nft_try_fullcone() {
+ let nft_test =
+ 'add table inet fw4-fullcone-test; ' +
+ 'add chain inet fw4-fullcone-test dstnat { ' +
+ 'type nat hook prerouting priority -100; policy accept; ' +
+ 'fullcone; ' +
+ '}; ' +
+ 'add chain inet fw4-fullcone-test srcnat { ' +
+ 'type nat hook postrouting priority -100; policy accept; ' +
+ 'fullcone; ' +
+ '}; ';
+ let cmd = sprintf("/usr/sbin/nft -c '%s' 2>/dev/null", replace(nft_test, "'", "'\\''"));
+ let ok = system(cmd) == 0;
+ if (!ok) {
+ warn("nft_try_fullcone: cmd "+ cmd + "\n");
+ }
+ return ok;
+}
+
return {
read_kernel_version: function() {
@@ -855,6 +876,18 @@ return {
warn(`[!] ${msg}\n`);
},
+ myinfo: function(fmt, ...args) {
+ if (getenv("QUIET"))
+ return;
+
+ let msg = sprintf(fmt, ...args);
+
+ if (getenv("TTY"))
+ warn(`\033[32m${msg}\033[m\n`);
+ else
+ warn(`[I] ${msg}\n`);
+ },
+
get: function(sid, opt) {
return this.cursor.get("firewall", sid, opt);
},
@@ -1036,6 +1069,21 @@ return {
}
},
+ myinfo_section: function(s, msg) {
+ if (s[".name"]) {
+ if (s.name)
+ this.myinfo("Section %s (%s) %s", this.section_id(s[".name"]), s.name, msg);
+ else
+ this.myinfo("Section %s %s", this.section_id(s[".name"]), msg);
+ }
+ else {
+ if (s.name)
+ this.myinfo("ubus %s (%s) %s", s.type || "rule", s.name, msg);
+ else
+ this.myinfo("ubus %s %s", s.type || "rule", msg);
+ }
+ },
+
parse_policy: function(val) {
return this.parse_enum(val, [
"accept",
@@ -1475,6 +1523,7 @@ return {
"dnat",
"snat",
"masquerade",
+ "fullcone",
"accept",
"reject",
"drop"
@@ -1946,6 +1995,8 @@ return {
}
let defs = this.parse_options(data, {
+ fullcone: [ "bool", "0" ],
+ fullcone6: [ "bool", "0" ],
input: [ "policy", "drop" ],
output: [ "policy", "drop" ],
forward: [ "policy", "drop" ],
@@ -1980,6 +2031,11 @@ return {
delete defs.syn_flood;
+ if (!nft_try_fullcone()) {
+ delete defs.fullcone;
+ warn("nft_try_fullcone failed, disable fullcone globally\n");
+ }
+
this.state.defaults = defs;
},
@@ -2205,10 +2261,23 @@ return {
zone.related_subnets = related_subnets;
zone.related_physdevs = related_physdevs;
- if (zone.masq || zone.masq6)
+ if (zone.masq) {
zone.dflags.snat = true;
+ if (this.state.defaults.fullcone) {
+ zone.dflags.dnat = true;
+ this.myinfo_section(data, "IPv4 fullcone enabled for zone '" + zone.name + "'");
+ }
+ }
+
+ if (zone.masq6) {
+ zone.dflags.snat = true;
+ if (this.state.defaults.fullcone6) {
+ zone.dflags.dnat = true;
+ this.myinfo_section(data, "IPv6 fullcone enabled for zone '" + zone.name + "'");
+ }
+ }
- if ((zone.auto_helper && !(zone.masq || zone.masq6)) || length(zone.helper)) {
+ if ((zone.auto_helper && !(zone.masq || zone.masq6 || this.state.defaults.fullcone || this.state.defaults.fullcone6)) || length(zone.helper)) {
zone.dflags.helper = true;
for (let helper in (length(zone.helper) ? zone.helper : this.state.helpers)) {