|
| 1 | +From d4081c498ddca184578903fe5199d390bbc0707b Mon Sep 17 00:00:00 2001 |
| 2 | +From: Syrone Wong <wong.syrone@gmail.com> |
| 3 | +Date: Sat, 9 Apr 2022 13:24:19 +0800 |
| 4 | +Subject: [PATCH] firewall4: add fullcone support |
| 5 | + |
| 6 | +fullcone is drop-in replacement of masq for non-udp traffic |
| 7 | + |
| 8 | +add runtime fullcone rule check, disable it globally if fullcone expr is |
| 9 | +invalid |
| 10 | + |
| 11 | +defaults.fullcone is the global switch, while zone.fullcone4 and |
| 12 | +zone.fullcone6 are switches for IPv4 and IPv6 respectively, most |
| 13 | +IPv6 traffic do NOT need this FullCone NAT functionality. |
| 14 | +--- |
| 15 | + root/etc/config/firewall | 3 + |
| 16 | + root/usr/share/firewall4/templates/ruleset.uc | 16 +++- |
| 17 | + .../firewall4/templates/zone-fullcone.uc | 4 + |
| 18 | + root/usr/share/ucode/fw4.uc | 76 ++++++++++++++++++- |
| 19 | + 4 files changed, 96 insertions(+), 3 deletions(-) |
| 20 | + create mode 100644 root/usr/share/firewall4/templates/zone-fullcone.uc |
| 21 | + |
| 22 | +diff --git a/root/etc/config/firewall b/root/etc/config/firewall |
| 23 | +index b9a4647..7187723 100644 |
| 24 | +--- a/root/etc/config/firewall |
| 25 | ++++ b/root/etc/config/firewall |
| 26 | +@@ -5,6 +5,7 @@ config defaults |
| 27 | + option forward REJECT |
| 28 | + # Uncomment this line to disable ipv6 rules |
| 29 | + # option disable_ipv6 1 |
| 30 | ++ option fullcone '1' |
| 31 | + |
| 32 | + config zone |
| 33 | + option name lan |
| 34 | +@@ -20,6 +21,8 @@ config zone |
| 35 | + option input REJECT |
| 36 | + option output ACCEPT |
| 37 | + option forward REJECT |
| 38 | ++ option fullcone4 '1' |
| 39 | ++ option fullcone6 '0' |
| 40 | + option masq 1 |
| 41 | + option mtu_fix 1 |
| 42 | + |
| 43 | +diff --git a/root/usr/share/firewall4/templates/ruleset.uc b/root/usr/share/firewall4/templates/ruleset.uc |
| 44 | +index eaa1f04..e29eae6 100644 |
| 45 | +--- a/root/usr/share/firewall4/templates/ruleset.uc |
| 46 | ++++ b/root/usr/share/firewall4/templates/ruleset.uc |
| 47 | +@@ -310,6 +310,12 @@ table inet fw4 { |
| 48 | + {% for (let redirect in fw4.redirects(`dstnat_${zone.name}`)): %} |
| 49 | + {%+ include("redirect.uc", { fw4, redirect }) %} |
| 50 | + {% endfor %} |
| 51 | ++{% if (zone.fullcone4): %} |
| 52 | ++ {%+ include("zone-fullcone.uc", { fw4, zone, family: 4, direction: "dstnat" }) %} |
| 53 | ++{% endif %} |
| 54 | ++{% if (zone.fullcone6): %} |
| 55 | ++ {%+ include("zone-fullcone.uc", { fw4, zone, family: 6, direction: "dstnat" }) %} |
| 56 | ++{% endif %} |
| 57 | + {% fw4.includes('chain-append', `dstnat_${zone.name}`) %} |
| 58 | + } |
| 59 | + |
| 60 | +@@ -320,20 +326,26 @@ table inet fw4 { |
| 61 | + {% for (let redirect in fw4.redirects(`srcnat_${zone.name}`)): %} |
| 62 | + {%+ include("redirect.uc", { fw4, redirect }) %} |
| 63 | + {% endfor %} |
| 64 | +-{% if (zone.masq): %} |
| 65 | ++{% if (zone.masq && !zone.fullcone4): %} |
| 66 | + {% for (let saddrs in zone.masq4_src_subnets): %} |
| 67 | + {% for (let daddrs in zone.masq4_dest_subnets): %} |
| 68 | + {%+ include("zone-masq.uc", { fw4, zone, family: 4, saddrs, daddrs }) %} |
| 69 | + {% endfor %} |
| 70 | + {% endfor %} |
| 71 | + {% endif %} |
| 72 | +-{% if (zone.masq6): %} |
| 73 | ++{% if (zone.masq6 && !zone.fullcone6): %} |
| 74 | + {% for (let saddrs in zone.masq6_src_subnets): %} |
| 75 | + {% for (let daddrs in zone.masq6_dest_subnets): %} |
| 76 | + {%+ include("zone-masq.uc", { fw4, zone, family: 6, saddrs, daddrs }) %} |
| 77 | + {% endfor %} |
| 78 | + {% endfor %} |
| 79 | + {% endif %} |
| 80 | ++{% if (zone.fullcone4): %} |
| 81 | ++ {%+ include("zone-fullcone.uc", { fw4, zone, family: 4, direction: "srcnat" }) %} |
| 82 | ++{% endif %} |
| 83 | ++{% if (zone.fullcone6): %} |
| 84 | ++ {%+ include("zone-fullcone.uc", { fw4, zone, family: 6, direction: "srcnat" }) %} |
| 85 | ++{% endif %} |
| 86 | + {% fw4.includes('chain-append', `srcnat_${zone.name}`) %} |
| 87 | + } |
| 88 | + |
| 89 | +diff --git a/root/usr/share/firewall4/templates/zone-fullcone.uc b/root/usr/share/firewall4/templates/zone-fullcone.uc |
| 90 | +new file mode 100644 |
| 91 | +index 0000000..77d9806 |
| 92 | +--- /dev/null |
| 93 | ++++ b/root/usr/share/firewall4/templates/zone-fullcone.uc |
| 94 | +@@ -0,0 +1,4 @@ |
| 95 | ++{# /usr/share/firewall4/templates/zone-fullcone.uc #} |
| 96 | ++ meta nfproto {{ fw4.nfproto(family) }} fullcone comment "!fw4: Handle {{ |
| 97 | ++ zone.name |
| 98 | ++}} {{ fw4.nfproto(family, true) }} fullcone NAT {{ direction }} traffic" |
| 99 | +diff --git a/root/usr/share/ucode/fw4.uc b/root/usr/share/ucode/fw4.uc |
| 100 | +index 1b4764c..c5716da 100644 |
| 101 | +--- a/root/usr/share/ucode/fw4.uc |
| 102 | ++++ b/root/usr/share/ucode/fw4.uc |
| 103 | +@@ -1,3 +1,5 @@ |
| 104 | ++// /usr/share/ucode/fw4.uc |
| 105 | ++ |
| 106 | + const fs = require("fs"); |
| 107 | + const uci = require("uci"); |
| 108 | + const ubus = require("ubus"); |
| 109 | +@@ -428,6 +430,25 @@ function nft_try_hw_offload(devices) { |
| 110 | + return (rc == 0); |
| 111 | + } |
| 112 | + |
| 113 | ++function nft_try_fullcone() { |
| 114 | ++ let nft_test = |
| 115 | ++ 'add table inet fw4-fullcone-test; ' + |
| 116 | ++ 'add chain inet fw4-fullcone-test dstnat { ' + |
| 117 | ++ 'type nat hook prerouting priority -100; policy accept; ' + |
| 118 | ++ 'fullcone; ' + |
| 119 | ++ '}; ' + |
| 120 | ++ 'add chain inet fw4-fullcone-test srcnat { ' + |
| 121 | ++ 'type nat hook postrouting priority -100; policy accept; ' + |
| 122 | ++ 'fullcone; ' + |
| 123 | ++ '}; '; |
| 124 | ++ let cmd = sprintf("/usr/sbin/nft -c '%s' 2>/dev/null", replace(nft_test, "'", "'\\''")); |
| 125 | ++ let ok = system(cmd) == 0; |
| 126 | ++ if (!ok) { |
| 127 | ++ warn("nft_try_fullcone: cmd "+ cmd + "\n"); |
| 128 | ++ } |
| 129 | ++ return ok; |
| 130 | ++} |
| 131 | ++ |
| 132 | + |
| 133 | + return { |
| 134 | + read_kernel_version: function() { |
| 135 | +@@ -765,6 +786,18 @@ return { |
| 136 | + warn(`[!] ${msg}\n`); |
| 137 | + }, |
| 138 | + |
| 139 | ++ myinfo: function(fmt, ...args) { |
| 140 | ++ if (getenv("QUIET")) |
| 141 | ++ return; |
| 142 | ++ |
| 143 | ++ let msg = sprintf(fmt, ...args); |
| 144 | ++ |
| 145 | ++ if (getenv("TTY")) |
| 146 | ++ warn(`\033[32m${msg}\033[m\n`); |
| 147 | ++ else |
| 148 | ++ warn(`[I] ${msg}\n`); |
| 149 | ++ }, |
| 150 | ++ |
| 151 | + get: function(sid, opt) { |
| 152 | + return this.cursor.get("firewall", sid, opt); |
| 153 | + }, |
| 154 | +@@ -946,6 +979,21 @@ return { |
| 155 | + } |
| 156 | + }, |
| 157 | + |
| 158 | ++ myinfo_section: function(s, msg) { |
| 159 | ++ if (s[".name"]) { |
| 160 | ++ if (s.name) |
| 161 | ++ this.myinfo("Section %s (%s) %s", this.section_id(s[".name"]), s.name, msg); |
| 162 | ++ else |
| 163 | ++ this.myinfo("Section %s %s", this.section_id(s[".name"]), msg); |
| 164 | ++ } |
| 165 | ++ else { |
| 166 | ++ if (s.name) |
| 167 | ++ this.myinfo("ubus %s (%s) %s", s.type || "rule", s.name, msg); |
| 168 | ++ else |
| 169 | ++ this.myinfo("ubus %s %s", s.type || "rule", msg); |
| 170 | ++ } |
| 171 | ++ }, |
| 172 | ++ |
| 173 | + parse_policy: function(val) { |
| 174 | + return this.parse_enum(val, [ |
| 175 | + "accept", |
| 176 | +@@ -1385,6 +1433,7 @@ return { |
| 177 | + "dnat", |
| 178 | + "snat", |
| 179 | + "masquerade", |
| 180 | ++ "fullcone", |
| 181 | + "accept", |
| 182 | + "reject", |
| 183 | + "drop" |
| 184 | +@@ -1852,6 +1901,7 @@ return { |
| 185 | + } |
| 186 | + |
| 187 | + let defs = this.parse_options(data, { |
| 188 | ++ fullcone: [ "bool", "0" ], |
| 189 | + input: [ "policy", "drop" ], |
| 190 | + output: [ "policy", "drop" ], |
| 191 | + forward: [ "policy", "drop" ], |
| 192 | +@@ -1884,6 +1934,11 @@ return { |
| 193 | + |
| 194 | + delete defs.syn_flood; |
| 195 | + |
| 196 | ++ if (!nft_try_fullcone()) { |
| 197 | ++ delete defs.fullcone; |
| 198 | ++ warn("nft_try_fullcone failed, disable fullcone globally\n"); |
| 199 | ++ } |
| 200 | ++ |
| 201 | + this.state.defaults = defs; |
| 202 | + }, |
| 203 | + |
| 204 | +@@ -1908,6 +1963,8 @@ return { |
| 205 | + masq_dest: [ "network", null, PARSE_LIST ], |
| 206 | + |
| 207 | + masq6: [ "bool" ], |
| 208 | ++ fullcone4: [ "bool", "0" ], |
| 209 | ++ fullcone6: [ "bool", "0" ], |
| 210 | + |
| 211 | + extra: [ "string", null, UNSUPPORTED ], |
| 212 | + extra_src: [ "string", null, UNSUPPORTED ], |
| 213 | +@@ -1940,6 +1997,18 @@ return { |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | ++ if (this.state.defaults && !this.state.defaults.fullcone) { |
| 218 | ++ this.warn_section(data, "fullcone in defaults not enabled, ignore zone fullcone settings"); |
| 219 | ++ zone.fullcone4 = false; |
| 220 | ++ zone.fullcone6 = false; |
| 221 | ++ } |
| 222 | ++ if (zone.fullcone4) { |
| 223 | ++ this.myinfo_section(data, "IPv4 fullcone enabled for zone '" + zone.name + "'"); |
| 224 | ++ } |
| 225 | ++ if (zone.fullcone6) { |
| 226 | ++ this.myinfo_section(data, "IPv6 fullcone enabled for zone '" + zone.name + "'"); |
| 227 | ++ } |
| 228 | ++ |
| 229 | + if (zone.mtu_fix && this.kernel < 0x040a0000) { |
| 230 | + this.warn_section(data, "option 'mtu_fix' requires kernel 4.10 or later"); |
| 231 | + return; |
| 232 | +@@ -2110,10 +2179,15 @@ return { |
| 233 | + zone.related_subnets = related_subnets; |
| 234 | + zone.related_physdevs = related_physdevs; |
| 235 | + |
| 236 | ++ if (zone.fullcone4 || zone.fullcone6) { |
| 237 | ++ zone.dflags.snat = true; |
| 238 | ++ zone.dflags.dnat = true; |
| 239 | ++ } |
| 240 | ++ |
| 241 | + if (zone.masq || zone.masq6) |
| 242 | + zone.dflags.snat = true; |
| 243 | + |
| 244 | +- if ((zone.auto_helper && !(zone.masq || zone.masq6)) || length(zone.helper)) { |
| 245 | ++ if ((zone.auto_helper && !(zone.masq || zone.masq6 || zone.fullcone4 || zone.fullcone6)) || length(zone.helper)) { |
| 246 | + zone.dflags.helper = true; |
| 247 | + |
| 248 | + for (let helper in (length(zone.helper) ? zone.helper : this.state.helpers)) { |
0 commit comments