From 08b4cda31bac8a0744f14e3951d45045f068b660 Mon Sep 17 00:00:00 2001 From: xiaorouji <60100640+xiaorouji@users.noreply.github.com> Date: Mon, 6 Feb 2023 18:57:37 +0800 Subject: [PATCH] luci: Xray outbound support network interface --- .../luasrc/model/cbi/passwall/api/api.lua | 6 ++-- .../model/cbi/passwall/api/gen_v2ray.lua | 17 ++++++++- .../model/cbi/passwall/client/node_config.lua | 5 +++ .../model/cbi/passwall/server/api/v2ray.lua | 35 +++++++++++++------ .../luasrc/model/cbi/passwall/server/user.lua | 5 +++ luci-app-passwall/po/zh-cn/passwall.po | 6 ++++ .../root/usr/share/passwall/app.sh | 12 +++++-- .../root/usr/share/passwall/iptables.sh | 5 +++ 8 files changed, 74 insertions(+), 17 deletions(-) diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/api/api.lua b/luci-app-passwall/luasrc/model/cbi/passwall/api/api.lua index 89d88f16f..402fae0b2 100755 --- a/luci-app-passwall/luasrc/model/cbi/passwall/api/api.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/api/api.lua @@ -110,7 +110,7 @@ function strToTable(str) end function is_normal_node(e) - if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt") then + if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then return false end return true @@ -220,7 +220,7 @@ function get_valid_nodes() uci:foreach(appname, "nodes", function(e) e.id = e[".name"] if e.type and e.remarks then - if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt") then + if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then e["remark"] = "%s:[%s] " % {i18n.translatef(e.type .. e.protocol), e.remarks} e["node_type"] = "special" nodes[#nodes + 1] = e @@ -257,7 +257,7 @@ end function get_full_node_remarks(n) local remarks = "" if n then - if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt") then + if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt" or n.protocol == "_iface") then remarks = "%s:[%s] " % {i18n.translatef(n.type .. n.protocol), n.remarks} else local type2 = n.type diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/api/gen_v2ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/api/gen_v2ray.lua index 27e5e6e0a..1944e57d8 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/api/gen_v2ray.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/api/gen_v2ray.lua @@ -526,7 +526,22 @@ if node_id then } end else - local outbound = gen_outbound(node) + local outbound = nil + if node.protocol == "_iface" then + if node.iface then + outbound = { + protocol = "freedom", + tag = "outbound", + streamSettings = { + sockopt = { + interface = node.iface + } + } + } + end + else + outbound = gen_outbound(node) + end if outbound then table.insert(outbounds, outbound) end routing = { domainStrategy = "AsIs", diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua index 6b00b5017..2e1ffcd5f 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua @@ -123,9 +123,14 @@ protocol:value("trojan", translate("Trojan")) protocol:value("wireguard", translate("WireGuard")) protocol:value("_balancing", translate("Balancing")) protocol:value("_shunt", translate("Shunt")) +protocol:value("_iface", translate("Custom Interface") .. " (Only Support Xray)") protocol:depends("type", "V2ray") protocol:depends("type", "Xray") +iface = s:option(Value, "iface", translate("Interface")) +iface.default = "eth1" +iface:depends("protocol", "_iface") + local nodes_table = {} for k, e in ipairs(api.get_valid_nodes()) do if e.node_type == "normal" then diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/api/v2ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/api/v2ray.lua index 95b04ecdd..ca883d038 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/api/v2ray.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/api/v2ray.lua @@ -117,19 +117,32 @@ function gen_config(user) } if user.outbound_node and user.outbound_node ~= "nil" then - local outbound_node_t = uci:get_all("passwall", user.outbound_node) - if user.outbound_node == "_socks" or user.outbound_node == "_http" then - outbound_node_t = { - type = user.type, - protocol = user.outbound_node:gsub("_", ""), - transport = "tcp", - address = user.outbound_node_address, - port = user.outbound_node_port, - username = (user.outbound_node_username and user.outbound_node_username ~= "") and user.outbound_node_username or nil, - password = (user.outbound_node_password and user.outbound_node_password ~= "") and user.outbound_node_password or nil, + local outbound = nil + if user.outbound_node == "_iface" and user.outbound_node_iface then + outbound = { + protocol = "freedom", + tag = "outbound", + streamSettings = { + sockopt = { + interface = user.outbound_node_iface + } + } } + else + local outbound_node_t = uci:get_all("passwall", user.outbound_node) + if user.outbound_node == "_socks" or user.outbound_node == "_http" then + outbound_node_t = { + type = user.type, + protocol = user.outbound_node:gsub("_", ""), + transport = "tcp", + address = user.outbound_node_address, + port = user.outbound_node_port, + username = (user.outbound_node_username and user.outbound_node_username ~= "") and user.outbound_node_username or nil, + password = (user.outbound_node_password and user.outbound_node_password ~= "") and user.outbound_node_password or nil, + } + end + outbound = require("luci.model.cbi.passwall.api.gen_v2ray").gen_outbound(outbound_node_t, "outbound") end - local outbound = require("luci.model.cbi.passwall.api.gen_v2ray").gen_outbound(outbound_node_t, "outbound") if outbound then table.insert(outbounds, 1, outbound) end diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua index 915797cfd..972659dcc 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua @@ -685,6 +685,7 @@ outbound_node = s:option(ListValue, "outbound_node", translate("outbound node")) outbound_node:value("nil", translate("Close")) outbound_node:value("_socks", translate("Custom Socks")) outbound_node:value("_http", translate("Custom HTTP")) +outbound_node:value("_iface", translate("Custom Interface") .. " (Only Support Xray)") for k, v in pairs(nodes_table) do outbound_node:value(v.id, v.remarks) end outbound_node.default = "nil" outbound_node:depends("type", "V2ray") @@ -708,6 +709,10 @@ outbound_node_password.password = true outbound_node_password:depends("outbound_node", "_socks") outbound_node_password:depends("outbound_node", "_http") +outbound_node_iface = s:option(Value, "outbound_node_iface", translate("Interface")) +outbound_node_iface.default = "eth1" +outbound_node_iface:depends("outbound_node", "_iface") + log = s:option(Flag, "log", translate("Log")) log.default = "1" log.rmempty = false diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po index ef8083874..aea10eeda 100644 --- a/luci-app-passwall/po/zh-cn/passwall.po +++ b/luci-app-passwall/po/zh-cn/passwall.po @@ -1162,6 +1162,12 @@ msgstr "自定义 Socks" msgid "Custom HTTP" msgstr "自定义 HTTP" +msgid "Custom Interface" +msgstr "自定义接口" + +msgid "Interface" +msgstr "接口" + msgid "Bind Local" msgstr "本机监听" diff --git a/luci-app-passwall/root/usr/share/passwall/app.sh b/luci-app-passwall/root/usr/share/passwall/app.sh index c4f350d2e..2d5fe2ff5 100755 --- a/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/luci-app-passwall/root/usr/share/passwall/app.sh @@ -24,6 +24,7 @@ DNS_PORT=15353 TUN_DNS="127.0.0.1#${DNS_PORT}" LOCAL_DNS=119.29.29.29 DEFAULT_DNS= +IFACES= NO_PROXY=0 PROXY_IPV6=0 PROXY_IPV6_UDP=0 @@ -360,6 +361,10 @@ run_v2ray() { _extra_param="${_extra_param} -loglevel $loglevel" lua $API_GEN_V2RAY ${_extra_param} > $config_file ln_run "$(first_type $(config_t_get global_app ${type}_file) ${type})" ${type} $log_file run -c "$config_file" + local protocol=$(config_n_get $node protocol) + [ "$protocol" == "_iface" ] && { + IFACES="$IFACES $(config_n_get $node iface)" + } } run_dns2socks() { @@ -412,8 +417,11 @@ run_socks() { error_msg="某种原因,此 Socks 服务的相关配置已失联,启动中止!" fi - if ([ "$type" == "v2ray" ] || [ "$type" == "xray" ]) && ([ -n "$(config_n_get $node balancing_node)" ] || [ "$(config_n_get $node default_node)" != "_direct" -a "$(config_n_get $node default_node)" != "_blackhole" ]); then - unset error_msg + if [ "$type" == "v2ray" ] || [ "$type" == "xray" ]; then + local protocol=$(config_n_get $node protocol) + if [ "$protocol" == "_balancing" ] || [ "$protocol" == "_shunt" ] || [ "$protocol" == "_iface" ]; then + unset error_msg + fi fi [ -n "${error_msg}" ] && { diff --git a/luci-app-passwall/root/usr/share/passwall/iptables.sh b/luci-app-passwall/root/usr/share/passwall/iptables.sh index 5956c3d55..ccd83b829 100755 --- a/luci-app-passwall/root/usr/share/passwall/iptables.sh +++ b/luci-app-passwall/root/usr/share/passwall/iptables.sh @@ -1222,6 +1222,11 @@ add_firewall_rule() { load_acl # dns_hijack "force" + + for iface in $IFACES; do + $ipt_n -I PSW_OUTPUT -o $iface -j RETURN + $ipt_m -I PSW_OUTPUT -o $iface -j RETURN + done [ -n "${is_tproxy}" -o -n "${udp_flag}" ] && { bridge_nf_ipt=$(sysctl -e -n net.bridge.bridge-nf-call-iptables)