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 dc1fd7ca..faaf71e5 100644 --- a/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js +++ b/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js @@ -38,6 +38,12 @@ const callNikkiUpdateSubscription = rpc.declare({ expect: { '': {} } }); +const callNikkiGetIdentifiers = rpc.declare({ + object: 'luci.nikki', + method: 'get_identifiers', + expect: { '': {} } +}); + const callNikkiDebug = rpc.declare({ object: 'luci.nikki', method: 'debug', @@ -136,6 +142,10 @@ return baseclass.extend({ return this.api('POST', '/upgrade/ui'); }, + getIdentifiers: function () { + return callNikkiGetIdentifiers(); + }, + listProfiles: function () { return L.resolveDefault(fs.list(this.profilesDir), []); }, @@ -167,16 +177,4 @@ return baseclass.extend({ debug: function () { return callNikkiDebug(); }, - - getUsers: function () { - return fs.lines('/etc/passwd').then(function (lines) { - return lines.map(function (line) { return line.split(/:/)[0] }).filter(function (user) { return user !== 'root' && user !== 'nikki' }); - }); - }, - - getGroups: function () { - return fs.lines('/etc/group').then(function (lines) { - return lines.map(function (line) { return line.split(/:/)[0] }).filter(function (group) { return group !== 'root' && group !== 'nikki' }); - }); - }, }) 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 9c931c10..aeb409d6 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 @@ -12,15 +12,15 @@ return view.extend({ uci.load('nikki'), network.getHostHints(), network.getNetworks(), - nikki.getUsers(), - nikki.getGroups() + nikki.getIdentifiers(), ]); }, render: function (data) { const hosts = data[1].hosts; const networks = data[2]; - const users = data[3]; - const groups = data[4]; + const users = data[3]?.users ?? []; + const groups = data[3]?.groups ?? []; + const cgroups = data[3]?.cgroups ?? []; let m, s, o; @@ -144,6 +144,13 @@ return view.extend({ o.value(group); }; + o = s.taboption('bypass', form.MultiValue, 'bypass_cgroup', _('Bypass cgroup')); + o.create = true; + + for (const cgroup of cgroups) { + o.value(cgroup); + }; + o = s.taboption('bypass', form.Flag, 'bypass_china_mainland_ip', _('Bypass China Mainland IP')); o.rmempty = false; diff --git a/luci-app-nikki/po/templates/nikki.pot b/luci-app-nikki/po/templates/nikki.pot index f0d93ee2..4794d5b9 100644 --- a/luci-app-nikki/po/templates/nikki.pot +++ b/luci-app-nikki/po/templates/nikki.pot @@ -17,8 +17,8 @@ msgstr "" msgid "All Mode" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:152 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:157 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:159 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:164 msgid "All Port" msgstr "" @@ -65,11 +65,11 @@ msgstr "" msgid "Bypass" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:147 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:154 msgid "Bypass China Mainland IP" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:167 msgid "Bypass DSCP" msgstr "" @@ -81,6 +81,10 @@ msgstr "" msgid "Bypass User" msgstr "" +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:147 +msgid "Bypass cgroup" +msgstr "" + #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:29 msgid "Choose File" msgstr "" @@ -94,8 +98,8 @@ msgstr "" msgid "Clear Log" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:153 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:158 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:165 msgid "Commonly Used Port" msgstr "" @@ -147,11 +151,11 @@ msgstr "" msgid "Destination Port" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:150 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:157 msgid "Destination TCP Port to Proxy" msgstr "" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:155 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:162 msgid "Destination UDP Port to Proxy" msgstr "" diff --git a/luci-app-nikki/po/zh_Hans/nikki.po b/luci-app-nikki/po/zh_Hans/nikki.po index 3a8a6984..fedaf894 100644 --- a/luci-app-nikki/po/zh_Hans/nikki.po +++ b/luci-app-nikki/po/zh_Hans/nikki.po @@ -24,8 +24,8 @@ msgstr "访问控制" msgid "All Mode" msgstr "全部模式" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:152 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:157 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:159 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:164 msgid "All Port" msgstr "全部端口" @@ -72,11 +72,11 @@ msgstr "黑名单模式" msgid "Bypass" msgstr "绕过" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:147 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:154 msgid "Bypass China Mainland IP" msgstr "绕过中国大陆 IP" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:167 msgid "Bypass DSCP" msgstr "绕过 DSCP" @@ -88,6 +88,10 @@ msgstr "绕过用户组" msgid "Bypass User" msgstr "绕过用户" +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:147 +msgid "Bypass cgroup" +msgstr "绕过资源控制组" + #: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/editor.js:29 msgid "Choose File" msgstr "选择文件" @@ -101,8 +105,8 @@ msgstr "选择配置文件" msgid "Clear Log" msgstr "清空日志" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:153 -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:158 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:160 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:165 msgid "Commonly Used Port" msgstr "常用端口" @@ -154,11 +158,11 @@ msgstr "目标 IP(Geo)" msgid "Destination Port" msgstr "目标端口" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:150 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:157 msgid "Destination TCP Port to Proxy" msgstr "要代理的 TCP 目标端口" -#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:155 +#: applications/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js:162 msgid "Destination UDP Port to Proxy" msgstr "要代理的 UDP 目标端口" diff --git a/luci-app-nikki/root/etc/uci-defaults/40_luci-nikki b/luci-app-nikki/root/etc/uci-defaults/40_luci-nikki deleted file mode 100644 index 08008689..00000000 --- a/luci-app-nikki/root/etc/uci-defaults/40_luci-nikki +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -rm -rf /var/luci-modulecache/; rm -f /var/luci-indexcache; -[ -x /etc/init.d/rpcd ] && /etc/init.d/rpcd reload; -exit 0 diff --git a/luci-app-nikki/root/usr/share/rpcd/acl.d/luci-app-nikki.json b/luci-app-nikki/root/usr/share/rpcd/acl.d/luci-app-nikki.json index 8f016c4f..4d566f3e 100644 --- a/luci-app-nikki/root/usr/share/rpcd/acl.d/luci-app-nikki.json +++ b/luci-app-nikki/root/usr/share/rpcd/acl.d/luci-app-nikki.json @@ -8,8 +8,6 @@ "luci.nikki": [ "*" ] }, "file": { - "/etc/passwd": ["read"], - "/etc/group": ["read"], "/etc/nikki/profiles/*.yaml": ["read"], "/etc/nikki/profiles/*.yml": ["read"], "/etc/nikki/subscriptions/*.yaml": ["read"], diff --git a/luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki b/luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki index d9fac626..7f1b28cc 100644 --- a/luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki +++ b/luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki @@ -3,6 +3,7 @@ 'use strict'; import { access, popen } from 'fs'; +import { get_users, get_groups, get_cgroups } from '/etc/nikki/ucode/include.uc'; const methods = { version: { @@ -56,6 +57,14 @@ const methods = { return { success: success }; } }, + get_identifiers: { + call: function() { + const users = filter(get_users(), (x) => x != '' && x != 'root'); + const groups = filter(get_groups(), (x) => x != '' && x != 'root'); + const cgroups = filter(get_cgroups(), (x) => x != '' && x != 'nikki'); + return { users: users, groups: groups, cgroups: cgroups }; + } + }, debug: { call: function() { const success = system('/etc/nikki/scripts/debug.sh > /var/run/nikki/debug.md') == 0; diff --git a/nikki/Makefile b/nikki/Makefile index aca6ae04..389317cc 100644 --- a/nikki/Makefile +++ b/nikki/Makefile @@ -5,9 +5,9 @@ PKG_RELEASE:=1 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git -PKG_SOURCE_DATE:=2025-03-10 -PKG_SOURCE_VERSION:=c0de3c0e42dcbd7f6176143b00741f7774b70a08 -PKG_MIRROR_HASH:=1131cc9abde1cfbc0c5f5c228e835e2d5ce630565db661b7297f286c019da950 +PKG_SOURCE_DATE:=2025-03-14 +PKG_SOURCE_VERSION:=1e22f4daa964c54abea4c8b0f09f8171398a2821 +PKG_MIRROR_HASH:=0b65cd12cb6927b118d3802303b97dbc3299db1b3e6d8a849dedb32a276c30ff PKG_LICENSE:=GPL3.0+ PKG_MAINTAINER:=Joseph Mory @@ -16,7 +16,7 @@ PKG_BUILD_DEPENDS:=golang/host PKG_BUILD_PARALLEL:=1 PKG_BUILD_FLAGS:=no-mips16 -PKG_BUILD_VERSION:=alpha-c0de3c0 +PKG_BUILD_VERSION:=alpha-1e22f4d PKG_BUILD_TIME:=$(shell date -u -Iseconds) GO_PKG:=github.com/metacubex/mihomo @@ -33,7 +33,6 @@ define Package/nikki URL:=https://wiki.metacubex.one DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle +curl +yq firewall4 +ip-full +kmod-inet-diag +kmod-nft-tproxy +kmod-tun PROVIDES:=nikki mihomo - USERID:=root:nikki=1206 endef define Package/nikki/description diff --git a/nikki/files/nikki.init b/nikki/files/nikki.init index fbb91171..a5c1d4d6 100644 --- a/nikki/files/nikki.init +++ b/nikki/files/nikki.init @@ -122,8 +122,6 @@ start_service() { procd_set_param reload_signal HUP fi procd_set_param respawn - procd_set_param user "$NIKKI_USER" - procd_set_param group "$NIKKI_GROUP" procd_set_param limits core="unlimited" nofile="1048576 1048576" diff --git a/nikki/files/scripts/include.sh b/nikki/files/scripts/include.sh index 94de9953..6a0b0910 100644 --- a/nikki/files/scripts/include.sh +++ b/nikki/files/scripts/include.sh @@ -1,9 +1,5 @@ #!/bin/sh -# permission -NIKKI_USER="root" -NIKKI_GROUP="nikki" - # routing TPROXY_FW_MARK="0x80" TUN_FW_MARK="0x81" diff --git a/nikki/files/ucode/hijack.ut b/nikki/files/ucode/hijack.ut index afdec5e2..9f0abfb0 100644 --- a/nikki/files/ucode/hijack.ut +++ b/nikki/files/ucode/hijack.ut @@ -3,13 +3,13 @@ {%- 'use strict'; - import { readfile } from 'fs'; import { cursor } from 'uci'; import { connect } from 'ubus'; - import { uci_bool, uci_array } from '/etc/nikki/ucode/include.uc'; + import { uci_bool, uci_array, get_users, get_groups, get_cgroups } from '/etc/nikki/ucode/include.uc'; - const users = map(split(readfile('/etc/passwd'), '\n'), (x) => split(x, ':')[0]); - const groups = map(split(readfile('/etc/group'), '\n'), (x) => split(x, ':')[0]); + const users = get_users(); + const groups = get_groups(); + const cgroups = get_cgroups(); const uci = cursor(); const ubus = connect(); @@ -41,8 +41,9 @@ const acl_mac = uci_array(uci.get('nikki', 'proxy', 'acl_mac')); const acl_interface = uci_array(uci.get('nikki', 'proxy', 'acl_interface')); - const bypass_user = filter(uci_array(uci.get('nikki', 'proxy', 'bypass_user')), (x) => x != "root" && index(users, x) >= 0); - const bypass_group = filter(uci_array(uci.get('nikki', 'proxy', 'bypass_group')), (x) => x != "root" && index(groups, x) >= 0); + const bypass_user = filter(uci_array(uci.get('nikki', 'proxy', 'bypass_user')), (x) => x != 'root' && index(users, x) >= 0); + const bypass_group = filter(uci_array(uci.get('nikki', 'proxy', 'bypass_group')), (x) => x != 'root' && index(groups, x) >= 0); + const bypass_cgroup = filter(uci_array(uci.get('nikki', 'proxy', 'bypass_cgroup')), (x) => x != 'nikki' && index(cgroups, x) >= 0); 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'), ' '); @@ -80,7 +81,7 @@ push(proxy_dport, `udp . ${port}`); } - push(bypass_group, nikki_group); + push(bypass_cgroup, 'nikki'); -%} table inet nikki { @@ -134,6 +135,19 @@ table inet nikki { {% endif %} } + set bypass_cgroup { + type cgroupsv2 + flags interval + auto-merge + {% if (length(bypass_cgroup) > 0): %} + elements = { + {% for (let x in bypass_cgroup): %} + services/{{ x }}, + {% endfor %} + } + {% endif %} + } + set reserved_ip { type ipv4_addr flags interval @@ -305,6 +319,7 @@ table inet nikki { {% if (router_proxy): %} chain nat_output { type nat hook output priority filter; policy accept; + socket cgroupv2 level 2 @bypass_cgroup counter return meta skuid @bypass_user counter return meta skgid @bypass_group counter return meta nfproto @dns_hijack_nfproto meta l4proto { tcp, udp } th dport 53 counter redirect to :{{ dns_port }} @@ -328,6 +343,7 @@ table inet nikki { chain mangle_output { type route hook output priority mangle; policy accept; + socket cgroupv2 level 2 @bypass_cgroup counter return meta skuid @bypass_user counter return meta skgid @bypass_group counter return fib daddr type { local, multicast, broadcast, anycast } counter return diff --git a/nikki/files/ucode/include.uc b/nikki/files/ucode/include.uc index 7282a457..a9985bb7 100644 --- a/nikki/files/ucode/include.uc +++ b/nikki/files/ucode/include.uc @@ -1,3 +1,5 @@ +import { readfile, lsdir, lstat } from 'fs'; + export function uci_bool(obj) { return obj == null ? null : obj == '1'; }; @@ -46,4 +48,16 @@ export function trim_all(obj) { return obj; } return obj; +}; + +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() { + return filter(lsdir('/sys/fs/cgroup/services'), (x) => lstat(`/sys/fs/cgroup/services/${x}`).type == 'directory'); }; \ No newline at end of file