mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-09-11 02:44:23 +08:00
1827 lines
57 KiB
Lua
1827 lines
57 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"
|
||
local nixio = require "nixio"
|
||
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" -p "/usr/libexec/%s" 2>/dev/null', e, 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("%c", "")
|
||
text = text:gsub("%s", "")
|
||
text = text:gsub("_", "/")
|
||
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
|
||
|
||
-- base64 编码
|
||
local function base64Encode(text)
|
||
if not text or text == "" then
|
||
return ''
|
||
end
|
||
local result = b64encode(text)
|
||
if result then
|
||
result = result:gsub("%z", "")
|
||
return result
|
||
else
|
||
return text
|
||
end
|
||
end
|
||
|
||
local function parse_realm_uri(uri)
|
||
uri = trim(uri)
|
||
if uri == "" then return nil end
|
||
-- realm[+http]://token@server/realm_id?query
|
||
local scheme = (uri:match("^realm%+http://") and "realm+http") or (uri:match("^realm://") and "realm")
|
||
if not scheme then return nil end
|
||
uri = uri:gsub("^realm%+http://", ""):gsub("^realm://", "")
|
||
local token, server_url, realm_id, query = uri:match("^([^@]+)@([^/]+)/([^?]*)%??(.*)$")
|
||
if not token or not server_url or not realm_id then return nil end
|
||
realm_id = realm_id:gsub("/+$", "")
|
||
local address, port = server_url:match("^%[([^%]]+)%]:(%d+)$") --ipv6:port
|
||
if not address then
|
||
address, port = server_url:match("^([^:]+):(%d+)$") --ipv4[domain]:port
|
||
end
|
||
address = address or server_url:match("^%[([^%]]+)%]$") or server_url
|
||
port = tonumber(port) or (scheme == "realm+http" and 80 or 443)
|
||
local realm = {
|
||
scheme = scheme,
|
||
token = token,
|
||
server_url = server_url,
|
||
address = address,
|
||
port = port,
|
||
realm_id = realm_id
|
||
}
|
||
-- 解析 query 中的 stun=
|
||
local stun_servers
|
||
for v in (query or ""):gmatch("[Ss][Tt][Uu][Nn]=([^&]+)") do
|
||
stun_servers = stun_servers or {}
|
||
stun_servers[#stun_servers + 1] = v
|
||
end
|
||
realm.stun_servers = stun_servers
|
||
return realm
|
||
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_ss_rust = is_finded("sslocal") or is_finded("ssserver")
|
||
local has_mihomo = is_finded("mihomo")
|
||
local has_xray = is_finded("xray")
|
||
|
||
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"
|
||
}
|
||
|
||
local function migrate_xray_protocol_nodes()
|
||
local changed = false
|
||
|
||
uci:foreach("shadowsocksr", "servers", function(section)
|
||
local sid = section[".name"]
|
||
local stype = section.type
|
||
local proto = section.v2ray_protocol
|
||
local escaped_sid = luci.util.shellquote(sid)
|
||
|
||
if stype == "ss" or stype == "ss-libev" then
|
||
if has_mihomo then
|
||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss'", escaped_sid))
|
||
changed = true
|
||
elseif has_ss_rust then
|
||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss-rust'", escaped_sid))
|
||
changed = true
|
||
elseif has_xray then
|
||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='shadowsocks'", escaped_sid, escaped_sid))
|
||
changed = true
|
||
end
|
||
elseif stype == "v2ray" and proto == "shadowsocks" and has_mihomo then
|
||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss' && " .. "uci delete shadowsocksr.%s.v2ray_protocol", escaped_sid, escaped_sid))
|
||
changed = true
|
||
end
|
||
if stype == "hysteria2" then
|
||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='hysteria2'", escaped_sid, escaped_sid))
|
||
changed = true
|
||
elseif stype == "trojan" then
|
||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='trojan'", escaped_sid, escaped_sid))
|
||
changed = true
|
||
end
|
||
end)
|
||
local subscribe_sid = uci:get_first("shadowsocksr", "server_subscribe")
|
||
if subscribe_sid then
|
||
local old_options = {"xray_hy2_type", "xray_tj_type", "ss_type"}
|
||
local escaped_sub_sid = luci.util.shellquote(subscribe_sid)
|
||
for _, opt in ipairs(old_options) do
|
||
if uci:get("shadowsocksr", subscribe_sid, opt) then
|
||
luci.sys.call(string.format("uci delete shadowsocksr.%s.%s", escaped_sub_sid, opt))
|
||
changed = true
|
||
end
|
||
end
|
||
end
|
||
if changed then
|
||
luci.sys.call("uci commit shadowsocksr")
|
||
end
|
||
end
|
||
|
||
migrate_xray_protocol_nodes()
|
||
|
||
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)
|
||
local old_after_save = m.on_after_save
|
||
m.on_after_save = function(self)
|
||
if old_after_save then old_after_save(self) end
|
||
local node_type = self.uci:get("shadowsocksr", sid, "type")
|
||
if node_type == "clash" then
|
||
luci.sys.call(string.format("/etc/init.d/shadowsocksr clash_cache %s >/dev/null 2>&1 &", sid))
|
||
end
|
||
end
|
||
|
||
local server_header = Template("/shadowsocksr/server_header")
|
||
server_header.section = sid
|
||
m:append(server_header)
|
||
|
||
-- [[ 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 或 Trojan 核心
|
||
o = s:option(ListValue, "type", translate("Server Node Type"))
|
||
if is_finded("xray") then
|
||
o:value("v2ray", translate("V2Ray/XRay"))
|
||
end
|
||
if is_finded("ssr-redir") then
|
||
o:value("ssr", translate("ShadowsocksR"))
|
||
end
|
||
if has_mihomo then
|
||
o:value("ss", translate("ShadowSocks"))
|
||
end
|
||
if has_ss_rust then
|
||
o:value("ss-rust", translate("ShadowSocks-Rust"))
|
||
end
|
||
if is_finded("naive") then
|
||
o:value("naiveproxy", translate("NaiveProxy"))
|
||
end
|
||
if is_finded("mihomo") then
|
||
o:value("clash", translate("Clash/Mihomo"))
|
||
end
|
||
if is_finded("mihomo") 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
|
||
local old_cfgvalue = o.cfgvalue
|
||
o.cfgvalue = function(self, section)
|
||
local val = self.map.uci:get("shadowsocksr", section, "type")
|
||
if old_cfgvalue then
|
||
return old_cfgvalue(self, section)
|
||
end
|
||
return val
|
||
end
|
||
o.description = translate("Using incorrect encryption mothod may causes service fail to start")
|
||
|
||
o = s:option(Value, "alias", translate("Alias(optional)"))
|
||
|
||
local function clash_source_formvalue(map, section, option)
|
||
local value = map:formvalue("cbid." .. map.config .. "." .. section .. "." .. option)
|
||
if value == nil then
|
||
value = map.uci:get(map.config, section, option)
|
||
end
|
||
return trim(value or "")
|
||
end
|
||
|
||
local function validate_clash_source(self, value, section)
|
||
local clash_url = clash_source_formvalue(self.map, section, "clash_url")
|
||
local clash_path = clash_source_formvalue(self.map, section, "clash_path")
|
||
if clash_url == "" and clash_path == "" then
|
||
return nil, translate("Please specify either a Clash subscription URL or a local YAML path.")
|
||
end
|
||
return value
|
||
end
|
||
|
||
o = s:option(Value, "clash_url", translate("Clash Subscription URL"))
|
||
o.placeholder = "https://example.com/config.yaml"
|
||
o.rmempty = true
|
||
o:depends("type", "clash")
|
||
o.validate = validate_clash_source
|
||
|
||
o = s:option(Value, "clash_path", translate("Clash YAML Path"))
|
||
o.placeholder = "/etc/ssrplus/clash/custom.yaml"
|
||
o.rmempty = true
|
||
o:depends("type", "clash")
|
||
o.validate = validate_clash_source
|
||
|
||
o = s:option(Value, "clash_user_agent", translate("Clash User-Agent"))
|
||
o.default = "clash"
|
||
o.rmempty = false
|
||
o:depends("type", "clash")
|
||
|
||
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
|
||
if is_finded("mihomo") then
|
||
o:value("snell", translate("Snell"))
|
||
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 = "or(host,ip6addr)"
|
||
o.rmempty = false
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss")
|
||
o:depends("type", "ss-rust")
|
||
o:depends("type", "trojan")
|
||
o:depends("type", "naiveproxy")
|
||
o:depends("type", "hysteria2")
|
||
o:depends("type", "tuic")
|
||
o:depends("type", "shadowtls")
|
||
o:depends("type", "socks5")
|
||
local protocols = s.fields["v2ray_protocol"].keylist
|
||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
||
for _, proto in ipairs(protocols) do
|
||
if not proto:find("^_") then
|
||
if proto == "hysteria2" then
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false})
|
||
else
|
||
o:depends({type = "v2ray", v2ray_protocol = proto})
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
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", "ss-rust")
|
||
o:depends("type", "trojan")
|
||
o:depends("type", "naiveproxy")
|
||
o:depends("type", "hysteria2")
|
||
o:depends("type", "tuic")
|
||
o:depends("type", "shadowtls")
|
||
o:depends("type", "socks5")
|
||
local protocols = s.fields["v2ray_protocol"].keylist
|
||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
||
for _, proto in ipairs(protocols) do
|
||
if not proto:find("^_") then
|
||
if proto == "hysteria2" then
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false})
|
||
else
|
||
o:depends({type = "v2ray", v2ray_protocol = proto})
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
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", "ss-rust")
|
||
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(Value, "snell_psk", translate("Snell PSK"))
|
||
o.password = true
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "snell"})
|
||
|
||
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-rust")
|
||
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:depends("type", "ss-rust")
|
||
o.default = "0"
|
||
|
||
-- Shadowsocks Plugin
|
||
o = s:option(ListValue, "plugin", translate("Obfs"))
|
||
o:value("none", translate("None"))
|
||
if has_mihomo or is_finded("obfs-local") then
|
||
o:value("obfs-local", translate("obfs-local"))
|
||
end
|
||
if has_mihomo or is_finded("v2ray-plugin") then
|
||
o:value("v2ray-plugin", translate("v2ray-plugin"))
|
||
end
|
||
if has_mihomo then
|
||
o:value("gost-plugin", translate("gost-plugin"))
|
||
end
|
||
if is_finded("xray-plugin") then
|
||
o:value("xray-plugin", translate("xray-plugin"))
|
||
end
|
||
if has_mihomo or is_finded("shadow-tls") then
|
||
o:value("shadow-tls", translate("Shadow-TLS"))
|
||
end
|
||
if has_mihomo then
|
||
o:value("restls", translate("restls"))
|
||
o:value("kcptun", translate("kcptun"))
|
||
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(Flag, "hysteria2_realms", translate("Hysteria2 Realms"))
|
||
o.default = "0"
|
||
if xray_version_val > 260509 then
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
else
|
||
o:depends({type = "v2ray", v2ray_protocol = "__hide"})
|
||
end
|
||
|
||
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||
o:depends("hysteria2_realms", true)
|
||
o.validate = function(self, value)
|
||
value = trim(value)
|
||
local realm = parse_realm_uri(value)
|
||
if realm then return value end
|
||
return nil, translate("Invalid Realm URL.")
|
||
end
|
||
|
||
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
|
||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||
o:depends("hysteria2_realms", true)
|
||
|
||
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", hysteria2_realms = false})
|
||
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 = "or(uinteger,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.description = translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5.")
|
||
o:depends({type = "hysteria2", flag_transport = true, flag_port_hopping = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_port_hopping = true})
|
||
o.datatype = "or(uinteger,portrange)"
|
||
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(ListValue, "obfs_type", translate("Obfuscation Type"))
|
||
o:value("", translate("Disable"))
|
||
o:value("salamander")
|
||
o:value("gecko")
|
||
o.rmempty = true
|
||
o:depends({type = "hysteria2", flag_obfs = true})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true})
|
||
|
||
o = s:option(Value, "obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
|
||
o.datatype = "uinteger"
|
||
o.placeholder = "512"
|
||
o.default = "512"
|
||
o:depends({type = "hysteria2", flag_obfs = true, obfs_type = "gecko"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true, obfs_type = "gecko"})
|
||
|
||
o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
|
||
o.datatype = "uinteger"
|
||
o.placeholder = "1200"
|
||
o.default = "1200"
|
||
o:depends({type = "hysteria2", flag_obfs = true, obfs_type = "gecko"})
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true, obfs_type = "gecko"})
|
||
|
||
o = s:option(Value, "salamander", translate("Obfuscation Password"))
|
||
o.password = true
|
||
o.rmempty = true
|
||
o:depends({type = "hysteria2", flag_obfs = true})
|
||
local obfs = s.fields["obfs_type"].keylist
|
||
if obfs and type(obfs) == "table" and #obfs > 0 then
|
||
for _, v in ipairs(obfs) do
|
||
if v and v ~= "" then
|
||
o:depends({
|
||
type = "v2ray",
|
||
v2ray_protocol = "hysteria2",
|
||
obfs_type = v,
|
||
flag_obfs = true
|
||
})
|
||
end
|
||
end
|
||
end
|
||
|
||
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") 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 = "ipaddr"
|
||
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, "snell_version", translate("Snell Version"))
|
||
o:value("1", "v1")
|
||
o:value("2", "v2")
|
||
o:value("3", "v3")
|
||
o:value("4", "v4")
|
||
o:value("5", "v5")
|
||
o.default = "4"
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "snell"})
|
||
|
||
o = s:option(ListValue, "snell_obfs", translate("Snell Obfs"))
|
||
o:value("", translate("Disable"))
|
||
o:value("http", "HTTP")
|
||
o:value("tls", "TLS")
|
||
o.default = ""
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "snell"})
|
||
|
||
o = s:option(Value, "snell_obfs_host", translate("Snell Obfs Host"))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", v2ray_protocol = "snell"})
|
||
|
||
-- 传输协议
|
||
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("xray") 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, "uplink_capacity", translate("Uplink Capacity(Default:Mbps)"))
|
||
o.datatype = "uinteger"
|
||
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("type", "hysteria2")
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||
o.placeholder = 20
|
||
o.rmempty = true
|
||
|
||
o = s:option(Value, "seed", translate("Obfuscate password (optional)"))
|
||
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("Supports decimal numbers separated by \",\" or Base64-encoded strings, with a maximum length of 3 bytes.")
|
||
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.default = ""
|
||
o:value("", translate("Default"))
|
||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", translate("chrome"))
|
||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", translate("firefox"))
|
||
o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", translate("safari"))
|
||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", translate("edge"))
|
||
o:value("Go-http-client/1.1", translate("golang"))
|
||
o:value("curl/7.68.0", translate("curl"))
|
||
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"
|
||
local protocols = s.fields["v2ray_protocol"].keylist
|
||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
||
for _, proto in ipairs(protocols) do
|
||
if not proto:find("^_") then
|
||
if proto == "hysteria2" then
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false})
|
||
elseif proto ~= "socks" and proto ~= "http" then
|
||
o:depends({type = "v2ray", v2ray_protocol = proto})
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
o = s:option(TextValue, "finalmask", " ")
|
||
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
|
||
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
|
||
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("unsafe", translate("unsafe"))
|
||
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"
|
||
local protocols = s.fields["v2ray_protocol"].keylist
|
||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
||
for _, proto in ipairs(protocols) do
|
||
if not proto:find("^_") then
|
||
if proto == "hysteria2" then
|
||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", tls = true, hysteria2_realms = false})
|
||
else
|
||
o:depends({type = "v2ray", v2ray_protocol = proto, tls = true})
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
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 = "full"
|
||
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)"))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", tls = true})
|
||
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
|
||
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
|
||
|
||
-- [[ Xray TLS verify leaf certificate name ]] --
|
||
o = s:option(Value, "tls_CertByName", translate("TLS Certificate Name (CertName)"))
|
||
o.rmempty = true
|
||
o:depends({type = "v2ray", tls = true})
|
||
o.description = translate("TLS is used to verify the leaf certificate name.") ..
|
||
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertByName(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
|
||
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("brutal", translate("BRUTAL"))
|
||
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", "ss-rust")
|
||
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"
|
||
|
||
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-rust")
|
||
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-rust")
|
||
o:depends("type", "ss")
|
||
|
||
o = s:option(Value, "kcp_password", translate("KcpTun Password"))
|
||
o.password = true
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss-rust")
|
||
o:depends("type", "ss")
|
||
|
||
o = s:option(Value, "kcp_param", translate("KcpTun Param"))
|
||
o.default = "--nocomp"
|
||
o:depends("type", "ssr")
|
||
o:depends("type", "ss-rust")
|
||
o:depends("type", "ss")
|
||
end
|
||
|
||
return m
|