meow_netmgrd 期3:管控层(三实体策略引擎+防自锁+SOCK_DESTROY 实测→仅 DNS 层)

NETMGR_DESIGN.md §6/§13 期3 落地(全在 patch_v2/meow_netmgrd/ + meow_ipc.h 追加):
1. 策略引擎全量:/data/meow/netmgrd/net_policy.conf(/data=/usr/data)读改写单键+原子写
   (temp+rename,绝不 O_TRUNC 整文件重写——watchdog 毁设置教训);per-实体(app:/svc:/proc:)
   net=allow|block + domain 白/黑(接通 DNS 层执行)+ global log_level;默认 allow。
2. 防自锁硬编码:dropbear/wpa_supplicant/udhcpc/meow_netmgrd/meow_svcd/meow_sysui/init 拒绝 block
   (CLI/IPC 拒 + 加载校验把手工改坏的文件里这些实体的 net=block 同样强制复位 allow——§14 红线)。
3. SOCK_DESTROY 真机实测(sockdestroy_probe.c,root @内核4.4):**内核接受 SOCK_DESTROY 请求(rc=0)
   但未真正销毁 socket(cookie=0 与 dump-cookie 两法均 dead=0)→ 判定 no-op/不可用**。
   ⇒ 连接级执行降级为「仅 DNS 层生效」(net=block → dns_query_domain_denied 全量 deny,DNS 层落地),
   policy show 头 enfor=dns_only 明示。SDK 自愿通道/设置页迁移期外。
4. IPC 0x00EA MSG_NET_POLICY(op: show/set net/set domain/del)+ RESULT 0x00EB:读级任意、写级仅系统身份。
   CLI:policy show [entity] | set <entity> allow|block | domain <entity> +host|-host | del <entity>。
5. 两小修:①classify_exe 增认 MeowPanel 精确路径(修面板 dns flush 403;面板已有 PIN 门禁)
   ②连接未归因(inode=0/查不到)一律落 proc:unknown——修 apps 表出现「IP:port」原始端点行。
6. WiFi connect 持久化:MeowOS 模式 connect 后 wpa_cli save_config(HibyOS 只读由服务端门控)。

验证:宿主 test_netmgrd.sh 61/61 + qemu 61/61;test_netmgrd_p2/p3 宿主+qemu 全过;netmgrd_authtest 全过;
交叉 -Wall -Wextra 0 warning。真机只读实测(DNS-on 固件共存,用独立 socket+/tmp 临时 policy.json):
policy set/domain/del 全链 + 防自锁拒绝 proc:dropbear(rc=1) + 空文件 del(size=0) + apps 无原始端点 +
resolv.conf 未触碰(测后核验固件两行形式);Firmware meow_netmgrd(pid 8142) running 未动。

新 md5:meow_netmgrd 62369ebe29e7ef4b55067a0f14e240d0(主会话更 fw_content.conf/热更设备)。
This commit is contained in:
MeowOS committed 2026-09-01 22:08:10 +08:00
1 parent 7ba0fde3e6
commit b37dc23cd9
10 files changed
+737 -35

No files matched your search

