mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 02:25:13 +08:00
🍓 Sync 2026-09-15 02:13:06
This commit is contained in:
1 parent
0e5393594c
commit
1dd96ac2bb
146 files changed
+26541
-508
No files matched your search
File diff suppressed because it is too large.
Load diff
+215
@@ -0,0 +1,215 @@
|
||||
#!/bin/sh
|
||||
# Generate nftables configuration for flowproxy
|
||||
|
||||
. /lib/functions.sh
|
||||
|
||||
[ -n "$UCI_CONFIG_DIR" ] && export UCI_CONFIG_DIR
|
||||
|
||||
CONFIG="flowproxy"
|
||||
NFT_TABLE="flowproxy"
|
||||
OUTPUT_FILE="/tmp/flowproxy/nft.conf"
|
||||
|
||||
mkdir -p "$(dirname "$OUTPUT_FILE")"
|
||||
|
||||
ENABLED_SETS=" "
|
||||
|
||||
log_debug() {
|
||||
[ "$FLOWPROXY_LOG_LEVEL" = "debug" ] && echo "DEBUG: $*" >&2
|
||||
}
|
||||
|
||||
gen_set_definition() {
|
||||
local section="$1"
|
||||
local type enabled auto_gen file_path elems is_interval
|
||||
|
||||
enabled=$(uci -q get "$CONFIG.$section.enabled")
|
||||
[ "$enabled" = "0" ] && { log_debug "Set $section is disabled, skipping"; return; }
|
||||
|
||||
log_debug "Generating set: $section"
|
||||
ENABLED_SETS="${ENABLED_SETS}@${section} "
|
||||
type=$(uci -q get "$CONFIG.$section.type")
|
||||
[ -z "$type" ] && type="ipv4_addr"
|
||||
|
||||
if [ "$section" = "private_dst_ip_v4" ]; then
|
||||
auto_gen=$(uci -q get "$CONFIG.$section.auto_generate")
|
||||
if [ "$auto_gen" = "1" ] || [ -z "$auto_gen" ]; then
|
||||
elems="10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16"
|
||||
fi
|
||||
fi
|
||||
|
||||
local uci_elems=""
|
||||
for e in $(uci -q get "$CONFIG.$section.elements"); do
|
||||
[ -n "$uci_elems" ] && uci_elems="${uci_elems}, "
|
||||
uci_elems="${uci_elems}${e}"
|
||||
done
|
||||
[ -n "$uci_elems" ] && { [ -n "$elems" ] && elems="${elems}, "; elems="${elems}${uci_elems}"; }
|
||||
|
||||
file_path=$(uci -q get "$CONFIG.$section.file_path")
|
||||
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
|
||||
|
||||
case "$type" in "ipv4_addr"|"inet_service"|"ether_addr") is_interval=1 ;; *) is_interval=0 ;; esac
|
||||
[ "$is_interval" = "0" ] && { case "$elems" in */*|*-*) is_interval=1 ;; esac; }
|
||||
|
||||
printf "\tset %s {\n" "$section"
|
||||
printf "\t\ttype %s\n" "$type"
|
||||
[ "$is_interval" = "1" ] && printf "\t\tflags interval\n"
|
||||
[ -n "$elems" ] && printf "\t\telements = { %s }\n" "$elems"
|
||||
printf "\t}\n\n"
|
||||
}
|
||||
|
||||
get_file_elements() {
|
||||
local file_path="$1"
|
||||
[ ! -f "$file_path" ] && return
|
||||
|
||||
if [ "$FLOWPROXY_PREVIEW" = "1" ]; then
|
||||
local total=$(wc -l < "$file_path")
|
||||
if [ "$total" -gt 100 ]; then
|
||||
awk 'BEGIN { count=0 }
|
||||
!/^($|#)/ {
|
||||
if (count < 100) {
|
||||
if (count > 0) {
|
||||
printf ", "
|
||||
if (count % 2 == 0) printf "\n\t\t\t "
|
||||
}
|
||||
printf "%s", $0
|
||||
count++
|
||||
}
|
||||
}
|
||||
END { printf ", ... (%d more items omitted for preview)", '"$total"'-100 }' "$file_path"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
awk 'BEGIN { count=0 }
|
||||
!/^($|#)/ {
|
||||
if (count > 0) {
|
||||
printf ", "
|
||||
if (count % 2 == 0) printf "\n\t\t\t "
|
||||
}
|
||||
printf "%s", $0
|
||||
count++
|
||||
}
|
||||
END { print "" }' "$file_path"
|
||||
}
|
||||
|
||||
process_rule() {
|
||||
local section="$1"; local proto="$2"
|
||||
local enabled match_type match_value action counter
|
||||
|
||||
enabled=$(uci -q get "$CONFIG.$section.enabled"); [ "$enabled" = "0" ] && { log_debug "Rule $section is disabled, skipping"; return; }
|
||||
match_type=$(uci -q get "$CONFIG.$section.match_type")
|
||||
[ -z "$match_type" ] && match_type="dst_ip"
|
||||
match_value=$(uci -q get "$CONFIG.$section.match_value")
|
||||
|
||||
log_debug "Processing $proto rule: $section ($match_type=$match_value)"
|
||||
action=$(uci -q get "$CONFIG.$section.action"); [ -z "$action" ] && action="return"
|
||||
counter=$(uci -q get "$CONFIG.$section.counter")
|
||||
[ -z "$match_value" ] && return
|
||||
|
||||
case "$match_value" in
|
||||
@*)
|
||||
local set_ref=$(echo "$match_value" | cut -d' ' -f1)
|
||||
if ! echo "$ENABLED_SETS" | grep -q " ${set_ref} "; then
|
||||
log_debug "Skipping rule $section: set ${set_ref} is not defined or disabled"
|
||||
return
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
match_value=$(echo "$match_value" | sed -E 's/ (return|accept|drop)$//g')
|
||||
local segment=""
|
||||
case "$match_type" in
|
||||
src_mac) segment="ether saddr $match_value ip protocol $proto" ;;
|
||||
src_ip) segment="ip saddr $match_value ip protocol $proto" ;;
|
||||
dst_ip) segment="ip daddr $match_value ip protocol $proto" ;;
|
||||
src_port) segment="ip protocol $proto $proto sport $match_value" ;;
|
||||
dst_port) segment="ip protocol $proto $proto dport $match_value" ;;
|
||||
*) segment="$match_value ip protocol $proto" ;;
|
||||
esac
|
||||
|
||||
local line="$segment"; [ "$counter" = "1" ] && line="$line counter"; line="$line $action"
|
||||
[ -n "$line" ] && echo " $line"
|
||||
}
|
||||
|
||||
SECTIONS_SET=$(uci -q show "$CONFIG" | grep "=nftset" | cut -d'.' -f2 | cut -d'=' -f1)
|
||||
SECTIONS_TCP=$(uci -q show "$CONFIG" | grep "=tcp_rule" | cut -d'.' -f2 | cut -d'=' -f1)
|
||||
SECTIONS_UDP=$(uci -q show "$CONFIG" | grep "=udp_rule" | cut -d'.' -f2 | cut -d'=' -f1)
|
||||
|
||||
if [ "$1" = "runtime" ]; then
|
||||
TRAFFIC_MARK=$(uci -q get "$CONFIG.global.traffic_mark" || echo "0x666")
|
||||
ROUTING_TABLE=$(uci -q get "$CONFIG.global.routing_table" || echo "888")
|
||||
echo "--- [ nftables live rules ] ---"
|
||||
nft list table inet "$CONFIG" 2>/dev/null | awk 'BEGIN { count=0; skip=0 }
|
||||
/elements = \{/ { print $0; skip=1; next }
|
||||
skip == 1 && /\}/ { printf "\t\t\t ... (items truncated for preview)\n"; print $0; skip=0; next }
|
||||
skip == 1 { count++; if (count < 20) print $0; next }
|
||||
{ print $0 }' || echo "(table not found)"
|
||||
echo ""
|
||||
echo "--- [ ip rule list ] ---"
|
||||
ip rule show 2>/dev/null
|
||||
echo ""
|
||||
echo "--- [ ip route table $ROUTING_TABLE ] ---"
|
||||
ip route show table "$ROUTING_TABLE" 2>/dev/null || echo "(table empty)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat > "$OUTPUT_FILE" << EOF
|
||||
#!/usr/sbin/nft -f
|
||||
table inet $NFT_TABLE
|
||||
flush table inet $NFT_TABLE
|
||||
|
||||
table inet $NFT_TABLE {
|
||||
EOF
|
||||
|
||||
for s in $SECTIONS_SET; do
|
||||
gen_set_definition "$s" >> "$OUTPUT_FILE"
|
||||
done
|
||||
|
||||
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")
|
||||
|
||||
if [ "$TCP_ENABLED" = "1" ]; then
|
||||
cat >> "$OUTPUT_FILE" << EOF
|
||||
chain LAN_MARKFLOW_TCP {
|
||||
type filter hook prerouting priority mangle; policy accept;
|
||||
fib daddr type { unspec, local, anycast, multicast } counter return
|
||||
EOF
|
||||
if [ -n "$PROXY_SERVER_IP_ADDR" ]; then
|
||||
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"
|
||||
echo " }" >> "$OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
if [ "$UDP_ENABLED" = "1" ]; then
|
||||
cat >> "$OUTPUT_FILE" << EOF
|
||||
chain LAN_MARKFLOW_UDP {
|
||||
type filter hook prerouting priority mangle; policy accept;
|
||||
fib daddr type { unspec, local, anycast, multicast } counter return
|
||||
EOF
|
||||
if [ -n "$PROXY_SERVER_IP_ADDR" ]; then
|
||||
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"
|
||||
echo " }" >> "$OUTPUT_FILE"
|
||||
fi
|
||||
echo "}" >> "$OUTPUT_FILE"
|
||||
cat "$OUTPUT_FILE"
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"admin/services/flowproxy": {
|
||||
"title": "FlowProxy",
|
||||
"description": "LuCI Support for FlowProxy Traffic Diversion",
|
||||
"index": 50,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "flowproxy/flowproxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"luci-app-flowproxy": {
|
||||
"description": "Grant access to flowproxy configuration",
|
||||
"read": {
|
||||
"uci": [ "flowproxy" ],
|
||||
"file": {
|
||||
"/usr/share/flowproxy/*": [ "read" ]
|
||||
},
|
||||
"ubus": {
|
||||
"luci.flowproxy": [ "*" ],
|
||||
"network.interface": [ "dump" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "flowproxy" ],
|
||||
"file": {
|
||||
"/usr/bin/wget": [ "exec" ],
|
||||
"/usr/bin/wc": [ "exec" ],
|
||||
"/usr/share/flowproxy/generate_nft.sh": [ "exec" ]
|
||||
},
|
||||
"ubus": {
|
||||
"luci.flowproxy": [ "*" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
'use strict';
|
||||
|
||||
import { access, readfile, writefile, stat } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { connect } from 'ubus';
|
||||
|
||||
const UCI_CONFIG = 'flowproxy';
|
||||
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';
|
||||
|
||||
// 辅助函数:安全 trim
|
||||
function safe_trim(str) {
|
||||
return (type(str) === 'string') ? trim(str) : '';
|
||||
}
|
||||
|
||||
const methods = {
|
||||
get_status: {
|
||||
call: function() {
|
||||
// 1. 快速探测
|
||||
let is_running = access(STATE_FILE, 'f');
|
||||
|
||||
// 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);
|
||||
|
||||
if (real_running && !is_running) {
|
||||
try { writefile(STATE_FILE, ''); } catch(e) {}
|
||||
} else if (!real_running && is_running) {
|
||||
try { system('rm -f ' + STATE_FILE); } catch(e) {}
|
||||
}
|
||||
|
||||
// 3. IP 获取 (带缓存)
|
||||
let lan_ip = readfile(IP_CACHE) || '';
|
||||
if (!lan_ip) {
|
||||
try {
|
||||
let ubus_conn = connect();
|
||||
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) {
|
||||
lan_ip = iface['ipv4-address'][0].address;
|
||||
break;
|
||||
}
|
||||
if (!fallback_ip && iface.interface !== 'loopback' && iface['ipv4-address']?.[0]?.address) {
|
||||
fallback_ip = iface['ipv4-address'][0].address;
|
||||
}
|
||||
}
|
||||
if (!lan_ip) lan_ip = fallback_ip;
|
||||
}
|
||||
|
||||
if (lan_ip) {
|
||||
system('mkdir -p /tmp/flowproxy');
|
||||
writefile(IP_CACHE, lan_ip);
|
||||
}
|
||||
ubus_conn.disconnect();
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
let uci_cursor = cursor();
|
||||
|
||||
let proxy_server_ip_addr = uci_cursor.get(UCI_CONFIG, 'global', 'proxy_server_ip_addr') || '';
|
||||
let dns_running = (uci_cursor.get(UCI_CONFIG, 'dns_backup') ? 1 : 0);
|
||||
|
||||
return {
|
||||
running: real_running ? 1 : 0,
|
||||
dns_running: dns_running,
|
||||
enabled: int(uci_cursor.get(UCI_CONFIG, 'global', 'enabled') || '0'),
|
||||
proxy_server_ip_addr: proxy_server_ip_addr,
|
||||
proxy_server_dns_port: uci_cursor.get(UCI_CONFIG, 'global', 'proxy_server_dns_port') || '5353',
|
||||
lan_ip: safe_trim(lan_ip),
|
||||
interface: uci_cursor.get(UCI_CONFIG, 'global', 'interface') || 'br-lan',
|
||||
traffic_mark: uci_cursor.get(UCI_CONFIG, 'global', 'traffic_mark') || '0x666',
|
||||
nft: { tcp: tcp_active ? 1 : 0, udp: udp_active ? 1 : 0 }
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
get_logs: {
|
||||
args: { lines: 100 },
|
||||
call: function(request) {
|
||||
let lines = request.args?.lines ?? 100;
|
||||
let logs = [];
|
||||
try {
|
||||
if (access(LOG_FILE, 'r')) {
|
||||
let log_stat = stat(LOG_FILE);
|
||||
let content = '';
|
||||
if (log_stat && log_stat.size > 65536) {
|
||||
let fd = popen('tail -c 65536 ' + LOG_FILE);
|
||||
if (fd) {
|
||||
content = fd.read('all');
|
||||
fd.close();
|
||||
}
|
||||
} else {
|
||||
content = readfile(LOG_FILE);
|
||||
}
|
||||
if (content) {
|
||||
let log_lines = split(content, "\n");
|
||||
let total = length(log_lines);
|
||||
let start = (total > lines) ? (total - lines) : 0;
|
||||
for (let i = start; i < total; i++) {
|
||||
if (log_lines[i]) push(logs, log_lines[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return { logs };
|
||||
}
|
||||
},
|
||||
|
||||
generate_nft_config: {
|
||||
call: function() {
|
||||
system('mkdir -p /tmp/flowproxy');
|
||||
system('FLOWPROXY_PREVIEW=1 /usr/share/flowproxy/generate_nft.sh 2>&1');
|
||||
return { config: safe_trim(readfile('/tmp/flowproxy/nft.conf') || '') };
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
|
||||
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) };
|
||||
}
|
||||
},
|
||||
|
||||
get_interfaces: {
|
||||
call: function() {
|
||||
let interfaces = [];
|
||||
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) {
|
||||
push(interfaces, { name: name });
|
||||
}
|
||||
}
|
||||
ubus_conn.disconnect();
|
||||
}
|
||||
} catch (e) {}
|
||||
return { interfaces };
|
||||
}
|
||||
},
|
||||
|
||||
start_service: { call: function() { return { result: (system('/etc/init.d/flowproxy start >/dev/null 2>&1') === 0) }; } },
|
||||
stop_service: { call: function() { return { result: (system('/etc/init.d/flowproxy stop >/dev/null 2>&1') === 0) }; } },
|
||||
restart_service: { call: function() { return { result: (system('/etc/init.d/flowproxy restart >/dev/null 2>&1') === 0) }; } },
|
||||
clear_logs: { call: function() { try { writefile(LOG_FILE, ''); return { result: true }; } catch(e) { return { result: false }; } } }
|
||||
};
|
||||
|
||||
return { 'luci.flowproxy': methods };
|
||||
reload_service() {
|
||||
restart
|
||||
}
|
||||
Reference in new issue
Block a user