mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 03:15:13 +08:00
🤞 Sync 2026-10-06 13:52:46
This commit is contained in:
1 parent
4d2d69bed9
commit
947eaea2ee
42 files changed
+2681
-1582
No files matched your search
@@ -1,19 +1,22 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-flowproxy
|
||||
PKG_VERSION:=1.0.4
|
||||
PKG_RELEASE:=4
|
||||
PKG_VERSION:=1.0.3
|
||||
PKG_RELEASE:=5
|
||||
|
||||
LUCI_TITLE:=LuCI Support for FlowProxy Traffic Diversion
|
||||
LUCI_DEPENDS:=+luci-base +nftables +kmod-nft-core +ip-full
|
||||
LUCI_DEPENDS:=+luci-base +nftables +ip-full
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/flowproxy
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/postinst
|
||||
#!/bin/sh
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
[ -f /etc/uci-defaults/99_flowproxy ] && ( . /etc/uci-defaults/99_flowproxy ) && rm -f /etc/uci-defaults/99_flowproxy
|
||||
(sleep 1; /etc/init.d/rpcd reload) >/dev/null 2>&1 &
|
||||
fi
|
||||
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||
/etc/init.d/rpcd restart
|
||||
}
|
||||
exit 0
|
||||
endef
|
||||
|
||||
|
||||
@@ -40,18 +40,6 @@ var callRestartService = rpc.declare({
|
||||
method: 'restart_service'
|
||||
});
|
||||
|
||||
var callGetDatasetInfo = rpc.declare({
|
||||
object: 'luci.flowproxy',
|
||||
method: 'get_dataset_info',
|
||||
params: ['file_path']
|
||||
});
|
||||
|
||||
var callUpdateDataset = rpc.declare({
|
||||
object: 'luci.flowproxy',
|
||||
method: 'update_dataset',
|
||||
params: ['url', 'file_path']
|
||||
});
|
||||
|
||||
return L.view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
@@ -108,12 +96,6 @@ return L.view.extend({
|
||||
}, [ _('Restart') ]));
|
||||
} else {
|
||||
content.push(E('span', { 'style': 'color: red; font-weight: bold;' }, [ _('stopped') ]));
|
||||
content.push(' ');
|
||||
content.push(E('button', {
|
||||
'class': 'btn cbi-button cbi-button-apply',
|
||||
'style': 'margin-left: 10px;',
|
||||
'click': ui.createHandlerFn(this, 'handleRestart')
|
||||
}, [ _('Restart') ]));
|
||||
}
|
||||
L.dom.content(statusEl, content);
|
||||
}
|
||||
@@ -207,54 +189,11 @@ return L.view.extend({
|
||||
s.taboption('settings', form.Flag, 'dns_proxy_enabled', _('Force upstream DNS to proxy server')).rmempty = false;
|
||||
|
||||
o = s.taboption('settings', form.Value, 'proxy_server_ip_addr', _('Proxy server IP address'));
|
||||
o.datatype = 'ip4addr'; o.rmempty = false;
|
||||
o.placeholder = '192.168.1.254';
|
||||
o.rmempty = true;
|
||||
o.validate = function(section_id, value) {
|
||||
var isEnabled = (s.formvalue(section_id, 'enabled') === '1');
|
||||
|
||||
// 1. 如果值为空:仅当启用了服务尝试保存时才提示必填;未启用时允许留空,绝不展示刺眼红框
|
||||
if (!value || value.trim() === '') {
|
||||
if (isEnabled) {
|
||||
return _('Proxy server IP address is required when FlowProxy is enabled.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
value = value.trim();
|
||||
|
||||
// 2. 基础 IPv4 格式校验
|
||||
if (!value.match(/^([0-9]{1,3}\.){3}[0-9]{1,3}$/)) {
|
||||
return _('Invalid IPv4 address format');
|
||||
}
|
||||
var octets = value.split('.');
|
||||
for (var oi = 0; oi < 4; oi++) {
|
||||
var oct = parseInt(octets[oi], 10);
|
||||
if (isNaN(oct) || oct < 0 || oct > 255) {
|
||||
return _('Invalid IPv4 address format');
|
||||
}
|
||||
}
|
||||
|
||||
if (value === '127.0.0.1' || value === '0.0.0.0') {
|
||||
return _('Invalid proxy server IP address.');
|
||||
}
|
||||
|
||||
// 3. 拦截本路由器自身的 IP 地址,彻底避免产生自指路由环路导致网络崩溃
|
||||
var allLocalIps = [];
|
||||
for (var k = 0; k < ifaces.length; k++) {
|
||||
var addrs = ifaces[k].getIPAddrs() || [];
|
||||
for (var a = 0; a < addrs.length; a++) {
|
||||
var pureIp = addrs[a].split('/')[0];
|
||||
if (pureIp && allLocalIps.indexOf(pureIp) === -1) {
|
||||
allLocalIps.push(pureIp);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allLocalIps.indexOf(value) !== -1) {
|
||||
return _('The proxy server IP address cannot be the local router IP (%s).').format(value);
|
||||
}
|
||||
|
||||
var ipInCidr = function(ipStr, cidrStr) {
|
||||
if (!cidrStr || cidrStr.indexOf(':') !== -1 || !ipStr || ipStr.indexOf(':') !== -1) return null;
|
||||
if (!cidrStr || cidrStr.indexOf(':') !== -1 || !ipStr || ipStr.indexOf(':') !== -1) return false;
|
||||
var parts = cidrStr.split('/');
|
||||
var cidrIp = parts[0];
|
||||
var maskBits = parseInt(parts[1] || '32', 10);
|
||||
@@ -270,19 +209,15 @@ return L.view.extend({
|
||||
|
||||
var ipNum = ipToNum(ipStr);
|
||||
var cidrNum = ipToNum(cidrIp);
|
||||
if (maskBits === 0) return { inSubnet: true, isBcastOrNet: false };
|
||||
|
||||
if (maskBits === 0) return true;
|
||||
|
||||
var mask = (0xffffffff << (32 - maskBits)) >>> 0;
|
||||
var inSubnet = ((ipNum & mask) === (cidrNum & mask));
|
||||
var netNum = (cidrNum & mask) >>> 0;
|
||||
var bcastNum = (netNum | (~mask >>> 0)) >>> 0;
|
||||
var isBcastOrNet = (maskBits < 31 && (ipNum === netNum || ipNum === bcastNum));
|
||||
|
||||
return { inSubnet: inSubnet, isBcastOrNet: isBcastOrNet };
|
||||
return (ipNum & mask) === (cidrNum & mask);
|
||||
};
|
||||
|
||||
var selected_interface = s.formvalue(section_id, 'interface');
|
||||
if (!selected_interface || selected_interface === 'auto') return true;
|
||||
if (!value || !selected_interface) return true;
|
||||
|
||||
var target_iface = null;
|
||||
for (var i = 0; i < ifaces.length; i++) {
|
||||
@@ -299,12 +234,9 @@ return L.view.extend({
|
||||
if (!ips || ips.length === 0) return true;
|
||||
|
||||
var inSubnet = false;
|
||||
var isBcastOrNet = false;
|
||||
for (var j = 0; j < ips.length; j++) {
|
||||
var res = ipInCidr(value, ips[j]);
|
||||
if (res && res.inSubnet) {
|
||||
if (ipInCidr(value, ips[j])) {
|
||||
inSubnet = true;
|
||||
if (res.isBcastOrNet) isBcastOrNet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -313,61 +245,19 @@ return L.view.extend({
|
||||
return _('The proxy server IP address must be within the subnet of the selected interface (%s).').format(ips.join(', '));
|
||||
}
|
||||
|
||||
if (isBcastOrNet) {
|
||||
return _('The proxy server IP address cannot be the network address or broadcast address.');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('settings', form.Value, 'proxy_server_dns_port', _('Proxy server DNS port'));
|
||||
o.datatype = 'port'; o.default = '5353'; o.rmempty = false;
|
||||
|
||||
var iface_opt = s.taboption('settings', form.ListValue, 'interface', _('Proxy server interface'));
|
||||
iface_opt.value('auto', _('Auto (Detect automatically)'));
|
||||
devices.forEach(function(d) { iface_opt.value(d.getName(), d.getName()); });
|
||||
iface_opt.default = 'auto';
|
||||
iface_opt.onchange = function(ev, sid, val) {
|
||||
var ip_opt = m.lookupOption('proxy_server_ip_addr', sid)[0];
|
||||
if (ip_opt) {
|
||||
var currVal = ip_opt.formvalue(sid);
|
||||
// 仅当输入框已有非空输入时才触发重新比对子网,避免在空值时派发失焦导致误报红框
|
||||
if (currVal && currVal.trim() !== '') {
|
||||
var el = document.getElementById(ip_opt.cbid(sid));
|
||||
if (el) {
|
||||
var inputEl = el.tagName === 'INPUT' ? el : el.querySelector('input');
|
||||
if (inputEl) {
|
||||
inputEl.dispatchEvent(new CustomEvent('input', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
o = s.taboption('settings', form.ListValue, 'interface', _('Proxy server interface'));
|
||||
devices.forEach(function(d) { o.value(d.getName(), d.getName()); });
|
||||
o.default = 'br-lan';
|
||||
|
||||
o = s.taboption('settings', form.Value, 'traffic_mark', _('Traffic Mark'));
|
||||
o.datatype = 'or(uinteger, hex)';
|
||||
o.default = '0x666';
|
||||
o.validate = function(section_id, value) {
|
||||
if (!value) return true;
|
||||
var num = parseInt(value, (typeof value === 'string' && value.indexOf('0x') === 0) ? 16 : 10);
|
||||
if (isNaN(num) || num <= 0) return _('Traffic mark cannot be 0.');
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('settings', form.Value, 'routing_table', _('Routing Table ID'));
|
||||
o.datatype = 'and(uinteger,range(1, 4294967295))';
|
||||
o.default = '888';
|
||||
o.validate = function(section_id, value) {
|
||||
var num = parseInt(value, 10);
|
||||
if (num === 0 || num === 253 || num === 254 || num === 255) {
|
||||
return _('Routing table ID %d is reserved by the system (0, 253, 254, 255). Please use another ID.').format(num);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('settings', form.Value, 'rule_priority', _('Rule Priority'));
|
||||
o.datatype = 'and(uinteger,range(10, 32765))';
|
||||
o.default = '1000';
|
||||
s.taboption('settings', form.Value, 'traffic_mark', _('Traffic Mark')).datatype = 'or(uinteger, hex)';
|
||||
s.taboption('settings', form.Value, 'routing_table', _('Routing Table ID')).datatype = 'and(uinteger,range(1, 4294967295))';
|
||||
s.taboption('settings', form.Value, 'rule_priority', _('Rule Priority')).datatype = 'and(uinteger,range(0, 4294967295))';
|
||||
|
||||
// --- Rules ---
|
||||
var nftsets = uci.sections('flowproxy', 'nftset').map(function(ss) { return '@' + ss['.name']; });
|
||||
@@ -529,14 +419,16 @@ return L.view.extend({
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// 联动:当 match_type 改变时,仅在已有输入内容时重新校验 match_value
|
||||
// 联动:当 match_type 改变时,强制触发 match_value 的校验
|
||||
match_type.onchange = function(ev, sid, val) {
|
||||
var value_input = m.lookupOption('match_value', sid)[0];
|
||||
if (value_input) setTimeout(function() {
|
||||
var el = document.getElementById(value_input.cbid(sid));
|
||||
if (el) {
|
||||
var inputEl = el.tagName === 'INPUT' ? el : el.querySelector('input');
|
||||
if (inputEl && inputEl.value && inputEl.value.trim() !== '') {
|
||||
if (inputEl) {
|
||||
inputEl.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
inputEl.dispatchEvent(new CustomEvent('blur', { bubbles: true }));
|
||||
inputEl.dispatchEvent(new CustomEvent('input', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
@@ -568,15 +460,7 @@ return L.view.extend({
|
||||
var path = uci.get('flowproxy', sid, 'file_path') || '/usr/share/flowproxy/chnroute.txt';
|
||||
var resNode = E('span', { 'style': 'margin-left: 10px; font-weight: bold; color: #444;', 'id': 'line-count-status' }, [ '...' ]);
|
||||
var input = node.querySelector('input'); if (input) input.parentNode.appendChild(resNode);
|
||||
callGetDatasetInfo(path).then(function(res) {
|
||||
if (res && res.exists) {
|
||||
L.dom.content(resNode, [ _('(%d lines)').format(res.lines) ]);
|
||||
} else {
|
||||
L.dom.content(resNode, [ _('(file not found)') ]);
|
||||
}
|
||||
}).catch(function() {
|
||||
L.dom.content(resNode, [ _('(n/a)') ]);
|
||||
});
|
||||
fs.exec('/usr/bin/wc', ['-l', path]).then(function(res) { L.dom.content(resNode, (res.code === 0) ? [ _('(%d lines)').format(res.stdout.trim().split(' ')[0]) ] : [ _('(n/a)') ]); });
|
||||
return node;
|
||||
});
|
||||
};
|
||||
@@ -590,21 +474,14 @@ return L.view.extend({
|
||||
if (!path) path = '/usr/share/flowproxy/chnroute.txt';
|
||||
if (!url) { ui.addNotification(null, E('p', _('Please set download_url first')), 'error'); return; }
|
||||
ui.showModal(null, [ E('p', { 'class': 'spinning', 'id': 'download-msg' }, [ _('Downloading chnroute data...') ]) ]);
|
||||
return callUpdateDataset(url, path).then(function(res) {
|
||||
return fs.exec('/usr/bin/wget', ['-q', '-O', path, url, '--timeout=10', '--no-check-certificate']).then(function(res) {
|
||||
var msgEl = document.getElementById('download-msg');
|
||||
if (res && res.success) {
|
||||
if (res.code === 0) {
|
||||
if (msgEl) { msgEl.classList.remove('spinning'); L.dom.content(msgEl, [ _('Updated successfully.') ]); }
|
||||
var cn = document.getElementById('line-count-status');
|
||||
if (cn) L.dom.content(cn, [ _('(%d lines)').format(res.lines || 0) ]);
|
||||
var cn = document.getElementById('line-count-status'); if (cn) fs.exec('/usr/bin/wc', ['-l', path]).then(function(r) { L.dom.content(cn, (r.code === 0) ? [ _('(%d lines)').format(r.stdout.trim().split(' ')[0]) ] : [ _('(n/a)') ]); });
|
||||
setTimeout(ui.hideModal, 1500);
|
||||
} else {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Download failed: %s').format(res ? res.error : 'Unknown')), 'error');
|
||||
}
|
||||
}).catch(function(e) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Error: %s').format(e.message || String(e))), 'error');
|
||||
});
|
||||
} else { ui.hideModal(); ui.addNotification(null, E('p', _('Download failed')), 'error'); }
|
||||
}).catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', _('Error: %s').format(e.message)), 'error'); });
|
||||
};
|
||||
|
||||
var sg = s.taboption('lists', form.SectionValue, '_list_custom', form.GridSection, 'nftset', _('Custom nftables sets'));
|
||||
|
||||
@@ -118,7 +118,7 @@ msgstr ""
|
||||
msgid "Match Value"
|
||||
msgstr ""
|
||||
|
||||
msgid "Proxy server interface"
|
||||
msgid "Network interface"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please set download_url first"
|
||||
@@ -145,9 +145,6 @@ msgstr ""
|
||||
msgid "Routing Table ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rule Priority"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rules"
|
||||
msgstr ""
|
||||
|
||||
@@ -229,49 +226,3 @@ msgstr ""
|
||||
msgid "this will delete ALL current %s rules and generate default templates. are you sure?"
|
||||
msgstr ""
|
||||
|
||||
msgid "(file not found)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download failed: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "The proxy server IP address must be within the subnet of the selected interface (%s)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid proxy server IP address."
|
||||
msgstr ""
|
||||
|
||||
msgid "The proxy server IP address cannot be the local router IP (%s)."
|
||||
msgstr ""
|
||||
|
||||
msgid "The proxy server IP address cannot be the network address or broadcast address."
|
||||
msgstr ""
|
||||
|
||||
msgid "Traffic mark cannot be 0."
|
||||
msgstr ""
|
||||
|
||||
msgid "Routing table ID %d is reserved by the system (0, 253, 254, 255). Please use another ID."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restarting FlowProxy service..."
|
||||
msgstr ""
|
||||
|
||||
msgid "FlowProxy service restarted successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to restart FlowProxy service."
|
||||
msgstr ""
|
||||
|
||||
msgid "RPC error: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Proxy server IP address is required when FlowProxy is enabled."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid IPv4 address format"
|
||||
msgstr ""
|
||||
msgid "Auto (Detect automatically)"
|
||||
msgstr ""
|
||||
@@ -250,31 +250,3 @@ msgstr "RPC 错误: %s"
|
||||
msgid "The proxy server IP address must be within the subnet of the selected interface (%s)."
|
||||
msgstr "代理服务器 IP 地址必须在所选网络接口的子网网段之内 (%s)。"
|
||||
|
||||
msgid "(file not found)"
|
||||
msgstr "(文件不存在)"
|
||||
|
||||
msgid "Download failed: %s"
|
||||
msgstr "下载失败: %s"
|
||||
|
||||
msgid "Invalid proxy server IP address."
|
||||
msgstr "无效的代理服务器 IP 地址。"
|
||||
|
||||
msgid "The proxy server IP address cannot be the local router IP (%s)."
|
||||
msgstr "代理服务器 IP 地址不能为本路由器自身 IP (%s)。"
|
||||
|
||||
msgid "The proxy server IP address cannot be the network address or broadcast address."
|
||||
msgstr "代理服务器 IP 地址不能为网络地址或广播地址。"
|
||||
|
||||
msgid "Traffic mark cannot be 0."
|
||||
msgstr "流量标记不能为 0。"
|
||||
|
||||
msgid "Routing table ID %d is reserved by the system (0, 253, 254, 255). Please use another ID."
|
||||
msgstr "路由表 ID %d 为系统保留路由表 (0, 253, 254, 255),请使用其他 ID。"
|
||||
|
||||
msgid "Proxy server IP address is required when FlowProxy is enabled."
|
||||
msgstr "启用代理分流时,代理服务器 IP 地址为必填项。"
|
||||
|
||||
msgid "Invalid IPv4 address format"
|
||||
msgstr "无效的 IPv4 地址格式"
|
||||
msgid "Auto (Detect automatically)"
|
||||
msgstr "自动探测"
|
||||
@@ -3,7 +3,7 @@ config flowproxy 'global'
|
||||
option tcp_enabled '1'
|
||||
option udp_enabled '1'
|
||||
option proxy_server_ip_addr ''
|
||||
option interface 'auto'
|
||||
option interface 'br-lan'
|
||||
option traffic_mark '0x666'
|
||||
option routing_table '888'
|
||||
option rule_priority '1000'
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Hotplug trigger for flowproxy to restore policy routing on interface reload
|
||||
|
||||
[ "$ACTION" = "ifup" ] || exit 0
|
||||
|
||||
enabled=$(uci -q get flowproxy.global.enabled || echo 0)
|
||||
[ "$enabled" = "1" ] || exit 0
|
||||
|
||||
proxy_ip=$(uci -q get flowproxy.global.proxy_server_ip_addr)
|
||||
[ -n "$proxy_ip" ] || exit 0
|
||||
|
||||
target_iface=""
|
||||
if [ -f "/var/run/flowproxy.mark" ]; then
|
||||
target_iface=$(cut -d':' -f4 /var/run/flowproxy.mark 2>/dev/null)
|
||||
fi
|
||||
if [ -z "$target_iface" ]; then
|
||||
target_iface=$(uci -q get flowproxy.global.interface)
|
||||
if [ -z "$target_iface" ] || [ "$target_iface" = "auto" ]; then
|
||||
target_iface=$(ip route get "$proxy_ip" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") {print $(i+1); exit}}')
|
||||
fi
|
||||
fi
|
||||
target_iface="${target_iface:-br-lan}"
|
||||
|
||||
if [ "$DEVICE" = "$target_iface" ] || [ "$INTERFACE" = "$target_iface" ]; then
|
||||
[ -x /etc/init.d/flowproxy ] && /etc/init.d/flowproxy add_route
|
||||
fi
|
||||
@@ -4,8 +4,6 @@
|
||||
START=99
|
||||
STOP=10
|
||||
USE_PROCD=1
|
||||
EXTRA_COMMANDS="add_route"
|
||||
EXTRA_HELP=" add_route Add routing rules and policy"
|
||||
|
||||
NAME="flowproxy"
|
||||
UCI_CONFIG="flowproxy"
|
||||
@@ -59,62 +57,9 @@ check_env() {
|
||||
check_config() {
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
[ -z "$proxy_server_ip_addr" ] && { log_msg error "proxy_server_ip_addr not configured"; return 1; }
|
||||
|
||||
if [ "$proxy_server_ip_addr" = "127.0.0.1" ] || [ "$proxy_server_ip_addr" = "0.0.0.0" ]; then
|
||||
log_msg error "Invalid proxy_server_ip_addr: $proxy_server_ip_addr"; return 1;
|
||||
fi
|
||||
|
||||
# 纵深防御:严禁将代理服务器设为本路由器自身的任一 IP,防止策略路由自指死循环导致网络崩溃
|
||||
if ip -4 -o addr show 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | grep -Fxq "$proxy_server_ip_addr"; then
|
||||
log_msg error "proxy_server_ip_addr ($proxy_server_ip_addr) is a local IP of this router, aborting to prevent routing loop"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查路由表 ID 是否为系统保留表 (0, 253, 254, 255)
|
||||
local routing_table=$(uci -q get "$UCI_CONFIG.global.routing_table" || echo "888")
|
||||
if [ "$routing_table" = "0" ] || [ "$routing_table" = "253" ] || [ "$routing_table" = "254" ] || [ "$routing_table" = "255" ]; then
|
||||
log_msg error "routing_table ($routing_table) is a reserved system table (0, 253, 254, 255), aborting"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
probe_proxy_server() {
|
||||
local proxy_ip="$1"
|
||||
local proxy_dns_port="$2"
|
||||
local interface="$3"
|
||||
|
||||
log_msg info "Probing proxy server $proxy_ip on interface $interface..."
|
||||
|
||||
# 1. 检查 ARP 缓存或快速唤醒以获取 MAC 地址
|
||||
local mac=""
|
||||
if [ -f "/proc/net/arp" ]; then
|
||||
mac=$(awk -v ip="$proxy_ip" '$1 == ip { print $4; exit }' /proc/net/arp)
|
||||
fi
|
||||
|
||||
if [ -n "$mac" ] && [ "$mac" != "00:00:00:00:00:00" ]; then
|
||||
log_msg info "Proxy server is REACHABLE: IP=$proxy_ip, MAC=$mac (found in ARP cache)"
|
||||
else
|
||||
# 快速单发 ping 1 秒唤醒 ARP 学习
|
||||
ping -c 1 -W 1 "$proxy_ip" >/dev/null 2>&1
|
||||
if [ -f "/proc/net/arp" ]; then
|
||||
mac=$(awk -v ip="$proxy_ip" '$1 == ip { print $4; exit }' /proc/net/arp)
|
||||
fi
|
||||
if [ -n "$mac" ] && [ "$mac" != "00:00:00:00:00:00" ]; then
|
||||
log_msg info "Proxy server is REACHABLE: IP=$proxy_ip, MAC=$mac"
|
||||
else
|
||||
log_msg warn "Proxy server $proxy_ip is NOT responding to ARP yet (it may be booting or offline, traffic will still be routed via policy table)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. 若启用了上游 DNS 代理,在日志中记录目标地址与端口
|
||||
local dns_proxy=$(uci -q get "$UCI_CONFIG.global.dns_proxy_enabled" || echo 0)
|
||||
if [ "$dns_proxy" = "1" ]; then
|
||||
log_msg info "Upstream DNS redirection target configured: $proxy_ip#$proxy_dns_port"
|
||||
fi
|
||||
}
|
||||
|
||||
apply_nft_rules() {
|
||||
log_msg info "Generating nftables configuration..."
|
||||
local conf_level=$(uci -q get "$UCI_CONFIG.global.log_level" || echo "info")
|
||||
@@ -126,14 +71,10 @@ apply_nft_rules() {
|
||||
FLOWPROXY_LOG_LEVEL="$conf_level" "$GENERATE_SCRIPT" >/dev/null 2> "$err_file"
|
||||
local ret=$?
|
||||
|
||||
# 将脚本的输出记录到日志:成功时按 debug 输出,失败时按 error 输出
|
||||
# 将脚本的输出记录到日志
|
||||
if [ -s "$err_file" ]; then
|
||||
while read -r line; do
|
||||
if [ $ret -eq 0 ]; then
|
||||
log_msg debug "Script: $line"
|
||||
else
|
||||
log_msg error "Generator: $line"
|
||||
fi
|
||||
log_msg debug "Script: $line"
|
||||
done < "$err_file"
|
||||
rm -f "$err_file"
|
||||
fi
|
||||
@@ -142,16 +83,14 @@ apply_nft_rules() {
|
||||
log_msg info "Applying nftables rules from $NFT_CONF"
|
||||
# 确保内核支持 inet 表
|
||||
nft delete table inet flowproxy 2>/dev/null
|
||||
local nft_err
|
||||
nft_err=$(nft -f "$NFT_CONF" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
if nft -f "$NFT_CONF"; then
|
||||
return 0
|
||||
else
|
||||
log_msg error "nftables rules application failed: $nft_err"
|
||||
log_msg error "nftables rules application failed (syntax error)"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_msg error "Failed to generate nftables config (exit code: $ret)"
|
||||
log_msg error "Failed to generate nftables config"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
@@ -160,66 +99,33 @@ apply_nft_rules() {
|
||||
fi
|
||||
}
|
||||
|
||||
get_proxy_interface() {
|
||||
local proxy_ip="$1"
|
||||
local cfg_iface=$(uci -q get "$UCI_CONFIG.global.interface")
|
||||
if [ -n "$cfg_iface" ] && [ "$cfg_iface" != "auto" ]; then
|
||||
echo "$cfg_iface"
|
||||
return
|
||||
fi
|
||||
|
||||
local auto_dev=""
|
||||
if [ -n "$proxy_ip" ]; then
|
||||
auto_dev=$(ip route get "$proxy_ip" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") {print $(i+1); exit}}')
|
||||
fi
|
||||
echo "${auto_dev:-br-lan}"
|
||||
}
|
||||
|
||||
add_route() {
|
||||
check_config || return 1
|
||||
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
local interface=$(get_proxy_interface "$proxy_server_ip_addr")
|
||||
local traffic_mark=$(uci -q get "$UCI_CONFIG.global.traffic_mark")
|
||||
traffic_mark="${traffic_mark:-0x666}"
|
||||
local routing_table=$(uci -q get "$UCI_CONFIG.global.routing_table")
|
||||
routing_table="${routing_table:-888}"
|
||||
local rule_priority=$(uci -q get "$UCI_CONFIG.global.rule_priority")
|
||||
rule_priority="${rule_priority:-1000}"
|
||||
local interface=$(uci -q get "$UCI_CONFIG.global.interface" || echo "br-lan")
|
||||
local traffic_mark=$(uci -q get "$UCI_CONFIG.global.traffic_mark" || echo "0x666")
|
||||
local routing_table=$(uci -q get "$UCI_CONFIG.global.routing_table" || echo "888")
|
||||
local rule_priority=$(uci -q get "$UCI_CONFIG.global.rule_priority" || echo "1000")
|
||||
|
||||
# 1. 先尝试用旧记录清理一次,防止由于配置变更导致的残留
|
||||
delete_route "quiet"
|
||||
|
||||
log_msg info "Setting up routing table $routing_table (dev: $interface) and rule for mark $traffic_mark with priority $rule_priority..."
|
||||
log_msg info "Setting up routing table $routing_table and rule for mark $traffic_mark with priority $rule_priority..."
|
||||
|
||||
# 2. 记录当前使用的标记值、路由表 ID、优先级和实际出口网卡
|
||||
echo "$traffic_mark:$routing_table:$rule_priority:$interface" > "/var/run/flowproxy.mark"
|
||||
# 2. 记录当前使用的标记值、路由表 ID 和优先级
|
||||
echo "$traffic_mark:$routing_table:$rule_priority" > "/var/run/flowproxy.mark"
|
||||
|
||||
# 3. 再次确保新标记和表的规则是干净的
|
||||
while ip rule del fwmark "$traffic_mark" lookup "$routing_table" 2>/dev/null; do :; done
|
||||
ip route flush table "$routing_table" 2>/dev/null
|
||||
|
||||
# 4. 添加路由和策略
|
||||
local route_err rule_err
|
||||
route_err=$(ip route add default via "$proxy_server_ip_addr" dev "$interface" table "$routing_table" 2>&1)
|
||||
if [ $? -ne 0 ]; then
|
||||
log_msg error "Failed to add default route to table $routing_table via $proxy_server_ip_addr (dev: $interface): $route_err"
|
||||
log_msg error "Proxy server IP $proxy_server_ip_addr is unreachable or has no valid route on interface $interface"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rule_err=$(ip rule add fwmark "$traffic_mark" lookup "$routing_table" priority "$rule_priority" 2>&1)
|
||||
local err
|
||||
ip route add default via "$proxy_server_ip_addr" dev "$interface" table "$routing_table" 2>/dev/null
|
||||
err=$(ip rule add fwmark "$traffic_mark" lookup "$routing_table" priority "$rule_priority" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
log_msg info "Route added: via $proxy_server_ip_addr dev $interface table $routing_table for mark $traffic_mark with priority $rule_priority"
|
||||
else
|
||||
log_msg error "Failed to add ip rule: $rule_err"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 5. 禁用目标网卡的 ICMP 重定向通告,防止客户端绕行或 TCP 连接挂起
|
||||
if [ -d "/proc/sys/net/ipv4/conf/$interface" ]; then
|
||||
sysctl -w "net.ipv4.conf.$interface.send_redirects=0" >/dev/null 2>&1
|
||||
log_msg debug "Disabled send_redirects for $interface"
|
||||
log_msg error "Failed to add ip rule: $err"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -228,23 +134,18 @@ delete_route() {
|
||||
local quiet="$1"
|
||||
|
||||
# 1. 获取两组清理目标:旧值(记录文件) 和 新值(当前UCI)
|
||||
local old_mark="" old_table="" old_prio="" old_iface=""
|
||||
local old_mark="" old_table="" old_prio=""
|
||||
if [ -f "/var/run/flowproxy.mark" ]; then
|
||||
local saved_val=$(cat "/var/run/flowproxy.mark")
|
||||
old_mark=$(echo "$saved_val" | cut -d':' -f1)
|
||||
old_table=$(echo "$saved_val" | cut -d':' -f2)
|
||||
old_prio=$(echo "$saved_val" | cut -d':' -f3)
|
||||
old_iface=$(echo "$saved_val" | cut -d':' -f4)
|
||||
[ "$quiet" != "quiet" ] && rm -f "/var/run/flowproxy.mark"
|
||||
fi
|
||||
|
||||
local curr_mark=$(uci -q get "$UCI_CONFIG.global.traffic_mark")
|
||||
curr_mark="${curr_mark:-0x666}"
|
||||
local curr_table=$(uci -q get "$UCI_CONFIG.global.routing_table")
|
||||
curr_table="${curr_table:-888}"
|
||||
local curr_prio=$(uci -q get "$UCI_CONFIG.global.rule_priority")
|
||||
curr_prio="${curr_prio:-1000}"
|
||||
local curr_iface=$(get_proxy_interface "$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")")
|
||||
local curr_mark=$(uci -q get "$UCI_CONFIG.global.traffic_mark" || echo "0x666")
|
||||
local curr_table=$(uci -q get "$UCI_CONFIG.global.routing_table" || echo "888")
|
||||
local curr_prio=$(uci -q get "$UCI_CONFIG.global.rule_priority" || echo "1000")
|
||||
|
||||
[ "$quiet" != "quiet" ] && log_msg info "Cleaning up FlowProxy routes (Atomic Mode)..."
|
||||
|
||||
@@ -252,10 +153,6 @@ delete_route() {
|
||||
safe_del_rule() {
|
||||
local m="$1" t="$2" p="$3"
|
||||
[ -z "$m" ] || [ -z "$t" ] && return
|
||||
if [ "$t" = "0" ] || [ "$t" = "253" ] || [ "$t" = "254" ] || [ "$t" = "255" ]; then
|
||||
log_msg error "Refusing to flush reserved system routing table $t"
|
||||
return
|
||||
fi
|
||||
log_msg debug "Attempting cleanup: mark=$m, table=$t, priority=${p:-any}"
|
||||
# 精准清理 (带优先级)
|
||||
[ -n "$p" ] && while ip rule del fwmark "$m" lookup "$t" priority "$p" 2>/dev/null; do
|
||||
@@ -274,12 +171,6 @@ delete_route() {
|
||||
safe_del_rule "$old_mark" "$old_table" "$old_prio"
|
||||
[ "$old_mark" != "$curr_mark" ] || [ "$old_table" != "$curr_table" ] && \
|
||||
safe_del_rule "$curr_mark" "$curr_table" "$curr_prio"
|
||||
|
||||
# 恢复 ICMP 重定向通告
|
||||
[ -n "$old_iface" ] && [ -d "/proc/sys/net/ipv4/conf/$old_iface" ] && \
|
||||
sysctl -w "net.ipv4.conf.$old_iface.send_redirects=1" >/dev/null 2>&1
|
||||
[ -n "$curr_iface" ] && [ "$curr_iface" != "$old_iface" ] && [ -d "/proc/sys/net/ipv4/conf/$curr_iface" ] && \
|
||||
sysctl -w "net.ipv4.conf.$curr_iface.send_redirects=1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
delete_nft() {
|
||||
@@ -301,46 +192,68 @@ setup_dns() {
|
||||
|
||||
[ -z "$proxy_server_ip_addr" ] && { log_msg error "proxy_server_ip_addr not configured, cannot setup DNS"; return 1; }
|
||||
|
||||
log_msg info "Setting up DNS redirection to $proxy_server_ip_addr#$proxy_server_dns_port via drop-in config..."
|
||||
log_msg info "Setting up DNS redirection to $proxy_server_ip_addr#$proxy_server_dns_port..."
|
||||
|
||||
mkdir -p /tmp/dnsmasq.d
|
||||
cat > /tmp/dnsmasq.d/flowproxy.conf << EOF
|
||||
# Generated by FlowProxy - DO NOT EDIT MANUALLY
|
||||
no-resolv
|
||||
server=$proxy_server_ip_addr#$proxy_server_dns_port
|
||||
EOF
|
||||
|
||||
# 同步分发至各个可能存在的 dnsmasq 实例目录
|
||||
for dir in /tmp/dnsmasq*.d; do
|
||||
if [ -d "$dir" ] && [ "$dir" != "/tmp/dnsmasq.d" ]; then
|
||||
cp -f /tmp/dnsmasq.d/flowproxy.conf "$dir/flowproxy.conf"
|
||||
fi
|
||||
done
|
||||
|
||||
# 历史遗留自愈:若早期版本存在残留的 dns_backup 节,静默移除避免脏数据
|
||||
if uci -q get "$UCI_CONFIG.dns_backup" >/dev/null; then
|
||||
uci -q delete "$UCI_CONFIG.dns_backup"
|
||||
uci -q commit "$UCI_CONFIG"
|
||||
# Persistent backup in UCI
|
||||
if ! uci -q get "$UCI_CONFIG.dns_backup" >/dev/null; then
|
||||
local old_noresolv=$(uci -q get dhcp.@dnsmasq[0].noresolv)
|
||||
local old_dns_redirect=$(uci -q get dhcp.@dnsmasq[0].dns_redirect)
|
||||
local old_servers=$(uci -q get dhcp.@dnsmasq[0].server | tr '\n' ',' | sed 's/,$//')
|
||||
|
||||
log_msg debug "Backing up DNS: noresolv=${old_noresolv:-empty}, redirect=${old_dns_redirect:-empty}, servers=${old_servers:-empty}"
|
||||
|
||||
uci set "$UCI_CONFIG.dns_backup=dns_backup"
|
||||
[ -n "$old_noresolv" ] && uci set "$UCI_CONFIG.dns_backup.noresolv=$old_noresolv"
|
||||
[ -n "$old_dns_redirect" ] && uci set "$UCI_CONFIG.dns_backup.dns_redirect=$old_dns_redirect"
|
||||
[ -n "$old_servers" ] && uci set "$UCI_CONFIG.dns_backup.servers=$old_servers"
|
||||
uci commit "$UCI_CONFIG"
|
||||
fi
|
||||
|
||||
log_msg debug "Applying new DNS: noresolv=1, redirect=0, server=$proxy_server_ip_addr#$proxy_server_dns_port"
|
||||
uci set dhcp.@dnsmasq[0].noresolv='1'
|
||||
uci set dhcp.@dnsmasq[0].dns_redirect='0'
|
||||
|
||||
# Delete current servers and add our proxy
|
||||
uci -q delete dhcp.@dnsmasq[0].server
|
||||
uci add_list dhcp.@dnsmasq[0].server="$proxy_server_ip_addr#$proxy_server_dns_port"
|
||||
|
||||
uci commit dhcp
|
||||
/etc/init.d/dnsmasq restart
|
||||
log_msg info "DNS drop-in configuration applied successfully"
|
||||
}
|
||||
|
||||
cleanup_dns() {
|
||||
log_msg info "Cleaning up DNS redirection drop-in configuration..."
|
||||
rm -f /tmp/dnsmasq.d/flowproxy.conf
|
||||
for dir in /tmp/dnsmasq*.d; do
|
||||
[ -d "$dir" ] && rm -f "$dir/flowproxy.conf"
|
||||
done
|
||||
|
||||
# 历史遗留自愈
|
||||
if uci -q get "$UCI_CONFIG.dns_backup" >/dev/null; then
|
||||
uci -q delete "$UCI_CONFIG.dns_backup"
|
||||
uci -q commit "$UCI_CONFIG"
|
||||
fi
|
||||
log_msg info "Restoring original DNS configuration..."
|
||||
|
||||
local noresolv=$(uci -q get "$UCI_CONFIG.dns_backup.noresolv")
|
||||
local dns_redirect=$(uci -q get "$UCI_CONFIG.dns_backup.dns_redirect")
|
||||
local servers=$(uci -q get "$UCI_CONFIG.dns_backup.servers")
|
||||
|
||||
log_msg debug "Restoring DNS values: noresolv=${noresolv:-0}, redirect=${dns_redirect:-1}, servers=${servers:-empty}"
|
||||
|
||||
/etc/init.d/dnsmasq restart
|
||||
# Restore noresolv (default to 0 if not backed up)
|
||||
[ -n "$noresolv" ] && uci set dhcp.@dnsmasq[0].noresolv="$noresolv" || uci set dhcp.@dnsmasq[0].noresolv="0"
|
||||
|
||||
# Restore dns_redirect (only restore if it originally existed, avoid polluting config)
|
||||
if [ -n "$dns_redirect" ]; then
|
||||
uci set dhcp.@dnsmasq[0].dns_redirect="$dns_redirect"
|
||||
else
|
||||
uci -q delete dhcp.@dnsmasq[0].dns_redirect
|
||||
fi
|
||||
|
||||
# Restore servers correctly splitting comma-separated list
|
||||
uci -q delete dhcp.@dnsmasq[0].server
|
||||
if [ -n "$servers" ]; then
|
||||
for s in $(echo "$servers" | tr ',' ' '); do
|
||||
[ -n "$s" ] && uci add_list dhcp.@dnsmasq[0].server="$s"
|
||||
done
|
||||
fi
|
||||
|
||||
uci commit dhcp
|
||||
uci delete "$UCI_CONFIG.dns_backup"
|
||||
uci commit "$UCI_CONFIG"
|
||||
/etc/init.d/dnsmasq restart
|
||||
fi
|
||||
}
|
||||
|
||||
start_service() {
|
||||
@@ -348,42 +261,23 @@ start_service() {
|
||||
|
||||
if [ "$enabled" -eq 0 ]; then
|
||||
log_msg info "FlowProxy is disabled, ensuring service is stopped"
|
||||
[ -x /etc/init.d/flowproxy ] && /etc/init.d/flowproxy disable 2>/dev/null
|
||||
stop_service
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 状态自愈:当 enabled=1 时,确保服务已在 rc.d 中 enable,防止 luci-reload 因未启用而静默跳过执行
|
||||
[ -x /etc/init.d/flowproxy ] && /etc/init.d/flowproxy enable 2>/dev/null
|
||||
|
||||
log_msg info "Starting FlowProxy..."
|
||||
|
||||
check_env || return 1
|
||||
check_config || return 1
|
||||
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
local proxy_server_dns_port=$(uci -q get "$UCI_CONFIG.global.proxy_server_dns_port" || echo 5353)
|
||||
local interface=$(get_proxy_interface "$proxy_server_ip_addr")
|
||||
probe_proxy_server "$proxy_server_ip_addr" "$proxy_server_dns_port" "$interface"
|
||||
|
||||
apply_nft_rules || return 1
|
||||
if ! add_route; then
|
||||
log_msg error "Routing setup failed, aborting and cleaning up service..."
|
||||
stop_service
|
||||
return 1
|
||||
fi
|
||||
add_route
|
||||
setup_dns
|
||||
|
||||
touch "$STATE_FILE"
|
||||
log_msg info "FlowProxy service started successfully"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
log_msg info "Stopping FlowProxy..."
|
||||
local enabled=$(uci -q get "$UCI_CONFIG.global.enabled" || echo 0)
|
||||
if [ "$enabled" -eq 0 ]; then
|
||||
[ -x /etc/init.d/flowproxy ] && /etc/init.d/flowproxy disable 2>/dev/null
|
||||
fi
|
||||
# 强制先移除标记,防止状态误报
|
||||
rm -f "$STATE_FILE"
|
||||
delete_route
|
||||
@@ -392,15 +286,13 @@ stop_service() {
|
||||
log_msg info "FlowProxy service stopped"
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
log_msg info "Reloading FlowProxy..."
|
||||
restart_service() {
|
||||
stop_service
|
||||
start_service
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
stop_service
|
||||
start_service
|
||||
reload_service() {
|
||||
restart_service
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
@@ -408,11 +300,4 @@ service_triggers() {
|
||||
procd_add_reload_trigger "$UCI_CONFIG"
|
||||
# 监听防火墙重载事件,防止规则被 fw4 清空后无法自动恢复
|
||||
procd_add_reload_trigger "firewall"
|
||||
# 监听网络接口事件(procd trigger 需接收 UCI 逻辑网络名,如 lan/wan)
|
||||
local dev=$(get_proxy_interface "$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")")
|
||||
local net_name=""
|
||||
if command -v ubus >/dev/null 2>&1 && [ -n "$dev" ]; then
|
||||
net_name=$(ubus call network.interface dump 2>/dev/null | jsonfilter -e "@.interface[@.device=\"$dev\"].interface" -e "@.interface[@.l3_device=\"$dev\"].interface" 2>/dev/null | head -n 1)
|
||||
fi
|
||||
procd_add_reload_interface_trigger "${net_name:-lan}"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/bin/sh
|
||||
[ ! -f "/usr/share/ucitrack/luci-app-flowproxy.json" ] && {
|
||||
cat > /usr/share/ucitrack/luci-app-flowproxy.json << EEOF
|
||||
{
|
||||
"config": "flowproxy",
|
||||
"init": "flowproxy"
|
||||
}
|
||||
EEOF
|
||||
}
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@flowproxy[-1]
|
||||
add ucitrack flowproxy
|
||||
set ucitrack.@flowproxy[-1].init=flowproxy
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
/etc/init.d/flowproxy enabled || /etc/init.d/flowproxy enable 2>/dev/null
|
||||
|
||||
rm -f /tmp/luci-indexcache* 2>/dev/null
|
||||
rm -f /tmp/luci-modulecache/* 2>/dev/null
|
||||
|
||||
exit 0
|
||||
@@ -44,15 +44,20 @@ gen_set_definition() {
|
||||
[ -n "$uci_elems" ] && { [ -n "$elems" ] && elems="${elems}, "; elems="${elems}${uci_elems}"; }
|
||||
|
||||
file_path=$(uci -q get "$CONFIG.$section.file_path")
|
||||
if [ -n "$file_path" ]; then
|
||||
if [ -f "$file_path" ]; then
|
||||
local file_elems=$(get_file_elements "$file_path")
|
||||
if [ -n "$file_elems" ]; then
|
||||
[ -n "$elems" ] && elems="${elems}, "
|
||||
elems="${elems}${file_elems}"
|
||||
fi
|
||||
else
|
||||
log_debug "Set file $file_path not found, continuing without file elements"
|
||||
download_url=$(uci -q get "$CONFIG.$section.download_url")
|
||||
if [ -n "$download_url" ] && [ -n "$file_path" ]; then
|
||||
if [ ! -f "$file_path" ]; then
|
||||
echo "Downloading $section data from $download_url to $file_path..." >&2
|
||||
wget -q -O "$file_path" "$download_url" --timeout=10 --no-check-certificate
|
||||
[ $? -ne 0 ] && { echo "Failed to download $section data" >&2; rm -f "$file_path"; }
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$file_path" ] && [ -f "$file_path" ]; then
|
||||
local file_elems=$(get_file_elements "$file_path")
|
||||
if [ -n "$file_elems" ]; then
|
||||
[ -n "$elems" ] && elems="${elems}, "
|
||||
elems="${elems}${file_elems}"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -178,68 +183,15 @@ TCP_ENABLED=$(uci -q get "$CONFIG.global.tcp_enabled" || echo "1")
|
||||
UDP_ENABLED=$(uci -q get "$CONFIG.global.udp_enabled" || echo "1")
|
||||
TRAFFIC_MARK=$(uci -q get "$CONFIG.global.traffic_mark" || echo "0x666")
|
||||
PROXY_SERVER_IP_ADDR=$(uci -q get "$CONFIG.global.proxy_server_ip_addr")
|
||||
get_lan_devices() {
|
||||
local dev="" devs="" nets=""
|
||||
local z_idx=0
|
||||
while :; do
|
||||
local z_name=$(uci -q get firewall.@zone[$z_idx].name)
|
||||
[ -z "$z_name" ] && break
|
||||
if [ "$z_name" = "lan" ]; then
|
||||
nets=$(uci -q get firewall.@zone[$z_idx].network)
|
||||
break
|
||||
fi
|
||||
z_idx=$((z_idx + 1))
|
||||
done
|
||||
|
||||
[ -z "$nets" ] && nets="lan"
|
||||
|
||||
for net in $nets; do
|
||||
dev=""
|
||||
if command -v ubus >/dev/null 2>&1; then
|
||||
dev=$(ubus call network.interface."$net" status 2>/dev/null | jsonfilter -e "@.l3_device" 2>/dev/null)
|
||||
[ -z "$dev" ] && dev=$(ubus call network.interface."$net" status 2>/dev/null | jsonfilter -e "@.device" 2>/dev/null)
|
||||
fi
|
||||
[ -z "$dev" ] && dev=$(uci -q get network."$net".device || uci -q get network."$net".device)
|
||||
if [ -n "$dev" ]; then
|
||||
case " $devs " in
|
||||
*" $dev "*) ;;
|
||||
*) devs="${devs}${devs:+ }$dev" ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${devs:-br-lan}"
|
||||
}
|
||||
|
||||
render_nft_lan_filter() {
|
||||
local devs="$1"
|
||||
set -- $devs
|
||||
local count=$#
|
||||
if [ "$count" -le 1 ]; then
|
||||
echo " iifname != \"$1\" return"
|
||||
else
|
||||
local set_str=""
|
||||
for d in "$@"; do
|
||||
[ -n "$set_str" ] && set_str="${set_str}, "
|
||||
set_str="${set_str}\"${d}\""
|
||||
done
|
||||
echo " iifname != { $set_str } return"
|
||||
fi
|
||||
}
|
||||
|
||||
LAN_DEVICES=$(get_lan_devices)
|
||||
LAN_NFT_FILTER=$(render_nft_lan_filter "$LAN_DEVICES")
|
||||
|
||||
if [ "$TCP_ENABLED" = "1" ]; then
|
||||
cat >> "$OUTPUT_FILE" << EOF
|
||||
chain LAN_MARKFLOW_TCP {
|
||||
type filter hook prerouting priority mangle; policy accept;
|
||||
$LAN_NFT_FILTER
|
||||
fib daddr type { unspec, local, anycast, multicast } counter return
|
||||
EOF
|
||||
if [ -n "$PROXY_SERVER_IP_ADDR" ]; then
|
||||
echo " ip saddr $PROXY_SERVER_IP_ADDR counter return" >> "$OUTPUT_FILE"
|
||||
echo " ip daddr $PROXY_SERVER_IP_ADDR counter return" >> "$OUTPUT_FILE"
|
||||
echo " ip saddr $PROXY_SERVER_IP_ADDR ip protocol tcp counter return" >> "$OUTPUT_FILE"
|
||||
fi
|
||||
for s in $SECTIONS_TCP; do process_rule "$s" "tcp" >> "$OUTPUT_FILE"; done
|
||||
echo " ip protocol tcp counter meta mark set $TRAFFIC_MARK" >> "$OUTPUT_FILE"
|
||||
@@ -250,12 +202,10 @@ if [ "$UDP_ENABLED" = "1" ]; then
|
||||
cat >> "$OUTPUT_FILE" << EOF
|
||||
chain LAN_MARKFLOW_UDP {
|
||||
type filter hook prerouting priority mangle; policy accept;
|
||||
$LAN_NFT_FILTER
|
||||
fib daddr type { unspec, local, anycast, multicast } counter return
|
||||
EOF
|
||||
if [ -n "$PROXY_SERVER_IP_ADDR" ]; then
|
||||
echo " ip saddr $PROXY_SERVER_IP_ADDR counter return" >> "$OUTPUT_FILE"
|
||||
echo " ip daddr $PROXY_SERVER_IP_ADDR counter return" >> "$OUTPUT_FILE"
|
||||
echo " ip saddr $PROXY_SERVER_IP_ADDR ip protocol udp counter return" >> "$OUTPUT_FILE"
|
||||
fi
|
||||
for s in $SECTIONS_UDP; do process_rule "$s" "udp" >> "$OUTPUT_FILE"; done
|
||||
echo " ip protocol udp counter meta mark set $TRAFFIC_MARK" >> "$OUTPUT_FILE"
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "flowproxy" ],
|
||||
"file": {
|
||||
"/usr/bin/wget": [ "exec" ],
|
||||
"/usr/bin/wc": [ "exec" ],
|
||||
"/usr/share/flowproxy/generate_nft.sh": [ "exec" ]
|
||||
},
|
||||
"ubus": {
|
||||
"luci.flowproxy": [ "*" ]
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ const STATE_FILE = '/var/run/flowproxy.running';
|
||||
const IP_CACHE = '/tmp/flowproxy/lan_ip.cache';
|
||||
const LOG_FILE = '/var/log/flowproxy.log';
|
||||
const NFT_TABLE = 'flowproxy';
|
||||
const DNS_DROPIN_FILE = '/tmp/dnsmasq.d/flowproxy.conf';
|
||||
|
||||
// 辅助函数:安全 trim
|
||||
function safe_trim(str) {
|
||||
@@ -19,10 +18,10 @@ function safe_trim(str) {
|
||||
const methods = {
|
||||
get_status: {
|
||||
call: function() {
|
||||
// 1. 快速探测标记文件
|
||||
// 1. 快速探测
|
||||
let is_running = access(STATE_FILE, 'f');
|
||||
|
||||
// 2. 自愈:检查内核状态 (nft 链与策略路由)
|
||||
// 2. 自愈:检查内核状态并同步标记文件
|
||||
let tcp_active = (system('nft list chain inet ' + NFT_TABLE + ' LAN_MARKFLOW_TCP >/dev/null 2>&1') === 0);
|
||||
let udp_active = (system('nft list chain inet ' + NFT_TABLE + ' LAN_MARKFLOW_UDP >/dev/null 2>&1') === 0);
|
||||
let real_running = (tcp_active || udp_active);
|
||||
@@ -41,6 +40,7 @@ const methods = {
|
||||
if (ubus_conn) {
|
||||
let ifaces = ubus_conn.call('network.interface', 'dump');
|
||||
if (type(ifaces?.interface) === 'array') {
|
||||
// 优先寻找名为 'lan' 的接口,或者寻找拥有 ipv4 地址的第一个非回环接口
|
||||
let fallback_ip = '';
|
||||
for (let iface in ifaces.interface) {
|
||||
if (iface.interface === 'lan' && iface['ipv4-address']?.[0]?.address) {
|
||||
@@ -64,8 +64,9 @@ const methods = {
|
||||
}
|
||||
|
||||
let uci_cursor = cursor();
|
||||
|
||||
let proxy_server_ip_addr = uci_cursor.get(UCI_CONFIG, 'global', 'proxy_server_ip_addr') || '';
|
||||
let dns_running = access(DNS_DROPIN_FILE, 'f') ? 1 : 0;
|
||||
let dns_running = (uci_cursor.get(UCI_CONFIG, 'dns_backup') ? 1 : 0);
|
||||
|
||||
return {
|
||||
running: real_running ? 1 : 0,
|
||||
@@ -113,67 +114,6 @@ const methods = {
|
||||
}
|
||||
},
|
||||
|
||||
get_dataset_info: {
|
||||
args: { file_path: 'string' },
|
||||
call: function(request) {
|
||||
let file_path = request.args?.file_path || '/usr/share/flowproxy/chnroute.txt';
|
||||
if (!match(file_path, /^\/(usr\/share\/flowproxy|tmp\/flowproxy)\/[a-zA-Z0-9_.-]+$/)) {
|
||||
return { error: 'Invalid path' };
|
||||
}
|
||||
if (!access(file_path, 'r')) {
|
||||
return { exists: false, lines: 0, size: 0 };
|
||||
}
|
||||
let s = stat(file_path);
|
||||
let lines = 0;
|
||||
let fd = popen(sprintf("wc -l '%s' 2>/dev/null", file_path));
|
||||
if (fd) {
|
||||
let out = fd.read('all');
|
||||
fd.close();
|
||||
if (out) {
|
||||
let parts = split(trim(out), /\s+/);
|
||||
lines = int(parts[0]) || 0;
|
||||
}
|
||||
}
|
||||
return { exists: true, lines: lines, size: s ? s.size : 0 };
|
||||
}
|
||||
},
|
||||
|
||||
update_dataset: {
|
||||
args: { url: 'string', file_path: 'string' },
|
||||
call: function(request) {
|
||||
let url = request.args?.url;
|
||||
let file_path = request.args?.file_path || '/usr/share/flowproxy/chnroute.txt';
|
||||
if (!url || !match(url, /^https?:\/\/[A-Za-z0-9_.\/:~%#?&=+-]+$/)) {
|
||||
return { success: false, error: 'Invalid URL' };
|
||||
}
|
||||
if (!match(file_path, /^\/(usr\/share\/flowproxy|tmp\/flowproxy)\/[a-zA-Z0-9_.-]+$/)) {
|
||||
return { success: false, error: 'Invalid target file path' };
|
||||
}
|
||||
let tmp_file = file_path + '.tmp';
|
||||
let cmd = sprintf("wget -q -O '%s' '%s' --timeout=15 --no-check-certificate 2>&1", tmp_file, url);
|
||||
let rc = system(cmd);
|
||||
if (rc === 0 && access(tmp_file, 'f')) {
|
||||
let s = stat(tmp_file);
|
||||
if (s && s.size > 0) {
|
||||
system(sprintf("mv -f '%s' '%s'", tmp_file, file_path));
|
||||
let lines = 0;
|
||||
let fd = popen(sprintf("wc -l '%s' 2>/dev/null", file_path));
|
||||
if (fd) {
|
||||
let out = fd.read('all');
|
||||
fd.close();
|
||||
if (out) {
|
||||
let parts = split(trim(out), /\s+/);
|
||||
lines = int(parts[0]) || 0;
|
||||
}
|
||||
}
|
||||
return { success: true, lines: lines };
|
||||
}
|
||||
}
|
||||
system(sprintf("rm -f '%s'", tmp_file));
|
||||
return { success: false, error: 'Download failed or file empty' };
|
||||
}
|
||||
},
|
||||
|
||||
generate_nft_config: {
|
||||
call: function() {
|
||||
system('mkdir -p /tmp/flowproxy');
|
||||
@@ -185,18 +125,19 @@ const methods = {
|
||||
get_runtime_config: {
|
||||
call: function() {
|
||||
let res_file = '/tmp/flowproxy/runtime.tmp';
|
||||
let dns_file = '/tmp/flowproxy/dns_runtime.tmp';
|
||||
|
||||
system('mkdir -p /tmp/flowproxy');
|
||||
system('/usr/share/flowproxy/generate_nft.sh runtime > ' + res_file + ' 2>&1');
|
||||
let nft_runtime = readfile(res_file) || "(failed)";
|
||||
system('rm -f ' + res_file);
|
||||
|
||||
let dns_runtime = "\n--- [ DNSmasq Drop-in Runtime Configuration ] ---\n";
|
||||
let dns_content = readfile(DNS_DROPIN_FILE);
|
||||
if (dns_content) {
|
||||
dns_runtime += dns_content;
|
||||
} else {
|
||||
dns_runtime += "(no active dnsmasq drop-in overrides)";
|
||||
}
|
||||
system('uci show dhcp.@dnsmasq[0] | grep -E "noresolv|dns_redirect|server" | sed -E "s/^dhcp\\.[^.]*\\.([^=]*)=(.*)/\\1: \\2/; s/\\x27//g; s/#/:/g" > ' + dns_file + ' 2>&1');
|
||||
let dns_out = readfile(dns_file);
|
||||
system('rm -f ' + dns_file);
|
||||
|
||||
let dns_runtime = "\n--- [ DNSmasq Runtime Configuration ] ---\n";
|
||||
dns_runtime += (dns_out || "(no active dnsmasq overrides)");
|
||||
|
||||
return { runtime: safe_trim(nft_runtime + dns_runtime) };
|
||||
}
|
||||
@@ -208,6 +149,7 @@ const methods = {
|
||||
try {
|
||||
let ubus_conn = connect();
|
||||
if (ubus_conn) {
|
||||
// 使用 network.device status 以获取所有物理设备 (包括 eth0 等 bridge 成员)
|
||||
let dev_status = ubus_conn.call('network.device', 'status');
|
||||
if (dev_status) {
|
||||
for (let name in dev_status) {
|
||||
|
||||
Reference in new issue
Block a user