mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 10:31:38 +08:00
1629 lines
50 KiB
Lua
1629 lines
50 KiB
Lua
-- Copyright (C) 2017 yushi studio <ywb94@qq.com> github.com/ywb94
|
||
-- Licensed to the public under the GNU General Public License v3.
|
||
|
||
require "nixio.fs"
|
||
require "luci.sys"
|
||
require "luci.http"
|
||
require "luci.jsonc"
|
||
require "luci.model.uci"
|
||
local uci = require "luci.model.uci".cursor()
|
||
|
||
local m, s, o
|
||
|
||
local sid = arg[1]
|
||
local uuid = luci.sys.exec("cat /proc/sys/kernel/random/uuid")
|
||
local b64decode = nixio.bin.b64decode
|
||
local b64encode = nixio.bin.b64encode
|
||
local xray_version = nil
|
||
local xray_version_val = 0
|
||
|
||
-- 确保正确判断程序是否存在
|
||
local function is_finded(e)
|
||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||
end
|
||
|
||
local function is_installed(e)
|
||
return luci.model.ipkg.installed(e)
|
||
end
|
||
|
||
local function is_js_luci()
|
||
return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
|
||
end
|
||
|
||
-- trim
|
||
local function trim(text)
|
||
if not text or text == "" then
|
||
return ""
|
||
end
|
||
return (text:gsub("^%s*(.-)%s*$", "%1"))
|
||
end
|
||
|
||
-- base64
|
||
local function base64Decode(text)
|
||
local raw = text
|
||
if not text or text == "" then
|
||
return ''
|
||
end
|
||
text = text:gsub("%z", "")
|
||
text = text:gsub("_", "/")
|
||
text = text:gsub("-", "+")
|
||
local mod4 = #text % 4
|
||
text = text .. string.sub('====', mod4 + 1)
|
||
local result = b64decode(text)
|
||
if result then
|
||
return result:gsub("%z", "")
|
||
else
|
||
return raw
|
||
end
|
||
end
|
||
|
||
local function base64Encode(text)
|
||
if not text or text == "" then
|
||
return ''
|
||
end
|
||
local result = b64encode(text)
|
||
if result then
|
||
result = result:gsub("%z", "")
|
||
result = result:gsub("/", "_")
|
||
result = result:gsub("+", "-")
|
||
result = result:gsub("=", "")
|
||
return result
|
||
else
|
||
return text
|
||
end
|
||
end
|
||
|
||
-- 获取 Xray 版本号
|
||
if is_finded("xray") then
|
||
local version = luci.sys.exec("xray version 2>&1")
|
||
if version and version ~= "" then
|
||
xray_version = version:match("Xray%s+([%d%.]+)")
|
||
-- xray_version = version:match("([0-9]+%.[0-9]+%.[0-9]+)")
|
||
end
|
||
end
|
||
|
||
-- 将 Xray 版本号转换为数字
|
||
if xray_version and xray_version ~= "" then
|
||
local major, minor, patch =
|
||
xray_version:match("(%d+)%.?(%d*)%.?(%d*)")
|
||
|
||
major = tonumber(major) or 0
|
||
minor = tonumber(minor) or 0
|
||
patch = tonumber(patch) or 0
|
||
|
||
xray_version_val = major * 10000 + minor * 100 + patch
|
||
end
|
||
|
||
local function url(...)
|
||
local url = string.format("admin/services/%s", "shadowsocksr")
|
||
local args = { ... }
|
||
for i, v in ipairs(args) do
|
||
if v and v ~= "" then
|
||
url = url .. "/" .. v
|
||
end
|
||
end
|
||
return require "luci.dispatcher".build_url(url)
|
||
end
|
||
|
||
-- 默认的保存并应用行为
|
||
local function apply_redirect(m)
|
||
local tmp_uci_file = "/etc/config/" .. "shadowsocksr" .. "_redirect"
|
||
if m.redirect and m.redirect ~= "" then
|
||
if nixio.fs.access(tmp_uci_file) then
|
||
local redirect
|
||
for line in io.lines(tmp_uci_file) do
|
||
redirect = line:match("option%s+url%s+['\"]([^'\"]+)['\"]")
|
||
if redirect and redirect ~= "" then break end
|
||
end
|
||
if redirect and redirect ~= "" then
|
||
luci.sys.call("/bin/rm -f " .. tmp_uci_file)
|
||
luci.http.redirect(redirect)
|
||
end
|
||
else
|
||
nixio.fs.writefile(tmp_uci_file, "config redirect\n")
|
||
end
|
||
m.on_after_save = function(self)
|
||
local redirect = self.redirect
|
||
if redirect and redirect ~= "" then
|
||
m.uci:set("shadowsocksr" .. "_redirect", "@redirect[0]", "url", redirect)
|
||
end
|
||
end
|
||
else
|
||
luci.sys.call("/bin/rm -f " .. tmp_uci_file)
|
||
end
|
||
end
|
||
|
||
local function set_apply_on_parse(map)
|
||
if not map then return end
|
||
if is_js_luci() then
|
||
apply_redirect(map)
|
||
local old = map.on_after_save
|
||
map.on_after_save = function(self)
|
||
if old then old(self) end
|
||
map:set("@global[0]", "timestamp", os.time())
|
||
end
|
||
end
|
||
end
|
||
|
||
local has_xray = is_finded("xray")
|
||
local has_hysteria2 = is_finded("hysteria")
|
||
|
||
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
|
||
local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local")
|
||
|
||
local server_table = {}
|
||
local encrypt_methods = {
|
||
-- ssr
|
||
"none",
|
||
"table",
|
||
"rc4",
|
||
"rc4-md5-6",
|
||
"rc4-md5",
|
||
"aes-128-cfb",
|
||
"aes-192-cfb",
|
||
"aes-256-cfb",
|
||
"aes-128-ctr",
|
||
"aes-192-ctr",
|
||
"aes-256-ctr",
|
||
"bf-cfb",
|
||
"camellia-128-cfb",
|
||
"camellia-192-cfb",
|
||
"camellia-256-cfb",
|
||
"cast5-cfb",
|
||
"des-cfb",
|
||
"idea-cfb",
|
||
"rc2-cfb",
|
||
"seed-cfb",
|
||
"salsa20",
|
||
"chacha20",
|
||
"chacha20-ietf"
|
||
}
|
||
|
||
local encrypt_methods_ss = {
|
||
-- plain
|
||
"none",
|
||
"plain",
|
||
-- aead
|
||
"aes-128-gcm",
|
||
"aes-192-gcm",
|
||
"aes-256-gcm",
|
||
"chacha20-ietf-poly1305",
|
||
"xchacha20-ietf-poly1305",
|
||
-- aead 2022
|
||
"2022-blake3-aes-128-gcm",
|
||
"2022-blake3-aes-256-gcm",
|
||
"2022-blake3-chacha20-poly1305"
|
||
--[[ stream
|
||
"none",
|
||
"plain",
|
||
"table",
|
||
"rc4",
|
||
"rc4-md5",
|
||
"aes-128-cfb",
|
||
"aes-192-cfb",
|
||
"aes-256-cfb",
|
||
"aes-128-ctr",
|
||
"aes-192-ctr",
|
||
"aes-256-ctr",
|
||
"bf-cfb",
|
||
"camellia-128-cfb",
|
||
"camellia-192-cfb",
|
||
"camellia-256-cfb",
|
||
"salsa20",
|
||
"chacha20",
|
||
"chacha20-ietf" ]]--
|
||
}
|
||
|
||
local protocol = {
|
||
-- ssr
|
||
"origin",
|
||
"verify_deflate",
|
||
"auth_sha1_v4",
|
||
"auth_aes128_sha1",
|
||
"auth_aes128_md5",
|
||
"auth_chain_a",
|
||
"auth_chain_b",
|
||
"auth_chain_c",
|
||
"auth_chain_d",
|
||
"auth_chain_e",
|
||
"auth_chain_f"
|
||
}
|
||
|
||
local obfs = {
|
||
-- ssr
|
||
"plain",
|
||
"http_simple",
|
||
"http_post",
|
||
"random_head",
|
||
"tls1.2_ticket_auth"
|
||
}
|
||
|
||
local securitys = {
|
||
-- vmess
|
||
"auto",
|
||
"none",
|
||
"zero",
|
||
"aes-128-gcm",
|
||
"chacha20-poly1305"
|
||
}
|
||
|
||
local tls_flows = {
|
||
-- tls
|
||
"xtls-rprx-vision",
|
||
"xtls-rprx-vision-udp443",
|
||
"none"
|
||
}
|
||
|
||
m = Map("shadowsocksr", translate("Edit ShadowSocksR Server"))
|
||
m.redirect = url("servers")
|
||
if not sid or m.uci:get("shadowsocksr", sid) ~= "servers" then
|
||
luci.http.redirect(m.redirect)
|
||
return
|
||
end
|
||
-- 保存&应用成功后跳转到节点列表
|
||
set_apply_on_parse(m)
|
||
|
||
-- [[ Servers Setting ]]--
|
||
s = m:section(NamedSection, sid, "servers")
|
||
s.anonymous = true
|
||
s.addremove = false
|
||
|
||
o = s:option(DummyValue, "ssr_url", "SS/SSR/V2RAY/TROJAN/TUIC/HYSTERIA2 URL")
|
||
o.rawhtml = true
|
||
o.template = "shadowsocksr/ssrurl"
|
||
o.value = sid
|
||
|
||
-- 新增一个选择框,用于选择 Xray 或 Hysteria2 核心
|
||
o = s:option(ListValue, "_xray_hy2_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Hysteria2")))
|
||
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
|
||
-- 设置默认 Xray 或 Hysteria2 核心
|
||
-- 动态添加选项
|
||
if has_xray then
|
||
o:value("v2ray", translate("Xray (Hysteria2)"))
|
||
end
|
||
if has_hysteria2 then
|
||
o:value("hysteria2", translate("Hysteria2"))
|
||
end
|
||
-- 读取全局 xray_hy2_type
|
||
o.cfgvalue = function(self, section)
|
||
return self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type") or "hysteria2"
|
||
end
|
||
o.rmempty = false
|
||
o.write = function(self, section, value)
|
||
-- 更新 server_subscribe 的 xray_hy2_type
|
||
local old_value = self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type")
|
||
if old_value ~= value then
|
||
self.map.uci:set("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type", value)
|
||
end
|
||
end
|
||
|
||
o = s:option(ListValue, "type", translate("Server Node Type"))
|
||
if is_finded("xray") or is_finded("v2ray") then
|
||
o:value("v2ray", translate("V2Ray/XRay"))
|
||
end
|
||
if is_finded("ssr-redir") then
|
||
o:value("ssr", translate("ShadowsocksR"))
|
||
end
|
||
if has_ss_rust or has_ss_libev then
|
||
o:value("ss", translate("ShadowSocks"))
|
||
end
|
||
if is_finded("trojan") then
|
||
o:value("trojan", translate("Trojan"))
|
||
end
|
||
if is_finded("naive") then
|
||
o:value("naiveproxy", translate("NaiveProxy"))
|
||
end
|
||
if is_finded("hysteria") then
|
||
o:value("hysteria2", translate("Hysteria2"))
|
||
end
|
||
if is_finded("tuic-client") then
|
||
o:value("tuic", translate("TUIC"))
|
||
end
|
||
if is_finded("shadow-tls") and is_finded("sslocal") then
|
||
o:value("shadowtls", translate("Shadow-TLS"))
|
||
end
|
||
if is_finded("ipt2socks") then
|
||
o:value("socks5", translate("Socks5"))
|
||
end
|
||
if is_finded("redsocks2") then
|
||
o:value("tun", translate("Network Tunnel"))
|
||
end
|
||
|
||
o.description = translate("Using incorrect encryption mothod may causes service fail to start")
|
||
|
||
o = s:option(Value, "alias", translate("Alias(optional)"))
|
||
|
||
o = s:option(ListValue, "iface", translate("Network interface to use"))
|
||
for _, e in ipairs(luci.sys.net.devices()) do
|
||
if e ~= "lo" then
|
||
o:value(e)
|
||
end
|
||
end
|
||
o:depends("type", "tun")
|
||
o.description = translate("Redirect traffic to this network interface")
|
||
|
||
-- 新增一个选择框,用于选择 Shadowsocks 版本
|
||
o = s:option(ListValue, "_has_ss_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Version", "ShadowSocks")))
|
||
o.description = translate("Selection ShadowSocks Node Use Version.")
|
||
-- 设置默认 Shadowsocks 版本
|
||
-- 动态添加选项
|
||
if has_ss_rust then
|
||
o:value("ss-rust", translate("ShadowSocks-rust Version"))
|
||
end
|
||
if has_ss_libev then
|
||
o:value("ss-libev", translate("ShadowSocks-libev Version"))
|
||
end
|
||
-- 读取全局 ss_type
|
||
o.cfgvalue = function(self, section)
|
||
return self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "ss_type") or "ss-rust"
|
||
end
|
||
o:depends("type", "ss")
|
||
o.rmempty = false
|
||
o.write = function(self, section, value)
|
||
-- 更新 server_subscribe 的 ss_type
|
||
local old_value = self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "ss_type")
|
||
if old_value ~= value then
|
||
self.map.uci:set("shadowsocksr", "@server_subscribe[0]", "ss_type", value)
|
||
end
|
||
end
|
||
|
||
o = s:option(ListValue, "v2ray_protocol", translate("V2Ray/XRay protocol"))
|
||
o:value("vless", translate("VLESS"))
|
||
o:value("vmess", translate("VMess"))
|
||
o:value("trojan", translate("Trojan"))
|
||
o:value("shadowsocks", translate("ShadowSocks"))
|
||
if is_finded("xray") then
|
||
o:value("wireguard", translate("WireGuard"))
|
||
end
|
||
if is_finded("xray") then
|
||
o:value("hysteria2", translate("Hysteria2"))
|
||
end
|
||
o:value("socks", translate("Socks"))
|
||
o:value("http", translate("HTTP"))
|
||
o:depends("type", "v2ray")
|
||
|
||
o = s:option(Value, "server", translate("Server Address"))
|
||
o.datatype = "host"
|
||
o.rmempty = false
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
o:depends("type", "v2ray")
|
||
o:depends("type", "trojan")
|
||
o:depends("type", "naiveproxy")
|
||
o:depends("type", "hysteria2")
|
||
o:depends("type", "tuic")
|
||
o:depends("type", "shadowtls")
|
||
o:depends("type", "socks5")
|
||
|
||
o = s:option(Value, "server_port", translate("Server Port"))
|
||
o.datatype = "port"
|
||
o.rmempty = true
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
o:depends("type", "v2ray")
|
||
o:depends("type", "trojan")
|
||
o:depends("type", "naiveproxy")
|
||
o:depends("type", "hysteria2")
|
||
o:depends("type", "tuic")
|
||
o:depends("type", "shadowtls")
|
||
o:depends("type", "socks5")
|
||
|
||
o = s:option(Flag, "auth_enable", translate("Enable Authentication"))
|
||
o.rmempty = false
|
||
o.default = "0"
|
||
o:depends("type", "socks5")
|
||
o:depends({type = "v2ray", v2ray_protocol = "http"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||
|
||
o = s:option(Value, "username", translate("Username"))
|
||
o.rmempty = true
|
||
o:depends("type", "naiveproxy")
|
||
o:depends({type = "socks5", auth_enable = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "http", auth_enable = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks", auth_enable = true})
|
||
|
||
o = s:option(Value, "password", translate("Password"))
|
||
o.password = true
|
||
o.rmempty = true
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
o:depends("type", "trojan")
|
||
o:depends("type", "naiveproxy")
|
||
o:depends("type", "shadowtls")
|
||
o:depends({type = "socks5", auth_enable = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "http", auth_enable = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks", socks_ver = "5", auth_enable = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||
|
||
o = s:option(ListValue, "encrypt_method", translate("Encrypt Method"))
|
||
for _, v in ipairs(encrypt_methods) do
|
||
o:value(v)
|
||
end
|
||
o.rmempty = true
|
||
o:depends("type", "ssr")
|
||
|
||
o = s:option(ListValue, "encrypt_method_ss", translate("Encrypt Method"))
|
||
for _, v in ipairs(encrypt_methods_ss) do
|
||
if v == "none" then
|
||
o.default = "none"
|
||
o:value("none", translate("none"))
|
||
else
|
||
o:value(v, translate(v))
|
||
end
|
||
end
|
||
o.rmempty = true
|
||
o:depends("type", "ss")
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
|
||
o = s:option(Flag, "uot", translate("UDP over TCP"))
|
||
o.description = translate("Enable the SUoT protocol, requires server support.")
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
o.default = "0"
|
||
|
||
o = s:option(Flag, "ivCheck", translate("Bloom Filter"))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
o.default = "1"
|
||
|
||
-- [[ Enable Shadowsocks Plugin ]]--
|
||
o = s:option(Flag, "enable_plugin", translate("Enable Plugin"))
|
||
o.rmempty = true
|
||
o:depends("type", "ss")
|
||
o.default = "0"
|
||
|
||
-- Shadowsocks Plugin
|
||
o = s:option(ListValue, "plugin", translate("Obfs"))
|
||
o:value("none", translate("None"))
|
||
if is_finded("obfs-local") then
|
||
o:value("obfs-local", translate("obfs-local"))
|
||
end
|
||
if is_finded("v2ray-plugin") then
|
||
o:value("v2ray-plugin", translate("v2ray-plugin"))
|
||
end
|
||
if is_finded("xray-plugin") then
|
||
o:value("xray-plugin", translate("xray-plugin"))
|
||
end
|
||
if is_finded("shadow-tls") then
|
||
o:value("shadow-tls", translate("shadow-tls"))
|
||
end
|
||
o:value("custom", translate("Custom"))
|
||
o.rmempty = true
|
||
o:depends({enable_plugin = true})
|
||
|
||
o = s:option(Value, "custom_plugin", translate("Custom Plugin Path"))
|
||
o.placeholder = "/path/to/custom-plugin"
|
||
o:depends({plugin = "custom"})
|
||
|
||
o = s:option(Value, "plugin_opts", translate("Plugin Opts"))
|
||
o.rmempty = true
|
||
o:depends({enable_plugin = true})
|
||
|
||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||
for _, v in ipairs(protocol) do
|
||
o:value(v)
|
||
end
|
||
o.rmempty = true
|
||
o:depends("type", "ssr")
|
||
|
||
o = s:option(Value, "protocol_param", translate("Protocol param (optional)"))
|
||
o:depends("type", "ssr")
|
||
|
||
o = s:option(ListValue, "obfs", translate("Obfs"))
|
||
for _, v in ipairs(obfs) do
|
||
o:value(v)
|
||
end
|
||
o.rmempty = true
|
||
o:depends("type", "ssr")
|
||
|
||
o = s:option(Value, "obfs_param", translate("Obfs param (optional)"))
|
||
o:depends("type", "ssr")
|
||
|
||
|
||
-- [[ Hysteria2 ]]--
|
||
o = s:option(Value, "hy2_auth", translate("Users Authentication"))
|
||
o:depends("type", "hysteria2")
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
o.password = true
|
||
o.rmempty = false
|
||
|
||
o = s:option(Flag, "flag_port_hopping", translate("Enable Port Hopping"))
|
||
o:depends("type", "hysteria2")
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
|
||
o = s:option(Value, "port_range", translate("Port hopping range"))
|
||
o.description = translate("Format as 10000:20000 or 10000-20000 Multiple groups are separated by commas (,).")
|
||
o:depends({type = "hysteria2", flag_port_hopping = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_port_hopping = true})
|
||
--o.datatype = "portrange"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Flag, "flag_transport", translate("Enable Transport Protocol Settings"))
|
||
o:depends("type", "hysteria2")
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
|
||
o = s:option(ListValue, "transport_protocol", translate("Transport Protocol"))
|
||
o:depends({type = "hysteria2", flag_transport = true})
|
||
o:value("udp", translate("UDP"))
|
||
o.default = "udp"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "hopinterval", translate("Port Hopping Interval(Unit:Second)"))
|
||
o:depends({type = "hysteria2", flag_transport = true, flag_port_hopping = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_port_hopping = true})
|
||
o.datatype = "uinteger"
|
||
o.rmempty = true
|
||
o.default = "30"
|
||
|
||
o = s:option(Flag, "flag_obfs", translate("Enable Obfuscation"))
|
||
o:depends("type", "hysteria2")
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
|
||
o = s:option(Flag, "lazy_mode", translate("Enable Lazy Mode"))
|
||
o:depends("type", "hysteria2")
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
|
||
o = s:option(Value, "obfs_type", translate("Obfuscation Type"))
|
||
o:depends({type = "hysteria2", flag_obfs = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true})
|
||
o.rmempty = true
|
||
o.placeholder = "salamander"
|
||
|
||
o = s:option(Value, "salamander", translate("Obfuscation Password"))
|
||
o:depends({type = "hysteria2", flag_obfs = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true})
|
||
o.password = true
|
||
o.rmempty = true
|
||
o.placeholder = "cry_me_a_r1ver"
|
||
|
||
o = s:option(Flag, "flag_quicparam", translate("Hysterir QUIC parameters"))
|
||
o:depends("type", "hysteria2")
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
|
||
o = s:option(Flag, "disablepathmtudiscovery", translate("Disable QUIC path MTU discovery"))
|
||
o:depends({type = "hysteria2",flag_quicparam = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_quicparam = true})
|
||
o.rmempty = true
|
||
o.default = false
|
||
|
||
--[[Hysteria2 QUIC parameters setting]]
|
||
o = s:option(Value, "initstreamreceivewindow", translate("QUIC initStreamReceiveWindow"))
|
||
o:depends({type = "hysteria2", flag_quicparam = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_quicparam = true})
|
||
o.datatype = "uinteger"
|
||
o.rmempty = true
|
||
o.default = "8388608"
|
||
|
||
o = s:option(Value, "maxstreamreceivewindow", translate("QUIC maxStreamReceiveWindow"))
|
||
o:depends({type = "hysteria2", flag_quicparam = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_quicparam = true})
|
||
o.datatype = "uinteger"
|
||
o.rmempty = true
|
||
o.default = "8388608"
|
||
|
||
o = s:option(Value, "initconnreceivewindow", translate("QUIC initConnReceiveWindow"))
|
||
o:depends({type = "hysteria2", flag_quicparam = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_quicparam = true})
|
||
o.datatype = "uinteger"
|
||
o.rmempty = true
|
||
o.default = "20971520"
|
||
|
||
o = s:option(Value, "maxconnreceivewindow", translate("QUIC maxConnReceiveWindow"))
|
||
o:depends({type = "hysteria2", flag_quicparam = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_quicparam = true})
|
||
o.datatype = "uinteger"
|
||
o.rmempty = true
|
||
o.default = "20971520"
|
||
|
||
o = s:option(Value, "maxidletimeout", translate("QUIC maxIdleTimeout(Unit:second)"))
|
||
o:depends({type = "hysteria2", flag_quicparam = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_quicparam = true})
|
||
o.rmempty = true
|
||
o.datatype = "uinteger"
|
||
o.default = "30"
|
||
|
||
o = s:option(Value, "keepaliveperiod", translate("The keep-alive period.(Unit:second)"))
|
||
o.description = translate("Default value 0 indicatesno heartbeat.")
|
||
o:depends({type = "hysteria2", flag_quicparam = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_quicparam = true})
|
||
o.rmempty = true
|
||
o.datatype = "uinteger"
|
||
o.default = "10"
|
||
|
||
|
||
--[[ Shadow-TLS Options ]]
|
||
o = s:option(ListValue, "shadowtls_protocol", translate("shadowTLS protocol Version"))
|
||
o:depends("type", "shadowtls")
|
||
o:value("v3", translate("Enable V3 protocol."))
|
||
o:value("v2", translate("Enable V2 protocol."))
|
||
o.default = "v3"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Flag, "strict", translate("TLS 1.3 Strict mode"))
|
||
o:depends("type", "shadowtls")
|
||
o.default = "1"
|
||
o.rmempty = false
|
||
|
||
o = s:option(Flag, "fastopen", translate("TCP Fast Open"), translate("Enabling TCP Fast Open Requires Server Support."))
|
||
o:depends("type", "shadowtls")
|
||
o.default = "0"
|
||
o.rmempty = false
|
||
|
||
o = s:option(Flag, "disable_nodelay", translate("Disable TCP No_delay"))
|
||
o:depends("type", "shadowtls")
|
||
o.default = "0"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "shadowtls_sni", translate("shadow-TLS SNI"))
|
||
o:depends("type", "shadowtls")
|
||
o.datatype = "host"
|
||
o.rmempty = true
|
||
o.default = ""
|
||
|
||
--[[ add a ListValue for Choose chain type,sslocal or vmess ]]
|
||
o = s:option(ListValue, "chain_type", translate("Shadow-TLS ChainPoxy type"))
|
||
o:depends("type", "shadowtls")
|
||
if is_finded("sslocal") then
|
||
o:value("sslocal", translate("ShadowSocks-rust Version"))
|
||
end
|
||
if is_finded("xray") or is_finded("v2ray") then
|
||
o:value("vmess", translate("Vmess Protocol"))
|
||
end
|
||
o.default = "sslocal"
|
||
o.rmempty = false
|
||
|
||
o = s:option(Value, "sslocal_password",translate("Shadowsocks password"))
|
||
o:depends({type = "shadowtls", chain_type = "sslocal"})
|
||
o.rmempty = true
|
||
|
||
o = s:option(ListValue, "sslocal_method", translate("Encrypt Method"))
|
||
o:depends({type = "shadowtls", chain_type = "sslocal"})
|
||
for _, v in ipairs(encrypt_methods_ss) do
|
||
o:value(v)
|
||
end
|
||
|
||
o = s:option(Value, "vmess_uuid", translate("Vmess UUID"))
|
||
o:depends({type = "shadowtls", chain_type = "vmess"})
|
||
o.rmempty = false
|
||
o.default = uuid
|
||
|
||
o = s:option(ListValue, "vmess_method", translate("Encrypt Method"))
|
||
o:depends({type = "shadowtls", chain_type = "vmess"})
|
||
for _, v in ipairs(securitys) do
|
||
o:value(v, v:lower())
|
||
end
|
||
o.rmempty = true
|
||
o.default="auto"
|
||
|
||
-- [[ TUIC ]]
|
||
-- TuicNameId
|
||
o = s:option(Value, "tuic_uuid", translate("TUIC User UUID"))
|
||
o.password = true
|
||
o.rmempty = true
|
||
o.default = uuid
|
||
o:depends("type", "tuic")
|
||
|
||
--Tuic IP
|
||
o = s:option(Value, "tuic_ip", translate("TUIC Server IP Address"))
|
||
o.rmempty = true
|
||
o.datatype = "ip4addr"
|
||
o.default = ""
|
||
o:depends("type", "tuic")
|
||
|
||
-- Tuic Password
|
||
o = s:option(Value, "tuic_passwd", translate("TUIC User Password"))
|
||
o.password = true
|
||
o.rmempty = true
|
||
o.default = ""
|
||
o:depends("type", "tuic")
|
||
|
||
o = s:option(ListValue, "udp_relay_mode", translate("UDP relay mode"))
|
||
o:depends("type", "tuic")
|
||
o:value("native", translate("native UDP characteristics"))
|
||
o:value("quic", translate("lossless UDP relay using QUIC streams"))
|
||
o.default = "native"
|
||
o.rmempty = true
|
||
|
||
o = s:option(ListValue, "congestion_control", translate("Congestion control algorithm"))
|
||
o:depends("type", "tuic")
|
||
o:value("bbr", translate("BBR"))
|
||
o:value("cubic", translate("CUBIC"))
|
||
o:value("new_reno", translate("New Reno"))
|
||
o.default = "cubic"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "heartbeat", translate("Heartbeat interval(second)"))
|
||
o:depends("type", "tuic")
|
||
o.datatype = "uinteger"
|
||
o.default = "3"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "timeout", translate("Timeout for establishing a connection to server(second)"))
|
||
o:depends("type", "tuic")
|
||
o.datatype = "uinteger"
|
||
o.default = "8"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "gc_interval", translate("Garbage collection interval(second)"))
|
||
o:depends("type", "tuic")
|
||
o.datatype = "uinteger"
|
||
o.default = "3"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "gc_lifetime", translate("Garbage collection lifetime(second)"))
|
||
o:depends("type", "tuic")
|
||
o.datatype = "uinteger"
|
||
o.default = "15"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "send_window", translate("TUIC send window"))
|
||
o:depends("type", "tuic")
|
||
o.datatype = "uinteger"
|
||
o.default = 20971520
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "receive_window", translate("TUIC receive window"))
|
||
o:depends("type", "tuic")
|
||
o.datatype = "uinteger"
|
||
o.default = 10485760
|
||
o.rmempty = true
|
||
|
||
o = s:option(Flag, "disable_sni", translate("Disable SNI"))
|
||
o:depends("type", "tuic")
|
||
o.default = "0"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Flag, "zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake"))
|
||
o:depends("type", "tuic")
|
||
o.default = "0"
|
||
o.rmempty = true
|
||
|
||
-- Tuic settings for the local inbound socks5 server
|
||
o = s:option(Flag, "tuic_dual_stack", translate("Dual-stack Listening Socket"))
|
||
o.description = translate("If this option is not set, the socket behavior is platform dependent.")
|
||
o:depends("type", "tuic")
|
||
o.default = "0"
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "tuic_max_package_size", translate("Maximum packet size the socks5 server can receive from external"))
|
||
o:depends("type", "tuic")
|
||
o.datatype = "uinteger"
|
||
o.default = 1500
|
||
o.rmempty = true
|
||
|
||
-- AlterId
|
||
o = s:option(Value, "alter_id", translate("AlterId"))
|
||
o.datatype = "port"
|
||
o.default = 0
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||
|
||
-- VmessId
|
||
o = s:option(Value, "vmess_id", translate("Vmess/VLESS ID (UUID)"))
|
||
o.password = true
|
||
o.rmempty = true
|
||
o.default = uuid
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||
|
||
-- VLESS Encryption
|
||
o = s:option(Value, "vless_encryption", translate("VLESS Encryption"))
|
||
o.rmempty = true
|
||
o.default = "none"
|
||
o.placeholder = "none"
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||
o.validate = function(self, value)
|
||
value = value and value:match("^%s*(.-)%s*$") or value
|
||
return value ~= "" and value or "none"
|
||
end
|
||
|
||
-- 加密方式
|
||
o = s:option(ListValue, "security", translate("Encrypt Method"))
|
||
for _, v in ipairs(securitys) do
|
||
o:value(v, v:upper())
|
||
end
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||
|
||
-- SOCKS Version
|
||
o = s:option(ListValue, "socks_ver", translate("Socks Version"))
|
||
o:value("4", "Socks4")
|
||
o:value("4a", "Socks4A")
|
||
o:value("5", "Socks5")
|
||
o.rmempty = true
|
||
o.default = "5"
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||
|
||
-- 传输协议
|
||
o = s:option(ListValue, "transport", translate("Transport"))
|
||
o:value("raw", "RAW (TCP)")
|
||
o:value("kcp", "mKCP")
|
||
o:value("ws", "WebSocket")
|
||
o:value("httpupgrade", "HTTPUpgrade")
|
||
o:value("xhttp", "XHTTP (SplitHTTP)")
|
||
o:value("h2", "HTTP/2")
|
||
o:value("quic", "QUIC")
|
||
o:value("grpc", "gRPC")
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "http"})
|
||
|
||
-- [[ RAW部分 ]]--
|
||
-- TCP伪装
|
||
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
|
||
o:depends("transport", "raw")
|
||
o:value("none", translate("None"))
|
||
o:value("http", translate("HTTP"))
|
||
o.rmempty = true
|
||
|
||
-- HTTP域名
|
||
o = s:option(Value, "http_host", translate("HTTP Host"))
|
||
o:depends("tcp_guise", "http")
|
||
o.rmempty = true
|
||
|
||
-- HTTP路径
|
||
o = s:option(Value, "http_path", translate("HTTP Path"))
|
||
o:depends("tcp_guise", "http")
|
||
o.rmempty = true
|
||
|
||
-- [[ WS部分 ]]--
|
||
-- WS域名
|
||
o = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||
o:depends({transport = "ws", tls = false})
|
||
o.datatype = "hostname"
|
||
o.rmempty = true
|
||
|
||
-- WS路径
|
||
o = s:option(Value, "ws_path", translate("WebSocket Path"))
|
||
o:depends("transport", "ws")
|
||
o.rmempty = true
|
||
|
||
-- WS间隔
|
||
o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)"))
|
||
o.datatype = "integer"
|
||
o:depends("transport", "ws")
|
||
|
||
if is_finded("v2ray") then
|
||
-- WS前置数据
|
||
o = s:option(Value, "ws_ed", translate("Max Early Data"))
|
||
o:depends("ws_ed_enable", true)
|
||
o.datatype = "uinteger"
|
||
o:value("2048")
|
||
o.rmempty = true
|
||
|
||
-- WS前置数据标头
|
||
o = s:option(Value, "ws_ed_header", translate("Early Data Header Name"))
|
||
o:depends("ws_ed_enable", true)
|
||
o:value("Sec-WebSocket-Protocol")
|
||
o.rmempty = true
|
||
end
|
||
|
||
-- [[ httpupgrade部分 ]]--
|
||
|
||
-- httpupgrade域名
|
||
o = s:option(Value, "httpupgrade_host", translate("Httpupgrade Host"))
|
||
o:depends({transport = "httpupgrade", tls = false})
|
||
o.rmempty = true
|
||
|
||
-- httpupgrade路径
|
||
o = s:option(Value, "httpupgrade_path", translate("Httpupgrade Path"))
|
||
o:depends("transport", "httpupgrade")
|
||
o.rmempty = true
|
||
|
||
-- [[ XHTTP部分 ]]--
|
||
|
||
-- XHTTP 模式
|
||
o = s:option(ListValue, "xhttp_mode", translate("XHTTP Mode"))
|
||
o:depends("transport", "xhttp")
|
||
o.default = "auto"
|
||
o:value("auto")
|
||
o:value("packet-up")
|
||
o:value("stream-up")
|
||
o:value("stream-one")
|
||
|
||
-- XHTTP 主机
|
||
o = s:option(Value, "xhttp_host", translate("XHTTP Host"))
|
||
o.datatype = "hostname"
|
||
o:depends("transport", "xhttp")
|
||
o.rmempty = true
|
||
|
||
-- XHTTP 路径
|
||
o = s:option(Value, "xhttp_path", translate("XHTTP Path"))
|
||
o.placeholder = "/"
|
||
o:depends("transport", "xhttp")
|
||
o.rmempty = true
|
||
|
||
-- XHTTP 附加项
|
||
o = s:option(Flag, "enable_xhttp_extra", translate("XHTTP Extra"))
|
||
o.description = translate("Enable this option to configure XHTTP Extra (JSON format).")
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends("transport", "xhttp")
|
||
|
||
o = s:option(TextValue, "xhttp_extra", " ")
|
||
o.description = translate(
|
||
"<font><b>" .. translate("Configure XHTTP Extra Settings (JSON format), see:") .. "</b></font>" ..
|
||
" <a href='https://xtls.github.io/config/transports/splithttp.html#extra' target='_blank'>" ..
|
||
"<font style='color:green'><b>" .. translate("Click to the page") .. "</b></font></a>")
|
||
o:depends("enable_xhttp_extra", true)
|
||
--o.rmempty = true
|
||
o.rows = 10
|
||
o.wrap = "off"
|
||
o.cfgvalue = function(self, section, value)
|
||
local raw = m:get(section, "xhttp_extra")
|
||
if raw then
|
||
return base64Decode(raw)
|
||
end
|
||
end
|
||
o.write = function(self, section, value)
|
||
m:set(section, "xhttp_extra", base64Encode(value) or "")
|
||
local success, data = pcall(luci.jsonc.parse, value)
|
||
if success and data then
|
||
local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
|
||
or (data.downloadSettings and data.downloadSettings.address)
|
||
if address and address ~= "" then
|
||
address = address:gsub("^%[", ""):gsub("%]$", "")
|
||
m:set(section, "download_address", address)
|
||
else
|
||
m:del(section, "download_address")
|
||
end
|
||
else
|
||
m:del(section, "download_address")
|
||
end
|
||
end
|
||
o.validate = function(self, value)
|
||
value = trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||
local ok, data = pcall(luci.jsonc.parse, value)
|
||
if ok and data then
|
||
return value
|
||
else
|
||
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
|
||
end
|
||
end
|
||
o.remove = function(self, section, value)
|
||
m:del(section, "xhttp_extra")
|
||
m:del(section, "download_address")
|
||
end
|
||
|
||
-- [[ H2部分 ]]--
|
||
|
||
-- H2域名
|
||
o = s:option(Value, "h2_host", translate("HTTP/2 Host"))
|
||
o:depends("transport", "h2")
|
||
o.rmempty = true
|
||
|
||
-- H2路径
|
||
o = s:option(Value, "h2_path", translate("HTTP/2 Path"))
|
||
o:depends("transport", "h2")
|
||
o.rmempty = true
|
||
|
||
-- gRPC
|
||
o = s:option(Value, "serviceName", translate("gRPC Service Name"))
|
||
o:depends("transport", "grpc")
|
||
o.rmempty = true
|
||
|
||
if is_finded("xray") then
|
||
-- gPRC模式
|
||
o = s:option(ListValue, "grpc_mode", translate("gRPC Mode"))
|
||
o:depends("transport", "grpc")
|
||
o:value("gun", translate("Gun"))
|
||
o:value("multi", translate("Multi"))
|
||
o.rmempty = true
|
||
end
|
||
|
||
if is_finded("xray") then
|
||
-- gRPC初始窗口
|
||
o = s:option(Value, "initial_windows_size", translate("Initial Windows Size"))
|
||
o.datatype = "uinteger"
|
||
o:depends("transport", "grpc")
|
||
o.default = 0
|
||
o.rmempty = true
|
||
|
||
-- H2/gRPC健康检查
|
||
o = s:option(Flag, "health_check", translate("H2/gRPC Health Check"))
|
||
o:depends("transport", "h2")
|
||
o:depends("transport", "grpc")
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "read_idle_timeout", translate("H2 Read Idle Timeout"))
|
||
o.datatype = "uinteger"
|
||
o:depends({health_check = true, transport = "h2"})
|
||
o.default = 60
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "idle_timeout", translate("gRPC Idle Timeout"))
|
||
o.datatype = "uinteger"
|
||
o:depends({health_check = true, transport = "grpc"})
|
||
o.default = 60
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "health_check_timeout", translate("Health Check Timeout"))
|
||
o.datatype = "uinteger"
|
||
o:depends("health_check", 1)
|
||
o.default = 20
|
||
o.rmempty = true
|
||
|
||
o = s:option(Flag, "permit_without_stream", translate("Permit Without Stream"))
|
||
o:depends({health_check = true, transport = "grpc"})
|
||
o.rmempty = true
|
||
end
|
||
|
||
-- [[ QUIC部分 ]]--
|
||
o = s:option(ListValue, "quic_security", translate("QUIC Security"))
|
||
o:depends("transport", "quic")
|
||
o:value("none", translate("None"))
|
||
o:value("aes-128-gcm", translate("aes-128-gcm"))
|
||
o:value("chacha20-poly1305", translate("chacha20-poly1305"))
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "quic_key", translate("QUIC Key"))
|
||
o:depends("transport", "quic")
|
||
o.rmempty = true
|
||
|
||
o = s:option(ListValue, "quic_guise", translate("Header"))
|
||
o:depends("transport", "quic")
|
||
o.rmempty = true
|
||
o:value("none", translate("None"))
|
||
o:value("srtp", translate("VideoCall (SRTP)"))
|
||
o:value("utp", translate("BitTorrent (uTP)"))
|
||
o:value("wechat-video", translate("WechatVideo"))
|
||
o:value("dtls", translate("DTLS 1.2"))
|
||
o:value("wireguard", translate("WireGuard"))
|
||
|
||
-- [[ mKCP部分 ]]--
|
||
o = s:option(ListValue, "kcp_guise", translate("Camouflage Type"))
|
||
o:depends("transport", "kcp")
|
||
o:value("none", translate("None"))
|
||
o:value("srtp", translate("VideoCall (SRTP)"))
|
||
o:value("utp", translate("BitTorrent (uTP)"))
|
||
o:value("wechat-video", translate("WechatVideo"))
|
||
o:value("dtls", translate("DTLS 1.2"))
|
||
o:value("wireguard", translate("WireGuard"))
|
||
o:value("dns", translate("DNS"))
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "kcp_domain", translate("Camouflage Domain"))
|
||
o.description = translate("Use it together with the DNS disguised type. You can fill in any domain.")
|
||
o:depends("kcp_guise", "dns")
|
||
|
||
o = s:option(Value, "mtu", translate("MTU"))
|
||
o.datatype = "uinteger"
|
||
o:depends("transport", "kcp")
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o.default = 1350
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "tti", translate("TTI"))
|
||
o.datatype = "uinteger"
|
||
o:depends("transport", "kcp")
|
||
o.default = 50
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "uplink_capacity", translate("Uplink Capacity(Default:Mbps)"))
|
||
o.datatype = "uinteger"
|
||
o:depends("transport", "kcp")
|
||
o:depends("type", "hysteria2")
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
o.placeholder = 5
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "downlink_capacity", translate("Downlink Capacity(Default:Mbps)"))
|
||
o.datatype = "uinteger"
|
||
o:depends("transport", "kcp")
|
||
o:depends("type", "hysteria2")
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
o.placeholder = 20
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "read_buffer_size", translate("Read Buffer Size"))
|
||
o.datatype = "uinteger"
|
||
o:depends("transport", "kcp")
|
||
o.default = 2
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "write_buffer_size", translate("Write Buffer Size"))
|
||
o.datatype = "uinteger"
|
||
o:depends("transport", "kcp")
|
||
o.default = 2
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "seed", translate("Obfuscate password (optional)"))
|
||
o:depends("transport", "kcp")
|
||
o.rmempty = true
|
||
|
||
o = s:option(Flag, "congestion", translate("Congestion"))
|
||
o:depends("transport", "kcp")
|
||
o.rmempty = true
|
||
|
||
-- [[ WireGuard 部分 ]]--
|
||
o = s:option(Flag, "kernelmode", translate("Enabled Kernel virtual NIC TUN(optional)"))
|
||
o.description = translate(
|
||
"<ul>" ..
|
||
"<li>" .. translate("Linux kernel TUN virtual NIC requires system support and root privileges.") .. "</li>" ..
|
||
"<li>" .. translate("When enabled, it occupies IPv6 routing table 1023.") .. "</li>" ..
|
||
"</ul>"
|
||
)
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o.default = "0"
|
||
o.rmempty = true
|
||
|
||
o = s:option(DynamicList, "local_addresses", translate("Local addresses"))
|
||
o.datatype = "cidr"
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o.rmempty = true
|
||
|
||
o = s:option(DynamicList, "reserved", translate("Reserved bytes(optional)"))
|
||
o.description = translate("Decimal numbers separated by \",\" or Base64-encoded strings.")
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "private_key", translate("Private key"))
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o.password = true
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "peer_pubkey", translate("Peer public key"))
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "preshared_key", translate("Pre-shared key"))
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o.password = true
|
||
o.rmempty = true
|
||
|
||
o = s:option(DynamicList, "allowedips", translate("allowedIPs(optional)"))
|
||
o.description = translate("Wireguard allows only traffic from specific source IP.")
|
||
o.datatype = "cidr"
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o.default = "0.0.0.0/0"
|
||
o.rmempty = true
|
||
|
||
-- [[ User-Agent部分 ]]--
|
||
o = s:option(Value, "user_agent", translate("User-Agent"))
|
||
o:depends("tcp_guise", "http")
|
||
o:depends("transport", "ws")
|
||
o:depends("transport", "httpupgrade")
|
||
o:depends("transport", "xhttp")
|
||
o:depends("transport", "grpc")
|
||
|
||
--[[ FinalMask部分 ]]--
|
||
o = s:option(Flag, "enable_finalmask", translate("FinalMask"))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
|
||
o = s:option(TextValue, "finalmask", " ")
|
||
o.description = translate("An FinalMaskObject in JSON format, used for sharing.")
|
||
o:depends("enable_finalmask", true)
|
||
o.rows = 10
|
||
o.wrap = "off"
|
||
o.custom_cfgvalue = function(self, section, value)
|
||
local raw = m:get(section, "finalmask")
|
||
if raw then
|
||
return base64Decode(raw)
|
||
end
|
||
end
|
||
o.custom_write = function(self, section, value)
|
||
m:set(section, "finalmask", base64Encode(value) or "")
|
||
end
|
||
o.validate = function(self, value)
|
||
value = trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||
if luci.jsonc.parse(value) then
|
||
return value
|
||
else
|
||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||
end
|
||
end
|
||
|
||
-- [[ TLS ]]--
|
||
o = s:option(Flag, "tls", translate("TLS"))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", reality = false})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess", reality = false})
|
||
o:depends({type = "v2ray", v2ray_protocol = "trojan", reality = false})
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks", reality = false})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", reality = false})
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks", socks_ver = "5", reality = false})
|
||
o:depends({type = "v2ray", v2ray_protocol = "http", reality = false})
|
||
o:depends("type", "trojan")
|
||
o:depends("type", "hysteria2")
|
||
|
||
-- [[ TLS部分 ]] --
|
||
o = s:option(Flag, "tls_sessionTicket", translate("Session Ticket"))
|
||
o:depends({type = "trojan", tls = true})
|
||
o.default = "0"
|
||
|
||
if is_finded("xray") then
|
||
-- [[ REALITY ]]
|
||
o = s:option(Flag, "reality", translate("REALITY"))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", tls = false})
|
||
|
||
o = s:option(Value, "reality_publickey", translate("Public key"))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", reality = true})
|
||
|
||
o = s:option(Value, "reality_shortid", translate("Short ID"))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", reality = true})
|
||
|
||
o = s:option(Value, "reality_spiderx", translate("spiderX"))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", reality = true})
|
||
|
||
-- [[ XTLS ]]--
|
||
o = s:option(ListValue, "tls_flow", translate("Flow"))
|
||
for _, v in ipairs(tls_flows) do
|
||
if v == "none" then
|
||
o.default = "none"
|
||
o:value("none", translate("none"))
|
||
else
|
||
o:value(v, translate(v))
|
||
end
|
||
end
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||
|
||
-- [[ uTLS ]]--
|
||
o = s:option(ListValue, "fingerprint", translate("Finger Print"))
|
||
o.default = ""
|
||
o:value("chrome", translate("chrome"))
|
||
o:value("firefox", translate("firefox"))
|
||
o:value("safari", translate("safari"))
|
||
o:value("ios", translate("ios"))
|
||
o:value("android", translate("android"))
|
||
o:value("edge", translate("edge"))
|
||
o:value("360", translate("360"))
|
||
o:value("qq", translate("qq"))
|
||
o:value("random", translate("random"))
|
||
o:value("randomized", translate("randomized"))
|
||
o:value("", translate("disable"))
|
||
o:depends({type = "v2ray", tls = true})
|
||
o:depends({type = "v2ray", reality = true})
|
||
|
||
o = s:option(Flag, "enable_ech", translate("Enable ECH(optional)"))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends({type = "v2ray", tls = true})
|
||
|
||
o = s:option(TextValue, "ech_config", translate("ECH Config"))
|
||
o.description = translate(
|
||
"<font><b>" .. translate("If it is not empty, it indicates that the Client has enabled Encrypted Client, see:") .. "</b></font>" ..
|
||
" <a href='https://xtls.github.io/config/transport.html#tlsobject' target='_blank'>" ..
|
||
"<font style='color:green'><b>" .. translate("Click to the page") .. "</b></font></a>")
|
||
o:depends("enable_ech", true)
|
||
o.default = ""
|
||
o.rows = 5
|
||
o.wrap = "soft"
|
||
o.validate = function(self, value)
|
||
-- 清理空行和多余换行
|
||
return (value:gsub("[\r\n]", "")):gsub("^%s*(.-)%s*$", "%1")
|
||
end
|
||
|
||
o = s:option(ListValue, "ech_ForceQuery", translate("ECH Query Policy"))
|
||
o.description = translate("Controls the policy used when performing DNS queries for ECH configuration.")
|
||
o.default = "none"
|
||
o:value("none")
|
||
o:value("half")
|
||
o:value("full")
|
||
o:depends("enable_ech", true)
|
||
|
||
o = s:option(Flag, "enable_mldsa65verify", translate("Enable ML-DSA-65(optional)"))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends({type = "v2ray", reality = true})
|
||
|
||
o = s:option(TextValue, "reality_mldsa65verify", translate("ML-DSA-65 Public key"))
|
||
o.description = translate(
|
||
"<font><b>" .. translate("The client has not configured mldsa65Verify, but it will not perform the \"additional verification\" step and can still connect normally, see:") .. "</b></font>" ..
|
||
" <a href='https://github.com/XTLS/Xray-core/pull/4915' target='_blank'>" ..
|
||
"<font style='color:green'><b>" .. translate("Click to the page") .. "</b></font></a>")
|
||
o:depends("enable_mldsa65verify", true)
|
||
o.default = ""
|
||
o.rows = 5
|
||
o.wrap = "soft"
|
||
o.validate = function(self, value)
|
||
-- 清理空行和多余换行
|
||
return (value:gsub("[\r\n]", "")):gsub("^%s*(.-)%s*$", "%1")
|
||
end
|
||
end
|
||
|
||
o = s:option(Value, "tls_host", translate("TLS Host"))
|
||
o.datatype = "hostname"
|
||
o:depends("tls", true)
|
||
o:depends("xtls", true)
|
||
o:depends("reality", true)
|
||
o.rmempty = true
|
||
|
||
-- TLS ALPN
|
||
o = s:option(ListValue, "tls_alpn", translate("TLS ALPN"))
|
||
o.default = ""
|
||
o:value("", translate("Default"))
|
||
o:value("h3")
|
||
o:value("h2")
|
||
o:value("h3,h2")
|
||
o:value("http/1.1")
|
||
o:value("h2,http/1.1")
|
||
o:value("h3,h2,http/1.1")
|
||
o:depends("tls", true)
|
||
|
||
-- TUIC ALPN
|
||
o = s:option(ListValue, "tuic_alpn", translate("TUIC ALPN"))
|
||
o.default = ""
|
||
o:value("", translate("Default"))
|
||
o:value("h3")
|
||
o:value("h2")
|
||
o:value("h3,h2")
|
||
o:value("http/1.1")
|
||
o:value("h2,http/1.1")
|
||
o:value("h3,h2,http/1.1")
|
||
o:value("spdy/3.1")
|
||
o:value("h3,spdy/3.1")
|
||
o:depends("type", "tuic")
|
||
|
||
-- IP STACK PREFERENCE
|
||
o = s:option(ListValue, "ipstack_prefer", translate("IP Stack Preference"))
|
||
o.default = ""
|
||
o:value("", translate("Default"))
|
||
o:value("v4first")
|
||
o:value("v6first")
|
||
o:depends("tuic_dual_stack", true)
|
||
|
||
-- [[ allowInsecure ]]--
|
||
o = s:option(Flag, "insecure", translate("allowInsecure"))
|
||
o.rmempty = false
|
||
o:depends("type", "hysteria2")
|
||
o:depends("type", "trojan")
|
||
o:depends("type", "tuic")
|
||
o.description = translate("If true, allowss insecure connection at TLS client, e.g., TLS server uses unverifiable certificates.")
|
||
-- Xray 支持时间判断
|
||
-- if os.time() < os.time({year=2026,month=6,day=1}) then
|
||
if os.date("%Y.%m.%d") < "2026.06.01" then
|
||
-- Xray 支持到 26.06.01
|
||
o:depends("tls", true)
|
||
o:depends({ type = "v2ray", v2ray_protocol = "vless", reality = true })
|
||
end
|
||
|
||
if xray_version_val >= 260131 then
|
||
-- Xray 版本大于等于 26.1.31
|
||
-- [[ Xray TLS pinSHA256 ]] --
|
||
o = s:option(Value, "tls_CertSha", translate("TLS Chain Fingerprint (SHA256)"), translate("Once set, connects only when the server’s chain fingerprint matches."))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", tls = true})
|
||
|
||
-- [[ Xray TLS verify leaf certificate name ]] --
|
||
o = s:option(Value, "tls_CertByName", translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", tls = true})
|
||
end
|
||
|
||
-- [[ Hysteria2 TLS pinSHA256 ]] --
|
||
o = s:option(Value, "pinsha256", translate("Certificate fingerprint"))
|
||
o:depends("type", "hysteria2")
|
||
o.rmempty = true
|
||
|
||
-- [[ Mux.Cool ]] --
|
||
o = s:option(Flag, "mux", translate("Mux"), translate("Enable Mux.Cool"))
|
||
o.rmempty = false
|
||
o.default = false
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "raw"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "ws"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "kcp"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "httpupgrade"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "h2"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "quic"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "grpc"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "http"})
|
||
|
||
-- [[ TCP 最大并发连接数 ]]--
|
||
o = s:option(Value, "concurrency", translate("concurrency"))
|
||
o.description = translate(
|
||
"<ul>"
|
||
.. "<li>" .. translate("Default: disable. When entering a negative number, such as -1, The Mux module will not be used to carry TCP traffic.") .. "</li>"
|
||
.. "<li>" .. translate("Min value is 1, Max value is 128. When omitted or set to 0, it equals 8.") .. "</li>"
|
||
.. "</ul>")
|
||
o.rmempty = true
|
||
o.default = "-1"
|
||
o:value("-1", translate("disable"))
|
||
o:value("8", translate("8"))
|
||
o:depends("mux", true)
|
||
|
||
-- [[ UDP 最大并发连接数 ]]--
|
||
o = s:option(Value, "xudpConcurrency", translate("xudpConcurrency"))
|
||
o.description = translate(
|
||
"<ul>"
|
||
.. "<li>" .. translate("Default:16. When entering a negative number, such as -1, The Mux module will not be used to carry UDP traffic, Use original UDP transmission method of proxy protocol.") .. "</li>"
|
||
.. "<li>" .. translate("Min value is 1, Max value is 1024. When omitted or set to 0, Will same path as TCP traffic.") .. "</li>"
|
||
.. "</ul>")
|
||
o.rmempty = true
|
||
o.default = "16"
|
||
o:value("-1", translate("disable"))
|
||
o:value("16", translate("16"))
|
||
o:depends("mux", true)
|
||
|
||
-- [[ 对被代理的 UDP/443 流量处理方式 ]]--
|
||
o = s:option(ListValue, "xudpProxyUDP443", translate("xudpProxyUDP443"))
|
||
o.description = translate(
|
||
"<ul>"
|
||
.. "<li>" .. translate("Default reject rejects traffic.") .. "</li>"
|
||
.. "<li>" .. translate("allow: Allows use Mux connection.") .. "</li>"
|
||
.. "<li>" .. translate("skip: Not use Mux module to carry UDP 443 traffic, Use original UDP transmission method of proxy protocol.") .. "</li>"
|
||
.. "</ul>")
|
||
o.rmempty = true
|
||
o.default = "reject"
|
||
o:value("reject", translate("reject"))
|
||
o:value("allow", translate("allow"))
|
||
o:value("skip", translate("skip"))
|
||
o:depends("mux", true)
|
||
|
||
-- [[ XHTTP TCP Fast Open ]]--
|
||
--o = s:option(Flag, "tcpfastopen", translate("TCP Fast Open"), translate("Enabling TCP Fast Open Requires Server Support."))
|
||
--o.rmempty = true
|
||
--o.default = "0"
|
||
--o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "xhttp"})
|
||
|
||
-- [[ MPTCP ]]--
|
||
o = s:option(Flag, "mptcp", translate("MPTCP"), translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "http"})
|
||
|
||
-- [[ TESTPRE ]]--
|
||
o = s:option(Value, "preconns", translate("Pre-connections"), translate("Number of early established connections to reduce latency."))
|
||
o.datatype = "uinteger"
|
||
o.placeholder = 0
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||
|
||
-- [[ custom_tcpcongestion 连接服务器节点的 TCP 拥塞控制算法 ]]--
|
||
o = s:option(ListValue, "custom_tcpcongestion", translate("custom_tcpcongestion"))
|
||
o.rmempty = true
|
||
o.default = ""
|
||
o:value("", translate("comment_tcpcongestion_disable"))
|
||
o:value("bbr", translate("BBR"))
|
||
o:value("cubic", translate("CUBIC"))
|
||
o:value("reno", translate("Reno"))
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "socks"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "http"})
|
||
|
||
-- [[ HYSTERIA2_tcpcongestion 连接服务器节点的 TCP 拥塞控制算法 ]]--
|
||
o = s:option(ListValue, "hy2_tcpcongestion", translate("custom_tcpcongestion"))
|
||
o.rmempty = true
|
||
o.default = ""
|
||
o:value("", translate("comment_tcpcongestion_disable"))
|
||
o:value("bbr", translate("BBR"))
|
||
o:value("brutal", translate("BRUTAL"))
|
||
o:value("force-brutal", translate("FORCE BRUTAL"))
|
||
o:value("reno", translate("Reno"))
|
||
o:value("cubic", translate("CUBIC"))
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
|
||
-- [[ Cert ]]--
|
||
o = s:option(Flag, "certificate", translate("Self-signed Certificate"))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends("type", "tuic")
|
||
o:depends({type = "hysteria2", insecure = false})
|
||
o:depends({type = "trojan", tls = true, insecure = false})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vmess", tls = true, insecure = false})
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", tls = true, insecure = false})
|
||
o.description = translate("If you have a self-signed certificate,please check the box")
|
||
|
||
o = s:option(DummyValue, "upload", translate("Upload"))
|
||
o.template = "shadowsocksr/certupload"
|
||
o:depends("certificate", 1)
|
||
|
||
cert_dir = "/etc/ssl/private/"
|
||
local path
|
||
|
||
luci.http.setfilehandler(function(meta, chunk, eof)
|
||
if not fd then
|
||
if (not meta) or (not meta.name) or (not meta.file) then
|
||
return
|
||
end
|
||
fd = nixio.open(cert_dir .. meta.file, "w")
|
||
if not fd then
|
||
path = translate("Create upload file error.")
|
||
return
|
||
end
|
||
end
|
||
if chunk and fd then
|
||
fd:write(chunk)
|
||
end
|
||
if eof and fd then
|
||
fd:close()
|
||
fd = nil
|
||
path = '/etc/ssl/private/' .. meta.file .. ''
|
||
end
|
||
end)
|
||
if luci.http.formvalue("upload") then
|
||
local f = luci.http.formvalue("ulfile")
|
||
if #f <= 0 then
|
||
path = translate("No specify upload file.")
|
||
end
|
||
end
|
||
|
||
o = s:option(Value, "certpath", translate("Current Certificate Path"))
|
||
o:depends("certificate", 1)
|
||
o:value("/etc/ssl/private/ca.crt")
|
||
o.description = translate("Please confirm the current certificate path")
|
||
o.default = "/etc/ssl/private/ca.crt"
|
||
|
||
o = s:option(Flag, "fast_open", translate("TCP Fast Open"), translate("Enabling TCP Fast Open Requires Server Support."))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
o:depends("type", "trojan")
|
||
o:depends("type", "hysteria2")
|
||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "xhttp"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
|
||
o = s:option(Flag, "switch_enable", translate("Enable Auto Switch"))
|
||
o.rmempty = false
|
||
o.default = "1"
|
||
|
||
o = s:option(Value, "local_port", translate("Local Port"))
|
||
o.datatype = "port"
|
||
o.default = 1234
|
||
o.rmempty = false
|
||
|
||
if is_finded("kcptun-client") then
|
||
o = s:option(Flag, "kcp_enable", translate("KcpTun Enable"))
|
||
o.rmempty = true
|
||
o.default = "0"
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
|
||
o = s:option(Value, "kcp_port", translate("KcpTun Port"))
|
||
o.datatype = "portrange"
|
||
o.default = 4000
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
|
||
o = s:option(Value, "kcp_password", translate("KcpTun Password"))
|
||
o.password = true
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
|
||
o = s:option(Value, "kcp_param", translate("KcpTun Param"))
|
||
o.default = "--nocomp"
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
end
|
||
|
||
return m
|