+178
View File
@@ -0,0 +1,178 @@
/* sockdestroy_probe.c — inet_diag SOCK_DESTROY 内核可用性探针(dev 工具,不入固件/不部署)。
*
* 目的:内核 4.4(R3 Pro II)是否支持 netlink SOCK_DESTROY 连接级杀 socket。NETWORK_HW_FACTS §5 待验证项。
* 安全:只对「本进程自建」的 127.0.0.1 临时 TCP 连接发 SOCK_DESTROY,绝不动设备现有连接。
*
* 方法:建 loopback TCP 服务端+客户端,accept 得已连接 socket。用 inet_diag DUMP(SOCK_DIAG_BY_FAMILY)
* 列出全部 TCP,按 tuple(sport/dport/src/dst)匹配出自建 socket 的 idiag_cookie(与 ss -K 同法——
* 无需 SO_COOKIE);再发 SOCK_DESTROY(tuple + dump cookie),对 accept 出的 socket recv 判是否已杀。
* 输出:SD_DUMP_DUMPED=<n> SD_MATCH=<0|1> SD_DESTROY_RC=<errno|0> SD_DESTROYED=<0|1>
* 结论:SD_DESTROY_RC==0 && SD_DESTROYED==1 → 内核支持;-EPERM → 需 root/CAP_NET_ADMIN;-EOPNOTSUPP/ENOENT → 不支持。
*
* 用法:交叉编译后 scp 到设备 /usr/data 前台跑(root):`./sockdestroy_probe`(无副作用残留)。
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <linux/netlink.h>
#include <linux/sock_diag.h>
#include <linux/inet_diag.h>
static int nl = -1;
static void open_nl(void)
{
nl = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
if (nl < 0) { perror("netlink socket"); exit(2); }
struct sockaddr_nl sa; memset(&sa, 0, sizeof sa); sa.nl_family = AF_NETLINK;
if (bind(nl, (struct sockaddr *)&sa, sizeof sa) < 0) { perror("bind"); exit(2); }
}
/* dump 全部 TCP,找匹配 tuple 的卡片,取回 cookie。返回 0=找到。 */
static int diag_find_cookie(uint16_t sport, uint16_t dport, uint32_t sip, uint32_t dip,
uint32_t *cookie_c0, uint32_t *cookie_c1)
{
struct {
struct nlmsghdr nlh;
struct inet_diag_req_v2 req;
} r;
memset(&r, 0, sizeof r);
r.nlh.nlmsg_len = sizeof r;
r.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
r.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
r.req.sdiag_family = AF_INET;
r.req.sdiag_protocol = IPPROTO_TCP;
r.req.idiag_states = 0xFFFFFFFF;
struct sockaddr_nl sa; memset(&sa, 0, sizeof sa); sa.nl_family = AF_NETLINK;
if (sendto(nl, &r, sizeof r, 0, (struct sockaddr *)&sa, sizeof sa) < 0) return -1;
struct timeval tv = { 2, 0 };
setsockopt(nl, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
int dumped = 0;
for (;;) {
char buf[8192];
ssize_t n = recv(nl, buf, sizeof buf, 0);
if (n <= 0) break;
for (struct nlmsghdr *h = (struct nlmsghdr *)buf; NLMSG_OK(h, (unsigned)n); h = NLMSG_NEXT(h, n)) {
if (h->nlmsg_type == NLMSG_DONE) goto done;
if (h->nlmsg_type == NLMSG_ERROR) { int e = ((struct nlmsgerr *)NLMSG_DATA(h))->error; if (e) return e; continue; }
if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY) {
struct inet_diag_msg *m = (struct inet_diag_msg *)NLMSG_DATA(h);
dumped++;
if (ntohs(m->id.idiag_sport) == sport && ntohs(m->id.idiag_dport) == dport &&
(ntohl(m->id.idiag_src[0]) == sip) && (ntohl(m->id.idiag_dst[0]) == dip)) {
*cookie_c0 = m->id.idiag_cookie[0];
*cookie_c1 = m->id.idiag_cookie[1];
printf("SD_DUMPED=%d SD_MATCH=1\n", dumped);
return 0;
}
}
}
}
done:
printf("SD_DUMPED=%d SD_MATCH=0\n", dumped);
return -1;
}
static int sd_try(uint16_t sport, uint16_t dport, uint32_t sip, uint32_t dip,
uint32_t c0, uint32_t c1, int with_cookie)
{
struct {
struct nlmsghdr nlh;
struct inet_diag_req_v2 req;
} r;
memset(&r, 0, sizeof r);
r.nlh.nlmsg_len = sizeof r;
r.nlh.nlmsg_type = SOCK_DESTROY;
r.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
r.req.sdiag_family = AF_INET;
r.req.sdiag_protocol = IPPROTO_TCP;
r.req.idiag_states = 0xFFFFFFFF;
r.req.id.idiag_sport = htons(sport);
r.req.id.idiag_dport = htons(dport);
r.req.id.idiag_src[0] = htonl(sip);
r.req.id.idiag_dst[0] = htonl(dip);
r.req.id.idiag_cookie[0] = with_cookie ? c0 : 0;
r.req.id.idiag_cookie[1] = with_cookie ? c1 : 0;
struct sockaddr_nl sa; memset(&sa, 0, sizeof sa); sa.nl_family = AF_NETLINK;
if (sendto(nl, &r, sizeof r, 0, (struct sockaddr *)&sa, sizeof sa) < 0) return -errno;
char buf[512];
struct timeval tv = { 0, 300000 };
setsockopt(nl, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
ssize_t rr = recv(nl, buf, sizeof buf, 0);
if (rr < 0) return -errno;
struct nlmsghdr *h = (struct nlmsghdr *)buf;
if (h->nlmsg_type == NLMSG_ERROR) return ((struct nlmsgerr *)NLMSG_DATA(h))->error;
return 0;
}
static int check_destroyed(int fd)
{
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
char b[16];
ssize_t r = recv(fd, b, sizeof b, 0);
if (r == 0) return 1;
if (r < 0) return (errno == ECONNRESET || errno == ECONNABORTED) ? 1 : 0;
return 0;
}
int main(void)
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
int cfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(0);
int one = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
bind(lfd, (struct sockaddr *)&addr, sizeof addr);
listen(lfd, 8);
socklen_t alen = sizeof addr;
getsockname(lfd, (struct sockaddr *)&addr, &alen);
connect(cfd, (struct sockaddr *)&addr, sizeof addr);
int sfd = accept(lfd, NULL, NULL);
struct sockaddr_in sa, sc;
socklen_t sla = sizeof sa, slc = sizeof sc;
getsockname(sfd, (struct sockaddr *)&sa, &sla);
getpeername(sfd, (struct sockaddr *)&sc, &slc);
uint16_t sport = ntohs(sa.sin_port);
uint16_t dport = ntohs(sc.sin_port);
uint32_t sip = ntohl(sa.sin_addr.s_addr);
uint32_t dip = ntohl(sc.sin_addr.s_addr);
open_nl();
uint32_t c0 = 0, c1 = 0;
int found = (diag_find_cookie(sport, dport, sip, dip, &c0, &c1) == 0);
if (found) {
/* 主测靠谱路径:cookie 版(与 ss -K 同法;无需 SO_COOKIE)。 */
int rc_c = sd_try(sport, dport, sip, dip, c0, c1, 1);
int dead_c = check_destroyed(sfd);
printf("SD_DESTROY(cookie) rc=%d destroyed=%d\n", rc_c, dead_c);
if (dead_c) {
printf("CONCL=SOCK_DESTROY_SUPPORTED\n");
} else if (rc_c == -EPERM) {
printf("CONCL=SOCK_DESTROY_NEED_CAP(rc=-EPERM; 需 root/CAP_NET_ADMIN)\n");
} else if (rc_c == 0) {
printf("CONCL=SOCK_DESTROY_NOOP(rc=0 但未杀; 内核未真正销毁——视为不可用)\n");
} else {
printf("CONCL=SOCK_DESTROY_UNSUPPORTED(rc=%d)\n", rc_c);
}
/* 附带:tuple+cookie=0 结果(诊断参考) */
int rc_t = sd_try(sport, dport, sip, dip, c0, c1, 0);
printf("SD_DESTROY(tuple,cookie=0) rc=%d destroyed=%d\n", rc_t, check_destroyed(sfd));
} else {
printf("CONCL=SOCK_DESTROY_UNKNOWN(dump no match)\n");
}
close(cfd); close(sfd); close(lfd);
return 0;
}