diff --git a/openwrt/files/luci/model/cbi/ua3f.lua b/openwrt/files/luci/model/cbi/ua3f.lua index c50b9b2..318944d 100644 --- a/openwrt/files/luci/model/cbi/ua3f.lua +++ b/openwrt/files/luci/model/cbi/ua3f.lua @@ -11,7 +11,13 @@ local ua3f = cbi.Map("ua3f", Across the Campus we can reach every corner in the world. ]] ) -local fields = require("luci.model.cbi.ua3f.fields") +local status = require("luci.model.cbi.ua3f.status") +local general = require("luci.model.cbi.ua3f.general") +local rule = require("luci.model.cbi.ua3f.rule") +local desync = require("luci.model.cbi.ua3f.desync") +local others = require("luci.model.cbi.ua3f.others") +local statistics = require("luci.model.cbi.ua3f.statistics") +local log = require("luci.model.cbi.ua3f.log") function create_sections(map) local sections = {} @@ -22,10 +28,10 @@ function create_sections(map) -- General Section with tabs sections.general = map:section(NamedSection, "main", "ua3f", translate("General")) sections.general:tab("general", translate("Settings")) - sections.general:tab("rewrite", translate("Rewrite Rules")) + sections.general:tab("rules", translate("Rewrite Rules")) sections.general:tab("desync", translate("Desync Settings")) sections.general:tab("others", translate("Others Settings")) - sections.general:tab("stats", translate("Statistics")) + sections.general:tab("statistics", translate("Statistics")) sections.general:tab("log", translate("Log")) return sections @@ -33,12 +39,12 @@ end local sections = create_sections(ua3f) -fields.add_status_fields(sections.status) -fields.add_general_fields(sections.general) -fields.add_rewrite_fields(sections.general) -fields.add_desync_fields(sections.general) -fields.add_others_fields(sections.general) -fields.add_stats_fields(sections.general) -fields.add_log_fields(sections.general) +status.add_status_fields(sections.status) +general.add_general_fields(sections.general) +rule.add_rule_fields(sections.general) +desync.add_desync_fields(sections.general) +others.add_others_fields(sections.general) +statistics.add_statistics_fields(sections.general) +log.add_log_fields(sections.general) return ua3f diff --git a/openwrt/files/luci/model/cbi/ua3f/desync.lua b/openwrt/files/luci/model/cbi/ua3f/desync.lua new file mode 100644 index 0000000..e8026e7 --- /dev/null +++ b/openwrt/files/luci/model/cbi/ua3f/desync.lua @@ -0,0 +1,42 @@ +local M = {} + +local cbi = require("luci.cbi") +local i18n = require("luci.i18n") +local utils = require("luci.model.cbi.ua3f.utils") +local translate = i18n.translate + +local Flag = cbi.Flag +local Value = cbi.Value +local DummyValue = cbi.DummyValue + +function M.add_desync_fields(section) + -- Enable TCP Desync + local desync_enabled = section:taboption("desync", Flag, "desync_enabled", translate("Enable TCP Desync")) + desync_enabled.description = translate("Enable TCP Desynchronization to evade DPI") + + if not utils.nfqueue_exists() then + local nfqueue_warning = section:taboption("desync", DummyValue, "_desync_nfqueue_warning", " ") + nfqueue_warning.rawhtml = true + nfqueue_warning:depends("desync_enabled", 1) + function nfqueue_warning.cfgvalue(self, section) + return "" .. + translate("Recommend install kmod-nft-queue package for NFQUEUE mode") .. "" + end + end + + -- CT Byte Setting + local ct_byte = section:taboption("desync", Value, "desync_ct_bytes", translate("Desync Bytes")) + ct_byte.placeholder = "1500" + ct_byte.datatype = "uinteger" + ct_byte.description = translate("Number of bytes for fragmented random emission") + ct_byte:depends("desync_enabled", "1") + + -- CT Packets Setting + local ct_packets = section:taboption("desync", Value, "desync_ct_packets", translate("Desync Packets")) + ct_packets.placeholder = "8" + ct_packets.datatype = "uinteger" + ct_packets.description = translate("Number of packets for fragmented random emission") + ct_packets:depends("desync_enabled", "1") +end + +return M diff --git a/openwrt/files/luci/model/cbi/ua3f/fields.lua b/openwrt/files/luci/model/cbi/ua3f/fields.lua deleted file mode 100644 index 7f03cff..0000000 --- a/openwrt/files/luci/model/cbi/ua3f/fields.lua +++ /dev/null @@ -1,303 +0,0 @@ -local M = {} - -local cbi = require("luci.cbi") -local i18n = require("luci.i18n") -local sys = require("luci.sys") -local translate = i18n.translate - -local Flag = cbi.Flag -local Value = cbi.Value -local ListValue = cbi.ListValue -local DummyValue = cbi.DummyValue -local TextValue = cbi.TextValue - -function cmd_exists(cmd) - return sys.call("command -v " .. cmd .. " >/dev/null 2>&1") == 0 -end - -function nfqueue_exists() - local opkg = cmd_exists("opkg") and sys.call("opkg list-installed kmod-nft-queue | grep -q kmod-nft-queue") == 0 - local apk = cmd_exists("apk") and (sys.call("apk info | grep -q kmod-nft-queue") == 0) - return opkg or apk -end - -function tproxy_exists() - local opkg = cmd_exists("opkg") and sys.call("opkg list-installed kmod-nft-tproxy | grep -q kmod-nft-tproxy") == 0 - local apk = cmd_exists("apk") and (sys.call("apk info | grep -q kmod-nft-tproxy") == 0) - return opkg or apk -end - --- Status Section Fields -function M.add_status_fields(section) - -- Enabled Flag - section:option(Flag, "enabled", translate("Enabled")) - - -- Running Status Display - local running = section:option(DummyValue, "running", translate("Status")) - running.rawhtml = true - running.cfgvalue = function(self, section) - local pid = sys.exec("pidof ua3f") - if pid == "" then - return "" - else - return "" - end - end -end - --- General Tab Fields -function M.add_general_fields(section) - -- Server Mode - local server_mode = section:taboption("general", ListValue, "server_mode", translate("Server Mode")) - server_mode:value("HTTP", "HTTP") - server_mode:value("SOCKS5", "SOCKS5") - server_mode:value("TPROXY", "TPROXY") - server_mode:value("REDIRECT", "REDIRECT") - server_mode:value("NFQUEUE", "NFQUEUE") - server_mode.default = "TPROXY" - - if not tproxy_exists() then - local tproxy_warning = section:taboption("general", DummyValue, "_tproxy_warning", " ") - tproxy_warning.rawhtml = true - tproxy_warning:depends("server_mode", "TPROXY") - function tproxy_warning.cfgvalue(self, section) - return "" .. - translate("Recommend install kmod-nft-tproxy package for TPROXY mode") .. "" - end - end - - if not nfqueue_exists() then - local nfqueue_warning = section:taboption("general", DummyValue, "_nfqueue_warning", " ") - nfqueue_warning.rawhtml = true - nfqueue_warning:depends("server_mode", "NFQUEUE") - function nfqueue_warning.cfgvalue(self, section) - return "" .. - translate("Recommend install kmod-nft-queue package for NFQUEUE mode") .. "" - end - end - - -- Bind Address - local bind = section:taboption("general", Value, "bind", translate("Bind Address")) - bind:value("127.0.0.1") - bind:value("0.0.0.0") - bind:depends("server_mode", "HTTP") - bind:depends("server_mode", "SOCKS5") - - -- Port - local port = section:taboption("general", Value, "port", translate("Port")) - port.placeholder = "1080" - port:depends("server_mode", "HTTP") - port:depends("server_mode", "SOCKS5") - port:depends("server_mode", "TPROXY") - port:depends("server_mode", "REDIRECT") - - -- Rewrite Mode - local rewrite_mode = section:taboption("general", ListValue, "rewrite_mode", translate("Rewrite Mode")) - rewrite_mode:value("DIRECT", translate("Direct Forward")) - rewrite_mode:value("GLOBAL", translate("Global Rewrite")) - rewrite_mode:value("RULES", translate("Rule Based")) - rewrite_mode.default = "GLOBAL" - rewrite_mode.description = translate( - "Direct Forward: No rewriting. Global Rewrite: Rewrite all User-Agents. Rule Based: Use rewrite rules to determine behavior.") - - -- User-Agent (for Global Rewrite) - local ua = section:taboption("general", Value, "ua", translate("User-Agent")) - ua.placeholder = "FFF" - ua.description = translate("User-Agent after rewrite") - ua:depends("rewrite_mode", "GLOBAL") - ua:depends("server_mode", "NFQUEUE") - - -- User-Agent Regex - local regex = section:taboption("general", Value, "ua_regex", translate("User-Agent Regex")) - regex.description = translate("Regular expression pattern for matching User-Agent") - regex:depends("rewrite_mode", "GLOBAL") - regex:depends("server_mode", "NFQUEUE") - - -- Partial Replace - local partialReplace = section:taboption("general", Flag, "partial_replace", translate("Partial Replace")) - partialReplace.description = - translate( - "Replace only the matched part of the User-Agent, only works when User-Agent Regex is not empty") - partialReplace.default = "0" - partialReplace:depends("rewrite_mode", "GLOBAL") - partialReplace:depends("server_mode", "NFQUEUE") -end - --- Rewrite Rules Tab Fields -function M.add_rewrite_fields(section) - local rules = section:taboption("rewrite", DummyValue, "") - rules.template = "ua3f/rules" -end - --- Statistics Tab Fields -function M.add_stats_fields(section) - local stats = section:taboption("stats", DummyValue, "") - stats.template = "ua3f/statistics" -end - --- Log Tab Fields -function M.add_log_fields(section) - -- Log Display - local log = section:taboption("log", TextValue, "log") - log.readonly = true - log.rows = 30 - log.wrap = "off" - function log.cfgvalue(self, section) - local logfile = "/var/log/ua3f/ua3f.log" - local fs = require("nixio.fs") - if not fs.access(logfile) then - return "" - end - local n = tonumber(luci.model.uci.cursor():get("ua3f", section, "log_lines")) or 1000 - return luci.sys.exec("tail -n " .. n .. " " .. logfile) - end - - function log.write(self, section, value) end - - function log.render(self, section, scope) - TextValue.render(self, section, scope) - luci.http.write("") - end - - -- Log Level - local log_level = section:taboption("log", ListValue, "log_level", translate("Log Level")) - log_level:value("DEBUG") - log_level:value("INFO") - log_level:value("WARN") - log_level:value("ERROR") - log_level.default = "WARN" - log_level.description = translate( - "Sets the logging level. Do not keep the log level set to DEBUG for an extended period of time.") - - -- Log Lines - local logLines = section:taboption("log", Value, "log_lines", translate("Display Lines")) - logLines.default = "1000" - logLines.datatype = "uinteger" - logLines.rmempty = false - - -- Button Container (DummyValue to hold all buttons) - local button_container = section:taboption("log", DummyValue, "_button_container", translate("Log Actions")) - button_container.rawhtml = true - - function button_container.cfgvalue(self, section) - return "" - end - - function button_container.render(self, section, scope) - luci.http.write([[ -
- -
- - - -
-
- - ]]) - end - - return log -end - -function M.add_desync_fields(section) - -- Enable TCP Desync - local desync_enabled = section:taboption("desync", Flag, "desync_enabled", translate("Enable TCP Desync")) - desync_enabled.description = translate("Enable TCP Desynchronization to evade DPI") - - if not nfqueue_exists() then - local nfqueue_warning = section:taboption("desync", DummyValue, "_desync_nfqueue_warning", " ") - nfqueue_warning.rawhtml = true - nfqueue_warning:depends("desync_enabled", 1) - function nfqueue_warning.cfgvalue(self, section) - return "" .. - translate("Recommend install kmod-nft-queue package for NFQUEUE mode") .. "" - end - end - - -- CT Byte Setting - local ct_byte = section:taboption("desync", Value, "desync_ct_bytes", translate("Desync Bytes")) - ct_byte.placeholder = "1500" - ct_byte.datatype = "uinteger" - ct_byte.description = translate("Number of bytes for fragmented random emission") - ct_byte:depends("desync_enabled", "1") - - -- CT Packets Setting - local ct_packets = section:taboption("desync", Value, "desync_ct_packets", translate("Desync Packets")) - ct_packets.placeholder = "8" - ct_packets.datatype = "uinteger" - ct_packets.description = translate("Number of packets for fragmented random emission") - ct_packets:depends("desync_enabled", "1") -end - --- Others Tab Fields -function M.add_others_fields(section) - -- TTL Setting - local ttl = section:taboption("others", Flag, "set_ttl", translate("Set TTL")) - ttl.description = translate("Set the TTL 64 for packets") - - -- TCP Timestamp Deletion - local tcpts = section:taboption("others", Flag, "del_tcpts", translate("Delete TCP Timestamps")) - tcpts.description = translate("Remove TCP Timestamp option") - - -- TCP Initial Window - local tcp_init_window = section:taboption("others", Flag, "set_tcp_init_window", translate("Set TCP Initial Window")) - tcp_init_window.description = translate("Set the TCP Initial Window to 65535 for SYN packets") - - -- IP ID Setting - local ipid = section:taboption("others", Flag, "set_ipid", translate("Set IP ID")) - ipid.description = translate("Set the IP ID to 0 for packets") - - if not nfqueue_exists() then - local nfqueue_warning = section:taboption("others", DummyValue, "_others_nfqueue_warning", " ") - nfqueue_warning.rawhtml = true - nfqueue_warning:depends("del_tcpts", 1) - nfqueue_warning:depends("set_tcp_init_window", 1) - nfqueue_warning:depends("set_ipid", 1) - function nfqueue_warning.cfgvalue(self, section) - return "" .. - translate("Recommend install kmod-nft-queue package for NFQUEUE mode") .. "" - end - end -end - -return M diff --git a/openwrt/files/luci/model/cbi/ua3f/general.lua b/openwrt/files/luci/model/cbi/ua3f/general.lua new file mode 100644 index 0000000..aeb643d --- /dev/null +++ b/openwrt/files/luci/model/cbi/ua3f/general.lua @@ -0,0 +1,90 @@ +local M = {} + +local cbi = require("luci.cbi") +local i18n = require("luci.i18n") +local utils = require("luci.model.cbi.ua3f.utils") +local translate = i18n.translate + +local Flag = cbi.Flag +local Value = cbi.Value +local ListValue = cbi.ListValue +local DummyValue = cbi.DummyValue + +function M.add_general_fields(section) + -- Server Mode + local server_mode = section:taboption("general", ListValue, "server_mode", translate("Server Mode")) + server_mode:value("HTTP", "HTTP") + server_mode:value("SOCKS5", "SOCKS5") + server_mode:value("TPROXY", "TPROXY") + server_mode:value("REDIRECT", "REDIRECT") + server_mode:value("NFQUEUE", "NFQUEUE") + server_mode.default = "TPROXY" + + if not utils.tproxy_exists() then + local tproxy_warning = section:taboption("general", DummyValue, "_tproxy_warning", " ") + tproxy_warning.rawhtml = true + tproxy_warning:depends("server_mode", "TPROXY") + function tproxy_warning.cfgvalue(self, section) + return "" .. + translate("Recommend install kmod-nft-tproxy package for TPROXY mode") .. "" + end + end + + if not utils.nfqueue_exists() then + local nfqueue_warning = section:taboption("general", DummyValue, "_nfqueue_warning", " ") + nfqueue_warning.rawhtml = true + nfqueue_warning:depends("server_mode", "NFQUEUE") + function nfqueue_warning.cfgvalue(self, section) + return "" .. + translate("Recommend install kmod-nft-queue package for NFQUEUE mode") .. "" + end + end + + -- Bind Address + local bind = section:taboption("general", Value, "bind", translate("Bind Address")) + bind:value("127.0.0.1") + bind:value("0.0.0.0") + bind:depends("server_mode", "HTTP") + bind:depends("server_mode", "SOCKS5") + + -- Port + local port = section:taboption("general", Value, "port", translate("Port")) + port.placeholder = "1080" + port:depends("server_mode", "HTTP") + port:depends("server_mode", "SOCKS5") + port:depends("server_mode", "TPROXY") + port:depends("server_mode", "REDIRECT") + + -- Rewrite Mode + local rewrite_mode = section:taboption("general", ListValue, "rewrite_mode", translate("Rewrite Mode")) + rewrite_mode:value("DIRECT", translate("Direct Forward")) + rewrite_mode:value("GLOBAL", translate("Global Rewrite")) + rewrite_mode:value("RULES", translate("Rule Based")) + rewrite_mode.default = "GLOBAL" + rewrite_mode.description = translate( + "Direct Forward: No rewriting. Global Rewrite: Rewrite all User-Agents. Rule Based: Use rewrite rules to determine behavior.") + + -- User-Agent (for Global Rewrite) + local ua = section:taboption("general", Value, "ua", translate("User-Agent")) + ua.placeholder = "FFF" + ua.description = translate("User-Agent after rewrite") + ua:depends("rewrite_mode", "GLOBAL") + ua:depends("server_mode", "NFQUEUE") + + -- User-Agent Regex + local regex = section:taboption("general", Value, "ua_regex", translate("User-Agent Regex")) + regex.description = translate("Regular expression pattern for matching User-Agent") + regex:depends("rewrite_mode", "GLOBAL") + regex:depends("server_mode", "NFQUEUE") + + -- Partial Replace + local partialReplace = section:taboption("general", Flag, "partial_replace", translate("Partial Replace")) + partialReplace.description = + translate( + "Replace only the matched part of the User-Agent, only works when User-Agent Regex is not empty") + partialReplace.default = "0" + partialReplace:depends("rewrite_mode", "GLOBAL") + partialReplace:depends("server_mode", "NFQUEUE") +end + +return M diff --git a/openwrt/files/luci/model/cbi/ua3f/log.lua b/openwrt/files/luci/model/cbi/ua3f/log.lua new file mode 100644 index 0000000..50e95e8 --- /dev/null +++ b/openwrt/files/luci/model/cbi/ua3f/log.lua @@ -0,0 +1,113 @@ +local M = {} + +local cbi = require("luci.cbi") +local i18n = require("luci.i18n") +local translate = i18n.translate + +local Value = cbi.Value +local ListValue = cbi.ListValue +local DummyValue = cbi.DummyValue +local TextValue = cbi.TextValue + +function M.add_log_fields(section) + -- Log Display + local log = section:taboption("log", TextValue, "log") + log.readonly = true + log.rows = 30 + log.wrap = "off" + function log.cfgvalue(self, section) + local logfile = "/var/log/ua3f/ua3f.log" + local fs = require("nixio.fs") + if not fs.access(logfile) then + return "" + end + local n = tonumber(luci.model.uci.cursor():get("ua3f", section, "log_lines")) or 1000 + return luci.sys.exec("tail -n " .. n .. " " .. logfile) + end + + function log.write(self, section, value) end + + function log.render(self, section, scope) + TextValue.render(self, section, scope) + luci.http.write("") + end + + -- Log Level + local log_level = section:taboption("log", ListValue, "log_level", translate("Log Level")) + log_level:value("DEBUG") + log_level:value("INFO") + log_level:value("WARN") + log_level:value("ERROR") + log_level.default = "WARN" + log_level.description = translate( + "Sets the logging level. Do not keep the log level set to DEBUG for an extended period of time.") + + -- Log Lines + local logLines = section:taboption("log", Value, "log_lines", translate("Display Lines")) + logLines.default = "1000" + logLines.datatype = "uinteger" + logLines.rmempty = false + + -- Button Container (DummyValue to hold all buttons) + local button_container = section:taboption("log", DummyValue, "_button_container", translate("Log Actions")) + button_container.rawhtml = true + + function button_container.cfgvalue(self, section) + return "" + end + + function button_container.render(self, section, scope) + luci.http.write([[ +
+ +
+ + + +
+
+ + ]]) + end + + return log +end + +return M diff --git a/openwrt/files/luci/model/cbi/ua3f/others.lua b/openwrt/files/luci/model/cbi/ua3f/others.lua new file mode 100644 index 0000000..d374154 --- /dev/null +++ b/openwrt/files/luci/model/cbi/ua3f/others.lua @@ -0,0 +1,41 @@ +local M = {} + +local cbi = require("luci.cbi") +local i18n = require("luci.i18n") +local utils = require("luci.model.cbi.ua3f.utils") +local translate = i18n.translate + +local Flag = cbi.Flag +local DummyValue = cbi.DummyValue + +function M.add_others_fields(section) + -- TTL Setting + local ttl = section:taboption("others", Flag, "set_ttl", translate("Set TTL")) + ttl.description = translate("Set the TTL 64 for packets") + + -- TCP Timestamp Deletion + local tcpts = section:taboption("others", Flag, "del_tcpts", translate("Delete TCP Timestamps")) + tcpts.description = translate("Remove TCP Timestamp option") + + -- TCP Initial Window + local tcp_init_window = section:taboption("others", Flag, "set_tcp_init_window", translate("Set TCP Initial Window")) + tcp_init_window.description = translate("Set the TCP Initial Window to 65535 for SYN packets") + + -- IP ID Setting + local ipid = section:taboption("others", Flag, "set_ipid", translate("Set IP ID")) + ipid.description = translate("Set the IP ID to 0 for packets") + + if not utils.nfqueue_exists() then + local nfqueue_warning = section:taboption("others", DummyValue, "_others_nfqueue_warning", " ") + nfqueue_warning.rawhtml = true + nfqueue_warning:depends("del_tcpts", 1) + nfqueue_warning:depends("set_tcp_init_window", 1) + nfqueue_warning:depends("set_ipid", 1) + function nfqueue_warning.cfgvalue(self, section) + return "" .. + translate("Recommend install kmod-nft-queue package for NFQUEUE mode") .. "" + end + end +end + +return M diff --git a/openwrt/files/luci/model/cbi/ua3f/rule.lua b/openwrt/files/luci/model/cbi/ua3f/rule.lua new file mode 100644 index 0000000..5c62e24 --- /dev/null +++ b/openwrt/files/luci/model/cbi/ua3f/rule.lua @@ -0,0 +1,12 @@ +local M = {} + +local cbi = require("luci.cbi") + +local DummyValue = cbi.DummyValue + +function M.add_rule_fields(section) + local rules = section:taboption("rules", DummyValue, "") + rules.template = "ua3f/rules" +end + +return M diff --git a/openwrt/files/luci/model/cbi/ua3f/statistics.lua b/openwrt/files/luci/model/cbi/ua3f/statistics.lua index 8440ee3..b78770a 100644 --- a/openwrt/files/luci/model/cbi/ua3f/statistics.lua +++ b/openwrt/files/luci/model/cbi/ua3f/statistics.lua @@ -1,6 +1,14 @@ --- UA3F Statistics Data Module local M = {} +local cbi = require("luci.cbi") + +local DummyValue = cbi.DummyValue + +function M.add_statistics_fields(section) + local stats = section:taboption("statistics", DummyValue, "") + stats.template = "ua3f/statistics" +end + -- Read rewrite statistics from file function M.read_rewrite_stats() local stats = {} diff --git a/openwrt/files/luci/model/cbi/ua3f/status.lua b/openwrt/files/luci/model/cbi/ua3f/status.lua new file mode 100644 index 0000000..ceb3701 --- /dev/null +++ b/openwrt/files/luci/model/cbi/ua3f/status.lua @@ -0,0 +1,30 @@ +local M = {} + +local cbi = require("luci.cbi") +local i18n = require("luci.i18n") +local sys = require("luci.sys") +local translate = i18n.translate + +local Flag = cbi.Flag +local DummyValue = cbi.DummyValue + +function M.add_status_fields(section) + -- Enabled Flag + section:option(Flag, "enabled", translate("Enabled")) + + -- Running Status Display + local running = section:option(DummyValue, "running", translate("Status")) + running.rawhtml = true + running.cfgvalue = function(self, section) + local pid = sys.exec("pidof ua3f") + if pid == "" then + return "" + else + return "" + end + end +end + +return M diff --git a/openwrt/files/luci/model/cbi/ua3f/utils.lua b/openwrt/files/luci/model/cbi/ua3f/utils.lua new file mode 100644 index 0000000..ace9fd4 --- /dev/null +++ b/openwrt/files/luci/model/cbi/ua3f/utils.lua @@ -0,0 +1,21 @@ +local M = {} + +local sys = require("luci.sys") + +local function cmd_exists(cmd) + return sys.call("command -v " .. cmd .. " >/dev/null 2>&1") == 0 +end + +function M.nfqueue_exists() + local opkg = cmd_exists("opkg") and sys.call("opkg list-installed kmod-nft-queue | grep -q kmod-nft-queue") == 0 + local apk = cmd_exists("apk") and (sys.call("apk info | grep -q kmod-nft-queue") == 0) + return opkg or apk +end + +function M.tproxy_exists() + local opkg = cmd_exists("opkg") and sys.call("opkg list-installed kmod-nft-tproxy | grep -q kmod-nft-tproxy") == 0 + local apk = cmd_exists("apk") and (sys.call("apk info | grep -q kmod-nft-tproxy") == 0) + return opkg or apk +end + +return M