diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua index 88351ff0ac2..6bd7aaafb33 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua @@ -204,12 +204,12 @@ for key, server_type in pairs(type_table) do end -- Socks User -o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when auth is password valid, Mandatory.")) +o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when Socks5 Auth Mode is password valid, Mandatory.")) o.rmempty = true o:depends("socks5_auth", "password") -- Socks Password -o = s:option(Value, "socks5_pass", translate("Socks5 Password"), translate("Only when auth is password valid, Not mandatory.")) +o = s:option(Value, "socks5_pass", translate("Socks5 Password"), translate("Only when Socks5 Auth Mode is password valid, Not mandatory.")) o.password = true o.rmempty = true o:depends("socks5_auth", "password") @@ -263,7 +263,7 @@ o.default = 0 s = m:section(TypedSection, "xray_noise_packets", translate("Xray Noise Packets")) s.description = translate( "" .. translate("To send noise packets, select \"Noise\" in Xray Settings.") .. "" .. - "
" .. translate("For specific usage, see: ") .. "" .. + "
" .. translate("For specific usage, see:") .. "" .. "" .. "" .. translate("Click to the page") .. "") s.template = "cbi/tblsection" diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua index c858b951572..5504de197e1 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua @@ -4,6 +4,7 @@ require "nixio.fs" require "luci.sys" require "luci.http" +require "luci.jsonc" require "luci.model.ipkg" local m, s, o @@ -504,7 +505,6 @@ 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")) @@ -623,6 +623,7 @@ o:value("kcp", "mKCP") o:value("ws", "WebSocket") o:value("httpupgrade", "HTTPUpgrade") o:value("splithttp", "SplitHTTP") +o:value("xhttp", "XHTTP") o:value("h2", "HTTP/2") o:value("quic", "QUIC") o:value("grpc", "gRPC") @@ -703,6 +704,78 @@ o = s:option(Value, "splithttp_path", translate("Splithttp Path")) o:depends("transport", "splithttp") o.rmempty = true +-- [[ XHTTP部分 ]]-- +o = s:option(ListValue, "xhttp_alpn", translate("XHTTP 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("transport", "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") + +o = s:option(Value, "xhttp_host", translate("XHTTP Host")) +o:depends({transport = "xhttp", tls = false}) +o.rmempty = true + +o = s:option(Value, "xhttp_path", translate("XHTTP Path")) +o.placeholder = "/" +o:depends("transport", "xhttp") +o.rmempty = true + +o = s:option(Flag, "enable_xhttp_extra", translate("XHTTP Extra")) +o.description = translate("Enable this option to configure XHTTP Extra (JSON format).") +o.default = "0" +o.rmempty = false +o:depends("transport", "xhttp") + +o = s:option(TextValue, "xhttp_extra", " ") +o.description = translate( + "" .. translate("Configure XHTTP Extra Settings (JSON format), see:") .. "" .. + " " .. + "" .. translate("Click to the page") .. "") +o:depends("enable_xhttp_extra", true) +o.rmempty = true +o.rows = 10 +o.wrap = "off" +o.custom_write = function(self, section, value) + m:set(section, "xhttp_extra", value) + 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 + 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 = value:gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n") + if value:sub(-1) == "\n" then + value = value:sub(1, -2) + end + local success, data = pcall(luci.jsonc.parse, value) + if not success or not data then + return nil, translate("Invalid JSON format") + end + + return value +end + -- [[ H2部分 ]]-- -- H2域名 diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm index 2607fb70848..d8dfbdd7784 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm @@ -263,6 +263,18 @@ } document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/"; break; + case "xhttp": + if (params.get("security") !== "tls") { + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : ""; + } + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_mode')[0].value = params.get("mode") || "auto"; + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/"; + if (params.get("extra") && params.get("extra").trim() !== "") { + document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].checked = true; // 设置 enable_xhttp_extra 为 true + document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].dispatchEvent(event); // 触发事件 + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_extra')[0].value = params.get("extra") || ""; + } + break; case "kcp": document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none"; document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || ""; @@ -338,6 +350,16 @@ document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_host')[0].value = ssm.host; document.getElementsByName('cbid.shadowsocksr.' + sid + '.splithttp_path')[0].value = ssm.path; } + if (ssm.net == "xhttp") { + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_mode')[0].value = ssm.mode; + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_host')[0].value = ssm.host; + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_path')[0].value = ssm.path; + if (params.get("extra") && params.get("extra").trim() !== "") { + document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].checked = true; // 设置 enable_xhttp_extra 为 true + document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].dispatchEvent(event); // 触发事件 + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_extra')[0].value = ssm.extra; + } + } if (ssm.net == "h2") { document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = ssm.host; document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = ssm.path; @@ -352,6 +374,7 @@ if (ssm.tls == "tls") { document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true; document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); + document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_alpn')[0].value = ssm.alpn; document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = ssm.sni || ssm.host; } if (ssm.mux !== undefined) { @@ -412,6 +435,7 @@ setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none"); dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event); + setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_alpn', params.get("alpn") || ""); setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || ""); setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || ""); } @@ -434,6 +458,18 @@ } setElementValue('cbid.shadowsocksr.' + sid + '.splithttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/"); break; + case "xhttp": + if (params.get("security") !== "tls") { + setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_host', params.get("host") ? decodeURIComponent(params.get("host")) : ""); + } + setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_mode', params.get("mode") || "auto"); + setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/"); + if (params.get("extra") && params.get("extra").trim() !== "") { + setElementValue('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', true); // 设置 enable_xhttp_extra 为 true + dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', event); // 触发事件 + setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_extra', params.get("extra") || ""); + } + break; case "kcp": setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none"); setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || ""); diff --git a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po index 9992e8173d0..03541f45c94 100644 --- a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po +++ b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po @@ -901,14 +901,14 @@ msgstr "Socks 协议的认证方式,默认值:noauth。" msgid "Socks5 User" msgstr "Socks5 用户名" -msgid "Only when auth is password valid, Mandatory." -msgstr "仅当 auth 为 password 时有效,必填。" +msgid "Only when Socks5 Auth Mode is password valid, Mandatory." +msgstr "仅当 Socks5 认证方式为 Password 时有效,必填。" msgid "Socks5 Password" msgstr "Socks5 密码" -msgid "Only when auth is password valid, Not mandatory." -msgstr "仅当 auth 为 password 时有效,非必填。" +msgid "Only when Socks5 Auth Mode is password valid, Not mandatory." +msgstr "仅当 Socks5 认证方式为 Password 时有效,非必填。" msgid "Enabled Mixed" msgstr "启用 Mixed" @@ -952,8 +952,8 @@ msgstr "UDP 噪声,在某些情况下可以绕过一些针对 UDP 协议的限 msgid "To send noise packets, select \"Noise\" in Xray Settings." msgstr "在 Xray 设置中勾选 “噪声” 以发送噪声包。" -msgid "For specific usage, see: " -msgstr "具体使用方法参见:" +msgid "For specific usage, see:" +msgstr "具体使用方法,请参见:" msgid "Click to the page" msgstr "点击前往" @@ -1018,6 +1018,27 @@ msgstr "SplitHTTP 主机名" msgid "Splithttp Path" msgstr "SplitHTTP 路径" +msgid "XHTTP Mode" +msgstr "XHTTP 模式" + +msgid "XHTTP Host" +msgstr "XHTTP 主机名" + +msgid "XHTTP Path" +msgstr "XHTTP 路径" + +msgid "XHTTP Extra" +msgstr "XHTTP 附加项" + +msgid "Enable this option to configure XHTTP Extra (JSON format)." +msgstr "启用此选项配置 XHTTP 附加项(JSON 格式)。" + +msgid "Configure XHTTP Extra Settings (JSON format), see:" +msgstr "配置 XHTTP 额外设置(JSON 格式),请参见:" + +msgid "Invalid JSON format" +msgstr "无效的 JSON 格式" + msgid "HTTP/2 Host" msgstr "HTTP/2 主机名" diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua index 8210ac36096..9c50aa5f081 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua @@ -4,7 +4,7 @@ local ucursor = require "luci.model.uci".cursor() local json = require "luci.jsonc" local server_section = arg[1] -local proto = arg[2] +local proto = arg[2] or "tcp" local local_port = arg[3] or "0" local socks_port = arg[4] or "0" @@ -179,7 +179,7 @@ end -- 开启 socks 代理 -- 检查是否启用 socks 代理 -if proto:find("tcp") and socks_port ~= "0" then +if proto and proto:find("tcp") and socks_port ~= "0" then table.insert(Xray.inbounds, { -- socks protocol = "socks", @@ -209,7 +209,7 @@ end security = (server.xtls == '1') and "xtls" or (server.tls == '1') and "tls" or (server.reality == '1') and "reality" or nil, tlsSettings = (server.tls == '1') and { -- tls - alpn = server.tls_alpn, + alpn = (server.transport == "xhttp" and server.xhttp_alpn ~= "") and server.xhttp_alpn or server.tls_alpn, fingerprint = server.fingerprint, allowInsecure = (server.insecure == "1"), serverName = server.tls_host, @@ -225,6 +225,7 @@ end minVersion = "1.3" } or nil, realitySettings = (server.reality == '1') and { + alpn = (server.transport == "xhttp" and server.xhttp_alpn ~= "") and server.xhttp_alpn or nil, publicKey = server.reality_publickey, shortId = server.reality_shortid, spiderX = server.reality_spiderx, @@ -271,6 +272,20 @@ end host = (server.splithttp_host or server.tls_host) or nil, path = server.splithttp_path or "/" } or nil, + xhttpSettings = (server.transport == "xhttp") and { + -- xhttp + mode = server.xhttp_mode or "auto", + host = (server.xhttp_host or server.tls_host) or nil, + path = server.xhttp_path or "/", + extra = (server.enable_xhttp_extra == "1" and server.xhttp_extra) and (function() + local success, parsed = pcall(json.parse, server.xhttp_extra) + if success then + return parsed.extra or parsed + else + return nil + end + end)() or nil + } or nil, httpSettings = (server.transport == "h2") and { -- h2 path = server.h2_path or "", @@ -387,7 +402,7 @@ local ss = { server_port = tonumber(server.server_port), local_address = "0.0.0.0", local_port = tonumber(local_port), - mode = (proto == "tcp,udp") and "tcp_and_udp" or proto .. "_only", + mode = (proto == "tcp,udp") and "tcp_and_udp" or (proto .. "_only"), password = server.password, method = server.encrypt_method_ss, timeout = tonumber(server.timeout), @@ -395,7 +410,7 @@ local ss = { reuse_port = true } local hysteria = { - server = (server.server_port and (server.port_range and (server.server .. ":" .. server.server_port .. "," .. server.port_range) or server.server .. ":" .. server.server_port) or (server.port_range and server.server .. ":" .. server.port_range or server.server .. ":443")), + server = (server.server_port and (server.port_range and (server.server .. ":" .. server.server_port .. "," .. server.port_range) or (server.server .. ":" .. server.server_port) or (server.port_range and server.server .. ":" .. server.port_range or server.server .. ":443"))), bandwidth = (server.uplink_capacity or server.downlink_capacity) and { up = tonumber(server.uplink_capacity) and tonumber(server.uplink_capacity) .. " mbps" or nil, down = tonumber(server.downlink_capacity) and tonumber(server.downlink_capacity) .. " mbps" or nil diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua index 777923a18b6..c4ad4768abf 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua @@ -193,6 +193,24 @@ local function processData(szType, content) result.splithttp_host = info.host result.splithttp_path = info.path end + if info.net == 'xhttp' then + result.xhttp_mode = info.mode + result.xhttp_host = info.host + result.xhttp_path = info.path + -- 检查 extra 参数是否存在且非空 + result.enable_xhttp_extra = (info.extra and info.extra ~= "") and "1" or nil + result.xhttp_extra = (info.extra and info.extra ~= "") and info.extra or nil + -- 尝试解析 JSON 数据 + local success, Data = pcall(jsonParse, info.extra) + 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) + result.download_address = address and address ~= "" and address or nil + else + -- 如果解析失败,清空下载地址 + result.download_address = nil + end + end if info.net == 'h2' then result.h2_host = info.host result.h2_path = info.path @@ -231,6 +249,9 @@ local function processData(szType, content) end if info.tls == "tls" or info.tls == "1" then result.tls = "1" + if info.alpn and info.alpn ~= "" then + result.xhttp_alpn = info.alpn + end if info.sni and info.sni ~= "" then result.tls_host = info.sni elseif info.host then @@ -353,6 +374,11 @@ local function processData(szType, content) local t = split(v, '=') params[t[1]] = t[2] end + if params.alpn then + -- 处理 alpn 参数 + result.xhttp_alpn = params.alpn + end + if params.sni then -- 未指定peer(sni)默认使用remote addr result.tls_host = params.sni @@ -385,6 +411,23 @@ local function processData(szType, content) elseif result.transport == "splithttp" then result.splithttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil result.splithttp_path = params.path and UrlDecode(params.path) or "/" + elseif result.transport == "xhttp" then + result.xhttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil + result.xhttp_mode = params.mode or "auto" + result.xhttp_path = params.path and UrlDecode(params.path) or "/" + -- 检查 extra 参数是否存在且非空 + result.enable_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil + result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil + -- 尝试解析 JSON 数据 + local success, Data = pcall(jsonParse, params.extra) + 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) + result.download_address = address and address ~= "" and address or nil + else + -- 如果解析失败,清空下载地址 + result.download_address = nil + end elseif result.transport == "http" or result.transport == "h2" then result.transport = "h2" result.h2_host = params.host and UrlDecode(params.host) or nil @@ -426,6 +469,7 @@ local function processData(szType, content) result.vless_encryption = params.encryption or "none" result.transport = params.type or "tcp" result.tls = (params.security == "tls" or params.security == "xtls") and "1" or "0" + result.xhttp_alpn = params.alpn or "" result.tls_host = params.sni result.tls_flow = (params.security == "tls" or params.security == "reality") and params.flow or nil result.fingerprint = params.fp @@ -442,6 +486,23 @@ local function processData(szType, content) elseif result.transport == "splithttp" then result.splithttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil result.splithttp_path = params.path and UrlDecode(params.path) or "/" + elseif result.transport == "xhttp" then + result.xhttp_host = (result.tls ~= "1") and (params.host and UrlDecode(params.host)) or nil + result.xhttp_mode = params.mode or "auto" + result.xhttp_path = params.path and UrlDecode(params.path) or "/" + -- 检查 extra 参数是否存在且非空 + result.enable_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil + result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil + -- 尝试解析 JSON 数据 + local success, Data = pcall(jsonParse, params.extra) + 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) + result.download_address = address and address ~= "" and address or nil + else + -- 如果解析失败,清空下载地址 + result.download_address = nil + end -- make it compatible with bullshit, "h2" transport is non-existent at all elseif result.transport == "http" or result.transport == "h2" then result.transport = "h2"