🐶 Sync 2025-11-02 14:26:26

This commit is contained in:
actions-user
2025-11-02 14:26:26 +08:00
parent 64bcc56c2a
commit ac011db799
1557 changed files with 746465 additions and 0 deletions

30
nikki/files/mixin.yaml Normal file
View File

@@ -0,0 +1,30 @@
# Mixin File
# You can set any mihomo profile's config at here, it will mixin to the profile.
# Mixin file have lower priority than the LuCI mixin options.
#
# Mihomo's Wiki: https://wiki.metacubex.one
#
# For example:
#
# experimental: # experimental config
# dialer-ip4p-convert: false # IP4P support
# listeners: # overwrite listeners
# - name: shadowsocks
# type: shadowsocks
# listen: "::"
# port: 12060
# nikki-proxies: # prepend proxies
# - name: PROXY
# type: ss
# server: proxy.example.com
# port: 443
# cipher: chacha20-ietf-poly1305
# password: password
# nikki-proxy-groups: # prepend proxy groups
# - name: PROXY_GROUP
# type: select
# proxies:
# - PROXY
# nikki-rules: # prepend rules
# - DOMAIN,direct.example.com,DIRECT
# - DOMAIN-SUFFIX,proxy.example.com,PROXY

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

224
nikki/files/nikki.conf Normal file
View File

@@ -0,0 +1,224 @@
config status 'status'
config config 'config'
option 'init' '1'
option 'enabled' '0'
option 'profile' 'subscription:subscription'
option 'start_delay' '0'
option 'scheduled_restart' '0'
option 'cron_expression' '0 3 * * *'
option 'test_profile' '1'
option 'core_only' '0'
config procd 'procd'
option 'fast_reload' '0'
option 'env_disable_loopback_detector' '0'
option 'env_disable_quic_go_gso' '0'
option 'env_disable_quic_go_ecn' '0'
option 'env_skip_system_ipv6_check' '0'
config subscription 'subscription'
option 'name' 'default'
option 'url' 'http://example.com/default.yaml'
option 'user_agent' 'clash'
option 'prefer' 'remote'
config mixin 'mixin'
option 'log_level' 'warning'
option 'mode' 'rule'
option 'match_process' 'off'
option 'ipv6' '1'
option 'ui_url' 'https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip'
option 'api_listen' '[::]:9090'
option 'selection_cache' '1'
option 'allow_lan' '1'
option 'http_port' '8080'
option 'socks_port' '1080'
option 'mixed_port' '7890'
option 'redir_port' '7891'
option 'tproxy_port' '7892'
option 'authentication' '1'
option 'tun_enabled' '1'
option 'tun_device' 'nikki'
option 'tun_stack' 'mixed'
option 'tun_dns_hijack' '0'
list 'tun_dns_hijacks' 'tcp://any:53'
list 'tun_dns_hijacks' 'udp://any:53'
option 'dns_enabled' '1'
option 'dns_listen' '[::]:1053'
option 'dns_ipv6' '1'
option 'dns_mode' 'fake-ip'
option 'fake_ip_range' '198.18.0.1/16'
option 'fake_ip_filter' '0'
list 'fake_ip_filters' '+.lan'
list 'fake_ip_filters' '+.local'
option 'fake_ip_cache' '1'
option 'hosts' '0'
option 'dns_nameserver' '0'
option 'dns_nameserver_policy' '0'
option 'sniffer_force_domain_name' '0'
option 'sniffer_ignore_domain_name' '0'
option 'sniffer_sniff' '0'
option 'rule' '0'
option 'rule_provider' '0'
option 'mixin_file_content' '0'
config authentication
option 'enabled' '1'
option 'username' 'nikki'
option 'password' ''
config hosts
option 'enabled' '0'
option 'domain_name' 'localhost'
list 'ip' '127.0.0.1'
list 'ip' '::1'
config nameserver
option 'enabled' '1'
option 'type' 'default-nameserver'
list 'nameserver' '223.5.5.5'
list 'nameserver' '223.6.6.6'
config nameserver
option 'enabled' '0'
option 'type' 'proxy-server-nameserver'
list 'nameserver' 'https://223.5.5.5/dns-query'
list 'nameserver' 'https://223.6.6.6/dns-query'
config nameserver
option 'enabled' '0'
option 'type' 'direct-nameserver'
list 'nameserver' 'https://223.5.5.5/dns-query'
list 'nameserver' 'https://223.6.6.6/dns-query'
config nameserver
option 'enabled' '1'
option 'type' 'nameserver'
list 'nameserver' 'https://223.5.5.5/dns-query'
list 'nameserver' 'https://223.6.6.6/dns-query'
config nameserver_policy
option 'enabled' '1'
option 'matcher' 'geosite:private,cn'
list 'nameserver' 'https://223.5.5.5/dns-query'
list 'nameserver' 'https://223.6.6.6/dns-query'
config nameserver_policy
option 'enabled' '1'
option 'matcher' 'geosite:geolocation-!cn'
list 'nameserver' 'https://1.1.1.1/dns-query'
list 'nameserver' 'https://8.8.8.8/dns-query'
config sniff
option 'enabled' '1'
option 'protocol' 'HTTP'
list 'port' '80'
list 'port' '8080'
option 'overwrite_destination' '1'
config sniff
option 'enabled' '1'
option 'protocol' 'TLS'
list 'port' '443'
list 'port' '8443'
option 'overwrite_destination' '1'
config sniff
option 'enabled' '1'
option 'protocol' 'QUIC'
list 'port' '443'
list 'port' '8443'
option 'overwrite_destination' '1'
config proxy 'proxy'
option 'enabled' '1'
option 'tcp_mode' 'redirect'
option 'udp_mode' 'tun'
option 'ipv4_dns_hijack' '1'
option 'ipv6_dns_hijack' '1'
option 'ipv4_proxy' '1'
option 'ipv6_proxy' '1'
option 'fake_ip_ping_hijack' '1'
option 'router_proxy' '1'
option 'lan_proxy' '1'
list 'lan_inbound_interface' 'lan'
list 'reserved_ip' '0.0.0.0/8'
list 'reserved_ip' '10.0.0.0/8'
list 'reserved_ip' '127.0.0.0/8'
list 'reserved_ip' '100.64.0.0/10'
list 'reserved_ip' '169.254.0.0/16'
list 'reserved_ip' '172.16.0.0/12'
list 'reserved_ip' '192.168.0.0/16'
list 'reserved_ip' '224.0.0.0/4'
list 'reserved_ip' '240.0.0.0/4'
list 'reserved_ip6' '::/128'
list 'reserved_ip6' '::1/128'
list 'reserved_ip6' '::ffff:0:0/96'
list 'reserved_ip6' '100::/64'
list 'reserved_ip6' '64:ff9b::/96'
list 'reserved_ip6' '2001::/32'
list 'reserved_ip6' '2001:10::/28'
list 'reserved_ip6' '2001:20::/28'
list 'reserved_ip6' '2001:db8::/32'
list 'reserved_ip6' '2002::/16'
list 'reserved_ip6' 'fc00::/7'
list 'reserved_ip6' 'fe80::/10'
list 'reserved_ip6' 'ff00::/8'
list 'bypass_dscp' '4'
option 'bypass_china_mainland_ip' '0'
option 'bypass_china_mainland_ip6' '0'
option 'proxy_tcp_dport' '0-65535'
option 'proxy_udp_dport' '0-65535'
option 'tun_timeout' '30'
option 'tun_interval' '1'
config router_access_control
option 'enabled' '1'
list 'user' 'dnsmasq'
list 'user' 'ftp'
list 'user' 'logd'
list 'user' 'nobody'
list 'user' 'ntp'
list 'user' 'ubus'
list 'group' 'dnsmasq'
list 'group' 'ftp'
list 'group' 'logd'
list 'group' 'nogroup'
list 'group' 'ntp'
list 'group' 'ubus'
list 'cgroup' 'services/adguardhome'
list 'cgroup' 'services/aria2'
list 'cgroup' 'services/dnsmasq'
list 'cgroup' 'services/netbird'
list 'cgroup' 'services/qbittorrent'
list 'cgroup' 'services/sysntpd'
list 'cgroup' 'services/tailscale'
list 'cgroup' 'services/zerotier'
option 'proxy' '0'
config router_access_control
option 'enabled' '1'
option 'dns' '1'
option 'proxy' '1'
config lan_access_control
option 'enabled' '1'
option 'dns' '1'
option 'proxy' '1'
config routing 'routing'
option 'tproxy_fw_mark' '0x80'
option 'tproxy_fw_mask' '0xFF'
option 'tun_fw_mark' '0x81'
option 'tun_fw_mask' '0xFF'
option 'tproxy_rule_pref' '1024'
option 'tun_rule_pref' '1025'
option 'tproxy_route_table' '80'
option 'tun_route_table' '81'
option 'cgroup_id' '0x12061206'
option 'cgroup_name' 'nikki'
config editor 'editor'
config log 'log'

553
nikki/files/nikki.init Normal file
View File

