diff --git a/luci-app-nikki/Makefile b/luci-app-nikki/Makefile index 9e9f0b4..64bf375 100644 --- a/luci-app-nikki/Makefile +++ b/luci-app-nikki/Makefile @@ -1,6 +1,6 @@ include $(TOPDIR)/rules.mk -PKG_VERSION:=1.23.3 +PKG_VERSION:=1.24.0 LUCI_TITLE:=LuCI Support for nikki LUCI_DEPENDS:=+luci-base +nikki diff --git a/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js b/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js index 7e7caa7..57d8e39 100644 --- a/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js +++ b/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js @@ -65,8 +65,6 @@ const appLogPath = `${logDir}/app.log`; const coreLogPath = `${logDir}/core.log`; const debugLogPath = `${logDir}/debug.log`; const nftDir = `${homeDir}/nftables`; -const reservedIPNFT = `${nftDir}/reserved_ip.nft`; -const reservedIP6NFT = `${nftDir}/reserved_ip6.nft`; return baseclass.extend({ homeDir: homeDir, @@ -80,8 +78,6 @@ return baseclass.extend({ appLogPath: appLogPath, coreLogPath: coreLogPath, debugLogPath: debugLogPath, - reservedIPNFT: reservedIPNFT, - reservedIP6NFT: reservedIP6NFT, status: async function () { return (await callRCList('nikki'))?.nikki?.running; diff --git a/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js b/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js index 37ff4ac..674b3f2 100644 --- a/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js +++ b/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js @@ -47,8 +47,6 @@ return view.extend({ o.value(nikki.mixinFilePath, _('File for Mixin')); o.value(nikki.runProfilePath, _('Profile for Startup')); - o.value(nikki.reservedIPNFT, _('File for Reserved IP')); - o.value(nikki.reservedIP6NFT, _('File for Reserved IP6')); o.write = function (section_id, formvalue) { return true; diff --git a/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js b/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js index 7004ab9..e76d51f 100644 --- a/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js +++ b/luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js @@ -117,10 +117,9 @@ return view.extend({ o.value('https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD'); o.value('https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord'); - o = s.taboption('external_control', form.Value, 'api_listen', '*' + ' ' + _('API Listen')); + o = s.taboption('external_control', form.Value, 'api_listen', _('API Listen')); o.datatype = 'ipaddrport(1)'; o.placeholder = _('Unmodified'); - o.rmempty = false; o = s.taboption('external_control', form.Value, 'api_secret', _('API Secret')); o.password = true; @@ -152,15 +151,13 @@ return view.extend({ o.datatype = 'port'; o.placeholder = _('Unmodified'); - o = s.taboption('inbound', form.Value, 'redir_port', '*' + ' ' + _('Redirect Port')); + o = s.taboption('inbound', form.Value, 'redir_port', _('Redirect Port')); o.datatype = 'port'; o.placeholder = _('Unmodified'); - o.rmempty = false; - o = s.taboption('inbound', form.Value, 'tproxy_port', '*' + ' ' + _('TPROXY Port')); + o = s.taboption('inbound', form.Value, 'tproxy_port', _('TPROXY Port')); o.datatype = 'port'; o.placeholder = _('Unmodified'); - o.rmempty = false; o = s.taboption('inbound', form.Flag, 'authentication', _('Overwrite Authentication')); o.rmempty = false; @@ -185,9 +182,14 @@ return view.extend({ s.tab('tun', _('TUN Config')); - o = s.taboption('tun', form.Value, 'tun_device', '*' + ' ' + _('Device Name')); + o = s.taboption('tun', form.ListValue, 'tun_enabled', _('Enable')); + o.optional = true; + o.placeholder = _('Unmodified'); + o.value('0', _('Disable')); + o.value('1', _('Enable')); + + o = s.taboption('tun', form.Value, 'tun_device', _('Device Name')); o.placeholder = _('Unmodified'); - o.rmempty = false; o = s.taboption('tun', form.ListValue, 'tun_stack', _('Stack')); o.optional = true; @@ -227,10 +229,15 @@ return view.extend({ s.tab('dns', _('DNS Config')); - o = s.taboption('dns', form.Value, 'dns_listen', '*' + ' ' + _('DNS Listen')); + o = s.taboption('dns', form.ListValue, 'dns_enabled', _('Enable')); + o.optional = true; + o.placeholder = _('Unmodified'); + o.value('0', _('Disable')); + o.value('1', _('Enable')); + + o = s.taboption('dns', form.Value, 'dns_listen', _('DNS Listen')); o.datatype = 'ipaddrport(1)'; o.placeholder = _('Unmodified'); - o.rmempty = false; o = s.taboption('dns', form.ListValue, 'dns_ipv6', 'IPv6'); o.optional = true; @@ -238,15 +245,15 @@ return view.extend({ o.value('0', _('Disable')); o.value('1', _('Enable')); - o = s.taboption('dns', form.ListValue, 'dns_mode', '*' + ' ' + _('DNS Mode')); + o = s.taboption('dns', form.ListValue, 'dns_mode', _('DNS Mode')); + o.optional = true; o.placeholder = _('Unmodified'); o.value('redir-host', 'Redir-Host'); o.value('fake-ip', 'Fake-IP'); - o = s.taboption('dns', form.Value, 'fake_ip_range', '*' + ' ' + _('Fake-IP Range')); + o = s.taboption('dns', form.Value, 'fake_ip_range', _('Fake-IP Range')); o.datatype = 'cidr4'; o.placeholder = _('Unmodified'); - o.rmempty = false; o = s.taboption('dns', form.Flag, 'fake_ip_filter', _('Overwrite Fake-IP Filter')); o.rmempty = false; diff --git a/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js b/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js index 4d2cd67..42a5a0a 100644 --- a/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js +++ b/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js @@ -132,6 +132,7 @@ return view.extend({ so.rmempty = false; so = o.subsection.option(form.DynamicList, 'ip', 'IP'); + so.datatype = 'ip4addr'; for (const mac in hosts) { const host = hosts[mac]; @@ -142,6 +143,7 @@ return view.extend({ }; so = o.subsection.option(form.DynamicList, 'ip6', 'IP6'); + so.datatype = 'ip6addr'; for (const mac in hosts) { const host = hosts[mac]; @@ -152,6 +154,7 @@ return view.extend({ }; so = o.subsection.option(form.DynamicList, 'mac', 'MAC'); + so.datatype = 'macaddr'; for (const mac in hosts) { const host = hosts[mac]; diff --git a/nikki/Makefile b/nikki/Makefile index f8510f8..88ff817 100644 --- a/nikki/Makefile +++ b/nikki/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=nikki PKG_VERSION:=2025.07.27 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) @@ -43,8 +43,6 @@ endef define Package/nikki/conffiles /etc/config/nikki /etc/nikki/mixin.yaml -/etc/nikki/nftables/reserved_ip.nft -/etc/nikki/nftables/reserved_ip6.nft endef define Package/nikki/install @@ -71,8 +69,6 @@ define Package/nikki/install $(INSTALL_BIN) $(CURDIR)/files/scripts/firewall_include.sh $(1)/etc/nikki/scripts/firewall_include.sh $(INSTALL_BIN) $(CURDIR)/files/scripts/debug.sh $(1)/etc/nikki/scripts/debug.sh - $(INSTALL_BIN) $(CURDIR)/files/nftables/reserved_ip.nft $(1)/etc/nikki/nftables/reserved_ip.nft - $(INSTALL_BIN) $(CURDIR)/files/nftables/reserved_ip6.nft $(1)/etc/nikki/nftables/reserved_ip6.nft $(INSTALL_BIN) $(CURDIR)/files/nftables/geoip_cn.nft $(1)/etc/nikki/nftables/geoip_cn.nft $(INSTALL_BIN) $(CURDIR)/files/nftables/geoip6_cn.nft $(1)/etc/nikki/nftables/geoip6_cn.nft diff --git a/nikki/files/nftables/reserved_ip.nft b/nikki/files/nftables/reserved_ip.nft deleted file mode 100644 index 012938f..0000000 --- a/nikki/files/nftables/reserved_ip.nft +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/sbin/nft -f - -table inet nikki { - set reserved_ip { - type ipv4_addr - flags interval - elements = { - 0.0.0.0/8, - 10.0.0.0/8, - 127.0.0.0/8, - 100.64.0.0/10, - 169.254.0.0/16, - 172.16.0.0/12, - 192.168.0.0/16, - 224.0.0.0/4, - 240.0.0.0/4 - } - } -} diff --git a/nikki/files/nftables/reserved_ip6.nft b/nikki/files/nftables/reserved_ip6.nft deleted file mode 100644 index 0fb0715..0000000 --- a/nikki/files/nftables/reserved_ip6.nft +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/sbin/nft -f - -table inet nikki { - set reserved_ip6 { - type ipv6_addr - flags interval - elements = { - ::/128, - ::1/128, - ::ffff:0:0/96, - 100::/64, - 64:ff9b::/96, - 2001::/32, - 2001:10::/28, - 2001:20::/28, - 2001:db8::/32, - 2002::/16, - fc00::/7, - fe80::/10, - ff00::/8 - } - } -} diff --git a/nikki/files/nikki.conf b/nikki/files/nikki.conf index 3ac304d..7e71b12 100644 --- a/nikki/files/nikki.conf +++ b/nikki/files/nikki.conf @@ -11,24 +11,11 @@ config config 'config' option 'fast_reload' '0' option 'core_only' '0' -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 'bypass_dscp' '4' - option 'bypass_china_mainland_ip' '0' - option 'proxy_tcp_dport' '0-65535' - option 'proxy_udp_dport' '0-65535' - option 'tun_timeout' '30' - option 'tun_interval' '1' +config env 'env' + option 'disable_loopback_detector' '0' + option 'disable_quic_go_gso' '0' + option 'disable_quic_go_ecn' '0' + option 'skip_system_ipv6_check' '0' config subscription 'subscription' option 'name' 'default' @@ -74,56 +61,6 @@ config mixin 'mixin' option 'rule_provider' '0' option 'mixin_file_content' '0' -config env 'env' - option 'disable_loopback_detector' '0' - option 'disable_quic_go_gso' '0' - option 'disable_quic_go_ecn' '0' - option 'skip_system_ipv6_check' '0' - -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 'tun_fw_mark' '0x81' - 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 authentication option 'enabled' '1' option 'username' 'nikki' @@ -192,6 +129,91 @@ config sniff 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 '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 'tun_fw_mark' '0x81' + 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' diff --git a/nikki/files/nikki.init b/nikki/files/nikki.init index 1cb213e..4a6554c 100644 --- a/nikki/files/nikki.init +++ b/nikki/files/nikki.init @@ -52,6 +52,13 @@ start_service() { config_get_bool test_profile "config" "test_profile" 0 config_get_bool fast_reload "config" "fast_reload" 0 config_get_bool core_only "config" "core_only" 0 + ## environment variable + local safe_paths disable_loopback_detector disable_quic_go_gso disable_quic_go_ecn skip_system_ipv6_check + config_get safe_paths "env" "safe_paths" + config_get_bool disable_loopback_detector "env" "disable_loopback_detector" 0 + config_get_bool disable_quic_go_gso "env" "disable_quic_go_gso" 0 + config_get_bool disable_quic_go_ecn "env" "disable_quic_go_ecn" 0 + config_get_bool skip_system_ipv6_check "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 @@ -67,13 +74,13 @@ start_service() { ### mixin file content local mixin_file_content config_get_bool mixin_file_content "mixin" "mixin_file_content" 0 - ## environment variable - local safe_paths disable_loopback_detector disable_quic_go_gso disable_quic_go_ecn skip_system_ipv6_check - config_get safe_paths "env" "safe_paths" - config_get_bool disable_loopback_detector "env" "disable_loopback_detector" 0 - config_get_bool disable_quic_go_gso "env" "disable_quic_go_gso" 0 - config_get_bool disable_quic_go_ecn "env" "disable_quic_go_ecn" 0 - config_get_bool skip_system_ipv6_check "env" "skip_system_ipv6_check" 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) @@ -144,6 +151,43 @@ start_service() { 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) | .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"))' "$RUN_PROFILE_PATH"); 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")' "$RUN_PROFILE_PATH"); 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")' "$RUN_PROFILE_PATH"); 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"))' "$RUN_PROFILE_PATH"); 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..." @@ -204,10 +248,6 @@ service_started() { ## app config local core_only config_get_bool core_only "config" "core_only" 0 - ## mixin - ### tun - local tun_device - config_get tun_device "mixin" "tun_device" "nikki" ## proxy config ### general local tcp_mode udp_mode ipv4_proxy ipv6_proxy tun_timeout tun_interval @@ -236,6 +276,7 @@ service_started() { 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." @@ -243,11 +284,9 @@ service_started() { if [ "$tun_enable" = 1 ]; then log "Proxy" "Waiting for tun device online within $tun_timeout seconds..." while [ "$tun_timeout" -gt 0 ]; do - if (ip link show dev "$tun_device" > /dev/null 2>&1); then - if [ "$(ip -json addr show dev "$tun_device" | tun_device="$tun_device" yq -M '.[] | select(.ifname = strenv(tun_device)) | .addr_info | length')" -gt 0 ]; then - log "Proxy" "TUN device is online." - break - fi + if (ip -j link show dev "$tun_device" | 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" @@ -354,11 +393,11 @@ cleanup() { # delete hijack nft delete table inet nikki > /dev/null 2>&1 local handles handle - handles=$(nft --json list table inet fw4 | yq -M '.nftables[] | select(has("rule")) | .rule | select(.chain == "input" and .comment == "nikki") | .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 | yq -M '.nftables[] | select(has("rule")) | .rule | select(.chain == "forward" and .comment == "nikki") | .handle') + 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 diff --git a/nikki/files/scripts/debug.sh b/nikki/files/scripts/debug.sh index aaecb74..2bcfebe 100644 --- a/nikki/files/scripts/debug.sh +++ b/nikki/files/scripts/debug.sh @@ -49,8 +49,8 @@ const config = uci.get_all("nikki"); const result = {}; for (let section_id in config) { - const section = config[section_id]; - const section_type = section[".type"]; + const section = config[section_id]; + const section_type = section[".type"]; if (result[section_type] == null) { result[section_type] = []; } diff --git a/nikki/files/scripts/firewall_include.sh b/nikki/files/scripts/firewall_include.sh index f270f0e..2a237de 100644 --- a/nikki/files/scripts/firewall_include.sh +++ b/nikki/files/scripts/firewall_include.sh @@ -9,10 +9,10 @@ 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" -config_get tun_device "mixin" "tun_device" 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" diff --git a/nikki/files/scripts/include.sh b/nikki/files/scripts/include.sh index 918c139..bff99a3 100644 --- a/nikki/files/scripts/include.sh +++ b/nikki/files/scripts/include.sh @@ -36,8 +36,6 @@ FIREWALL_INCLUDE_SH="$SH_DIR/firewall_include.sh" # nftables NFT_DIR="$HOME_DIR/nftables" -RESERVED_IP_NFT="$NFT_DIR/reserved_ip.nft" -RESERVED_IP6_NFT="$NFT_DIR/reserved_ip6.nft" GEOIP_CN_NFT="$NFT_DIR/geoip_cn.nft" GEOIP6_CN_NFT="$NFT_DIR/geoip6_cn.nft" diff --git a/nikki/files/uci-defaults/migrate.sh b/nikki/files/uci-defaults/migrate.sh index f17dff2..afa3e3b 100644 --- a/nikki/files/uci-defaults/migrate.sh +++ b/nikki/files/uci-defaults/migrate.sh @@ -156,6 +156,35 @@ uci show nikki | grep -o -E 'nikki.@lan_access_control\[[[:digit:]]+\]=lan_acces [ -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 +} + # commit uci commit nikki diff --git a/nikki/files/ucode/hijack.ut b/nikki/files/ucode/hijack.ut index dd37443..15c1487 100644 --- a/nikki/files/ucode/hijack.ut +++ b/nikki/files/ucode/hijack.ut @@ -5,7 +5,7 @@ import { cursor } from 'uci'; import { connect } from 'ubus'; - import { uci_bool, uci_array, get_cgroups_version, get_users, get_groups, get_cgroups } from '/etc/nikki/ucode/include.uc'; + import { uci_bool, uci_array, get_cgroups_version, get_users, get_groups, get_cgroups, load_profile } from '/etc/nikki/ucode/include.uc'; const cgroups_version = get_cgroups_version(); @@ -16,16 +16,18 @@ const uci = cursor(); const ubus = connect(); - uci.load('nikki'); + const profile = load_profile(); - const redir_port = uci.get('nikki', 'mixin', 'redir_port'); - const tproxy_port = uci.get('nikki', 'mixin', 'tproxy_port'); + const redir_port = profile['redir-port']; + const tproxy_port = profile['tproxy-port']; - const dns_listen = uci.get('nikki', 'mixin', 'dns_listen'); + const dns_listen = profile['dns']['listen']; const dns_port = substr(dns_listen, rindex(dns_listen, ':') + 1); - const fake_ip_range = uci.get('nikki', 'mixin', 'fake_ip_range'); + const fake_ip_range = profile['dns']['fake-ip-range']; - const tun_device = uci.get('nikki', 'mixin', 'tun_device'); + const 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'); @@ -68,6 +70,8 @@ push(lan_access_control, access_control); }); + const reserved_ip = uci_array(uci.get('momo', 'proxy', 'reserved_ip')); + const reserved_ip6 = uci_array(uci.get('momo', '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 proxy_tcp_dport = split((uci.get('nikki', 'proxy', 'proxy_tcp_dport') ?? '0-65535'), ' '); @@ -128,12 +132,22 @@ table inet nikki { 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 { @@ -180,6 +194,7 @@ table inet nikki { } {% 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']): %} @@ -205,7 +220,9 @@ table inet nikki { {% endif %} {% endfor %} } + {% endif %} + {% if (tcp_mode == 'redirect'): %} chain router_redirect { {% for (let access_control in router_access_control): %} {% if (access_control['enabled']): %} @@ -231,7 +248,9 @@ table inet nikki { {% 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']): %} @@ -257,7 +276,9 @@ table inet nikki { {% 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']): %} @@ -284,8 +305,10 @@ table inet nikki { {% 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']): %} @@ -309,7 +332,9 @@ table inet nikki { {% endif %} {% endfor %} } + {% endif %} + {% if (tcp_mode == 'redirect'): %} chain lan_redirect { {% for (let access_control in lan_access_control): %} {% if (access_control['enabled']): %} @@ -333,7 +358,9 @@ table inet nikki { {% 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']): %} @@ -357,7 +384,9 @@ table inet nikki { {% 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']): %} @@ -382,6 +411,7 @@ table inet nikki { {% endfor %} } {% endif %} + {% endif %} {% if (router_proxy): %} chain nat_output { @@ -399,14 +429,16 @@ table inet nikki { 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 ip daddr != {{ fake_ip_range }} 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 ip daddr != {{ fake_ip_range }} 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): %} - ip protocol icmp icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect + {% if (fake_ip_range ): %} + icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect + {% endif %} {% endif %} } @@ -423,9 +455,9 @@ table inet nikki { 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 ip daddr != {{ fake_ip_range }} 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 ip daddr != {{ fake_ip_range }} 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 @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return {% if (tcp_mode == 'tproxy'): %} @@ -462,14 +494,16 @@ table inet nikki { 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 ip daddr != {{ fake_ip_range }} 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 ip daddr != {{ fake_ip_range }} 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): %} - ip protocol icmp icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect + {% if (fake_ip_range): %} + icmp type echo-request ip daddr {{ fake_ip_range }} counter redirect + {% endif %} {% endif %} } @@ -481,9 +515,9 @@ table inet nikki { 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 ip daddr != {{ fake_ip_range }} 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 ip daddr != {{ fake_ip_range }} 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 @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter return {% if (tcp_mode == 'tproxy'): %} @@ -500,9 +534,6 @@ table inet nikki { {% endif %} } -include "/etc/nikki/nftables/reserved_ip.nft" -include "/etc/nikki/nftables/reserved_ip6.nft" - {% if (bypass_china_mainland_ip): %} include "/etc/nikki/nftables/geoip_cn.nft" include "/etc/nikki/nftables/geoip6_cn.nft" diff --git a/nikki/files/ucode/include.uc b/nikki/files/ucode/include.uc index a6411f2..97f824f 100644 --- a/nikki/files/ucode/include.uc +++ b/nikki/files/ucode/include.uc @@ -74,4 +74,14 @@ export function get_cgroups() { } } 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; }; \ No newline at end of file diff --git a/nikki/files/ucode/mixin.uc b/nikki/files/ucode/mixin.uc index e716573..9d17cf5 100644 --- a/nikki/files/ucode/mixin.uc +++ b/nikki/files/ucode/mixin.uc @@ -51,26 +51,24 @@ if (uci_bool(uci.get('nikki', 'mixin', 'authentication'))) { } config['tun'] = {}; -if (uci.get('nikki', 'proxy', 'tcp_mode') == 'tun' || uci.get('nikki', 'proxy', 'udp_mode') == 'tun') { - config['tun']['enable'] = true; +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')); +config['tun']['endpoint-independent-nat'] = uci_bool(uci.get('nikki', 'mixin', 'tun_endpoint_independent_nat')); +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['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')); - config['tun']['endpoint-independent-nat'] = uci_bool(uci.get('nikki', 'mixin', 'tun_endpoint_independent_nat')); - if (uci_bool(uci.get('nikki', 'mixin', 'tun_dns_hijack'))) { - config['tun']['dns-hijack'] = uci_array(uci.get('nikki', 'mixin', 'tun_dns_hijacks')); - } -} else { - config['tun']['enable'] = false; } config['dns'] = {}; -config['dns']['enable'] = true; +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'); @@ -182,7 +180,7 @@ if (uci_bool(uci.get('nikki', 'mixin', 'rule'))) { } const geoip_format = uci.get('nikki', 'mixin', 'geoip_format'); -config['geodata-mode'] = geoip_format == null ? null : geoip_format == 'dat'; +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');