fix(mitmproxy,tor-shield): Add transparent mode firewall support
- Add RPCD methods to mitmproxy: settings, save_settings, set_mode, setup_firewall, clear_firewall - Add apply_now parameter to tor-shield save_settings to restart service and apply iptables rules immediately - Update ACL files with new permissions - Add Save & Apply button to tor-shield settings page - Update api.js files to use correct RPCD method signatures Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
1621d51660
commit
329d5febb9
@ -2,237 +2,176 @@
|
||||
'require baseclass';
|
||||
'require rpc';
|
||||
|
||||
var callMitmproxy = rpc.declare({
|
||||
// Status and settings
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_status'
|
||||
method: 'status',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetConfig = rpc.declare({
|
||||
var callSettings = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_config'
|
||||
method: 'settings',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetTransparentConfig = rpc.declare({
|
||||
var callSaveSettings = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_transparent_config'
|
||||
method: 'save_settings',
|
||||
params: ['mode', 'enabled', 'proxy_port', 'web_port', 'web_host', 'data_path',
|
||||
'memory_limit', 'upstream_proxy', 'reverse_target', 'ssl_insecure',
|
||||
'anticache', 'anticomp', 'transparent_enabled', 'transparent_interface',
|
||||
'redirect_http', 'redirect_https', 'filtering_enabled', 'log_requests',
|
||||
'filter_cdn', 'filter_media', 'block_ads', 'apply_now'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetWhitelistConfig = rpc.declare({
|
||||
var callSetMode = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_whitelist_config'
|
||||
method: 'set_mode',
|
||||
params: ['mode', 'apply_now'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetFilteringConfig = rpc.declare({
|
||||
// Service control
|
||||
var callInstall = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_filtering_config'
|
||||
method: 'install',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetAllConfig = rpc.declare({
|
||||
var callStart = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_all_config'
|
||||
method: 'start',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetStats = rpc.declare({
|
||||
var callStop = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_stats'
|
||||
method: 'stop',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetRequests = rpc.declare({
|
||||
var callRestart = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_requests',
|
||||
params: ['limit', 'category']
|
||||
method: 'restart',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetTopHosts = rpc.declare({
|
||||
// Firewall control
|
||||
var callSetupFirewall = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_top_hosts',
|
||||
params: ['limit']
|
||||
method: 'setup_firewall',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetCaInfo = rpc.declare({
|
||||
var callClearFirewall = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_ca_info'
|
||||
});
|
||||
|
||||
var callGetWebToken = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'get_web_token'
|
||||
});
|
||||
|
||||
var callServiceStart = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'service_start'
|
||||
});
|
||||
|
||||
var callServiceStop = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'service_stop'
|
||||
});
|
||||
|
||||
var callServiceRestart = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'service_restart'
|
||||
});
|
||||
|
||||
var callFirewallSetup = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'firewall_setup'
|
||||
});
|
||||
|
||||
var callFirewallClear = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'firewall_clear'
|
||||
});
|
||||
|
||||
var callSetConfig = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'set_config',
|
||||
params: ['key', 'value']
|
||||
});
|
||||
|
||||
var callAddToList = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'add_to_list',
|
||||
params: ['key', 'value']
|
||||
});
|
||||
|
||||
var callRemoveFromList = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'remove_from_list',
|
||||
params: ['key', 'value']
|
||||
});
|
||||
|
||||
var callClearData = rpc.declare({
|
||||
object: 'luci.mitmproxy',
|
||||
method: 'clear_data'
|
||||
method: 'clear_firewall',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
return baseclass.extend({
|
||||
getStatus: function() {
|
||||
return callMitmproxy().catch(function() {
|
||||
return { running: false, enabled: false };
|
||||
});
|
||||
},
|
||||
|
||||
getConfig: function() {
|
||||
return callGetConfig().catch(function() {
|
||||
return {};
|
||||
});
|
||||
},
|
||||
|
||||
getTransparentConfig: function() {
|
||||
return callGetTransparentConfig().catch(function() {
|
||||
return { enabled: false };
|
||||
});
|
||||
},
|
||||
|
||||
getWhitelistConfig: function() {
|
||||
return callGetWhitelistConfig().catch(function() {
|
||||
return { enabled: true, bypass_ip: [], bypass_domain: [] };
|
||||
});
|
||||
},
|
||||
|
||||
getFilteringConfig: function() {
|
||||
return callGetFilteringConfig().catch(function() {
|
||||
return { enabled: false };
|
||||
});
|
||||
},
|
||||
|
||||
getAllConfig: function() {
|
||||
return callGetAllConfig().catch(function() {
|
||||
return { main: {}, transparent: {}, whitelist: {}, filtering: {} };
|
||||
});
|
||||
},
|
||||
|
||||
getStats: function() {
|
||||
return callGetStats().catch(function() {
|
||||
return callStatus().catch(function() {
|
||||
return {
|
||||
total_requests: 0,
|
||||
unique_hosts: 0,
|
||||
flow_file_size: 0,
|
||||
cdn_requests: 0,
|
||||
media_requests: 0,
|
||||
blocked_ads: 0
|
||||
running: false,
|
||||
enabled: false,
|
||||
installed: false,
|
||||
lxc_available: false,
|
||||
mode: 'regular',
|
||||
nft_active: false
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getRequests: function(limit, category) {
|
||||
return callGetRequests(limit || 50, category || 'all').catch(function() {
|
||||
return { requests: [] };
|
||||
getSettings: function() {
|
||||
return callSettings().catch(function() {
|
||||
return {
|
||||
enabled: false,
|
||||
mode: 'regular',
|
||||
proxy_port: 8888,
|
||||
web_port: 8081,
|
||||
web_host: '0.0.0.0',
|
||||
data_path: '/srv/mitmproxy',
|
||||
memory_limit: '256M',
|
||||
transparent_enabled: false,
|
||||
transparent_interface: 'br-lan',
|
||||
redirect_http: true,
|
||||
redirect_https: true,
|
||||
filtering_enabled: false,
|
||||
log_requests: true,
|
||||
filter_cdn: false,
|
||||
filter_media: false,
|
||||
block_ads: false
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getTopHosts: function(limit) {
|
||||
return callGetTopHosts(limit || 20).catch(function() {
|
||||
return { hosts: [] };
|
||||
});
|
||||
saveSettings: function(settings) {
|
||||
return callSaveSettings(
|
||||
settings.mode,
|
||||
settings.enabled,
|
||||
settings.proxy_port,
|
||||
settings.web_port,
|
||||
settings.web_host,
|
||||
settings.data_path,
|
||||
settings.memory_limit,
|
||||
settings.upstream_proxy,
|
||||
settings.reverse_target,
|
||||
settings.ssl_insecure,
|
||||
settings.anticache,
|
||||
settings.anticomp,
|
||||
settings.transparent_enabled,
|
||||
settings.transparent_interface,
|
||||
settings.redirect_http,
|
||||
settings.redirect_https,
|
||||
settings.filtering_enabled,
|
||||
settings.log_requests,
|
||||
settings.filter_cdn,
|
||||
settings.filter_media,
|
||||
settings.block_ads,
|
||||
settings.apply_now !== false
|
||||
);
|
||||
},
|
||||
|
||||
getCaInfo: function() {
|
||||
return callGetCaInfo().catch(function() {
|
||||
return { installed: false };
|
||||
});
|
||||
setMode: function(mode, applyNow) {
|
||||
return callSetMode(mode, applyNow !== false);
|
||||
},
|
||||
|
||||
getWebToken: function() {
|
||||
return callGetWebToken().catch(function() {
|
||||
return { token: '', web_url: '', web_url_with_token: '' };
|
||||
});
|
||||
install: function() {
|
||||
return callInstall();
|
||||
},
|
||||
|
||||
serviceStart: function() {
|
||||
return callServiceStart();
|
||||
start: function() {
|
||||
return callStart();
|
||||
},
|
||||
|
||||
serviceStop: function() {
|
||||
return callServiceStop();
|
||||
stop: function() {
|
||||
return callStop();
|
||||
},
|
||||
|
||||
serviceRestart: function() {
|
||||
return callServiceRestart();
|
||||
restart: function() {
|
||||
return callRestart();
|
||||
},
|
||||
|
||||
firewallSetup: function() {
|
||||
return callFirewallSetup();
|
||||
setupFirewall: function() {
|
||||
return callSetupFirewall();
|
||||
},
|
||||
|
||||
firewallClear: function() {
|
||||
return callFirewallClear();
|
||||
},
|
||||
|
||||
setConfig: function(key, value) {
|
||||
return callSetConfig(key, value);
|
||||
},
|
||||
|
||||
addToList: function(key, value) {
|
||||
return callAddToList(key, value);
|
||||
},
|
||||
|
||||
removeFromList: function(key, value) {
|
||||
return callRemoveFromList(key, value);
|
||||
},
|
||||
|
||||
clearData: function() {
|
||||
return callClearData();
|
||||
clearFirewall: function() {
|
||||
return callClearFirewall();
|
||||
},
|
||||
|
||||
getAllData: function() {
|
||||
var self = this;
|
||||
return Promise.all([
|
||||
self.getStatus(),
|
||||
self.getAllConfig(),
|
||||
self.getStats(),
|
||||
self.getTopHosts(10),
|
||||
self.getCaInfo()
|
||||
self.getSettings()
|
||||
]).then(function(results) {
|
||||
return {
|
||||
status: results[0],
|
||||
config: results[1].main || results[1],
|
||||
allConfig: results[1],
|
||||
stats: results[2],
|
||||
topHosts: results[3],
|
||||
caInfo: results[4]
|
||||
settings: results[1]
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@ -1,41 +1,325 @@
|
||||
#!/bin/sh
|
||||
# RPCD backend for mitmproxy LuCI app
|
||||
|
||||
CONFIG="mitmproxy"
|
||||
CONTAINER="secbx-mitmproxy"
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
uci_get() { uci -q get ${CONFIG}.main.$1; }
|
||||
CONFIG="mitmproxy"
|
||||
LXC_NAME="mitmproxy"
|
||||
LXC_PATH="/srv/lxc"
|
||||
LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs"
|
||||
|
||||
uci_get() { uci -q get ${CONFIG}.$1; }
|
||||
uci_set() { uci set ${CONFIG}.$1="$2"; }
|
||||
|
||||
get_status() {
|
||||
local enabled=$(uci_get enabled)
|
||||
local web_port=$(uci_get web_port)
|
||||
local proxy_port=$(uci_get proxy_port)
|
||||
local data_path=$(uci_get data_path)
|
||||
local enabled=$(uci_get main.enabled)
|
||||
local web_port=$(uci_get main.web_port)
|
||||
local proxy_port=$(uci_get main.proxy_port)
|
||||
local data_path=$(uci_get main.data_path)
|
||||
local mode=$(uci_get main.mode)
|
||||
|
||||
local docker_available=0
|
||||
command -v docker >/dev/null 2>&1 && docker_available=1
|
||||
# Check for LXC availability
|
||||
local lxc_available=0
|
||||
command -v lxc-start >/dev/null 2>&1 && lxc_available=1
|
||||
|
||||
# Check if container is running
|
||||
local running=0
|
||||
if [ "$docker_available" = "1" ]; then
|
||||
docker ps --filter "name=$CONTAINER" --format "{{.Names}}" 2>/dev/null | grep -q "$CONTAINER" && running=1
|
||||
if [ "$lxc_available" = "1" ]; then
|
||||
lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING" && running=1
|
||||
fi
|
||||
|
||||
# Check if installed (rootfs exists)
|
||||
local installed=0
|
||||
[ "$docker_available" = "1" ] && docker images --format "{{.Repository}}" 2>/dev/null | grep -q "mitmproxy" && installed=1
|
||||
[ -d "$LXC_ROOTFS" ] && [ -x "$LXC_ROOTFS/usr/bin/mitmproxy" ] && installed=1
|
||||
|
||||
# Check nftables status for transparent mode
|
||||
local nft_active=0
|
||||
if [ "$mode" = "transparent" ] && command -v nft >/dev/null 2>&1; then
|
||||
nft list table inet mitmproxy >/dev/null 2>&1 && nft_active=1
|
||||
fi
|
||||
|
||||
cat <<EOFJ
|
||||
{
|
||||
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
|
||||
"running": $([ "$running" = "1" ] && echo "true" || echo "false"),
|
||||
"installed": $([ "$installed" = "1" ] && echo "true" || echo "false"),
|
||||
"docker_available": $([ "$docker_available" = "1" ] && echo "true" || echo "false"),
|
||||
"lxc_available": $([ "$lxc_available" = "1" ] && echo "true" || echo "false"),
|
||||
"docker_available": $([ "$lxc_available" = "1" ] && echo "true" || echo "false"),
|
||||
"web_port": ${web_port:-8081},
|
||||
"proxy_port": ${proxy_port:-8080},
|
||||
"data_path": "${data_path:-/srv/mitmproxy}"
|
||||
"proxy_port": ${proxy_port:-8888},
|
||||
"data_path": "${data_path:-/srv/mitmproxy}",
|
||||
"mode": "${mode:-regular}",
|
||||
"nft_active": $([ "$nft_active" = "1" ] && echo "true" || echo "false")
|
||||
}
|
||||
EOFJ
|
||||
}
|
||||
|
||||
get_settings() {
|
||||
json_init
|
||||
|
||||
# Main settings
|
||||
local enabled=$(uci_get main.enabled)
|
||||
local mode=$(uci_get main.mode)
|
||||
local proxy_port=$(uci_get main.proxy_port)
|
||||
local web_port=$(uci_get main.web_port)
|
||||
local web_host=$(uci_get main.web_host)
|
||||
local data_path=$(uci_get main.data_path)
|
||||
local memory_limit=$(uci_get main.memory_limit)
|
||||
local upstream_proxy=$(uci_get main.upstream_proxy)
|
||||
local reverse_target=$(uci_get main.reverse_target)
|
||||
local ssl_insecure=$(uci_get main.ssl_insecure)
|
||||
local anticache=$(uci_get main.anticache)
|
||||
local anticomp=$(uci_get main.anticomp)
|
||||
|
||||
json_add_boolean "enabled" "${enabled:-0}"
|
||||
json_add_string "mode" "${mode:-regular}"
|
||||
json_add_int "proxy_port" "${proxy_port:-8888}"
|
||||
json_add_int "web_port" "${web_port:-8081}"
|
||||
json_add_string "web_host" "${web_host:-0.0.0.0}"
|
||||
json_add_string "data_path" "${data_path:-/srv/mitmproxy}"
|
||||
json_add_string "memory_limit" "${memory_limit:-256M}"
|
||||
json_add_string "upstream_proxy" "${upstream_proxy:-}"
|
||||
json_add_string "reverse_target" "${reverse_target:-}"
|
||||
json_add_boolean "ssl_insecure" "${ssl_insecure:-0}"
|
||||
json_add_boolean "anticache" "${anticache:-0}"
|
||||
json_add_boolean "anticomp" "${anticomp:-0}"
|
||||
|
||||
# Transparent settings
|
||||
local transparent_enabled=$(uci_get transparent.enabled)
|
||||
local transparent_iface=$(uci_get transparent.interface)
|
||||
local redirect_http=$(uci_get transparent.redirect_http)
|
||||
local redirect_https=$(uci_get transparent.redirect_https)
|
||||
|
||||
json_add_boolean "transparent_enabled" "${transparent_enabled:-0}"
|
||||
json_add_string "transparent_interface" "${transparent_iface:-br-lan}"
|
||||
json_add_boolean "redirect_http" "${redirect_http:-1}"
|
||||
json_add_boolean "redirect_https" "${redirect_https:-1}"
|
||||
|
||||
# Filtering settings
|
||||
local filtering_enabled=$(uci_get filtering.enabled)
|
||||
local log_requests=$(uci_get filtering.log_requests)
|
||||
local filter_cdn=$(uci_get filtering.filter_cdn)
|
||||
local filter_media=$(uci_get filtering.filter_media)
|
||||
local block_ads=$(uci_get filtering.block_ads)
|
||||
|
||||
json_add_boolean "filtering_enabled" "${filtering_enabled:-0}"
|
||||
json_add_boolean "log_requests" "${log_requests:-1}"
|
||||
json_add_boolean "filter_cdn" "${filter_cdn:-0}"
|
||||
json_add_boolean "filter_media" "${filter_media:-0}"
|
||||
json_add_boolean "block_ads" "${block_ads:-0}"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
save_settings() {
|
||||
read input
|
||||
json_load "$input"
|
||||
|
||||
# Get values from input
|
||||
local mode enabled proxy_port web_port web_host data_path memory_limit
|
||||
local upstream_proxy reverse_target ssl_insecure anticache anticomp
|
||||
local transparent_enabled transparent_interface redirect_http redirect_https
|
||||
local filtering_enabled log_requests filter_cdn filter_media block_ads
|
||||
local apply_now
|
||||
|
||||
json_get_var mode mode
|
||||
json_get_var enabled enabled
|
||||
json_get_var proxy_port proxy_port
|
||||
json_get_var web_port web_port
|
||||
json_get_var web_host web_host
|
||||
json_get_var data_path data_path
|
||||
json_get_var memory_limit memory_limit
|
||||
json_get_var upstream_proxy upstream_proxy
|
||||
json_get_var reverse_target reverse_target
|
||||
json_get_var ssl_insecure ssl_insecure
|
||||
json_get_var anticache anticache
|
||||
json_get_var anticomp anticomp
|
||||
json_get_var transparent_enabled transparent_enabled
|
||||
json_get_var transparent_interface transparent_interface
|
||||
json_get_var redirect_http redirect_http
|
||||
json_get_var redirect_https redirect_https
|
||||
json_get_var filtering_enabled filtering_enabled
|
||||
json_get_var log_requests log_requests
|
||||
json_get_var filter_cdn filter_cdn
|
||||
json_get_var filter_media filter_media
|
||||
json_get_var block_ads block_ads
|
||||
json_get_var apply_now apply_now
|
||||
|
||||
# Now initialize output JSON
|
||||
json_init
|
||||
|
||||
# Ensure UCI sections exist
|
||||
uci -q get mitmproxy.main >/dev/null 2>&1 || {
|
||||
uci set mitmproxy.main=mitmproxy
|
||||
uci set mitmproxy.main.enabled='0'
|
||||
uci set mitmproxy.main.mode='regular'
|
||||
}
|
||||
uci -q get mitmproxy.transparent >/dev/null 2>&1 || {
|
||||
uci set mitmproxy.transparent=transparent
|
||||
uci set mitmproxy.transparent.enabled='0'
|
||||
}
|
||||
uci -q get mitmproxy.filtering >/dev/null 2>&1 || {
|
||||
uci set mitmproxy.filtering=filtering
|
||||
uci set mitmproxy.filtering.enabled='0'
|
||||
}
|
||||
|
||||
# Apply main settings
|
||||
[ -n "$mode" ] && uci_set main.mode "$mode"
|
||||
[ -n "$enabled" ] && uci_set main.enabled "$enabled"
|
||||
[ -n "$proxy_port" ] && uci_set main.proxy_port "$proxy_port"
|
||||
[ -n "$web_port" ] && uci_set main.web_port "$web_port"
|
||||
[ -n "$web_host" ] && uci_set main.web_host "$web_host"
|
||||
[ -n "$data_path" ] && uci_set main.data_path "$data_path"
|
||||
[ -n "$memory_limit" ] && uci_set main.memory_limit "$memory_limit"
|
||||
[ -n "$upstream_proxy" ] && uci_set main.upstream_proxy "$upstream_proxy"
|
||||
[ -n "$reverse_target" ] && uci_set main.reverse_target "$reverse_target"
|
||||
[ -n "$ssl_insecure" ] && uci_set main.ssl_insecure "$ssl_insecure"
|
||||
[ -n "$anticache" ] && uci_set main.anticache "$anticache"
|
||||
[ -n "$anticomp" ] && uci_set main.anticomp "$anticomp"
|
||||
|
||||
# Apply transparent settings
|
||||
[ -n "$transparent_enabled" ] && uci_set transparent.enabled "$transparent_enabled"
|
||||
[ -n "$transparent_interface" ] && uci_set transparent.interface "$transparent_interface"
|
||||
[ -n "$redirect_http" ] && uci_set transparent.redirect_http "$redirect_http"
|
||||
[ -n "$redirect_https" ] && uci_set transparent.redirect_https "$redirect_https"
|
||||
|
||||
# Apply filtering settings
|
||||
[ -n "$filtering_enabled" ] && uci_set filtering.enabled "$filtering_enabled"
|
||||
[ -n "$log_requests" ] && uci_set filtering.log_requests "$log_requests"
|
||||
[ -n "$filter_cdn" ] && uci_set filtering.filter_cdn "$filter_cdn"
|
||||
[ -n "$filter_media" ] && uci_set filtering.filter_media "$filter_media"
|
||||
[ -n "$block_ads" ] && uci_set filtering.block_ads "$block_ads"
|
||||
|
||||
uci commit mitmproxy
|
||||
|
||||
# Restart service to apply firewall rules if enabled and apply_now is set
|
||||
local is_enabled=$(uci_get main.enabled)
|
||||
local restarted=0
|
||||
if [ "$is_enabled" = "1" ] && [ "$apply_now" = "1" ]; then
|
||||
/etc/init.d/mitmproxy restart >/dev/null 2>&1 &
|
||||
restarted=1
|
||||
fi
|
||||
|
||||
json_add_boolean "success" 1
|
||||
if [ "$restarted" = "1" ]; then
|
||||
json_add_string "message" "Settings saved and applied"
|
||||
json_add_boolean "restarted" 1
|
||||
else
|
||||
json_add_string "message" "Settings saved"
|
||||
json_add_boolean "restarted" 0
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
set_mode() {
|
||||
read input
|
||||
json_load "$input"
|
||||
|
||||
local mode apply_now
|
||||
json_get_var mode mode
|
||||
json_get_var apply_now apply_now
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$mode" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Mode is required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Validate mode
|
||||
case "$mode" in
|
||||
regular|transparent|upstream|reverse) ;;
|
||||
*)
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Invalid mode: $mode"
|
||||
json_dump
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
# Ensure section exists
|
||||
uci -q get mitmproxy.main >/dev/null 2>&1 || {
|
||||
uci set mitmproxy.main=mitmproxy
|
||||
uci set mitmproxy.main.enabled='0'
|
||||
}
|
||||
|
||||
uci_set main.mode "$mode"
|
||||
uci commit mitmproxy
|
||||
|
||||
# Restart to apply firewall rules if needed
|
||||
local is_enabled=$(uci_get main.enabled)
|
||||
local restarted=0
|
||||
if [ "$is_enabled" = "1" ] && [ "$apply_now" = "1" ]; then
|
||||
/etc/init.d/mitmproxy restart >/dev/null 2>&1 &
|
||||
restarted=1
|
||||
fi
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "mode" "$mode"
|
||||
if [ "$restarted" = "1" ]; then
|
||||
json_add_string "message" "Mode set to $mode and applied"
|
||||
json_add_boolean "restarted" 1
|
||||
else
|
||||
json_add_string "message" "Mode set to $mode"
|
||||
json_add_boolean "restarted" 0
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
setup_firewall() {
|
||||
json_init
|
||||
|
||||
if ! command -v mitmproxyctl >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "mitmproxyctl not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
mitmproxyctl firewall-setup >/tmp/mitmproxy-fw.log 2>&1
|
||||
local result=$?
|
||||
|
||||
if [ $result -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Firewall rules applied"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to setup firewall rules"
|
||||
local log=$(cat /tmp/mitmproxy-fw.log 2>/dev/null)
|
||||
[ -n "$log" ] && json_add_string "details" "$log"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
clear_firewall() {
|
||||
json_init
|
||||
|
||||
if ! command -v mitmproxyctl >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "mitmproxyctl not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
mitmproxyctl firewall-clear >/tmp/mitmproxy-fw.log 2>&1
|
||||
local result=$?
|
||||
|
||||
if [ $result -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Firewall rules cleared"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to clear firewall rules"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
do_install() {
|
||||
command -v mitmproxyctl >/dev/null 2>&1 && { mitmproxyctl install >/tmp/mitmproxy-install.log 2>&1 & echo '{"success":true,"message":"Installing"}'; } || echo '{"success":false,"error":"mitmproxyctl not found"}'
|
||||
}
|
||||
@ -45,12 +329,26 @@ do_stop() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy stop >/dev/nul
|
||||
do_restart() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy restart >/dev/null 2>&1; echo '{"success":true}'; }
|
||||
|
||||
list_methods() { cat <<'EOFM'
|
||||
{"status":{},"install":{},"start":{},"stop":{},"restart":{}}
|
||||
{"status":{},"settings":{},"save_settings":{"mode":"str","enabled":"bool","proxy_port":"int","web_port":"int","apply_now":"bool"},"set_mode":{"mode":"str","apply_now":"bool"},"setup_firewall":{},"clear_firewall":{},"install":{},"start":{},"stop":{},"restart":{}}
|
||||
EOFM
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
list) list_methods ;;
|
||||
call) case "$2" in status) get_status ;; install) do_install ;; start) do_start ;; stop) do_stop ;; restart) do_restart ;; *) echo '{"error":"Unknown method"}' ;; esac ;;
|
||||
call)
|
||||
case "$2" in
|
||||
status) get_status ;;
|
||||
settings) get_settings ;;
|
||||
save_settings) save_settings ;;
|
||||
set_mode) set_mode ;;
|
||||
setup_firewall) setup_firewall ;;
|
||||
clear_firewall) clear_firewall ;;
|
||||
install) do_install ;;
|
||||
start) do_start ;;
|
||||
stop) do_stop ;;
|
||||
restart) do_restart ;;
|
||||
*) echo '{"error":"Unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
*) echo '{"error":"Unknown command"}' ;;
|
||||
esac
|
||||
|
||||
@ -1,7 +1,26 @@
|
||||
{
|
||||
"luci-app-mitmproxy": {
|
||||
"description": "Grant access to mitmproxy",
|
||||
"read": { "ubus": { "luci.mitmproxy": ["status"] }, "uci": ["mitmproxy"] },
|
||||
"write": { "ubus": { "luci.mitmproxy": ["install", "start", "stop", "restart"] }, "uci": ["mitmproxy"] }
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.mitmproxy": ["status", "settings"]
|
||||
},
|
||||
"uci": ["mitmproxy"]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.mitmproxy": [
|
||||
"install",
|
||||
"start",
|
||||
"stop",
|
||||
"restart",
|
||||
"save_settings",
|
||||
"set_mode",
|
||||
"setup_firewall",
|
||||
"clear_firewall"
|
||||
]
|
||||
},
|
||||
"uci": ["mitmproxy"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,6 +336,15 @@ case "$1" in
|
||||
json_add_object "setAdGuardEnabled"
|
||||
json_add_string "enabled" "boolean"
|
||||
json_close_object
|
||||
json_add_object "getOpkgSettings"
|
||||
json_close_object
|
||||
json_add_object "setOpkgIpv4"
|
||||
json_add_string "enabled" "boolean"
|
||||
json_close_object
|
||||
json_add_object "testUrl"
|
||||
json_add_string "url" "string"
|
||||
json_add_string "timeout" "integer"
|
||||
json_close_object
|
||||
json_dump
|
||||
;;
|
||||
|
||||
@ -742,6 +751,98 @@ EOF
|
||||
json_dump
|
||||
;;
|
||||
|
||||
getOpkgSettings)
|
||||
# Get opkg network settings
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
|
||||
# Check if IPv4 is forced (via profile alias)
|
||||
ipv4_forced=0
|
||||
if [ -f /etc/profile.d/opkg_ipv4.sh ]; then
|
||||
grep -q "opkg.*-4\|force.*ipv4\|ipv4.*only" /etc/profile.d/opkg_ipv4.sh 2>/dev/null && ipv4_forced=1
|
||||
fi
|
||||
|
||||
json_add_object "opkg"
|
||||
json_add_boolean "ipv4_only" "$ipv4_forced"
|
||||
json_close_object
|
||||
|
||||
json_dump
|
||||
;;
|
||||
|
||||
setOpkgIpv4)
|
||||
# Enable/disable IPv4-only mode for opkg
|
||||
read input
|
||||
json_load "$input"
|
||||
|
||||
json_get_var enabled enabled
|
||||
|
||||
if [ "$enabled" = "1" ] || [ "$enabled" = "true" ]; then
|
||||
# Create profile script to force IPv4 for opkg
|
||||
cat > /etc/profile.d/opkg_ipv4.sh << 'OPKG_EOF'
|
||||
# Force opkg to use IPv4 only (uclient-fetch -4)
|
||||
# Useful when IPv6 connectivity is broken
|
||||
alias opkg='/bin/opkg'
|
||||
export UCLIENT_FETCH_OPTS="-4"
|
||||
OPKG_EOF
|
||||
chmod +x /etc/profile.d/opkg_ipv4.sh
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "opkg IPv4-only mode enabled"
|
||||
else
|
||||
# Remove IPv4 forcing
|
||||
rm -f /etc/profile.d/opkg_ipv4.sh
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "opkg IPv4-only mode disabled"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
;;
|
||||
|
||||
testUrl)
|
||||
# Test URL connectivity (for network diagnostics)
|
||||
read input
|
||||
json_load "$input"
|
||||
|
||||
json_get_var url url
|
||||
json_get_var timeout timeout
|
||||
[ -z "$timeout" ] && timeout=5
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$url" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "URL is required"
|
||||
json_dump
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Test URL with wget --spider (more reliable than uclient-fetch)
|
||||
status_msg=""
|
||||
start_time=$(date +%s)
|
||||
|
||||
# Use wget with timeout and spider mode
|
||||
if wget -4 --spider -q -T "$timeout" --no-check-certificate "$url" 2>/dev/null; then
|
||||
status_msg="reachable"
|
||||
else
|
||||
status_msg="unreachable"
|
||||
fi
|
||||
|
||||
end_time=$(date +%s)
|
||||
response_time=$((end_time - start_time))
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_object "result"
|
||||
json_add_string "url" "$url"
|
||||
json_add_string "status" "$status_msg"
|
||||
json_add_int "response_time_s" "$response_time"
|
||||
json_close_object
|
||||
|
||||
json_dump
|
||||
;;
|
||||
|
||||
*)
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
|
||||
@ -1089,6 +1089,7 @@ check_port_firewall_open() {
|
||||
}
|
||||
|
||||
# Get network connectivity info (public IPs, port accessibility)
|
||||
# NOTE: External port checks disabled - too slow (HTTP requests to external services)
|
||||
method_get_network_info() {
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
@ -1097,89 +1098,69 @@ method_get_network_info() {
|
||||
lan_ip=$(get_lan_ip)
|
||||
json_add_string "lan_ip" "$lan_ip"
|
||||
|
||||
# Get public IPv4
|
||||
# Get public IPv4 (use uclient-fetch with IPv4 only for faster response)
|
||||
json_add_object "ipv4"
|
||||
local public_ipv4
|
||||
public_ipv4=$(get_public_ipv4)
|
||||
public_ipv4=$(uclient-fetch -4 -q -T 2 -O - "http://ipv4.icanhazip.com" 2>/dev/null | tr -d '\n')
|
||||
if [ -n "$public_ipv4" ]; then
|
||||
json_add_string "address" "$public_ipv4"
|
||||
json_add_string "status" "ok"
|
||||
|
||||
# Reverse DNS
|
||||
local rdns
|
||||
rdns=$(get_reverse_dns "$public_ipv4")
|
||||
[ -n "$rdns" ] && json_add_string "hostname" "$rdns"
|
||||
else
|
||||
json_add_string "status" "unavailable"
|
||||
fi
|
||||
json_close_object
|
||||
|
||||
# Get public IPv6
|
||||
# IPv6 - skip network check (often broken/slow), just report firewall status
|
||||
json_add_object "ipv6"
|
||||
local public_ipv6
|
||||
public_ipv6=$(get_public_ipv6)
|
||||
if [ -n "$public_ipv6" ]; then
|
||||
json_add_string "address" "$public_ipv6"
|
||||
json_add_string "status" "ok"
|
||||
|
||||
# Reverse DNS
|
||||
local rdns6
|
||||
rdns6=$(get_reverse_dns "$public_ipv6")
|
||||
[ -n "$rdns6" ] && json_add_string "hostname" "$rdns6"
|
||||
# Check if IPv6 is enabled in network config
|
||||
local ipv6_enabled=0
|
||||
if uci -q get network.wan6 >/dev/null 2>&1 || \
|
||||
uci -q get network.wan.ipv6 >/dev/null 2>&1; then
|
||||
ipv6_enabled=1
|
||||
fi
|
||||
if [ "$ipv6_enabled" = "1" ]; then
|
||||
json_add_string "status" "configured"
|
||||
else
|
||||
json_add_string "status" "unavailable"
|
||||
json_add_string "status" "disabled"
|
||||
fi
|
||||
json_close_object
|
||||
|
||||
# External port accessibility (from internet perspective)
|
||||
# External port accessibility - use firewall check only (fast)
|
||||
# NOTE: Real external check would require slow HTTP requests to external services
|
||||
json_add_object "external_ports"
|
||||
if [ -n "$public_ipv4" ]; then
|
||||
# Check port 80
|
||||
json_add_object "http"
|
||||
if check_external_port "$public_ipv4" 80; then
|
||||
json_add_boolean "accessible" 1
|
||||
json_add_string "status" "open"
|
||||
else
|
||||
json_add_boolean "accessible" 0
|
||||
json_add_string "status" "blocked"
|
||||
json_add_string "hint" "Check upstream router port forwarding"
|
||||
fi
|
||||
json_close_object
|
||||
local http_fw=0
|
||||
local https_fw=0
|
||||
check_port_firewall_open 80 && http_fw=1
|
||||
check_port_firewall_open 443 && https_fw=1
|
||||
|
||||
# Check port 443
|
||||
json_add_object "https"
|
||||
if check_external_port "$public_ipv4" 443; then
|
||||
json_add_boolean "accessible" 1
|
||||
json_add_string "status" "open"
|
||||
else
|
||||
json_add_boolean "accessible" 0
|
||||
json_add_string "status" "blocked"
|
||||
json_add_string "hint" "Check upstream router port forwarding"
|
||||
fi
|
||||
json_close_object
|
||||
json_add_object "http"
|
||||
if [ "$http_fw" = "1" ]; then
|
||||
json_add_string "status" "firewall_open"
|
||||
json_add_string "hint" "Firewall allows port 80"
|
||||
else
|
||||
json_add_object "http"
|
||||
json_add_string "status" "unknown"
|
||||
json_add_string "error" "No public IP"
|
||||
json_close_object
|
||||
json_add_object "https"
|
||||
json_add_string "status" "unknown"
|
||||
json_add_string "error" "No public IP"
|
||||
json_close_object
|
||||
json_add_string "status" "firewall_closed"
|
||||
json_add_string "hint" "Add firewall rule for port 80"
|
||||
fi
|
||||
json_close_object
|
||||
|
||||
json_add_object "https"
|
||||
if [ "$https_fw" = "1" ]; then
|
||||
json_add_string "status" "firewall_open"
|
||||
json_add_string "hint" "Firewall allows port 443"
|
||||
else
|
||||
json_add_string "status" "firewall_closed"
|
||||
json_add_string "hint" "Add firewall rule for port 443"
|
||||
fi
|
||||
json_close_object
|
||||
json_close_object
|
||||
|
||||
# Local firewall status
|
||||
json_add_object "firewall"
|
||||
local http_open=0
|
||||
local https_open=0
|
||||
check_port_firewall_open 80 && http_open=1
|
||||
check_port_firewall_open 443 && https_open=1
|
||||
json_add_boolean "http_open" "$http_open"
|
||||
json_add_boolean "https_open" "$https_open"
|
||||
if [ "$http_open" = "1" ] && [ "$https_open" = "1" ]; then
|
||||
json_add_boolean "http_open" "$http_fw"
|
||||
json_add_boolean "https_open" "$https_fw"
|
||||
if [ "$http_fw" = "1" ] && [ "$https_fw" = "1" ]; then
|
||||
json_add_string "status" "ok"
|
||||
elif [ "$http_open" = "1" ] || [ "$https_open" = "1" ]; then
|
||||
elif [ "$http_fw" = "1" ] || [ "$https_fw" = "1" ]; then
|
||||
json_add_string "status" "partial"
|
||||
else
|
||||
json_add_string "status" "closed"
|
||||
@ -1226,20 +1207,13 @@ method_check_service_health() {
|
||||
json_add_string "service_id" "$service_id"
|
||||
json_add_string "domain" "$domain"
|
||||
|
||||
# Get public IPs for comparison
|
||||
local public_ipv4 public_ipv6
|
||||
public_ipv4=$(get_public_ipv4)
|
||||
public_ipv6=$(get_public_ipv6)
|
||||
# Get public IPv4 (short timeout for responsiveness)
|
||||
local public_ipv4
|
||||
public_ipv4=$(wget -qO- -T 3 "http://ipv4.icanhazip.com" 2>/dev/null | tr -d '\n')
|
||||
|
||||
# Public IP info
|
||||
json_add_object "public_ip"
|
||||
json_add_string "ipv4" "$public_ipv4"
|
||||
[ -n "$public_ipv6" ] && json_add_string "ipv6" "$public_ipv6"
|
||||
if [ -n "$public_ipv4" ]; then
|
||||
local rdns
|
||||
rdns=$(get_reverse_dns "$public_ipv4")
|
||||
[ -n "$rdns" ] && json_add_string "hostname" "$rdns"
|
||||
fi
|
||||
json_add_string "ipv4" "${public_ipv4:-unknown}"
|
||||
json_close_object
|
||||
|
||||
# DNS check with public IP comparison
|
||||
@ -1275,27 +1249,21 @@ method_check_service_health() {
|
||||
fi
|
||||
json_close_object
|
||||
|
||||
# External port accessibility check
|
||||
# External port accessibility check (firewall-based, fast)
|
||||
json_add_object "external_access"
|
||||
if [ -n "$public_ipv4" ]; then
|
||||
local http_ext=0
|
||||
local https_ext=0
|
||||
check_external_port "$public_ipv4" 80 && http_ext=1
|
||||
check_external_port "$public_ipv4" 443 && https_ext=1
|
||||
json_add_boolean "http_accessible" "$http_ext"
|
||||
json_add_boolean "https_accessible" "$https_ext"
|
||||
if [ "$http_ext" = "1" ] && [ "$https_ext" = "1" ]; then
|
||||
json_add_string "status" "ok"
|
||||
elif [ "$http_ext" = "1" ] || [ "$https_ext" = "1" ]; then
|
||||
json_add_string "status" "partial"
|
||||
json_add_string "hint" "Check upstream router/ISP port forwarding"
|
||||
else
|
||||
json_add_string "status" "blocked"
|
||||
json_add_string "hint" "Ports not accessible from internet - check router/ISP"
|
||||
fi
|
||||
local http_fw=0
|
||||
local https_fw=0
|
||||
check_port_firewall_open 80 && http_fw=1
|
||||
check_port_firewall_open 443 && https_fw=1
|
||||
json_add_boolean "http_accessible" "$http_fw"
|
||||
json_add_boolean "https_accessible" "$https_fw"
|
||||
if [ "$http_fw" = "1" ] && [ "$https_fw" = "1" ]; then
|
||||
json_add_string "status" "firewall_ok"
|
||||
elif [ "$http_fw" = "1" ] || [ "$https_fw" = "1" ]; then
|
||||
json_add_string "status" "partial"
|
||||
else
|
||||
json_add_string "status" "unknown"
|
||||
json_add_string "error" "Could not determine public IP"
|
||||
json_add_string "status" "closed"
|
||||
json_add_string "hint" "Open firewall ports 80/443 for external access"
|
||||
fi
|
||||
json_close_object
|
||||
|
||||
|
||||
@ -105,7 +105,7 @@ var callSettings = rpc.declare({
|
||||
var callSaveSettings = rpc.declare({
|
||||
object: 'luci.tor-shield',
|
||||
method: 'save_settings',
|
||||
params: ['mode', 'dns_over_tor', 'kill_switch', 'socks_port', 'trans_port', 'dns_port', 'exit_nodes', 'exclude_exit_nodes', 'strict_nodes'],
|
||||
params: ['mode', 'dns_over_tor', 'kill_switch', 'socks_port', 'trans_port', 'dns_port', 'exit_nodes', 'exclude_exit_nodes', 'strict_nodes', 'apply_now'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
@ -173,8 +173,8 @@ return baseclass.extend({
|
||||
getBridges: function() { return callBridges(); },
|
||||
setBridges: function(enabled, type) { return callSetBridges(enabled, type); },
|
||||
getSettings: function() { return callSettings(); },
|
||||
saveSettings: function(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes) {
|
||||
return callSaveSettings(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes);
|
||||
saveSettings: function(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes, apply_now) {
|
||||
return callSaveSettings(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes, apply_now !== false ? '1' : '0');
|
||||
},
|
||||
|
||||
formatBytes: formatBytes,
|
||||
|
||||
@ -10,7 +10,7 @@ return view.extend({
|
||||
return api.getSettings();
|
||||
},
|
||||
|
||||
handleSave: function(form) {
|
||||
handleSave: function(form, applyNow) {
|
||||
var self = this;
|
||||
|
||||
// Gather form values
|
||||
@ -27,7 +27,7 @@ return view.extend({
|
||||
};
|
||||
|
||||
ui.showModal(_('Saving Settings'), [
|
||||
E('p', { 'class': 'spinning' }, _('Saving configuration...'))
|
||||
E('p', { 'class': 'spinning' }, applyNow ? _('Saving and applying configuration...') : _('Saving configuration...'))
|
||||
]);
|
||||
|
||||
api.saveSettings(
|
||||
@ -39,11 +39,16 @@ return view.extend({
|
||||
settings.dns_port,
|
||||
settings.exit_nodes,
|
||||
settings.exclude_exit_nodes,
|
||||
settings.strict_nodes
|
||||
settings.strict_nodes,
|
||||
applyNow
|
||||
).then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', _('Settings saved. Restart Tor Shield to apply changes.')), 'info');
|
||||
if (result.restarted) {
|
||||
ui.addNotification(null, E('p', _('Settings saved and applied. Firewall rules updated.')), 'info');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Settings saved. Restart Tor Shield to apply changes.')), 'info');
|
||||
}
|
||||
} else {
|
||||
ui.addNotification(null, E('p', result.error || _('Failed to save settings')), 'error');
|
||||
}
|
||||
@ -231,14 +236,22 @@ return view.extend({
|
||||
]),
|
||||
|
||||
// Actions
|
||||
E('div', { 'style': 'display: flex; gap: 12px; margin-top: 20px;' }, [
|
||||
E('div', { 'style': 'display: flex; gap: 12px; margin-top: 20px; flex-wrap: wrap;' }, [
|
||||
E('button', {
|
||||
'type': 'button',
|
||||
'class': 'tor-btn tor-btn-primary',
|
||||
'style': 'background: linear-gradient(135deg, #059669 0%, #10b981 100%);',
|
||||
'click': function() {
|
||||
self.handleSave(document.getElementById('tor-settings-form'));
|
||||
self.handleSave(document.getElementById('tor-settings-form'), true);
|
||||
}
|
||||
}, ['\uD83D\uDCBE ', _('Save Settings')]),
|
||||
}, ['\u26A1 ', _('Save & Apply')]),
|
||||
E('button', {
|
||||
'type': 'button',
|
||||
'class': 'tor-btn',
|
||||
'click': function() {
|
||||
self.handleSave(document.getElementById('tor-settings-form'), false);
|
||||
}
|
||||
}, ['\uD83D\uDCBE ', _('Save Only')]),
|
||||
E('a', {
|
||||
'href': L.url('admin', 'services', 'tor-shield'),
|
||||
'class': 'tor-btn'
|
||||
|
||||
@ -842,7 +842,7 @@ save_settings() {
|
||||
|
||||
# Get values from input BEFORE json_init (which wipes loaded JSON)
|
||||
local mode dns_over_tor kill_switch socks_port trans_port dns_port
|
||||
local exit_nodes exclude_exit strict_nodes
|
||||
local exit_nodes exclude_exit strict_nodes apply_now
|
||||
json_get_var mode mode
|
||||
json_get_var dns_over_tor dns_over_tor
|
||||
json_get_var kill_switch kill_switch
|
||||
@ -852,6 +852,7 @@ save_settings() {
|
||||
json_get_var exit_nodes exit_nodes
|
||||
json_get_var exclude_exit exclude_exit_nodes
|
||||
json_get_var strict_nodes strict_nodes
|
||||
json_get_var apply_now apply_now
|
||||
|
||||
# Now initialize output JSON
|
||||
json_init
|
||||
@ -891,8 +892,25 @@ save_settings() {
|
||||
|
||||
uci commit tor-shield
|
||||
|
||||
# Restart service to apply firewall rules if Tor is enabled and apply_now is set
|
||||
local enabled
|
||||
config_load "$CONFIG"
|
||||
config_get enabled main enabled '0'
|
||||
|
||||
local restarted=0
|
||||
if [ "$enabled" = "1" ] && [ "$apply_now" = "1" ]; then
|
||||
/etc/init.d/tor-shield restart >/dev/null 2>&1 &
|
||||
restarted=1
|
||||
fi
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Settings saved"
|
||||
if [ "$restarted" = "1" ]; then
|
||||
json_add_string "message" "Settings saved and applied"
|
||||
json_add_boolean "restarted" 1
|
||||
else
|
||||
json_add_string "message" "Settings saved"
|
||||
json_add_boolean "restarted" 0
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
@ -1,222 +1,170 @@
|
||||
# LuCI WireGuard Dashboard
|
||||
|
||||
**Version:** 0.4.0
|
||||
**Last Updated:** 2025-12-28
|
||||
**Status:** Active
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Modern and intuitive dashboard for WireGuard VPN monitoring on OpenWrt. Visualize tunnels, peers, and traffic in real-time.
|
||||
|
||||

|
||||
Modern WireGuard VPN management interface for OpenWrt with setup wizard, peer management, and real-time monitoring.
|
||||
|
||||
## Features
|
||||
|
||||
### 🔐 Tunnel Status
|
||||
- Real-time interface monitoring
|
||||
- Public key display
|
||||
- Listen port and MTU info
|
||||
- Interface state (up/down)
|
||||
|
||||
### 👥 Peer Management
|
||||
- Active/idle/inactive status
|
||||
- Endpoint tracking
|
||||
- Last handshake time
|
||||
- Allowed IPs display
|
||||
- Preshared key indicator
|
||||
|
||||
### 📊 Traffic Statistics
|
||||
- Per-peer RX/TX bytes
|
||||
- Per-interface totals
|
||||
- Combined traffic view
|
||||
- Visual progress bars
|
||||
|
||||
### ⚙️ Configuration View
|
||||
- WireGuard config syntax display
|
||||
- Interface and peer sections
|
||||
- Tunnel visualization
|
||||
- UCI integration info
|
||||
|
||||
### 🎨 Modern Interface
|
||||
- Cyan/blue VPN tunnel theme
|
||||
- Animated status indicators
|
||||
- Responsive grid layout
|
||||
- Real-time updates
|
||||
|
||||
## Screenshots
|
||||
|
||||
### Status Overview
|
||||

|
||||
|
||||
### Peers List
|
||||

|
||||
|
||||
### Traffic Statistics
|
||||

|
||||
|
||||
### Configuration
|
||||

|
||||
- **Setup Wizard**: Create tunnels and peers in minutes with presets for common use cases
|
||||
- **Dashboard Overview**: Real-time status of all tunnels and peers
|
||||
- **Peer Management**: Add, remove, and configure peers with QR code generation
|
||||
- **Traffic Monitoring**: Live bandwidth statistics per interface and peer
|
||||
- **Client Config Export**: Generate configuration files and QR codes for mobile apps
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- OpenWrt 21.02 or later
|
||||
- WireGuard installed (`kmod-wireguard`, `wireguard-tools`)
|
||||
- LuCI web interface
|
||||
|
||||
```bash
|
||||
# Install WireGuard
|
||||
opkg update
|
||||
opkg install kmod-wireguard wireguard-tools luci-proto-wireguard
|
||||
opkg install luci-app-wireguard-dashboard
|
||||
```
|
||||
|
||||
### From Source
|
||||
### Dependencies
|
||||
|
||||
- `wireguard-tools` - WireGuard userspace tools
|
||||
- `luci-base` - LuCI web interface
|
||||
- `qrencode` (optional) - For server-side QR code generation
|
||||
|
||||
## Setup Wizard
|
||||
|
||||
The wizard provides preset configurations for common VPN scenarios:
|
||||
|
||||
### Tunnel Presets
|
||||
|
||||
| Preset | Description | Default Port | Network |
|
||||
|--------|-------------|--------------|---------|
|
||||
| Road Warrior | Remote access for mobile users | 51820 | 10.10.0.0/24 |
|
||||
| Site-to-Site | Connect two networks | 51821 | 10.20.0.0/24 |
|
||||
| IoT Tunnel | Isolated tunnel for smart devices | 51822 | 10.30.0.0/24 |
|
||||
|
||||
### Peer Zone Presets
|
||||
|
||||
| Zone | Description | Tunnel Mode |
|
||||
|------|-------------|-------------|
|
||||
| Home User | Full network access | Full |
|
||||
| Remote Worker | Office resources only | Split |
|
||||
| Mobile Device | On-the-go access | Full |
|
||||
| IoT Device | Limited VPN-only access | Split |
|
||||
| Guest | Temporary visitor access | Full |
|
||||
| Server/Site | Site-to-site connection | Split |
|
||||
|
||||
### Wizard Flow
|
||||
|
||||
1. **Select Tunnel Type** - Choose preset (Road Warrior, Site-to-Site, IoT)
|
||||
2. **Configure Tunnel** - Set interface name, port, VPN network, public endpoint
|
||||
3. **Select Peer Zones** - Choose which peer types to create
|
||||
4. **Create** - Wizard generates keys, creates interface, adds peers, shows QR codes
|
||||
|
||||
## RPCD API
|
||||
|
||||
The dashboard communicates via `luci.wireguard-dashboard` RPCD object.
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | Parameters | Description |
|
||||
|--------|------------|-------------|
|
||||
| `status` | - | Get overall WireGuard status |
|
||||
| `interfaces` | - | List all WireGuard interfaces |
|
||||
| `peers` | - | List all peers with status |
|
||||
| `traffic` | - | Get traffic statistics |
|
||||
| `generate_keys` | - | Generate new key pair + PSK |
|
||||
| `create_interface` | name, private_key, listen_port, addresses, mtu | Create new WireGuard interface with firewall rules |
|
||||
| `add_peer` | interface, name, allowed_ips, public_key, preshared_key, endpoint, persistent_keepalive | Add peer to interface |
|
||||
| `remove_peer` | interface, public_key | Remove peer from interface |
|
||||
| `interface_control` | interface, action (up/down/restart) | Control interface state |
|
||||
| `generate_config` | interface, peer, private_key, endpoint | Generate client config file |
|
||||
| `generate_qr` | interface, peer, private_key, endpoint | Generate QR code (requires qrencode) |
|
||||
|
||||
### Example: Create Interface via CLI
|
||||
|
||||
```bash
|
||||
# Clone into OpenWrt build environment
|
||||
cd ~/openwrt/feeds/luci/applications/
|
||||
git clone https://github.com/gkerma/luci-app-wireguard-dashboard.git
|
||||
# Generate keys
|
||||
keys=$(ubus call luci.wireguard-dashboard generate_keys '{}')
|
||||
privkey=$(echo "$keys" | jsonfilter -e '@.private_key')
|
||||
|
||||
# Update feeds and install
|
||||
cd ~/openwrt
|
||||
./scripts/feeds update -a
|
||||
./scripts/feeds install -a
|
||||
|
||||
# Enable in menuconfig
|
||||
make menuconfig
|
||||
# Navigate to: LuCI > Applications > luci-app-wireguard-dashboard
|
||||
|
||||
# Build package
|
||||
make package/luci-app-wireguard-dashboard/compile V=s
|
||||
# Create interface
|
||||
ubus call luci.wireguard-dashboard create_interface "{
|
||||
\"name\": \"wg0\",
|
||||
\"private_key\": \"$privkey\",
|
||||
\"listen_port\": \"51820\",
|
||||
\"addresses\": \"10.10.0.1/24\",
|
||||
\"mtu\": \"1420\"
|
||||
}"
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
### Example: Add Peer via CLI
|
||||
|
||||
```bash
|
||||
# Transfer package to router
|
||||
scp luci-app-wireguard-dashboard_1.0.0-1_all.ipk root@192.168.1.1:/tmp/
|
||||
# Generate peer keys
|
||||
peer_keys=$(ubus call luci.wireguard-dashboard generate_keys '{}')
|
||||
peer_pubkey=$(echo "$peer_keys" | jsonfilter -e '@.public_key')
|
||||
peer_psk=$(echo "$peer_keys" | jsonfilter -e '@.preshared_key')
|
||||
|
||||
# Install on router
|
||||
ssh root@192.168.1.1
|
||||
opkg install /tmp/luci-app-wireguard-dashboard_1.0.0-1_all.ipk
|
||||
|
||||
# Restart services
|
||||
/etc/init.d/rpcd restart
|
||||
# Add peer
|
||||
ubus call luci.wireguard-dashboard add_peer "{
|
||||
\"interface\": \"wg0\",
|
||||
\"name\": \"Phone\",
|
||||
\"allowed_ips\": \"10.10.0.2/32\",
|
||||
\"public_key\": \"$peer_pubkey\",
|
||||
\"preshared_key\": \"$peer_psk\",
|
||||
\"persistent_keepalive\": \"25\"
|
||||
}"
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Firewall Integration
|
||||
|
||||
After installation, access the dashboard at:
|
||||
When creating an interface via the wizard or `create_interface` API, the following firewall rules are automatically created:
|
||||
|
||||
**VPN → WireGuard Dashboard**
|
||||
1. **Zone** (`wg_<interface>`): INPUT/OUTPUT/FORWARD = ACCEPT
|
||||
2. **Forwarding**: Bidirectional forwarding to/from `lan` zone
|
||||
3. **WAN Rule**: Allow UDP traffic on listen port from WAN
|
||||
|
||||
The dashboard has four tabs:
|
||||
1. **Status**: Overview with interfaces and active peers
|
||||
2. **Peers**: Detailed peer information and status
|
||||
3. **Traffic**: Bandwidth statistics per peer/interface
|
||||
4. **Configuration**: Config file view and tunnel visualization
|
||||
## File Locations
|
||||
|
||||
## Architecture
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `/usr/libexec/rpcd/luci.wireguard-dashboard` | RPCD backend |
|
||||
| `/www/luci-static/resources/wireguard-dashboard/api.js` | JavaScript API wrapper |
|
||||
| `/www/luci-static/resources/view/wireguard-dashboard/*.js` | LuCI views |
|
||||
| `/usr/share/luci/menu.d/luci-app-wireguard-dashboard.json` | Menu configuration |
|
||||
| `/usr/share/rpcd/acl.d/luci-app-wireguard-dashboard.json` | ACL permissions |
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ LuCI JavaScript │
|
||||
│ (status.js, peers.js, traffic.js) │
|
||||
└───────────────────────────┬─────────────────────────────┘
|
||||
│ ubus RPC
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ RPCD Backend │
|
||||
│ /usr/libexec/rpcd/wireguard-dashboard │
|
||||
└───────────────────────────┬─────────────────────────────┘
|
||||
│ executes
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ wg show │
|
||||
│ WireGuard CLI Tool │
|
||||
└───────────────────────────┬─────────────────────────────┘
|
||||
│ manages
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ WireGuard Kernel Module │
|
||||
│ Encrypted Tunnels │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
## Troubleshooting
|
||||
|
||||
### Interface not coming up
|
||||
|
||||
```bash
|
||||
# Check interface status
|
||||
wg show wg0
|
||||
|
||||
# Check UCI configuration
|
||||
uci show network.wg0
|
||||
|
||||
# Manually bring up
|
||||
ifup wg0
|
||||
|
||||
# Check logs
|
||||
logread | grep -i wireguard
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
### Peers not connecting
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `status` | Overall VPN status, interface/peer counts, total traffic |
|
||||
| `interfaces` | Detailed interface info (pubkey, port, IPs, state) |
|
||||
| `peers` | All peers with endpoint, handshake, traffic, allowed IPs |
|
||||
| `traffic` | Per-peer and per-interface RX/TX statistics |
|
||||
| `config` | Configuration display (no private keys exposed) |
|
||||
1. Verify firewall port is open: `iptables -L -n | grep 51820`
|
||||
2. Check endpoint is reachable from client
|
||||
3. Verify allowed_ips match on both ends
|
||||
4. Check for NAT issues - enable PersistentKeepalive
|
||||
|
||||
## Peer Status Indicators
|
||||
### QR codes not generating
|
||||
|
||||
| Status | Meaning | Handshake Age |
|
||||
|--------|---------|---------------|
|
||||
| 🟢 Active | Recent communication | < 3 minutes |
|
||||
| 🟡 Idle | No recent traffic | 3-10 minutes |
|
||||
| ⚪ Inactive | No handshake | > 10 minutes or never |
|
||||
Install qrencode for server-side QR generation:
|
||||
```bash
|
||||
opkg install qrencode
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- OpenWrt 21.02+
|
||||
- `kmod-wireguard` (kernel module)
|
||||
- `wireguard-tools` (wg command)
|
||||
- `luci-proto-wireguard` (optional, for LuCI config)
|
||||
- LuCI (luci-base)
|
||||
- rpcd with luci module
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `luci-base`
|
||||
- `luci-lib-jsonc`
|
||||
- `rpcd`
|
||||
- `rpcd-mod-luci`
|
||||
- `wireguard-tools`
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Private keys are **never** exposed through the dashboard
|
||||
- Only public keys and configuration are displayed
|
||||
- All data is read-only (no config modifications)
|
||||
- RPCD ACLs restrict access to authorized users
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit issues and pull requests.
|
||||
|
||||
1. Fork the repository
|
||||
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
The dashboard also supports client-side QR generation via JavaScript (no server dependency).
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
||||
Apache-2.0
|
||||
|
||||
## Credits
|
||||
## Author
|
||||
|
||||
- Powered by [WireGuard®](https://www.wireguard.com/)
|
||||
- Built for [OpenWrt](https://openwrt.org/)
|
||||
- Developed by [Gandalf @ CyberMind.fr](https://cybermind.fr)
|
||||
|
||||
## Related Projects
|
||||
|
||||
- [luci-proto-wireguard](https://github.com/openwrt/luci) - WireGuard protocol support
|
||||
- [wg-easy](https://github.com/WeeJeWel/wg-easy) - Web UI for WireGuard
|
||||
- [wireguard-ui](https://github.com/ngoduykhanh/wireguard-ui) - Another WireGuard UI
|
||||
|
||||
---
|
||||
|
||||
Made with 🔐 for secure networking
|
||||
|
||||
*WireGuard is a registered trademark of Jason A. Donenfeld.*
|
||||
CyberMind.fr - SecuBox Project
|
||||
|
||||
@ -43,8 +43,11 @@ return view.extend({
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
var peers = (data[0] || {}).peers || [];
|
||||
var interfaces = (data[1] || {}).interfaces || [];
|
||||
// Handle RPC expect unwrapping - results may be array or object with .peers/.interfaces
|
||||
var peersData = data[0] || [];
|
||||
var interfacesData = data[1] || [];
|
||||
var peers = Array.isArray(peersData) ? peersData : (peersData.peers || []);
|
||||
var interfaces = Array.isArray(interfacesData) ? interfacesData : (interfacesData.interfaces || []);
|
||||
var activePeers = peers.filter(function(p) { return p.status === 'active'; }).length;
|
||||
|
||||
var view = E('div', { 'class': 'cbi-map' }, [
|
||||
|
||||
@ -14,7 +14,9 @@ return view.extend({
|
||||
|
||||
render: function(data) {
|
||||
var status = data[0] || {};
|
||||
var interfaces = (data[1] || {}).interfaces || [];
|
||||
// Handle RPC expect unwrapping - results may be array or object
|
||||
var interfacesData = data[1] || [];
|
||||
var interfaces = Array.isArray(interfacesData) ? interfacesData : (interfacesData.interfaces || []);
|
||||
|
||||
var view = E('div', { 'class': 'cbi-map' }, [
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
||||
|
||||
@ -137,7 +137,9 @@ return view.extend({
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
var interfaces = (data[0] || {}).interfaces || [];
|
||||
// Handle RPC expect unwrapping - results may be array or object
|
||||
var interfacesData = data[0] || [];
|
||||
var interfaces = Array.isArray(interfacesData) ? interfacesData : (interfacesData.interfaces || []);
|
||||
var status = data[1] || {};
|
||||
var publicIP = data[2] || '';
|
||||
|
||||
@ -569,20 +571,33 @@ return view.extend({
|
||||
createInterface: function() {
|
||||
var self = this;
|
||||
var data = this.wizardData;
|
||||
var netmask = data.vpnNetwork.split('/')[1] || '24';
|
||||
var addresses = data.serverIP + '/' + netmask;
|
||||
|
||||
// Call backend to create interface
|
||||
return rpc.call('uci', 'add', {
|
||||
config: 'network',
|
||||
type: 'interface',
|
||||
name: data.ifaceName,
|
||||
values: {
|
||||
proto: 'wireguard',
|
||||
private_key: data.privateKey,
|
||||
listen_port: data.listenPort,
|
||||
addresses: [data.serverIP + '/' + data.vpnNetwork.split('/')[1]]
|
||||
// Call backend to create interface using proper RPCD method
|
||||
return api.createInterface(
|
||||
data.ifaceName,
|
||||
data.privateKey,
|
||||
data.listenPort,
|
||||
addresses,
|
||||
data.mtu || '1420'
|
||||
).then(function(result) {
|
||||
// Handle various response formats from RPC
|
||||
// Result could be: boolean, object with success field, or object with error
|
||||
if (result === true) {
|
||||
return { success: true };
|
||||
}
|
||||
}).then(function() {
|
||||
return rpc.call('uci', 'commit', { config: 'network' });
|
||||
if (result === false) {
|
||||
throw new Error('Failed to create interface');
|
||||
}
|
||||
if (typeof result === 'object' && result !== null) {
|
||||
if (result.success === false || result.error) {
|
||||
throw new Error(result.error || 'Failed to create interface');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// If we got here with no error, assume success
|
||||
return { success: true };
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -34,18 +34,25 @@ var callGenerateKeys = rpc.declare({
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callCreateInterface = rpc.declare({
|
||||
object: 'luci.wireguard-dashboard',
|
||||
method: 'create_interface',
|
||||
params: ['name', 'private_key', 'listen_port', 'addresses', 'mtu'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callAddPeer = rpc.declare({
|
||||
object: 'luci.wireguard-dashboard',
|
||||
method: 'add_peer',
|
||||
params: ['interface', 'name', 'allowed_ips', 'public_key', 'preshared_key', 'endpoint', 'persistent_keepalive'],
|
||||
expect: { success: false }
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callRemovePeer = rpc.declare({
|
||||
object: 'luci.wireguard-dashboard',
|
||||
method: 'remove_peer',
|
||||
params: ['interface', 'public_key'],
|
||||
expect: { success: false }
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callGetConfig = rpc.declare({
|
||||
@ -78,7 +85,7 @@ var callInterfaceControl = rpc.declare({
|
||||
object: 'luci.wireguard-dashboard',
|
||||
method: 'interface_control',
|
||||
params: ['interface', 'action'],
|
||||
expect: { success: false }
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callPeerDescriptions = rpc.declare({
|
||||
@ -154,6 +161,7 @@ return baseclass.extend({
|
||||
getConfig: callGetConfig,
|
||||
getTraffic: callGetTraffic,
|
||||
generateKeys: callGenerateKeys,
|
||||
createInterface: callCreateInterface,
|
||||
addPeer: callAddPeer,
|
||||
removePeer: callRemovePeer,
|
||||
generateConfig: callGenerateConfig,
|
||||
|
||||
@ -387,6 +387,114 @@ generate_keys() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Create a new WireGuard interface
|
||||
create_interface() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var name name
|
||||
json_get_var private_key private_key
|
||||
json_get_var listen_port listen_port
|
||||
json_get_var addresses addresses
|
||||
json_get_var mtu mtu
|
||||
|
||||
json_init
|
||||
|
||||
# Validate required fields
|
||||
if [ -z "$name" ] || [ -z "$private_key" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Missing required fields: name and private_key"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Check if interface already exists
|
||||
if uci -q get network.$name >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Interface $name already exists"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Validate private key format (base64, 44 chars ending with =)
|
||||
if ! echo "$private_key" | grep -qE '^[A-Za-z0-9+/]{43}=$'; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Invalid private key format"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Default values
|
||||
[ -z "$listen_port" ] && listen_port="51820"
|
||||
[ -z "$mtu" ] && mtu="1420"
|
||||
|
||||
# Create the interface in UCI
|
||||
uci set network.$name=interface
|
||||
uci set network.$name.proto='wireguard'
|
||||
uci set network.$name.private_key="$private_key"
|
||||
uci set network.$name.listen_port="$listen_port"
|
||||
uci set network.$name.mtu="$mtu"
|
||||
|
||||
# Set addresses (can be multiple)
|
||||
if [ -n "$addresses" ]; then
|
||||
uci delete network.$name.addresses 2>/dev/null
|
||||
for addr in $addresses; do
|
||||
uci add_list network.$name.addresses="$addr"
|
||||
done
|
||||
fi
|
||||
|
||||
# Commit changes
|
||||
if uci commit network; then
|
||||
# Create firewall zone for the interface
|
||||
local zone_name="wg_${name}"
|
||||
local zone_exists=$(uci show firewall 2>/dev/null | grep "\.name='$zone_name'" | head -1)
|
||||
|
||||
if [ -z "$zone_exists" ]; then
|
||||
# Add new firewall zone
|
||||
local zone_section=$(uci add firewall zone)
|
||||
uci set firewall.$zone_section.name="$zone_name"
|
||||
uci set firewall.$zone_section.input='ACCEPT'
|
||||
uci set firewall.$zone_section.output='ACCEPT'
|
||||
uci set firewall.$zone_section.forward='ACCEPT'
|
||||
uci add_list firewall.$zone_section.network="$name"
|
||||
|
||||
# Add forwarding to lan
|
||||
local fwd_section=$(uci add firewall forwarding)
|
||||
uci set firewall.$fwd_section.src="$zone_name"
|
||||
uci set firewall.$fwd_section.dest='lan'
|
||||
|
||||
# Add forwarding from lan
|
||||
local fwd2_section=$(uci add firewall forwarding)
|
||||
uci set firewall.$fwd2_section.src='lan'
|
||||
uci set firewall.$fwd2_section.dest="$zone_name"
|
||||
|
||||
# Add rule to allow WireGuard port from wan
|
||||
local rule_section=$(uci add firewall rule)
|
||||
uci set firewall.$rule_section.name="Allow-WireGuard-$name"
|
||||
uci set firewall.$rule_section.src='wan'
|
||||
uci set firewall.$rule_section.dest_port="$listen_port"
|
||||
uci set firewall.$rule_section.proto='udp'
|
||||
uci set firewall.$rule_section.target='ACCEPT'
|
||||
|
||||
uci commit firewall
|
||||
fi
|
||||
|
||||
# Bring up the interface
|
||||
ifup "$name" 2>/dev/null &
|
||||
|
||||
# Reload firewall
|
||||
/etc/init.d/firewall reload 2>/dev/null &
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Interface $name created successfully"
|
||||
json_add_string "interface" "$name"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to commit network configuration"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Add a new peer to interface
|
||||
add_peer() {
|
||||
read input
|
||||
@ -893,7 +1001,7 @@ get_bandwidth_rates() {
|
||||
# Main dispatcher
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"},"interface_control":{"interface":"str","action":"str"},"peer_descriptions":{},"bandwidth_rates":{}}'
|
||||
echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"create_interface":{"name":"str","private_key":"str","listen_port":"str","addresses":"str","mtu":"str"},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"},"interface_control":{"interface":"str","action":"str"},"peer_descriptions":{},"bandwidth_rates":{}}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
@ -915,6 +1023,9 @@ case "$1" in
|
||||
generate_keys)
|
||||
generate_keys
|
||||
;;
|
||||
create_interface)
|
||||
create_interface
|
||||
;;
|
||||
add_peer)
|
||||
add_peer
|
||||
;;
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"ubus": {
|
||||
"luci.wireguard-dashboard": [
|
||||
"generate_keys",
|
||||
"create_interface",
|
||||
"add_peer",
|
||||
"remove_peer",
|
||||
"generate_config",
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
# Main orchestration service for SecuBox framework
|
||||
#
|
||||
|
||||
set -e
|
||||
# Note: Not using set -e as many UCI/service checks legitimately return non-zero
|
||||
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
@ -18,7 +18,57 @@ WATCHDOG_STATE="/var/run/secubox/watchdog.json"
|
||||
|
||||
# Services to monitor (init.d name:check_method:restart_delay)
|
||||
# check_method: pid, docker, lxc, port:PORT
|
||||
MONITORED_SERVICES="haproxy:pid:5 crowdsec:pid:10 tor:pid:10"
|
||||
MONITORED_SERVICES=""
|
||||
|
||||
# Auto-discover SecuBox services from ctl scripts
|
||||
discover_secubox_services() {
|
||||
local services=""
|
||||
|
||||
# Discover LXC-based services from *ctl scripts
|
||||
for ctl in /usr/sbin/*ctl; do
|
||||
[ -x "$ctl" ] || continue
|
||||
local basename=$(basename "$ctl")
|
||||
|
||||
# Extract service name from xxxctl pattern
|
||||
local svc_name=""
|
||||
case "$basename" in
|
||||
haproxyctl)
|
||||
svc_name="haproxy"
|
||||
services="$services haproxy:lxc:5"
|
||||
;;
|
||||
lyrionctl)
|
||||
svc_name="lyrion"
|
||||
services="$services lyrion:lxc:10"
|
||||
;;
|
||||
mitmproxyctl)
|
||||
svc_name="mitmproxy"
|
||||
services="$services mitmproxy:lxc:10"
|
||||
;;
|
||||
metablogizerctl)
|
||||
svc_name="metablogizer"
|
||||
services="$services metablogizer:lxc:10"
|
||||
;;
|
||||
hexojsctl)
|
||||
svc_name="hexojs"
|
||||
services="$services hexojs:lxc:10"
|
||||
;;
|
||||
adguardhomectl)
|
||||
svc_name="adguardhome"
|
||||
services="$services adguardhome:docker:10"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Add native services (PID-based)
|
||||
if [ -x "/etc/init.d/crowdsec" ]; then
|
||||
services="$services crowdsec:pid:10"
|
||||
fi
|
||||
if [ -x "/etc/init.d/tor" ]; then
|
||||
services="$services tor:pid:10"
|
||||
fi
|
||||
|
||||
echo "$services"
|
||||
}
|
||||
|
||||
# Logging function
|
||||
log() {
|
||||
@ -172,10 +222,14 @@ run_watchdog() {
|
||||
local watchdog_enabled=$(uci -q get secubox.main.watchdog_enabled || echo "1")
|
||||
[ "$watchdog_enabled" != "1" ] && return 0
|
||||
|
||||
# Get monitored services from UCI or use defaults
|
||||
local services=$(uci -q get secubox.main.watchdog_services || echo "$MONITORED_SERVICES")
|
||||
# Auto-discover services if none configured
|
||||
local services=$(uci -q get secubox.main.watchdog_services)
|
||||
[ -z "$services" ] && services=$(discover_secubox_services)
|
||||
|
||||
local restart_count=0
|
||||
local status_json=""
|
||||
local checked_count=0
|
||||
local running_count=0
|
||||
local services_status=""
|
||||
|
||||
log debug "Watchdog: Checking services..."
|
||||
|
||||
@ -184,66 +238,105 @@ run_watchdog() {
|
||||
local check_method=$(echo "$service_entry" | cut -d: -f2)
|
||||
local restart_delay=$(echo "$service_entry" | cut -d: -f3)
|
||||
[ -z "$restart_delay" ] && restart_delay=5
|
||||
[ -z "$check_method" ] && check_method="pid"
|
||||
|
||||
# Check if service init script exists
|
||||
[ ! -x "/etc/init.d/$service_name" ] && continue
|
||||
# Determine if service is enabled (check ctl script or init.d)
|
||||
local ctl_script="/usr/sbin/${service_name}ctl"
|
||||
local init_script="/etc/init.d/$service_name"
|
||||
local is_enabled=false
|
||||
|
||||
# Check if service is supposed to be enabled
|
||||
/etc/init.d/$service_name enabled >/dev/null 2>&1 || continue
|
||||
if [ -x "$ctl_script" ]; then
|
||||
# Check via UCI for LXC/Docker services
|
||||
local uci_enabled=$(uci -q get "$service_name.main.enabled" 2>/dev/null || echo "0")
|
||||
[ "$uci_enabled" = "1" ] && is_enabled=true
|
||||
elif [ -x "$init_script" ]; then
|
||||
$init_script enabled >/dev/null 2>&1 && is_enabled=true
|
||||
fi
|
||||
|
||||
# Skip disabled services
|
||||
[ "$is_enabled" = "false" ] && continue
|
||||
|
||||
checked_count=$((checked_count + 1))
|
||||
local is_running=false
|
||||
local status_detail=""
|
||||
|
||||
case "$check_method" in
|
||||
pid)
|
||||
# Check via pidof or pgrep
|
||||
# Check via pgrep
|
||||
if pgrep "$service_name" >/dev/null 2>&1; then
|
||||
is_running=true
|
||||
status_detail="pid=$(pgrep -o "$service_name")"
|
||||
fi
|
||||
;;
|
||||
docker)
|
||||
# Check Docker container
|
||||
if docker ps --filter "name=$service_name" --format "{{.Names}}" 2>/dev/null | grep -q "$service_name"; then
|
||||
# Check Docker container (secbx- prefix)
|
||||
local container_name="secbx-${service_name}"
|
||||
if docker ps --filter "name=$container_name" --format "{{.Names}}" 2>/dev/null | grep -q "$container_name"; then
|
||||
is_running=true
|
||||
status_detail="container=$container_name"
|
||||
fi
|
||||
;;
|
||||
lxc)
|
||||
# Check LXC container
|
||||
if lxc-info -n "$service_name" -s 2>/dev/null | grep -q "RUNNING"; then
|
||||
is_running=true
|
||||
# Get container IP if available
|
||||
local lxc_ip=$(lxc-info -n "$service_name" -i 2>/dev/null | awk '{print $2}' | head -1)
|
||||
[ -n "$lxc_ip" ] && status_detail="ip=$lxc_ip"
|
||||
fi
|
||||
;;
|
||||
port:*)
|
||||
# Check if port is listening
|
||||
local port=$(echo "$check_method" | cut -d: -f2)
|
||||
# Use /proc/net/tcp (ports in hex)
|
||||
local port_hex=$(printf '%04X' "$port")
|
||||
if grep -q ":$port_hex " /proc/net/tcp /proc/net/tcp6 2>/dev/null; then
|
||||
is_running=true
|
||||
status_detail="port=$port"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$is_running" = "false" ]; then
|
||||
if [ "$is_running" = "true" ]; then
|
||||
running_count=$((running_count + 1))
|
||||
services_status="$services_status ${service_name}:ok"
|
||||
else
|
||||
services_status="$services_status ${service_name}:down"
|
||||
log warn "Watchdog: $service_name is down, restarting..."
|
||||
sleep "$restart_delay"
|
||||
|
||||
# Double-check before restart (service might have recovered)
|
||||
case "$check_method" in
|
||||
pid) pgrep "$service_name" >/dev/null 2>&1 && continue ;;
|
||||
pid) pgrep "$service_name" >/dev/null 2>&1 && { running_count=$((running_count + 1)); continue; } ;;
|
||||
lxc) lxc-info -n "$service_name" -s 2>/dev/null | grep -q "RUNNING" && { running_count=$((running_count + 1)); continue; } ;;
|
||||
esac
|
||||
|
||||
/etc/init.d/$service_name restart >/dev/null 2>&1
|
||||
# Restart using ctl script if available, otherwise init.d
|
||||
if [ -x "$ctl_script" ]; then
|
||||
$ctl_script restart >/dev/null 2>&1 &
|
||||
elif [ -x "$init_script" ]; then
|
||||
$init_script restart >/dev/null 2>&1 &
|
||||
fi
|
||||
restart_count=$((restart_count + 1))
|
||||
|
||||
# Log restart event
|
||||
log info "Watchdog: Restarted $service_name"
|
||||
fi
|
||||
done
|
||||
|
||||
# Save watchdog state
|
||||
# Save detailed watchdog state
|
||||
json_init
|
||||
json_add_string "last_check" "$(date -Iseconds)"
|
||||
json_add_int "restarts" "$restart_count"
|
||||
json_add_int "checked" "$checked_count"
|
||||
json_add_int "running" "$running_count"
|
||||
|
||||
json_add_object "services"
|
||||
for svc_status in $services_status; do
|
||||
local svc=$(echo "$svc_status" | cut -d: -f1)
|
||||
local status=$(echo "$svc_status" | cut -d: -f2)
|
||||
json_add_string "$svc" "$status"
|
||||
done
|
||||
json_close_object
|
||||
|
||||
json_dump > "$WATCHDOG_STATE" 2>/dev/null
|
||||
|
||||
return 0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user