@@ -0,0 +1,553 @@
#!/bin/sh /etc/rc.common
START=99
STOP=10
USE_PROCD=1
PROG="/usr/bin/mihomo"
. "$IPKG_INSTROOT/etc/nikki/scripts/include.sh"
extra_command 'update_subscription' 'Update subscription by section id'
boot_func() {
# prepare files
prepare_files
# load config
config_load nikki
# start delay
local enabled start_delay
config_get_bool enabled "config" "enabled" 0
config_get start_delay "config" "start_delay" 0
if [ "$enabled" = 1 ] && [ "$start_delay" -gt 0 ]; then
log "App" "Start after $start_delay seconds."
sleep "$start_delay"
fi
# start
start
}
boot() {
boot_func &
}
start_service() {
# prepare files
prepare_files
# load config
config_load nikki
# check if enabled
local enabled
config_get_bool enabled "config" "enabled" 0
if [ "$enabled" = 0 ]; then
log "App" "Disabled."
log "App" "Exit."
return
fi
# start
log "App" "Enabled."
log "App" "Start."
# check if executable
if [ ! -x $PROG ]; then
log "App" "Core is not executable."
log "App" "Exit."
return
fi
# get config
## app config
local scheduled_restart cron_expression profile test_profile fast_reload core_only
config_get_bool scheduled_restart "config" "scheduled_restart" 0
config_get cron_expression "config" "cron_expression"
config_get profile "config" "profile"
config_get_bool test_profile "config" "test_profile" 0
config_get_bool core_only "config" "core_only" 0
## procd config
### fast reload
config_get_bool fast_reload "procd" "fast_reload" 0
### rlimit
local rlimit_address_space_soft rlimit_address_space_hard rlimit_data_soft rlimit_data_hard rlimit_stack_soft rlimit_stack_hard rlimit_nofile_soft rlimit_nofile_hard
config_get rlimit_address_space_soft "procd" "rlimit_address_space_soft"
config_get rlimit_address_space_hard "procd" "rlimit_address_space_hard"
config_get rlimit_data_soft "procd" "rlimit_data_soft"
config_get rlimit_data_hard "procd" "rlimit_data_hard"
config_get rlimit_stack_soft "procd" "rlimit_stack_soft"
config_get rlimit_stack_hard "procd" "rlimit_stack_hard"
config_get rlimit_nofile_soft "procd" "rlimit_nofile_soft"
config_get rlimit_nofile_hard "procd" "rlimit_nofile_hard"
### environment variable
local env_safe_paths env_disable_loopback_detector env_disable_quic_go_gso env_disable_quic_go_ecn env_skip_system_ipv6_check
config_get env_safe_paths "procd" "env_safe_paths"
config_get_bool env_disable_loopback_detector "procd" "env_disable_loopback_detector" 0
config_get_bool env_disable_quic_go_gso "procd" "env_disable_quic_go_gso" 0
config_get_bool env_disable_quic_go_ecn "procd" "env_disable_quic_go_ecn" 0
config_get_bool env_skip_system_ipv6_check "procd" "env_skip_system_ipv6_check" 0
## mixin config
### overwrite
local overwrite_authentication overwrite_tun_dns_hijack overwrite_fake_ip_filter overwrite_hosts overwrite_dns_nameserver overwrite_dns_nameserver_policy overwrite_sniffer_sniff overwrite_sniffer_force_domain_name overwrite_sniffer_ignore_domain_name
config_get_bool overwrite_authentication "mixin" "authentication" 0
config_get_bool overwrite_tun_dns_hijack "mixin" "tun_dns_hijack" 0
config_get_bool overwrite_fake_ip_filter "mixin" "fake_ip_filter" 0
config_get_bool overwrite_hosts "mixin" "hosts" 0
config_get_bool overwrite_dns_nameserver "mixin" "dns_nameserver" 0
config_get_bool overwrite_dns_nameserver_policy "mixin" "dns_nameserver_policy" 0
config_get_bool overwrite_sniffer_force_domain_name "mixin" "sniffer_force_domain_name" 0
config_get_bool overwrite_sniffer_ignore_domain_name "mixin" "sniffer_ignore_domain_name" 0
config_get_bool overwrite_sniffer_sniff "mixin" "sniffer_sniff" 0
### mixin file content
local mixin_file_content
config_get_bool mixin_file_content "mixin" "mixin_file_content" 0
## proxy config
local proxy_enabled ipv4_dns_hijack ipv6_dns_hijack tcp_mode udp_mode
config_get_bool proxy_enabled "proxy" "enabled" 0
config_get_bool ipv4_dns_hijack "proxy" "ipv4_dns_hijack" 0
config_get_bool ipv6_dns_hijack "proxy" "ipv6_dns_hijack" 0
config_get tcp_mode "proxy" "tcp_mode"
config_get udp_mode "proxy" "udp_mode"
# get profile
local profile_type; profile_type=$(echo "$profile" | cut -d ':' -f 1)
local profile_id; profile_id=$(echo "$profile" | cut -d ':' -f 2)
if [ "$profile_type" = "file" ]; then
local profile_name; profile_name="$profile_id"
local profile_file; profile_file="$PROFILES_DIR/$profile_name"
log "Profile" "Use file: $profile_name."
if [ ! -f "$profile_file" ]; then
log "Profile" "File not found."
log "App" "Exit."
return
fi
cp -f "$profile_file" "$RUN_PROFILE_PATH"
elif [ "$profile_type" = "subscription" ]; then
local subscription_section; subscription_section="$profile_id"
local subscription_name subscription_prefer
config_get subscription_name "$subscription_section" "name"
config_get subscription_prefer "$subscription_section" "prefer" "remote"
log "Profile" "Use subscription: $subscription_name."
local subscription_file; subscription_file="$SUBSCRIPTIONS_DIR/$subscription_section.yaml"
if [ "$subscription_prefer" = "remote" ] || [ ! -f "$subscription_file" ]; then
update_subscription "$subscription_section"
fi
if [ ! -f "$subscription_file" ]; then
log "Profile" "Subscription file not found."
log "App" "Exit."
return
fi
cp -f "$subscription_file" "$RUN_PROFILE_PATH"
else
log "Profile" "No profile/subscription selected."
log "App" "Exit."
return
fi
# mixin
if [ "$core_only" = 0 ]; then
log "Mixin" "Mixin config."
if [ "$overwrite_authentication" = 1 ]; then
yq -M -i 'del(.authentication)' "$RUN_PROFILE_PATH"
fi
if [ "$overwrite_tun_dns_hijack" = 1 ]; then
yq -M -i 'del(.tun.dns-hijack)' "$RUN_PROFILE_PATH"
fi
if [ "$overwrite_fake_ip_filter" = 1 ]; then
yq -M -i 'del(.dns.fake-ip-filter)' "$RUN_PROFILE_PATH"
fi
if [ "$overwrite_hosts" = 1 ]; then
yq -M -i 'del(.hosts)' "$RUN_PROFILE_PATH"
fi
if [ "$overwrite_dns_nameserver" = 1 ]; then
yq -M -i 'del(.dns.default-nameserver) | del(.dns.proxy-server-nameserver) | del(.dns.direct-nameserver) | del(.dns.nameserver) | del(.dns.fallback) ' "$RUN_PROFILE_PATH"
fi
if [ "$overwrite_dns_nameserver_policy" = 1 ]; then
yq -M -i 'del(.dns.nameserver-policy)' "$RUN_PROFILE_PATH"
fi
if [ "$overwrite_sniffer_force_domain_name" = 1 ]; then
yq -M -i 'del(.sniffer.force-domain)' "$RUN_PROFILE_PATH"
fi
if [ "$overwrite_sniffer_ignore_domain_name" = 1 ]; then
yq -M -i 'del(.sniffer.skip-domain)' "$RUN_PROFILE_PATH"
fi
if [ "$overwrite_sniffer_sniff" = 1 ]; then
yq -M -i 'del(.sniffer.sniff)' "$RUN_PROFILE_PATH"
fi
if [ "$mixin_file_content" = 0 ]; then
ucode -S "$MIXIN_UC" | yq -M -p json -o yaml | yq -M -i ea '... comments="" | . as $item ireduce ({}; . * $item ) | .proxies = .nikki-proxies + .proxies | del(.nikki-proxies) | .proxy-groups = .nikki-proxy-groups + .proxy-groups | del(.nikki-proxy-groups) | .rules = .nikki-rules + .rules | del(.nikki-rules)' "$RUN_PROFILE_PATH" -
elif [ "$mixin_file_content" = 1 ]; then
ucode -S "$MIXIN_UC" | yq -M -p json -o yaml | yq -M -i ea '... comments="" | . as $item ireduce ({}; . * $item ) | .proxies = .nikki-proxies + .proxies | del(.nikki-proxies) | .proxy-groups = .nikki-proxy-groups + .proxy-groups | del(.nikki-proxy-groups) | .rules = .nikki-rules + .rules | del(.nikki-rules)' "$RUN_PROFILE_PATH" "$MIXIN_FILE_PATH" -
fi
fi
# check profile
if [ "$core_only" = 0 ] && [ "$proxy_enabled" = 1 ]; then
log "Profile" "Checking..."
if [ "$ipv4_dns_hijack" = 1 ] || [ "$ipv6_dns_hijack" = 1 ]; then
if yq -M -e '(has("dns") and (.dns | .enable) and (.dns | has("listen"))) | not' "$RUN_PROFILE_PATH" > /dev/null 2>&1; then
log "Profile" "Check failed."
log "Profile" "DNS should be enabled and listen should be defined."
log "App" "Exit."
return
fi
fi
if [ "$tcp_mode" = "redirect" ]; then
if yq -M -e '(has("redir-port")) | not' "$RUN_PROFILE_PATH" > /dev/null 2>&1; then
log "Profile" "Check failed."
log "Profile" "Redirect Port should be defined."
log "App" "Exit."
return
fi
fi
if [ "$tcp_mode" = "tproxy" ] || [ "$udp_mode" = "tproxy" ]; then
if yq -M -e '(has("tproxy-port")) | not' "$RUN_PROFILE_PATH" > /dev/null 2>&1; then
log "Profile" "Check failed."
log "Profile" "TPROXY Port should be defined."
log "App" "Exit."
return
fi
fi
if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then
if yq -M -e '(has("tun") and (.tun | .enable) and (.tun | has("device"))) | not' "$RUN_PROFILE_PATH" > /dev/null 2>&1; then
log "Profile" "Check failed."
log "Profile" "TUN should be enabled and device should be defined."
log "App" "Exit."
return
fi
fi
log "Profile" "Check passed."
fi
# test profile
if [ "$test_profile" = 1 ]; then
log "Profile" "Testing..."
if $PROG -d "$RUN_DIR" -t >> "$CORE_LOG_PATH" 2>&1; then
log "Profile" "Test passed."
else
log "Profile" "Test failed."
log "Profile" "Please check the core log to find out the problem."
log "App" "Exit."
return
fi
fi
# start core
log "Core" "Start."
procd_open_instance nikki
procd_set_param command /bin/sh -c "$PROG -d $RUN_DIR >> $CORE_LOG_PATH 2>&1"
procd_set_param file "$RUN_PROFILE_PATH"
procd_append_param env SAFE_PATHS="$env_safe_paths"
procd_append_param env DISABLE_LOOPBACK_DETECTOR="$env_disable_loopback_detector"
procd_append_param env QUIC_GO_DISABLE_GSO="$env_disable_quic_go_gso"
procd_append_param env QUIC_GO_DISABLE_ECN="$env_disable_quic_go_ecn"
procd_append_param env SKIP_SYSTEM_IPV6_CHECK="$env_skip_system_ipv6_check"
if [ "$fast_reload" = 1 ]; then
procd_set_param reload_signal HUP
fi
procd_set_param pidfile "$PID_FILE_PATH"
procd_set_param respawn
procd_append_param limits as="${rlimit_address_space_soft:-unlimited} ${rlimit_address_space_hard:-unlimited}"
procd_append_param limits data="${rlimit_data_soft:-unlimited} ${rlimit_data_hard:-unlimited}"
procd_append_param limits stack="${rlimit_stack_soft:-unlimited} ${rlimit_stack_hard:-unlimited}"
procd_append_param limits nofile="${rlimit_nofile_soft:-unlimited} ${rlimit_nofile_hard:-unlimited}"
procd_close_instance
# cron
if [ "$scheduled_restart" = 1 ] && [ -n "$cron_expression" ]; then
log "App" "Set scheduled restart."
echo "$cron_expression /etc/init.d/nikki restart #nikki" >> "/etc/crontabs/root"
/etc/init.d/cron restart
fi
# set started flag
touch "$STARTED_FLAG_PATH"
}
service_started() {
# check if started
if [ ! -f "$STARTED_FLAG_PATH" ]; then
return
fi
# load config
config_load nikki
# check if proxy enabled
local proxy_enabled
config_get_bool proxy_enabled "proxy" "enabled" 0
if [ "$proxy_enabled" = 0 ]; then
log "Proxy" "Disabled."
return
fi
# get config
## app config
local core_only
config_get_bool core_only "config" "core_only" 0
## proxy config
### general
local tcp_mode udp_mode ipv4_proxy ipv6_proxy tun_timeout tun_interval
config_get tcp_mode "proxy" "tcp_mode"
config_get udp_mode "proxy" "udp_mode"
config_get_bool ipv4_proxy "proxy" "ipv4_proxy" 0
config_get_bool ipv6_proxy "proxy" "ipv6_proxy" 0
config_get tun_timeout "proxy" "tun_timeout" 30
config_get tun_interval "proxy" "tun_interval" 1
## routing config
local tproxy_fw_mark tproxy_fw_mask tun_fw_mark tun_fw_mask tproxy_rule_pref tun_rule_pref tproxy_route_table tun_route_table cgroup_id cgroup_name
config_get tproxy_fw_mark "routing" "tproxy_fw_mark" "0x80"
config_get tproxy_fw_mask "routing" "tproxy_fw_mask" "0xFF"
config_get tun_fw_mark "routing" "tun_fw_mark" "0x81"
config_get tun_fw_mask "routing" "tun_fw_mask" "0xFF"
config_get tproxy_rule_pref "routing" "tproxy_rule_pref" "1024"
config_get tun_rule_pref "routing" "tun_rule_pref" "1025"
config_get tproxy_route_table "routing" "tproxy_route_table" "80"
config_get tun_route_table "routing" "tun_route_table" "81"
config_get cgroup_id "routing" "cgroup_id" "0x12061206"
config_get cgroup_name "routing" "cgroup_name" "nikki"
# prepare config
local tproxy_enable; tproxy_enable=0
if [ "$tcp_mode" = "tproxy" ] || [ "$udp_mode" = "tproxy" ]; then
tproxy_enable=1
fi
local tun_enable; tun_enable=0
if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then
tun_enable=1
fi
local tun_device; tun_device=$(yq -M '.tun.device' "$RUN_PROFILE_PATH")
if [ "$core_only" = 0 ]; then
# proxy
log "Proxy" "Enabled."
# wait for tun device online
if [ "$tun_enable" = 1 ]; then
log "Proxy" "Waiting for tun device online within $tun_timeout seconds..."
while [ "$tun_timeout" -gt 0 ]; do
if ip -j link show dev "$tun_device" 2>/dev/null | jsonfilter -q -e "@[@['flags'][@='UP']]" > /dev/null 2>&1; then
log "Proxy" "TUN device is online."
break
fi
tun_timeout=$((tun_timeout - tun_interval))
sleep "$tun_interval"
done
if [ "$tun_timeout" -le 0 ]; then
log "Proxy" "Timeout, TUN device is not online."
log "App" "Exit."
return
fi
fi
# fix compatible with dockerd
## cgroupfs-mount
### when cgroupfs-mount is installed, cgroupv1 will mounted instead of cgroupv2, we need to create cgroup manually
if mount | grep -q -w "^cgroup"; then
mkdir -p "/sys/fs/cgroup/net_cls/$cgroup_name"
echo "$cgroup_id" > "/sys/fs/cgroup/net_cls/$cgroup_name/net_cls.classid"
cat "$PID_FILE_PATH" > "/sys/fs/cgroup/net_cls/$cgroup_name/cgroup.procs"
fi
## kmod-br-netfilter
### when kmod-br-netfilter is loaded, bridge-nf-call-iptables and bridge-nf-call-ip6tables are set to 1, we need to set them to 0 if tproxy is enabled
if [ "$tproxy_enable" = 1 ] && (lsmod | grep -q br_netfilter); then
if [ "$ipv4_proxy" = 1 ]; then
local bridge_nf_call_iptables; bridge_nf_call_iptables=$(sysctl -e -n net.bridge.bridge-nf-call-iptables)
if [ "$bridge_nf_call_iptables" = 1 ]; then
touch "$BRIDGE_NF_CALL_IPTABLES_FLAG_PATH"
sysctl -q -w net.bridge.bridge-nf-call-iptables=0
fi
fi
if [ "$ipv6_proxy" = 1 ]; then
local bridge_nf_call_ip6tables; bridge_nf_call_ip6tables=$(sysctl -e -n net.bridge.bridge-nf-call-ip6tables)
if [ "$bridge_nf_call_ip6tables" = 1 ]; then
touch "$BRIDGE_NF_CALL_IP6TABLES_FLAG_PATH"
sysctl -q -w net.bridge.bridge-nf-call-ip6tables=0
fi
fi
fi
# ip route and rule
if [ "$tproxy_enable" = 1 ]; then
if [ "$ipv4_proxy" = 1 ]; then
ip -4 route add local default dev lo table "$tproxy_route_table"
ip -4 rule add pref "$tproxy_rule_pref" fwmark "$tproxy_fw_mark/$tproxy_fw_mask" table "$tproxy_route_table"
fi
if [ "$ipv6_proxy" = 1 ]; then
ip -6 route add local default dev lo table "$tproxy_route_table"
ip -6 rule add pref "$tproxy_rule_pref" fwmark "$tproxy_fw_mark/$tproxy_fw_mask" table "$tproxy_route_table"
fi
fi
if [ "$tun_enable" = 1 ]; then
if [ "$ipv4_proxy" = 1 ]; then
ip -4 route add unicast default dev "$tun_device" table "$tun_route_table"
ip -4 rule add pref "$tun_rule_pref" fwmark "$tun_fw_mark/$tun_fw_mask" table "$tun_route_table"
fi
if [ "$ipv6_proxy" = 1 ]; then
ip -6 route add unicast default dev "$tun_device" table "$tun_route_table"
ip -6 rule add pref "$tun_rule_pref" fwmark "$tun_fw_mark/$tun_fw_mask" table "$tun_route_table"
fi
$FIREWALL_INCLUDE_SH
fi
# hijack
utpl -S "$HIJACK_UT" | nft -f -
# check hijack
if nft list tables | grep -q nikki; then
log "Proxy" "Hijack successful."
else
log "Proxy" "Hijack failed."
log "App" "Exit."
fi
fi
}
service_stopped() {
cleanup
}
reload_service() {
cleanup
start
}
service_triggers() {
procd_add_reload_trigger "nikki"
}
cleanup() {
# clear log
clear_log
# load config
config_load nikki
# get config
## routing config
local tproxy_route_table tun_route_table
config_get tproxy_route_table "routing" "tproxy_route_table" "80"
config_get tun_route_table "routing" "tun_route_table" "81"
# delete routing policy
ip -4 rule del table "$tproxy_route_table" > /dev/null 2>&1
ip -4 rule del table "$tun_route_table" > /dev/null 2>&1
ip -6 rule del table "$tproxy_route_table" > /dev/null 2>&1
ip -6 rule del table "$tun_route_table" > /dev/null 2>&1
# delete routing table
ip -4 route flush table "$tproxy_route_table" > /dev/null 2>&1
ip -4 route flush table "$tun_route_table" > /dev/null 2>&1
ip -6 route flush table "$tproxy_route_table" > /dev/null 2>&1
ip -6 route flush table "$tun_route_table" > /dev/null 2>&1
# delete hijack
nft delete table inet nikki > /dev/null 2>&1
local handles handle
handles=$(nft --json list table inet fw4 | jsonfilter -q -e "@['nftables'][*]['rule']" | jsonfilter -q -a -e "@[@['chain']='input']" | jsonfilter -q -a -e "@[@['comment']='nikki']" | jsonfilter -q -a -e "@[*]['handle']")
for handle in $handles; do
nft delete rule inet fw4 input handle "$handle"
done
handles=$(nft --json list table inet fw4 | jsonfilter -q -e "@['nftables'][*]['rule']" | jsonfilter -q -a -e "@[@['chain']='forward']" | jsonfilter -q -a -e "@[@['comment']='nikki']" | jsonfilter -q -a -e "@[*]['handle']")
for handle in $handles; do
nft delete rule inet fw4 forward handle "$handle"
done
# delete started flag
rm "$STARTED_FLAG_PATH" > /dev/null 2>&1
# revert fix compatible with dockerd
## kmod-br-netfilter
if rm "$BRIDGE_NF_CALL_IPTABLES_FLAG_PATH" > /dev/null 2>&1; then
sysctl -q -w net.bridge.bridge-nf-call-iptables=1
fi
if rm "$BRIDGE_NF_CALL_IP6TABLES_FLAG_PATH" > /dev/null 2>&1; then
sysctl -q -w net.bridge.bridge-nf-call-ip6tables=1
fi
# delete cron
sed -i "/#nikki/d" "/etc/crontabs/root" > /dev/null 2>&1
/etc/init.d/cron restart
}
update_subscription() {
local subscription_section; subscription_section="$1"
if [ -z "$subscription_section" ]; then
return
fi
# load config
config_load nikki
# get subscription config
local subscription_name subscription_info_url subscription_url subscription_user_agent
config_get subscription_name "$subscription_section" "name"
config_get subscription_info_url "$subscription_section" "info_url"
config_get subscription_url "$subscription_section" "url"
config_get subscription_user_agent "$subscription_section" "user_agent"
# reset subscription info
uci_remove "nikki" "$subscription_section" "expire" > /dev/null 2>&1
uci_remove "nikki" "$subscription_section" "upload" > /dev/null 2>&1
uci_remove "nikki" "$subscription_section" "download" > /dev/null 2>&1
uci_remove "nikki" "$subscription_section" "total" > /dev/null 2>&1
uci_remove "nikki" "$subscription_section" "used" > /dev/null 2>&1
uci_remove "nikki" "$subscription_section" "avaliable" > /dev/null 2>&1
uci_remove "nikki" "$subscription_section" "update" > /dev/null 2>&1
uci_remove "nikki" "$subscription_section" "success" > /dev/null 2>&1
# update subscription
log "Profile" "Update subscription: $subscription_name."
local success
local subscription_info_header_tmpfile; subscription_info_header_tmpfile="$TEMP_DIR/$subscription_section.info.header"
local subscription_header_tmpfile; subscription_header_tmpfile="$TEMP_DIR/$subscription_section.header"
local subscription_tmpfile; subscription_tmpfile="$TEMP_DIR/$subscription_section.yaml"
local subscription_info_file
local subscription_file; subscription_file="$SUBSCRIPTIONS_DIR/$subscription_section.yaml"
# fetch subscription info
if [ -n "$subscription_info_url" ]; then
log "Profile" "Fetch subscription info."
if curl -s -f -m 120 --connect-timeout 15 --retry 3 -L -X GET -A "$subscription_user_agent" -D "$subscription_info_header_tmpfile" "$subscription_info_url" > /dev/null 2>&1; then
if [ ! -f "$subscription_info_file" ] && grep -q -i "subscription-userinfo:" "$subscription_info_header_tmpfile"; then
subscription_info_file="$subscription_info_header_tmpfile"
fi
fi
fi
# download subscription
log "Profile" "Download subscription."
if curl -s -f -m 120 --connect-timeout 15 --retry 3 -L -X GET -A "$subscription_user_agent" -D "$subscription_header_tmpfile" -o "$subscription_tmpfile" "$subscription_url" > /dev/null 2>&1; then
log "Profile" "Subscription download successful."
if [ ! -f "$subscription_info_file" ] && grep -q -i "subscription-userinfo:" "$subscription_header_tmpfile"; then
subscription_info_file="$subscription_header_tmpfile"
fi
if yq -p yaml -o yaml -e 'has("proxies") or has("proxy-providers")' "$subscription_tmpfile" > /dev/null 2>&1; then
log "Profile" "Subscription is valid."
success=1
else
log "Profile" "Subscription is not valid."
success=0
fi
else
log "Profile" "Subscription download failed."
success=0
fi
# check if success
if [ "$success" = 1 ]; then
log "Profile" "Subscription update successful."
if [ -f "$subscription_info_file" ]; then
local subscription_expire subscription_upload subscription_download subscription_total subscription_used subscription_avaliable
subscription_expire=$(grep -i "subscription-userinfo: " "$subscription_info_file" | grep -i -o -E "expire=[[:digit:]]+" | cut -d '=' -f 2)
subscription_upload=$(grep -i "subscription-userinfo: " "$subscription_info_file" | grep -i -o -E "upload=[[:digit:]]+" | cut -d '=' -f 2)
subscription_download=$(grep -i "subscription-userinfo: " "$subscription_info_file" | grep -i -o -E "download=[[:digit:]]+" | cut -d '=' -f 2)
subscription_total=$(grep -i "subscription-userinfo: " "$subscription_info_file" | grep -i -o -E "total=[[:digit:]]+" | cut -d '=' -f 2)
if [ -n "$subscription_upload" ] && [ -n "$subscription_download" ]; then
subscription_used=$((subscription_upload + subscription_download))
if [ -n "$subscription_total" ]; then
subscription_avaliable=$((subscription_total - subscription_used))
fi
fi
# update subscription info
if [ -n "$subscription_expire" ]; then
uci_set "nikki" "$subscription_section" "expire" "$(date "+%Y-%m-%d %H:%M:%S" -d "@$subscription_expire")"
fi
if [ -n "$subscription_upload" ]; then
uci_set "nikki" "$subscription_section" "upload" "$(format_filesize "$subscription_upload")"
fi
if [ -n "$subscription_download" ]; then
uci_set "nikki" "$subscription_section" "download" "$(format_filesize "$subscription_download")"
fi
if [ -n "$subscription_total" ]; then
uci_set "nikki" "$subscription_section" "total" "$(format_filesize "$subscription_total")"
fi
if [ -n "$subscription_used" ]; then
uci_set "nikki" "$subscription_section" "used" "$(format_filesize "$subscription_used")"
fi
if [ -n "$subscription_avaliable" ]; then
uci_set "nikki" "$subscription_section" "avaliable" "$(format_filesize "$subscription_avaliable")"
fi
fi
uci_set "nikki" "$subscription_section" "update" "$(date "+%Y-%m-%d %H:%M:%S")"
uci_set "nikki" "$subscription_section" "success" "1"
# update subscription file
rm -f "$subscription_info_header_tmpfile"
rm -f "$subscription_header_tmpfile"
mv -f "$subscription_tmpfile" "$subscription_file"
elif [ "$success" = 0 ]; then
log "Profile" "Subscription update failed."
# update subscription info
uci_set "nikki" "$subscription_section" "success" "0"
# remove tmpfile
rm -f "$subscription_info_header_tmpfile"
rm -f "$subscription_header_tmpfile"
rm -f "$subscription_tmpfile"
fi
uci_commit "nikki"
}

