diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile
index 4486ac269..6ede0e2f7 100644
--- a/luci-app-passwall2/Makefile
+++ b/luci-app-passwall2/Makefile
@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
-PKG_VERSION:=1.16-8
+PKG_VERSION:=1.17-1
PKG_RELEASE:=
PKG_CONFIG_DEPENDS:= \
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua
index 226ae562c..b3a2b0338 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua
@@ -98,6 +98,9 @@ end
if api.is_finded("hysteria") then
type:value("Hysteria", translate("Hysteria"))
end
+if api.is_finded("tuic-client") then
+ type:value("TUIC", translate("TUIC"))
+end
protocol = s:option(ListValue, "protocol", translate("Protocol"))
protocol:value("vmess", translate("Vmess"))
@@ -279,6 +282,7 @@ address:depends("type", "SSR")
address:depends("type", "Brook")
address:depends("type", "Naiveproxy")
address:depends("type", "Hysteria")
+address:depends("type", "TUIC")
address:depends({ type = "V2ray", protocol = "vmess" })
address:depends({ type = "V2ray", protocol = "vless" })
address:depends({ type = "V2ray", protocol = "http" })
@@ -324,6 +328,7 @@ port:depends("type", "SSR")
port:depends("type", "Brook")
port:depends("type", "Naiveproxy")
port:depends("type", "Hysteria")
+port:depends("type", "TUIC")
port:depends({ type = "V2ray", protocol = "vmess" })
port:depends({ type = "V2ray", protocol = "vless" })
port:depends({ type = "V2ray", protocol = "http" })
@@ -535,6 +540,7 @@ uuid:depends({ type = "V2ray", protocol = "vmess" })
uuid:depends({ type = "V2ray", protocol = "vless" })
uuid:depends({ type = "Xray", protocol = "vmess" })
uuid:depends({ type = "Xray", protocol = "vless" })
+uuid:depends({ type = "TUIC"})
tls = s:option(Flag, "tls", translate("TLS"))
tls.default = 0
@@ -893,6 +899,102 @@ hysteria_disable_mtu_discovery:depends("type", "Hysteria")
hysteria_lazy_start = s:option(Flag, "hysteria_lazy_start", translate("Lazy Start"))
hysteria_lazy_start:depends("type", "Hysteria")
+--[[
+-- Tuic username for local socks connect
+tuic_passwd = s:option(Value, "tuic_socks_username", translate("TUIC UserName For Local Socks"))
+tuic_passwd.rmempty = true
+tuic_passwd.default = ""
+tuic_passwd:depends("type", "TUIC")
+-- Tuic Password for local socks connect
+tuic_passwd = s:option(Value, "tuic_socks_password", translate("TUIC Password For Local Socks"))
+tuic_passwd.password = true
+tuic_passwd.rmempty = true
+tuic_passwd.default = ""
+tuic_passwd:depends("type", "TUIC")
+--]]
+
+tuic_ip = s:option(Value, "tuic_ip", translate("Set the TUIC proxy server ip address"))
+tuic_ip:depends("type", "TUIC")
+tuic_ip.datatype = "ipaddr"
+tuic_ip.rmempty = true
+
+tuic_udp_relay_mode = s:option(ListValue, "tuic_udp_relay_mode", translate("UDP relay mode"))
+tuic_udp_relay_mode:depends("type", "TUIC")
+tuic_udp_relay_mode:value("native", translate("native"))
+tuic_udp_relay_mode:value("quic", translate("QUIC"))
+tuic_udp_relay_mode.default = "native"
+tuic_udp_relay_mode.rmempty = true
+
+tuic_congestion_control = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm"))
+tuic_congestion_control:depends("type", "TUIC")
+tuic_congestion_control:value("bbr", translate("BBR"))
+tuic_congestion_control:value("cubic", translate("CUBIC"))
+tuic_congestion_control:value("new_reno", translate("New Reno"))
+tuic_congestion_control.default = "cubic"
+tuic_congestion_control.rmempty = true
+
+tuic_heartbeat = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)"))
+tuic_heartbeat:depends("type", "TUIC")
+tuic_heartbeat.datatype = "uinteger"
+tuic_heartbeat.default = "3"
+tuic_heartbeat.rmempty = true
+
+tuic_timeout = s:option(Value, "tuic_timeout", translate("Timeout for establishing a connection to server(second)"))
+tuic_timeout:depends("type", "TUIC")
+tuic_timeout.datatype = "uinteger"
+tuic_timeout.default = "8"
+tuic_timeout.rmempty = true
+
+tuic_gc_interval = s:option(Value, "tuic_gc_interval", translate("Garbage collection interval(second)"))
+tuic_gc_interval:depends("type", "TUIC")
+tuic_gc_interval.datatype = "uinteger"
+tuic_gc_interval.default = "3"
+tuic_gc_interval.rmempty = true
+
+tuic_gc_lifetime = s:option(Value, "tuic_gc_lifetime", translate("Garbage collection lifetime(second)"))
+tuic_gc_lifetime:depends("type", "TUIC")
+tuic_gc_lifetime.datatype = "uinteger"
+tuic_gc_lifetime.default = "15"
+tuic_gc_lifetime.rmempty = true
+
+tuic_send_window = s:option(Value, "tuic_send_window", translate("TUIC send window"))
+tuic_send_window.datatype = "uinteger"
+tuic_send_window:depends("type", "TUIC")
+tuic_send_window.default = 20971520
+tuic_send_window.rmempty = true
+
+tuic_receive_window = s:option(Value, "tuic_receive_window", translate("TUIC receive window"))
+tuic_receive_window.datatype = "uinteger"
+tuic_receive_window:depends("type", "TUIC")
+tuic_receive_window.default = 10485760
+tuic_receive_window.rmempty = true
+
+tuic_max_package_size = s:option(Value, "tuic_max_package_size", translate("TUIC Maximum packet size the socks5 server can receive from external, in bytes"))
+tuic_max_package_size.datatype = "uinteger"
+tuic_max_package_size:depends("type", "TUIC")
+tuic_max_package_size.default = 1500
+tuic_max_package_size.rmempty = true
+
+--Tuic settings for the local inbound socks5 server
+tuic_dual_stack = s:option(Flag, "tuic_dual_stack", translate("Set if the listening socket should be dual-stack"))
+tuic_dual_stack:depends("type", "TUIC")
+tuic_dual_stack.default = 0
+tuic_dual_stack.rmempty = true
+
+tuic_disable_sni = s:option(Flag, "tuic_disable_sni", translate("Disable SNI"))
+tuic_disable_sni:depends("type", "TUIC")
+tuic_disable_sni.default = 0
+tuic_disable_sni.rmempty = true
+
+tuic_zero_rtt_handshake = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake"))
+tuic_zero_rtt_handshake:depends("type", "TUIC")
+tuic_zero_rtt_handshake.default = 0
+tuic_zero_rtt_handshake.rmempty = true
+
+tuic_tls_alpn = s:option(DynamicList, "tuic_tls_alpn", translate("TLS ALPN"))
+tuic_tls_alpn:depends({ type = "TUIC"})
+tuic_tls_alpn.rmempty = true
+
protocol.validate = function(self, value)
if value == "_shunt" or value == "_balancing" then
address.rmempty = true
diff --git a/luci-app-passwall2/luasrc/passwall2/util_tuic.lua b/luci-app-passwall2/luasrc/passwall2/util_tuic.lua
new file mode 100644
index 000000000..b37027c4f
--- /dev/null
+++ b/luci-app-passwall2/luasrc/passwall2/util_tuic.lua
@@ -0,0 +1,57 @@
+module("luci.passwall2.util_tuic", package.seeall)
+local api = require "luci.passwall2.api"
+local uci = api.uci
+local json = api.jsonc
+
+function gen_config(var)
+ local node_id = var["-node"]
+ if not node_id then
+ print("-node 不能为空")
+ return
+ end
+ local node = uci:get_all("passwall2", node_id)
+ local local_addr = var["-local_addr"]
+ local local_port = var["-local_port"]
+ local server_host = var["-server_host"] or node.address
+ local server_port = var["-server_port"] or node.port
+ local loglevel = var["-loglevel"] or "warn"
+
+ local tuic= {
+ relay = {
+ server = server_host .. ":" .. server_port,
+ ip = node.tuic_ip,
+ uuid = node.uuid,
+ password = node.tuic_password,
+ -- certificates = node.tuic_certificate and { node.tuic_certpath } or nil,
+ udp_relay_mode = node.tuic_udp_relay_mode,
+ congestion_control = node.tuic_congestion_control,
+ heartbeat = node.tuic_heartbeat .. "s",
+ timeout = node.tuic_timeout .. "s",
+ gc_interval = node.tuic_gc_interval .. "s",
+ gc_lifetime = node.tuic_gc_lifetime .. "s",
+ alpn = node.tuic_tls_alpn,
+ disable_sni = (node.tuic_disable_sni == "1"),
+ zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1"),
+ send_window = tonumber(node.tuic_send_window),
+ receive_window = tonumber(node.tuic_receive_window)
+ },
+ ["local"] = {
+ server = "[::]:" .. local_port,
+ username = node.tuic_socks_username,
+ password = node.tuic_socks_password,
+ dual_stack = (node.tuic_dual_stack == "1") and true or false,
+ max_packet_size = tonumber(node.tuic_max_package_size)
+ },
+ log_level = loglevel
+ }
+ return json.stringify(tuic, 1)
+end
+
+_G.gen_config = gen_config
+
+if arg[1] then
+ local func =_G[arg[1]]
+ if func then
+ print(func(api.get_function_args(arg)))
+ end
+end
diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po
index d7ff4d0bf..1f38d6047 100644
--- a/luci-app-passwall2/po/zh-cn/passwall2.po
+++ b/luci-app-passwall2/po/zh-cn/passwall2.po
@@ -1063,6 +1063,54 @@ msgstr "SS AEAD节点使用类型"
msgid "Trojan Node Use Type"
msgstr "Trojan节点使用类型"
+msgid "Set the TUIC proxy server ip address"
+msgstr "指定远程TUIC服务器IP"
+
+msgid "TUIC User Password For Connect Remote Server"
+msgstr "用于远程TUIC服务器连接的密码"
+
+msgid "TUIC UserName For Local Socks"
+msgstr "用于本地Socks服务器连接的用户名"
+
+msgid "TUIC Password For Local Socks"
+msgstr "用于本地Socks服务器连接的密码"
+
+msgid "UDP relay mode"
+msgstr "UDP中继模式"
+
+msgid "Congestion control algorithm"
+msgstr "拥塞控制算法"
+
+msgid "Heartbeat interval(second)"
+msgstr "保活心跳包发送间隔(单位:秒)"
+
+msgid "Timeout for establishing a connection to server(second)"
+msgstr "连接超时时间(单位:秒)"
+
+msgid "Garbage collection interval(second)"
+msgstr "UDP数据包片残片清理间隔(单位:秒)"
+
+msgid "Garbage collection lifetime(second)"
+msgstr "UDP数据包残片在服务器的保留时间(单位:秒)"
+
+msgid "Disable SNI"
+msgstr "关闭SNI服务器名称指示"
+
+msgid "Enable 0-RTT QUIC handshake"
+msgstr "客户端启用 0-RTT QUIC 连接握手"
+
+msgid "TUIC send window"
+msgstr "发送窗口(无需确认即可发送的最大字节数:默认8Mb*2)"
+
+msgid "TUIC receive window"
+msgstr "接收窗口(无需确认即可接收的最大字节数:默认8Mb)"
+
+msgid "TUIC Maximum packet size the socks5 server can receive from external, in bytes"
+msgstr "TUIC socks5 服务器可以从外部接收的最大数据包大小(以字节为单位)"
+
+msgid "Set if the listening socket should be dual-stack"
+msgstr "设置监听套接字为双栈"
+
msgid "
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)"
msgstr "
none:默认值,不进行伪装,发送的数据是没有特征的数据包。
srtp:伪装成 SRTP 数据包,会被识别为视频通话数据(如 FaceTime)。
utp:伪装成 uTP 数据包,会被识别为 BT 下载数据。
wechat-video:伪装成微信视频通话的数据包。
dtls:伪装成 DTLS 1.2 数据包。
wireguard:伪装成 WireGuard 数据包。(并不是真正的 WireGuard 协议)"
diff --git a/luci-app-passwall2/root/usr/share/passwall2/app.sh b/luci-app-passwall2/root/usr/share/passwall2/app.sh
index 5fc71ab8d..7f23bbf63 100755
--- a/luci-app-passwall2/root/usr/share/passwall2/app.sh
+++ b/luci-app-passwall2/root/usr/share/passwall2/app.sh
@@ -31,6 +31,7 @@ UTIL_SS=$LUA_UTIL_PATH/util_shadowsocks.lua
UTIL_XRAY=$LUA_UTIL_PATH/util_xray.lua
UTIL_NAIVE=$LUA_UTIL_PATH/util_naiveproxy.lua
UTIL_HYSTERIA=$LUA_UTIL_PATH/util_hysteria.lua
+UTIL_TUIC=$LUA_UTIL_PATH/util_tuic.lua
V2RAY_ARGS=""
V2RAY_CONFIG=""
@@ -529,6 +530,10 @@ run_socks() {
lua $UTIL_HYSTERIA gen_config -node $node -local_socks_port $socks_port -server_host $server_host -server_port $port ${_extra_param} > $config_file
ln_run "$(first_type $(config_t_get global_app hysteria_file))" "hysteria" $log_file -c "$config_file" client
;;
+ tuic)
+ lua $UTIL_TUIC gen_config -node $node -local_addr $bind -local_port $socks_port -server_host $server_host -server_port $port > $config_file
+ ln_run "$(first_type tuic-client)" "tuic-client" $log_file -c "$config_file"
+ ;;
esac
# http to socks