View File

@@ -0,0 +1,5 @@
/etc/nikki/profiles/
/etc/nikki/subscriptions/
/etc/nikki/mixin.yaml
/etc/nikki/run/providers/rule/
/etc/nikki/run/providers/proxy/

View File

@@ -0,0 +1,248 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/nikki/scripts/include.sh"
enabled=`uci get nikki.config.enabled`
if [ "$enabled" == "0" ]; then
uci set nikki.config.enabled=1
uci commit nikki
/etc/init.d/nikki restart
fi
echo \
"
# Nikki Debug Info
## system
\`\`\`shell
`
cat /etc/openwrt_release
`
\`\`\`
## kernel
\`\`\`
`
uname -a
`
\`\`\`
## application
\`\`\`
`
if [ -x "/bin/opkg" ]; then
opkg list-installed "nikki"
opkg list-installed "luci-app-nikki"
elif [ -x "/usr/bin/apk" ]; then
apk list -I "nikki"
apk list -I "luci-app-nikki"
fi
`
\`\`\`
## config
\`\`\`json
`
ucode -S -e '
import { cursor } from "uci";
const uci = cursor();
const config = uci.get_all("nikki");
const result = {};
for (let section_id in config) {
const section = config[section_id];
const section_type = section[".type"];
if (result[section_type] == null) {
result[section_type] = [];
}
push(result[section_type], section);
}
for (let section_type in result) {
for (let section in result[section_type]) {
delete section[".anonymous"];
delete section[".type"];
delete section[".name"];
delete section[".index"];
}
}
if (exists(result, "mixin")) {
for (let x in result["mixin"]) {
if (exists(x, "api_secret")) {
x["api_secret"] = "*";
}
}
}
if (exists(result, "authentication")) {
for (let x in result["authentication"]) {
if (exists(x, "password")) {
x["password"] = "*";
}
}
}
if (exists(result, "subscription")) {
for (let x in result["subscription"]) {
if (exists(x, "url")) {
x["url"] = "*";
}
}
}
if (exists(result, "lan_access_control")) {
for (let x in result["lan_access_control"]) {
if (exists(x, "ip")) {
for (let i = 0; i < length(x["ip"]); i++) {
x["ip"][i] = "*";
}
}
if (exists(x, "ip6")) {
for (let i = 0; i < length(x["ip6"]); i++) {
x["ip6"][i] = "*";
}
}
if (exists(x, "mac")) {
for (let i = 0; i < length(x["mac"]); i++) {
x["mac"][i] = "*";
}
}
}
}
delete result["status"];
delete result["editor"];
delete result["log"];
print(result);
'
`
\`\`\`
## profile
\`\`\`json
`
ucode -S -e '
import { popen } from "fs";
function desensitize_proxies(proxies) {
for (let x in proxies) {
if (exists(x, "server")) {
x["server"] = "*";
}
if (exists(x, "servername")) {
x["servername"] = "*";
}
if (exists(x, "sni")) {
x["sni"] = "*";
}
if (exists(x, "port")) {
x["port"] = "*";
}
if (exists(x, "ports")) {
x["ports"] = "*";
}
if (exists(x, "port-range")) {
x["port-range"] = "*";
}
if (exists(x, "uuid")) {
x["uuid"] = "*";
}
if (exists(x, "private-key")) {
x["private-key"] = "*";
}
if (exists(x, "public-key")) {
x["public-key"] = "*";
}
if (exists(x, "token")) {
x["token"] = "*";
}
if (exists(x, "username")) {
x["username"] = "*";
}
if (exists(x, "password")) {
x["password"] = "*";
}
}
}
function desensitize_profile() {
let profile = {};
const process = popen("yq -p yaml -o json /etc/nikki/run/config.yaml");
if (process) {
profile = json(process);
if (exists(profile, "secret")) {
profile["secret"] = "*";
}
if (exists(profile, "authentication")) {
profile["authentication"] = [];
}
if (exists(profile, "proxy-providers")) {
for (let x in profile["proxy-providers"]) {
if (exists(profile["proxy-providers"][x], "url")) {
profile["proxy-providers"][x]["url"] = "*";
}
if (exists(profile["proxy-providers"][x], "payload")) {
desensitize_proxies(profile["proxy-providers"][x]["payload"]);
}
}
}
if (exists(profile, "proxies")) {
desensitize_proxies(profile["proxies"]);
}
process.close();
}
return profile;
}
print(desensitize_profile());
'
`
\`\`\`
## ip rule
\`\`\`
`
ip rule list
`
\`\`\`
## ip route
\`\`\`
TPROXY:
`
ip route list table "$(uci get nikki.routing.tproxy_route_table)"
`
TUN:
`
ip route list table "$(uci get nikki.routing.tun_route_table)"
`
\`\`\`
## ip6 rule
\`\`\`
`
ip -6 rule list
`
\`\`\`
## ip6 route
\`\`\`
TPROXY:
`
ip -6 route list table "$(uci get nikki.routing.tproxy_route_table)"
`
TUN:
`
ip -6 route list table "$(uci get nikki.routing.tun_route_table)"
`
\`\`\`
## nftables
\`\`\`
`
nft list table inet nikki
`
\`\`\`
## service
\`\`\`json
`
/etc/init.d/nikki info
`
\`\`\`
"
if [ "$enabled" == "0" ]; then
uci set nikki.config.enabled=0
uci commit nikki
/etc/init.d/nikki restart
fi

View File

@@ -0,0 +1,22 @@
#!/bin/sh
. "$IPKG_INSTROOT/lib/functions.sh"
. "$IPKG_INSTROOT/etc/nikki/scripts/include.sh"
config_load nikki
config_get_bool enabled "config" "enabled" 0
config_get_bool core_only "config" "core_only" 0
config_get_bool proxy_enabled "proxy" "enabled" 0
config_get tcp_mode "proxy" "tcp_mode"
config_get udp_mode "proxy" "udp_mode"
if [ "$enabled" = 1 ] && [ "$core_only" = 0 ] && [ "$proxy_enabled" = 1 ]; then
if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then
tun_device=$(yq -M '.tun.device' "$RUN_PROFILE_PATH")
nft insert rule inet fw4 input iifname "$tun_device" counter accept comment "nikki"
nft insert rule inet fw4 forward oifname "$tun_device" counter accept comment "nikki"
nft insert rule inet fw4 forward iifname "$tun_device" counter accept comment "nikki"
fi
fi
exit 0

View File

@@ -0,0 +1,90 @@
#!/bin/sh
# paths
HOME_DIR="/etc/nikki"
PROFILES_DIR="$HOME_DIR/profiles"
SUBSCRIPTIONS_DIR="$HOME_DIR/subscriptions"
MIXIN_FILE_PATH="$HOME_DIR/mixin.yaml"
RUN_DIR="$HOME_DIR/run"
RUN_PROFILE_PATH="$RUN_DIR/config.yaml"
PROVIDERS_DIR="$RUN_DIR/providers"
RULE_PROVIDERS_DIR="$PROVIDERS_DIR/rule"
PROXY_PROVIDERS_DIR="$PROVIDERS_DIR/proxy"
# log
LOG_DIR="/var/log/nikki"
APP_LOG_PATH="$LOG_DIR/app.log"
CORE_LOG_PATH="$LOG_DIR/core.log"
# temp
TEMP_DIR="/var/run/nikki"
PID_FILE_PATH="$TEMP_DIR/nikki.pid"
STARTED_FLAG_PATH="$TEMP_DIR/started.flag"
BRIDGE_NF_CALL_IPTABLES_FLAG_PATH="$TEMP_DIR/bridge_nf_call_iptables.flag"
BRIDGE_NF_CALL_IP6TABLES_FLAG_PATH="$TEMP_DIR/bridge_nf_call_ip6tables.flag"
# ucode
UCODE_DIR="$HOME_DIR/ucode"
INCLUDE_UC="$UCODE_DIR/include.uc"
MIXIN_UC="$UCODE_DIR/mixin.uc"
HIJACK_UT="$UCODE_DIR/hijack.ut"
# scripts
SH_DIR="$HOME_DIR/scripts"
INCLUDE_SH="$SH_DIR/include.sh"
FIREWALL_INCLUDE_SH="$SH_DIR/firewall_include.sh"
# nftables
NFT_DIR="$HOME_DIR/nftables"
GEOIP_CN_NFT="$NFT_DIR/geoip_cn.nft"
GEOIP6_CN_NFT="$NFT_DIR/geoip6_cn.nft"
# functions
format_filesize() {
local b; b=1
local kb; kb=$((b * 1024))
local mb; mb=$((kb * 1024))
local gb; gb=$((mb * 1024))
local tb; tb=$((gb * 1024))
local pb; pb=$((tb * 1024))
local size; size="$1"
if [ -n "$size" ]; then
if [ "$size" -lt "$kb" ]; then
echo "$(awk "BEGIN {print $size / $b}") B"
elif [ "$size" -lt "$mb" ]; then
echo "$(awk "BEGIN {print $size / $kb}") KB"
elif [ "$size" -lt "$gb" ]; then
echo "$(awk "BEGIN {print $size / $mb}") MB"
elif [ "$size" -lt "$tb" ]; then
echo "$(awk "BEGIN {print $size / $gb}") GB"
elif [ "$size" -lt "$pb" ]; then
echo "$(awk "BEGIN {print $size / $tb}") TB"
else
echo "$(awk "BEGIN {print $size / $pb}") PB"
fi
fi
}
prepare_files() {
if [ ! -d "$LOG_DIR" ]; then
mkdir -p "$LOG_DIR"
fi
if [ ! -f "$APP_LOG_PATH" ]; then
touch "$APP_LOG_PATH"
fi
if [ ! -f "$CORE_LOG_PATH" ]; then
touch "$CORE_LOG_PATH"
fi
if [ ! -d "$TEMP_DIR" ]; then
mkdir -p "$TEMP_DIR"
fi
}
clear_log() {
echo -n > "$APP_LOG_PATH"
echo -n > "$CORE_LOG_PATH"
}
log() {
echo "[$(date "+%Y-%m-%d %H:%M:%S")] [$1] $2" >> "$APP_LOG_PATH"
}

View File

@@ -0,0 +1,12 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/nikki/scripts/include.sh"
uci -q batch <<-EOF > /dev/null
del firewall.nikki
set firewall.nikki=include
set firewall.nikki.type=script
set firewall.nikki.path=$FIREWALL_INCLUDE_SH
set firewall.nikki.fw4_compatible=1
commit firewall
EOF

View File

@@ -0,0 +1,24 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/nikki/scripts/include.sh"
# check nikki.config.init
init=$(uci -q get nikki.config.init); [ -z "$init" ] && return
# generate random string for api secret and authentication password
random=$(awk 'BEGIN{srand(); print int(rand() * 1000000)}')
# set nikki.mixin.api_secret
uci set nikki.mixin.api_secret="$random"
# set nikki.@authentication[0].password
uci set nikki.@authentication[0].password="$random"
# remove nikki.config.init
uci del nikki.config.init
# commit
uci commit nikki
# exit with 0
exit 0

View File

@@ -0,0 +1,218 @@
#!/bin/sh
. "$IPKG_INSTROOT/etc/nikki/scripts/include.sh"
# since v1.18.0
mixin_rule=$(uci -q get nikki.mixin.rule); [ -z "$mixin_rule" ] && uci set nikki.mixin.rule=0
mixin_rule_provider=$(uci -q get nikki.mixin.rule_provider); [ -z "$mixin_rule_provider" ] && uci set nikki.mixin.rule_provider=0
# since v1.19.0
mixin_ui_path=$(uci -q get nikki.mixin.ui_path); [ -z "$mixin_ui_path" ] && uci set nikki.mixin.ui_path=ui
uci show nikki | grep -E 'nikki\.@rule\[[[:digit:]]+\].match=' | sed 's/nikki.@rule\[\([[:digit:]]\+\)\].match=.*/rename nikki.@rule[\1].match=matcher/' | uci batch
# since v1.19.1
proxy_fake_ip_ping_hijack=$(uci -q get nikki.proxy.fake_ip_ping_hijack); [ -z "$proxy_fake_ip_ping_hijack" ] && uci set nikki.proxy.fake_ip_ping_hijack=0
# since v1.20.0
mixin_api_port=$(uci -q get nikki.mixin.api_port); [ -n "$mixin_api_port" ] && {
uci del nikki.mixin.api_port
uci set nikki.mixin.api_listen="[::]:$mixin_api_port"
}
mixin_dns_port=$(uci -q get nikki.mixin.dns_port); [ -n "$mixin_dns_port" ] && {
uci del nikki.mixin.dns_port
uci set nikki.mixin.dns_listen="[::]:$mixin_dns_port"
}
# since v1.22.0
proxy_transparent_proxy=$(uci -q get nikki.proxy.transparent_proxy); [ -n "$proxy_transparent_proxy" ] && {
uci rename nikki.proxy.transparent_proxy=enabled
uci rename nikki.proxy.tcp_transparent_proxy_mode=tcp_mode
uci rename nikki.proxy.udp_transparent_proxy_mode=udp_mode
uci add nikki router_access_control
uci set nikki.@router_access_control[-1].enabled=1
proxy_bypass_user=$(uci -q get nikki.proxy.bypass_user); [ -n "$proxy_bypass_user" ] && {
for router_access_control_user in $proxy_bypass_user; do
uci add_list nikki.@router_access_control[-1].user="$router_access_control_user"
done
}
proxy_bypass_group=$(uci -q get nikki.proxy.bypass_group); [ -n "$proxy_bypass_group" ] && {
for router_access_control_group in $proxy_bypass_group; do
uci add_list nikki.@router_access_control[-1].group="$router_access_control_group"
done
}
proxy_bypass_cgroup=$(uci -q get nikki.proxy.bypass_cgroup); [ -n "$proxy_bypass_cgroup" ] && {
for router_access_control_cgroup in $proxy_bypass_cgroup; do
uci add_list nikki.@router_access_control[-1].cgroup="$router_access_control_cgroup"
done
}
uci set nikki.@router_access_control[-1].proxy=0
uci add nikki router_access_control
uci set nikki.@router_access_control[-1].enabled=1
uci set nikki.@router_access_control[-1].proxy=1
uci add_list nikki.proxy.lan_inbound_interface=lan
proxy_access_control_mode=$(uci -q get nikki.proxy.access_control_mode)
[ "$proxy_access_control_mode" != "all" ] && {
proxy_acl_ip=$(uci -q get nikki.proxy.acl_ip); [ -n "$proxy_acl_ip" ] && {
for ip in $proxy_acl_ip; do
uci add nikki lan_access_control
uci set nikki.@lan_access_control[-1].enabled=1
uci add_list nikki.@lan_access_control[-1].ip="$ip"
[ "$proxy_access_control_mode" = "allow" ] && uci set nikki.@lan_access_control[-1].proxy=1
[ "$proxy_access_control_mode" = "block" ] && uci set nikki.@lan_access_control[-1].proxy=0
done
}
proxy_acl_ip6=$(uci -q get nikki.proxy.acl_ip6); [ -n "$proxy_acl_ip6" ] && {
for ip6 in $proxy_acl_ip6; do
uci add nikki lan_access_control
uci set nikki.@lan_access_control[-1].enabled=1
uci add_list nikki.@lan_access_control[-1].ip6="$ip6"
[ "$proxy_access_control_mode" = "allow" ] && uci set nikki.@lan_access_control[-1].proxy=1
[ "$proxy_access_control_mode" = "block" ] && uci set nikki.@lan_access_control[-1].proxy=0
done
}
proxy_acl_mac=$(uci -q get nikki.proxy.acl_mac); [ -n "$proxy_acl_mac" ] && {
for mac in $proxy_acl_mac; do
uci add nikki lan_access_control
uci set nikki.@lan_access_control[-1].enabled=1
uci add_list nikki.@lan_access_control[-1].mac="$mac"
[ "$proxy_access_control_mode" = "allow" ] && uci set nikki.@lan_access_control[-1].proxy=1
[ "$proxy_access_control_mode" = "block" ] && uci set nikki.@lan_access_control[-1].proxy=0
done
}
}
[ "$proxy_access_control_mode" != "allow" ] && {
uci add nikki lan_access_control
uci set nikki.@lan_access_control[-1].enabled=1
uci set nikki.@lan_access_control[-1].proxy=1
}
uci del nikki.proxy.access_control_mode
uci del nikki.proxy.acl_ip
uci del nikki.proxy.acl_ip6
uci del nikki.proxy.acl_mac
uci del nikki.proxy.acl_interface
uci del nikki.proxy.bypass_user
uci del nikki.proxy.bypass_group
uci del nikki.proxy.bypass_cgroup
}
# since v1.23.0
routing=$(uci -q get nikki.routing); [ -z "$routing" ] && {
uci set nikki.routing=routing
uci set nikki.routing.tproxy_fw_mark=0x80
uci set nikki.routing.tun_fw_mark=0x81
uci set nikki.routing.tproxy_rule_pref=1024
uci set nikki.routing.tun_rule_pref=1025
uci set nikki.routing.tproxy_route_table=80
uci set nikki.routing.tun_route_table=81
uci set nikki.routing.cgroup_id=0x12061206
uci set nikki.routing.cgroup_name=nikki
}
proxy_tun_timeout=$(uci -q get nikki.proxy.tun_timeout); [ -z "$proxy_tun_timeout" ] && uci set nikki.proxy.tun_timeout=30
proxy_tun_interval=$(uci -q get nikki.proxy.tun_interval); [ -z "$proxy_tun_interval" ] && uci set nikki.proxy.tun_interval=1
# since v1.23.1
uci show nikki | grep -o -E 'nikki\.@router_access_control\[[[:digit:]]+\]=router_access_control' | cut -d '=' -f 1 | while read -r router_access_control; do
for router_access_control_cgroup in $(uci -q get "$router_access_control.cgroup"); do
[ -d "/sys/fs/cgroup/$router_access_control_cgroup" ] && continue
[ -d "/sys/fs/cgroup/services/$router_access_control_cgroup" ] && {
uci del_list "$router_access_control.cgroup=$router_access_control_cgroup"
uci add_list "$router_access_control.cgroup=services/$router_access_control_cgroup"
}
done
done
# since v1.23.2
env_disable_safe_path_check=$(uci -q get nikki.env.disable_safe_path_check); [ -n "$env_disable_safe_path_check" ] && uci del nikki.env.disable_safe_path_check
env_skip_system_ipv6_check=$(uci -q get nikki.env.skip_system_ipv6_check); [ -z "$env_skip_system_ipv6_check" ] && uci set nikki.env.skip_system_ipv6_check=0
# since v1.23.3
uci show nikki | grep -o -E 'nikki\.@router_access_control\[[[:digit:]]+\]=router_access_control' | cut -d '=' -f 1 | while read -r router_access_control; do
router_access_control_proxy=$(uci -q get "$router_access_control.proxy")
router_access_control_dns=$(uci -q get "$router_access_control.dns")
[ -z "$router_access_control_dns" ] && uci set "$router_access_control.dns=$router_access_control_proxy"
done
uci show nikki | grep -o -E 'nikki\.@lan_access_control\[[[:digit:]]+\]=lan_access_control' | cut -d '=' -f 1 | while read -r lan_access_control; do
lan_access_control_proxy=$(uci -q get "$lan_access_control.proxy")
lan_access_control_dns=$(uci -q get "$lan_access_control.dns")
[ -z "$lan_access_control_dns" ] && uci set "$lan_access_control.dns=$lan_access_control_proxy"
done
# since v1.24.0
proxy_reserved_ip=$(uci -q get nikki.proxy.reserved_ip); [ -z "$proxy_reserved_ip" ] && {
uci add_list nikki.proxy.reserved_ip=0.0.0.0/8
uci add_list nikki.proxy.reserved_ip=10.0.0.0/8
uci add_list nikki.proxy.reserved_ip=127.0.0.0/8
uci add_list nikki.proxy.reserved_ip=100.64.0.0/10
uci add_list nikki.proxy.reserved_ip=169.254.0.0/16
uci add_list nikki.proxy.reserved_ip=172.16.0.0/12
uci add_list nikki.proxy.reserved_ip=192.168.0.0/16
uci add_list nikki.proxy.reserved_ip=224.0.0.0/4
uci add_list nikki.proxy.reserved_ip=240.0.0.0/4
}
proxy_reserved_ip6=$(uci -q get nikki.proxy.reserved_ip6); [ -z "$proxy_reserved_ip6" ] && {
uci add_list nikki.proxy.reserved_ip6=::/128
uci add_list nikki.proxy.reserved_ip6=::1/128
uci add_list nikki.proxy.reserved_ip6=::ffff:0:0/96
uci add_list nikki.proxy.reserved_ip6=100::/64
uci add_list nikki.proxy.reserved_ip6=64:ff9b::/96
uci add_list nikki.proxy.reserved_ip6=2001::/32
uci add_list nikki.proxy.reserved_ip6=2001:10::/28
uci add_list nikki.proxy.reserved_ip6=2001:20::/28
uci add_list nikki.proxy.reserved_ip6=2001:db8::/32
uci add_list nikki.proxy.reserved_ip6=2002::/16
uci add_list nikki.proxy.reserved_ip6=fc00::/7
uci add_list nikki.proxy.reserved_ip6=fe80::/10
uci add_list nikki.proxy.reserved_ip6=ff00::/8
}
# since v1.24.3
proxy_bypass_china_mainland_ip=$(uci -q get nikki.proxy.bypass_china_mainland_ip)
proxy_bypass_china_mainland_ip6=$(uci -q get nikki.proxy.bypass_china_mainland_ip6)
[ -z "$proxy_bypass_china_mainland_ip6" ] && uci set nikki.proxy.bypass_china_mainland_ip6=$proxy_bypass_china_mainland_ip
routing_tproxy_fw_mask=$(uci -q get nikki.routing.tproxy_fw_mask); [ -z "$routing_tproxy_fw_mask" ] && uci set nikki.routing.tproxy_fw_mask=0xFF
routing_tun_fw_mask=$(uci -q get nikki.routing.tun_fw_mask); [ -z "$routing_tun_fw_mask" ] && uci set nikki.routing.tun_fw_mask=0xFF
procd=$(uci -q get nikki.procd); [ -z "$procd" ] && {
uci set nikki.procd=procd
uci set nikki.procd.fast_reload=$(uci -q get nikki.config.fast_reload)
uci set nikki.procd.env_safe_paths=$(uci -q get nikki.env.safe_paths)
uci set nikki.procd.env_disable_loopback_detector=$(uci -q get nikki.env.disable_loopback_detector)
uci set nikki.procd.env_disable_quic_go_gso=$(uci -q get nikki.env.disable_quic_go_gso)
uci set nikki.procd.env_disable_quic_go_ecn=$(uci -q get nikki.env.disable_quic_go_ecn)
uci set nikki.procd.env_skip_system_ipv6_check=$(uci -q get nikki.env.skip_system_ipv6_check)
uci del nikki.config.fast_reload
uci del nikki.env
}
# commit
uci commit nikki
# exit with 0
exit 0

568
nikki/files/ucode/hijack.ut Normal file
View File

@@ -0,0 +1,568 @@
#!/usr/bin/utpl
{%-
'use strict';
import { cursor } from 'uci';
import { connect } from 'ubus';
import { uci_bool, uci_array, get_cgroups_version, get_users, get_groups, get_cgroups, load_profile } from '/etc/nikki/ucode/include.uc';
const fw4 = require('fw4');
const cgroups_version = get_cgroups_version();
const users = get_users();
const groups = get_groups();
const cgroups = get_cgroups();
const uci = cursor();
const ubus = connect();
const profile = load_profile();
const redir_port = profile['redir-port'];
const tproxy_port = profile['tproxy-port'];
let dns_listen;
let dns_port;
let fake_ip_range;
if (profile['dns']) {
dns_listen = profile['dns']['listen'];
const dns_listen_rindex = rindex(dns_listen, ':');
if (dns_listen_rindex >= 0 && dns_listen_rindex + 1 < length(dns_listen)) {
dns_port = substr(dns_listen, dns_listen_rindex + 1);
}
fake_ip_range = profile['dns']['fake-ip-range'];
}
let tun_device;
if (profile['tun']) {
tun_device = profile['tun']['device'];
}
uci.load('nikki');
const tcp_mode = uci.get('nikki', 'proxy', 'tcp_mode');
const udp_mode = uci.get('nikki', 'proxy', 'udp_mode');
const ipv4_dns_hijack = uci_bool(uci.get('nikki', 'proxy', 'ipv4_dns_hijack'));
const ipv6_dns_hijack = uci_bool(uci.get('nikki', 'proxy', 'ipv6_dns_hijack'));
const ipv4_proxy = uci_bool(uci.get('nikki', 'proxy', 'ipv4_proxy'));
const ipv6_proxy = uci_bool(uci.get('nikki', 'proxy', 'ipv6_proxy'));
const fake_ip_ping_hijack = uci_bool(uci.get('nikki', 'proxy', 'fake_ip_ping_hijack'));
const router_proxy = uci_bool(uci.get('nikki', 'proxy', 'router_proxy'));
const router_access_control = [];
uci.foreach('nikki', 'router_access_control', (access_control) => {
access_control['enabled'] = uci_bool(access_control['enabled']);
access_control['user'] = filter(uci_array(access_control['user']), (x) => index(users, x) >= 0);
access_control['group'] = filter(uci_array(access_control['group']), (x) => index(groups, x) >= 0);
access_control['cgroup'] = filter(uci_array(access_control['cgroup']), (x) => index(cgroups, x) >= 0);
access_control['proxy'] = uci_bool(access_control['proxy']);
access_control['dns'] = uci_bool(access_control['dns']);
push(router_access_control, access_control);
});
const lan_proxy = uci_bool(uci.get('nikki', 'proxy', 'lan_proxy'));
const lan_inbound_interface = uci_array(uci.get('nikki', 'proxy', 'lan_inbound_interface'));
const lan_inbound_device = [];
for (let interface in lan_inbound_interface) {
const status = ubus.call('network.interface', 'status', { 'interface': interface });
const device = status?.l3_device ?? status?.device ?? '';
if (device != '') {
push(lan_inbound_device, device);
}
}
const lan_access_control = [];
uci.foreach('nikki', 'lan_access_control', (access_control) => {
access_control['enabled'] = uci_bool(access_control['enabled']);
access_control['ip'] = uci_array(access_control['ip']);
access_control['ip6'] = uci_array(access_control['ip6']);
access_control['mac'] = uci_array(access_control['mac']);
access_control['proxy'] = uci_bool(access_control['proxy']);
access_control['dns'] = uci_bool(access_control['dns']);
push(lan_access_control, access_control);
});
const reserved_ip = uci_array(uci.get('nikki', 'proxy', 'reserved_ip'));
const reserved_ip6 = uci_array(uci.get('nikki', 'proxy', 'reserved_ip6'));
const bypass_dscp = uci_array(uci.get('nikki', 'proxy', 'bypass_dscp'));
const bypass_china_mainland_ip = uci_bool(uci.get('nikki', 'proxy', 'bypass_china_mainland_ip'));
const bypass_china_mainland_ip6 = uci_bool(uci.get('nikki', 'proxy', 'bypass_china_mainland_ip6'));
const proxy_tcp_dport = split((uci.get('nikki', 'proxy', 'proxy_tcp_dport') ?? '0-65535'), ' ');
const proxy_udp_dport = split((uci.get('nikki', 'proxy', 'proxy_udp_dport') ?? '0-65535'), ' ');
const cgroup_id = uci.get('nikki', 'routing', 'cgroup_id') ?? '0x12061206';
const cgroup_name = uci.get('nikki', 'routing', 'cgroup_name') ?? 'nikki';
const tproxy_fw_mark = uci.get('nikki', 'routing', 'tproxy_fw_mark') ?? '0x80';
const tproxy_fw_mask = uci.get('nikki', 'routing', 'tproxy_fw_mask') ?? '0xFF';
const tproxy_fw_umask = fw4.hex(~tproxy_fw_mask & 0xFFFFFFFF);
const tun_fw_mark = uci.get('nikki', 'routing', 'tun_fw_mark') ?? '0x81';
const tun_fw_mask = uci.get('nikki', 'routing', 'tun_fw_mask') ?? '0xFF';
const tun_fw_umask = fw4.hex(~tun_fw_mask & 0xFFFFFFFF);
const dns_hijack_nfproto = [];
if (ipv4_dns_hijack) {
push(dns_hijack_nfproto, 'ipv4');
}
if (ipv6_dns_hijack) {
push(dns_hijack_nfproto, 'ipv6');
}
const proxy_nfproto = [];
if (ipv4_proxy) {
push(proxy_nfproto, 'ipv4');
}
if (ipv6_proxy) {
push(proxy_nfproto, 'ipv6');
}
const proxy_dport = [];
for (let port in proxy_tcp_dport) {
push(proxy_dport, `tcp . ${port}`);
}
for (let port in proxy_udp_dport) {
push(proxy_dport, `udp . ${port}`);
}
-%}
table inet nikki {
{% if (length(dns_hijack_nfproto) > 0): %}
set dns_hijack_nfproto {
type nf_proto
flags interval
elements = {
{{ join(', ', dns_hijack_nfproto) }}
}
}
{% endif %}
set proxy_nfproto {
type nf_proto
flags interval
{% if (length(proxy_nfproto) > 0): %}
elements = {
{{ join(', ', proxy_nfproto) }}
}
{% endif %}
}
set reserved_ip {
type ipv4_addr
flags interval
auto-merge
{% if (length(reserved_ip) > 0): %}
elements = {
{{ join(', ', reserved_ip) }}
}
{% endif %}
}
set reserved_ip6 {
type ipv6_addr
flags interval
auto-merge
{% if (length(reserved_ip6) > 0): %}
elements = {
{{ join(', ', reserved_ip6) }}
}
{% endif %}
}
set lan_inbound_device {
type ifname
flags interval
auto-merge
{% if (length(lan_inbound_device) > 0): %}
elements = {
{{ join(', ', map(lan_inbound_device, (x) => `"${x}"`)) }}
}
{% endif %}
}
set china_ip {
type ipv4_addr
flags interval
}
set china_ip6 {
type ipv6_addr
flags interval
}
set proxy_dport {
type inet_proto . inet_service
flags interval
auto-merge
{% if (length(proxy_dport) > 0): %}
elements = {
{{ join(', ', proxy_dport) }}
}
{% endif %}
}
set bypass_dscp {
type dscp
flags interval
auto-merge
{% if (length(bypass_dscp) > 0): %}
elements = {
{{ join(', ', bypass_dscp) }}
}
{% endif %}
}
{% if (router_proxy): %}
{% if (length(dns_hijack_nfproto) > 0): %}
chain router_dns_hijack {
{% for (let access_control in router_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
meta l4proto { tcp, udp } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% else %}
{% if (length(access_control['user']) > 0): %}
meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['group']) > 0): %}
meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
{% for (let cgroup in access_control['cgroup']): %}
meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'redirect'): %}
chain router_redirect {
{% for (let access_control in router_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
meta l4proto tcp counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% else %}
{% if (length(access_control['user']) > 0): %}
meta l4proto tcp meta skuid { {{ join(', ', access_control['user']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['group']) > 0): %}
meta l4proto tcp meta skgid { {{ join(', ', access_control['group']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
{% for (let cgroup in access_control['cgroup']): %}
meta l4proto tcp socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
chain router_tproxy {
{% for (let access_control in router_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
{% else %}
{% if (length(access_control['user']) > 0): %}
meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (length(access_control['group']) > 0): %}
meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
{% for (let cgroup in access_control['cgroup']): %}
meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
chain router_tun {
{% for (let access_control in router_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['user']) == 0 && length(access_control['group']) == 0 && length(access_control['cgroup']) == 0): %}
meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
{% else %}
{% if (length(access_control['user']) > 0): %}
meta l4proto { tcp, udp } meta skuid { {{ join(', ', access_control['user']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (length(access_control['group']) > 0): %}
meta l4proto { tcp, udp } meta skgid { {{ join(', ', access_control['group']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (cgroups_version == 2 && length(access_control['cgroup']) > 0): %}
{% for (let cgroup in access_control['cgroup']): %}
meta l4proto { tcp, udp } socket cgroupv2 level {{ length(split(cgroup, '/')) }} "{{ cgroup }}" {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %} counter return {% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% endif %}
{% if (lan_proxy): %}
{% if (length(dns_hijack_nfproto) > 0): %}
chain lan_dns_hijack {
{% for (let access_control in lan_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
meta l4proto { tcp, udp } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% else %}
{% if (length(access_control['ip']) > 0): %}
meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['ip6']) > 0): %}
meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['mac']) > 0): %}
meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } th dport 53 counter {% if (access_control.dns == '1'): %} redirect to :{{ dns_port }} {% else %} return {% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'redirect'): %}
chain lan_redirect {
{% for (let access_control in lan_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
meta l4proto tcp counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} counter return {% endif %}
{% else %}
{% if (length(access_control['ip']) > 0): %}
meta l4proto tcp ip saddr { {{ join(', ', access_control['ip']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['ip6']) > 0): %}
meta l4proto tcp ip6 saddr { {{ join(', ', access_control['ip6']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% if (length(access_control['mac']) > 0): %}
meta l4proto tcp ether saddr { {{ join(', ', access_control['mac']) }} } counter {% if (access_control.proxy == '1'): %} redirect to :{{ redir_port }} {% else %} return {% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
chain lan_tproxy {
{% for (let access_control in lan_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}
{% else %}
{% if (length(access_control['ip']) > 0): %}
meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy ip to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (length(access_control['ip6']) > 0): %}
meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy ip6 to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% if (length(access_control['mac']) > 0): %}
meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tproxy_fw_umask }} | {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept {% else %} counter return {% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
chain lan_tun {
{% for (let access_control in lan_access_control): %}
{% if (access_control['enabled']): %}
{% if (length(access_control['ip']) == 0 && length(access_control['ip6']) == 0 && length(access_control['mac']) == 0): %}
meta l4proto { tcp, udp } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}
{% else %}
{% if (length(access_control['ip']) > 0): %}
meta l4proto { tcp, udp } ip saddr { {{ join(', ', access_control['ip']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}
{% endif %}
{% if (length(access_control['ip6']) > 0): %}
meta l4proto { tcp, udp } ip6 saddr { {{ join(', ', access_control['ip6']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}
{% endif %}
{% if (length(access_control['mac']) > 0): %}
meta l4proto { tcp, udp } ether saddr { {{ join(', ', access_control['mac']) }} } {% if (access_control.proxy == '1'): %} meta mark set meta mark & {{ tun_fw_umask }} | {{ tun_fw_mark }} counter accept {% else %}counter return {% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
}
{% endif %}
{% endif %}
{% if (router_proxy): %}
chain nat_output {
type nat hook output priority filter; policy accept;
{% if (cgroups_version == 1): %}
meta cgroup {{ cgroup_id }} counter return
{% elif (cgroups_version == 2): %}
socket cgroupv2 level 2 "services/{{ cgroup_name }}" counter return
{% endif %}
{% if (length(dns_hijack_nfproto) > 0): %}
meta nfproto @dns_hijack_nfproto jump router_dns_hijack
{% endif %}
{% if (tcp_mode == 'redirect'): %}
fib daddr type { local, broadcast, anycast, multicast } counter return
ct direction reply counter return
ip daddr @reserved_ip counter return
ip6 daddr @reserved_ip6 counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta nfproto ipv6 meta l4proto . th dport != @proxy_dport counter return
meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip6 dscp @bypass_dscp counter return
meta nfproto @proxy_nfproto jump router_redirect
{% endif %}
{% if (fake_ip_ping_hijack): %}
{% if (fake_ip_range ): %}
icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect
{% endif %}
{% endif %}
}
chain mangle_output {
type route hook output priority mangle; policy accept;
{% if (cgroups_version == 1): %}
meta cgroup {{ cgroup_id }} counter return
{% elif (cgroups_version == 2): %}
socket cgroupv2 level 2 "services/{{ cgroup_name }}" counter return
{% endif %}
fib daddr type { local, broadcast, anycast, multicast } counter return
ct direction reply counter return
ip daddr @reserved_ip counter return
ip6 daddr @reserved_ip6 counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta nfproto ipv6 meta l4proto . th dport != @proxy_dport counter return
meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip6 dscp @bypass_dscp counter return
{% if (length(dns_hijack_nfproto) > 0): %}
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return
{% endif %}
{% if (tcp_mode == 'tproxy'): %}
meta nfproto @proxy_nfproto meta l4proto tcp jump router_tproxy
{% elif (tcp_mode == 'tun'): %}
meta nfproto @proxy_nfproto meta l4proto tcp jump router_tun
{% endif %}
{% if (udp_mode == 'tproxy'): %}
meta nfproto @proxy_nfproto meta l4proto udp jump router_tproxy
{% elif (udp_mode == 'tun'): %}
meta nfproto @proxy_nfproto meta l4proto udp jump router_tun
{% endif %}
}
chain mangle_prerouting_router {
type filter hook prerouting priority mangle - 1; policy accept;
{% if (tcp_mode == 'tproxy' || udp_mode == 'tproxy'): %}
iifname lo meta l4proto { tcp, udp } meta mark & {{ tproxy_fw_mask }} == {{ tproxy_fw_mark }} tproxy to :{{ tproxy_port }} counter accept
{% endif %}
{% if (tcp_mode == 'tun' || udp_mode == 'tun'): %}
iifname "{{ tun_device }}" meta l4proto { icmp, tcp, udp } counter accept
{% endif %}
}
{% endif %}
{% if (lan_proxy): %}
chain dstnat {
type nat hook prerouting priority dstnat + 1; policy accept;
{% if (length(dns_hijack_nfproto) > 0): %}
iifname @lan_inbound_device meta nfproto @dns_hijack_nfproto jump lan_dns_hijack
{% endif %}
{% if (tcp_mode == 'redirect'): %}
fib daddr type { local, broadcast, anycast, multicast } counter return
ct direction reply counter return
ip daddr @reserved_ip counter return
ip6 daddr @reserved_ip6 counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta nfproto ipv6 meta l4proto . th dport != @proxy_dport counter return
meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip6 dscp @bypass_dscp counter return
iifname @lan_inbound_device meta nfproto @proxy_nfproto jump lan_redirect
{% endif %}
{% if (fake_ip_ping_hijack): %}
{% if (fake_ip_range): %}
icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect
{% endif %}
{% endif %}
}
chain mangle_prerouting_lan {
type filter hook prerouting priority mangle; policy accept;
fib daddr type { local, broadcast, anycast, multicast } counter return
ct direction reply counter return
ip daddr @reserved_ip counter return
ip6 daddr @reserved_ip6 counter return
ip daddr @china_ip counter return
ip6 daddr @china_ip6 counter return
meta nfproto ipv4 meta l4proto . th dport != @proxy_dport {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta nfproto ipv6 meta l4proto . th dport != @proxy_dport counter return
meta l4proto { tcp, udp } ip dscp @bypass_dscp {% if (fake_ip_range): %} ip daddr != {{ fake_ip_range }} {% endif %} counter return
meta l4proto { tcp, udp } ip6 dscp @bypass_dscp counter return
{% if (length(dns_hijack_nfproto) > 0): %}
meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return
{% endif %}
{% if (tcp_mode == 'tproxy'): %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto tcp jump lan_tproxy
{% elif (tcp_mode == 'tun'): %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto tcp jump lan_tun
{% endif %}
{% if (udp_mode == 'tproxy'): %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto udp jump lan_tproxy
{% elif (udp_mode == 'tun'): %}
iifname @lan_inbound_device meta nfproto @proxy_nfproto meta l4proto udp jump lan_tun
{% endif %}
}
{% endif %}
}
{% if (bypass_china_mainland_ip): %}
include "/etc/nikki/nftables/geoip_cn.nft"
{% endif %}
{% if (bypass_china_mainland_ip6): %}
include "/etc/nikki/nftables/geoip6_cn.nft"
{% endif %}

View File

@@ -0,0 +1,87 @@
import { readfile, popen } from 'fs';
export function uci_bool(obj) {
return obj == null ? null : obj == '1';
};
export function uci_int(obj) {
return obj == null ? null : int(obj);
};
export function uci_array(obj) {
if (obj == null) {
return [];
}
if (type(obj) == 'array') {
return uniq(obj);
}
return [obj];
};
export function trim_all(obj) {
if (obj == null) {
return null;
}
if (type(obj) == 'string') {
if (length(obj) == 0) {
return null;
}
return obj;
}
if (type(obj) == 'array') {
if (length(obj) == 0) {
return null;
}
return obj;
}
if (type(obj) == 'object') {
const obj_keys = keys(obj);
for (let key in obj_keys) {
obj[key] = trim_all(obj[key]);
if (obj[key] == null) {
delete obj[key];
}
}
if (length(keys(obj)) == 0) {
return null;
}
return obj;
}
return obj;
};
export function get_cgroups_version() {
return system('mount | grep -q -w "^cgroup"') == 0 ? 1 : 2;
};
export function get_users() {
return map(split(readfile('/etc/passwd'), '\n'), (x) => split(x, ':')[0]);
};
export function get_groups() {
return map(split(readfile('/etc/group'), '\n'), (x) => split(x, ':')[0]);
};
export function get_cgroups() {
const result = [];
if (get_cgroups_version() == 2) {
const cgroup_path = '/sys/fs/cgroup/';
const process = popen(`find ${cgroup_path} -type d -mindepth 1`);
if (process) {
for (let line = process.read('line'); length(line); line = process.read('line')) {
push(result, substr(trim(line), length(cgroup_path)));
}
}
}
return result;
};
export function load_profile() {
let result = {};
const process = popen('yq -M -p yaml -o json /etc/nikki/run/config.yaml');
if (process) {
result = json(process);
process.close();
}
return result;
};

192
nikki/files/ucode/mixin.uc Normal file
View File

@@ -0,0 +1,192 @@
#!/usr/bin/ucode
'use strict';
import { cursor } from 'uci';
import { connect } from 'ubus';
import { uci_bool, uci_int, uci_array, trim_all } from '/etc/nikki/ucode/include.uc';
const uci = cursor();
const ubus = connect();
const config = {};
const outbound_interface = uci.get('nikki', 'mixin', 'outbound_interface');
const outbound_interface_status = ubus.call('network.interface', 'status', { 'interface': outbound_interface });
const outbound_device = outbound_interface_status?.l3_device ?? outbound_interface_status?.device ?? '';
config['log-level'] = uci.get('nikki', 'mixin', 'log_level');
config['mode'] = uci.get('nikki', 'mixin', 'mode');
config['find-process-mode'] = uci.get('nikki', 'mixin', 'match_process');
config['interface-name'] = outbound_device;
config['ipv6'] = uci_bool(uci.get('nikki', 'mixin', 'ipv6'));
config['unified-delay'] = uci_bool(uci.get('nikki', 'mixin', 'unify_delay'));
config['tcp-concurrent'] = uci_bool(uci.get('nikki', 'mixin', 'tcp_concurrent'));
config['disable-keep-alive'] = uci_bool(uci.get('nikki', 'mixin', 'disable_tcp_keep_alive'));
config['keep-alive-idle'] = uci_int(uci.get('nikki', 'mixin', 'tcp_keep_alive_idle'));
config['keep-alive-interval'] = uci_int(uci.get('nikki', 'mixin', 'tcp_keep_alive_interval'));
config['global-client-fingerprint'] = uci.get('nikki', 'mixin', 'global_client_fingerprint');
config['external-ui'] = uci.get('nikki', 'mixin', 'ui_path');
config['external-ui-name'] = uci.get('nikki', 'mixin', 'ui_name');
config['external-ui-url'] = uci.get('nikki', 'mixin', 'ui_url');
config['external-controller'] = uci.get('nikki', 'mixin', 'api_listen');
config['secret'] = uci.get('nikki', 'mixin', 'api_secret');
config['allow-lan'] = uci_bool(uci.get('nikki', 'mixin', 'allow_lan'));
config['port'] = uci_int(uci.get('nikki', 'mixin', 'http_port'));
config['socks-port'] = uci_int(uci.get('nikki', 'mixin', 'socks_port'));
config['mixed-port'] = uci_int(uci.get('nikki', 'mixin', 'mixed_port'));
config['redir-port'] = uci_int(uci.get('nikki', 'mixin', 'redir_port'));
config['tproxy-port'] = uci_int(uci.get('nikki', 'mixin', 'tproxy_port'));
if (uci_bool(uci.get('nikki', 'mixin', 'authentication'))) {
config['authentication'] = [];
uci.foreach('nikki', 'authentication', (section) => {
if (!uci_bool(section.enabled)) {
return;
}
push(config['authentication'], `${section.username}:${section.password}`);
});
}
config['tun'] = {};
config['tun']['enable'] = uci_bool(uci.get('nikki', 'mixin', 'tun_enabled'));
config['tun']['device'] = uci.get('nikki', 'mixin', 'tun_device');
config['tun']['stack'] = uci.get('nikki', 'mixin', 'tun_stack');
config['tun']['mtu'] = uci_int(uci.get('nikki', 'mixin', 'tun_mtu'));
config['tun']['gso'] = uci_bool(uci.get('nikki', 'mixin', 'tun_gso'));
config['tun']['gso-max-size'] = uci_int(uci.get('nikki', 'mixin', 'tun_gso_max_size'));
if (uci_bool(uci.get('nikki', 'mixin', 'tun_dns_hijack'))) {
config['tun']['dns-hijack'] = uci_array(uci.get('nikki', 'mixin', 'tun_dns_hijacks'));
}
if (uci_bool(uci.get('nikki', 'proxy', 'enabled'))) {
config['tun']['auto-route'] = false;
config['tun']['auto-redirect'] = false;
config['tun']['auto-detect-interface'] = false;
}
config['dns'] = {};
config['dns']['enable'] = uci_bool(uci.get('nikki', 'mixin', 'dns_enabled'));
config['dns']['listen'] = uci.get('nikki', 'mixin', 'dns_listen');
config['dns']['ipv6'] = uci_bool(uci.get('nikki', 'mixin', 'dns_ipv6'));
config['dns']['enhanced-mode'] = uci.get('nikki', 'mixin', 'dns_mode');
config['dns']['fake-ip-range'] = uci.get('nikki', 'mixin', 'fake_ip_range');
if (uci_bool(uci.get('nikki', 'mixin', 'fake_ip_filter'))) {
config['dns']['fake-ip-filter'] = uci_array(uci.get('nikki', 'mixin', 'fake_ip_filters'));
}
config['dns']['fake-ip-filter-mode'] = uci.get('nikki', 'mixin', 'fake_ip_filter_mode');
config['dns']['respect-rules'] = uci_bool(uci.get('nikki', 'mixin', 'dns_respect_rules'));
config['dns']['prefer-h3'] = uci_bool(uci.get('nikki', 'mixin', 'dns_doh_prefer_http3'));
config['dns']['use-system-hosts'] = uci_bool(uci.get('nikki', 'mixin', 'dns_system_hosts'));
config['dns']['use-hosts'] = uci_bool(uci.get('nikki', 'mixin', 'dns_hosts'));
if (uci_bool(uci.get('nikki', 'mixin', 'hosts'))) {
config['hosts'] = {};
uci.foreach('nikki', 'hosts', (section) => {
if (!uci_bool(section.enabled)) {
return;
}
config['hosts'][section.domain_name] = uci_array(section.ip);
});
}
if (uci_bool(uci.get('nikki', 'mixin', 'dns_nameserver'))) {
config['dns']['default-nameserver'] = [];
config['dns']['proxy-server-nameserver'] = [];
config['dns']['direct-nameserver'] = [];
config['dns']['nameserver'] = [];
config['dns']['fallback'] = [];
uci.foreach('nikki', 'nameserver', (section) => {
if (!uci_bool(section.enabled)) {
return;
}
push(config['dns'][section.type], ...uci_array(section.nameserver));
})
}
if (uci_bool(uci.get('nikki', 'mixin', 'dns_nameserver_policy'))) {
config['dns']['nameserver-policy'] = {};
uci.foreach('nikki', 'nameserver_policy', (section) => {
if (!uci_bool(section.enabled)) {
return;
}
config['dns']['nameserver-policy'][section.matcher] = uci_array(section.nameserver);
});
}
config['sniffer'] = {};
config['sniffer']['enable'] = uci_bool(uci.get('nikki', 'mixin', 'sniffer'));
config['sniffer']['force-dns-mapping'] = uci_bool(uci.get('nikki', 'mixin', 'sniffer_sniff_dns_mapping'));
config['sniffer']['parse-pure-ip'] = uci_bool(uci.get('nikki', 'mixin', 'sniffer_sniff_pure_ip'));
if (uci_bool(uci.get('nikki', 'mixin', 'sniffer_force_domain_name'))) {
config['sniffer']['force-domain'] = uci_array(uci.get('nikki', 'mixin', 'sniffer_force_domain_names'));
}
if (uci_bool(uci.get('nikki', 'mixin', 'sniffer_ignore_domain_name'))) {
config['sniffer']['skip-domain'] = uci_array(uci.get('nikki', 'mixin', 'sniffer_ignore_domain_names'));
}
if (uci_bool(uci.get('nikki', 'mixin', 'sniffer_sniff'))) {
config['sniffer']['sniff'] = {};
config['sniffer']['sniff']['HTTP'] = {};
config['sniffer']['sniff']['TLS'] = {};
config['sniffer']['sniff']['QUIC'] = {};
uci.foreach('nikki', 'sniff', (section) => {
if (!uci_bool(section.enabled)) {
return;
}
config['sniffer']['sniff'][section.protocol]['port'] = uci_array(section.port);
config['sniffer']['sniff'][section.protocol]['override-destination'] = uci_bool(section.overwrite_destination);
});
}
config['profile'] = {};
config['profile']['store-selected'] = uci_bool(uci.get('nikki', 'mixin', 'selection_cache'));
config['profile']['store-fake-ip'] = uci_bool(uci.get('nikki', 'mixin', 'fake_ip_cache'));
if (uci_bool(uci.get('nikki', 'mixin', 'rule_provider'))) {
config['rule-providers'] = {};
uci.foreach('nikki', 'rule_provider', (section) => {
if (!uci_bool(section.enabled)) {
return;
}
if (section.type == 'http') {
config['rule-providers'][section.name] = {
type: section.type,
url: section.url,
proxy: section.node,
size_limit: section.file_size_limit,
format: section.file_format,
behavior: section.behavior,
interval: section.update_interval,
}
} else if (section.type == 'file') {
config['rule-providers'][section.name] = {
type: section.type,
path: section.file_path,
format: section.file_format,
behavior: section.behavior,
}
}
})
}
if (uci_bool(uci.get('nikki', 'mixin', 'rule'))) {
config['nikki-rules'] = [];
uci.foreach('nikki', 'rule', (section) => {
if (!uci_bool(section.enabled)) {
return;
}
const rule = [ section.type, section.matcher, section.node, uci_bool(section.no_resolve) ? 'no-resolve' : null ];
push(config['nikki-rules'], join(',', filter(rule, (item) => item != null && item != '')));
})
}
const geoip_format = uci.get('nikki', 'mixin', 'geoip_format');
config['geodata-mode'] = geoip_format == null ? null : geoip_format == 'dat';
config['geodata-loader'] = uci.get('nikki', 'mixin', 'geodata_loader');
config['geox-url'] = {};
config['geox-url']['geosite'] = uci.get('nikki', 'mixin', 'geosite_url');
config['geox-url']['mmdb'] = uci.get('nikki', 'mixin', 'geoip_mmdb_url');
config['geox-url']['geoip'] = uci.get('nikki', 'mixin', 'geoip_dat_url');
config['geox-url']['asn'] = uci.get('nikki', 'mixin', 'geoip_asn_url');
config['geo-auto-update'] = uci_bool(uci.get('nikki', 'mixin', 'geox_auto_update'));
config['geo-update-interval'] = uci_int(uci.get('nikki', 'mixin', 'geox_update_interval'));
print(trim_all(config));