Files
MeowOS-For-R3P2/patch_v2/meow_netmgrd/test_netmgrd_p2.c
T
MeowOS 52edf0c9e4 meow_netmgrd 期2:观测层(PID→实体归因+连接普查)+DNS 代理+事件日志+连接/DNS IPC 与 CLI
NETMGR_DESIGN.md 期2范围落地(全在 patch_v2/meow_netmgrd/ + meow_ipc.h 追加):
- 连接普查(§4):每 2s 解析 /proc/net/{tcp,udp}(IPv4) → socket inode→pid(由 inode 集驱动扫
  /proc/<pid>/fd,不逐 pid 盲扫)→ 三实体归因 app:<pkg>(amsd status)/svc:<svc>(svcd 落盘状态
  /tmp/meow_svcd.status,socket 查询兜底)/proc:<comm>(无则 proc:unknown)。连接表静态 ≤512:
  entity/五元组/状态/寿命;远端 IP 由 DNS 缓存反查域名。
- DNS 代理(§5):监听 127.0.0.1:53 UDP;启动备份 resolv.conf 写双行(127.0.0.1+原上游,崩溃零断流);
  透传转发+缓存(TTL 尊重;≤256 条);按源端口(port→entity)归因;域名规则 per-entity+global allow/deny
  (deny→NXDOMAIN+日志);atexit/SIGTERM/SIGHUP 恢复;5s 自检被第三方改回即重写;HibyOS 绝不启用/绝不改 resolv。
  DNS wire(deny NXDOMAIN / forward→A+cache / NXDOMAIN id 回显) 宿主+qemu 实测 PASS。
- 事件日志(§8):新连接/关闭/dns deny/接口状态变化;同 entity 同 dst 1 分钟去重(首条+计数)。
- IPC meow_ipc.h 追加 0x00E8 CONNS_REQ/RESULT、0x00EC DNS_CTL/RESULT(纯追加不改既有行;
  0x00EA 策略(期3)/0x00EE 事件推送(预留) 保留)。
- CLI meow_net 补 apps / conns [entity] / top(2s 刷新) / dns status|stats|flush。
- 内存:静态表禁 malloc,mipsel bss 228KB → RSS≈374KB,≤600KB 目标 / ≤1MB 预算。

宿主编译 -Wall -Wextra 0 warning;test_netmgrd.sh 48/48(宿主+qemu)、test_netmgrd_p2 全过(宿主+qemu)、
netmgrd_authtest 全过;真机(M6.0/0.8.0)只读实测 apps/conns/dns status 正确
(svc:meowpanel/svc:mdnsd/proc:dropbear 真实三实体归因),resolv.conf 测后核验原值未触碰。
2026-09-01 20:40:46 +08:00

214 lines
9.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test_netmgrd_p2.c — meow_netmgrd 期2 观测层/DNS 代理 纯逻辑宿主自测(本程序非生产代码)。
*
* 通过 #include 生产源文件(meow_netmgrd.c,用 #define main 改名避免入口冲突),同一 TU 直接访问
* FILE-STATIC 函数/全局:dns_parse_query / dns_build_nxdomain / dns_cache_* / suffix_match /
* rules_load / dns_query_domain_denied / pid_ent_put / entity_for_pid / parse_proc_net /
* build_conns_text / build_dns_ctl_text / dns_cache_from_response / hex_ip / ip_fmt。
* 覆盖 DNS wire 编解码、缓存 TTL、域名规则判定、实体归因(app/svc/proc:comm)、/proc/net 解析、
* 连接表文本构建。真实 amsd/svcd socket + /proc/<pid>/fd 归因留真机验证。
*
* 用法:cc -Wall -Wextra -I../meow_launcherd test_netmgrd_p2.c -o test_netmgrd_p2 && ./test_netmgrd_p2
*/
#define main meow_netmgrd_prod_main_unused
#include "meow_netmgrd.c"
#undef main
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
static int g_fail = 0;
#define CHECK(cond) do { if (!(cond)) { printf(" FAIL: %s (line %d)\n", #cond, __LINE__); g_fail++; } \
else { printf(" ok : %s\n", #cond); } } while (0)
static void wf(const char *p, const char *s)
{
FILE *f = fopen(p, "w");
if (f) { fputs(s, f); fclose(f); }
}
static int make_query(const char *name, uint8_t *out)
{
memset(out, 0, 16);
out[0] = 0x12; out[1] = 0x34; /* id */
out[2] = 0x01; out[3] = 0x00; /* RD */
out[4] = 0; out[5] = 1; /* qdcount=1 */
int off = 12;
const char *p = name;
while (*p) {
const char *dot = strchr(p, '.');
size_t l = dot ? (size_t)(dot - p) : strlen(p);
out[off++] = (uint8_t)l;
memcpy(out + off, p, l); off += (int)l;
p = dot ? dot + 1 : p + l;
}
out[off++] = 0;
out[off++] = 0; out[off++] = 1; /* qtype=A */
out[off++] = 0; out[off++] = 1; /* qclass=IN */
return off;
}
static int make_a_response(const char *name, const char *ip, uint8_t *out)
{
int ql = make_query(name, out);
out[0] = 0x12; out[1] = 0x34;
out[2] = 0x81; out[3] = 0x80; /* QR|RD|RA */
out[6] = 0; out[7] = 1; /* ancount=1 */
int off = ql;
out[off++] = 0xC0; out[off++] = 0x0C; /* answer name pointer → offset 12 (question) */
out[off++] = 0; out[off++] = 1; /* type A */
out[off++] = 0; out[off++] = 1; /* class IN */
out[off++] = 0; out[off++] = 0; out[off++] = 0; out[off++] = 0x3C; /* ttl=60 */
out[off++] = 0; out[off++] = 4; /* rdlen=4 */
int b0 = 0, b1 = 0, b2 = 0, b3 = 0;
sscanf(ip, "%d.%d.%d.%d", &b0, &b1, &b2, &b3);
out[off++] = (uint8_t)b0; out[off++] = (uint8_t)b1;
out[off++] = (uint8_t)b2; out[off++] = (uint8_t)b3;
return off;
}
int main(void)
{
static const char *T = "/tmp/nmg_p2";
char path[192];
mkdir(T, 0755);
uint8_t q[256], r[512];
printf("== dns_parse_query ==\n");
{
int ql = make_query("music.163.com", q);
char dom[DNS_QNAME_MAX]; int qend = 0;
CHECK(dns_parse_query(q, ql, dom, sizeof dom, &qend) == 0);
CHECK(strcmp(dom, "music.163.com") == 0);
CHECK(qend == ql);
}
printf("== dns_build_nxdomain ==\n");
{
int ql = make_query("ads.example.com", q);
uint8_t resp[300];
int rl = dns_build_nxdomain(q, ql, resp, sizeof resp);
CHECK(rl > 12);
CHECK((resp[2] & 0x80) != 0); /* QR */
CHECK((resp[3] & 0x0f) == 3); /* NXDOMAIN */
CHECK(resp[0] == 0x12 && resp[1] == 0x34); /* id echoed */
}
printf("== dns_cache insert/lookup/flush (TTL 尊重) ==\n");
{
dns_cache_flush();
CHECK(dns_cache_insert("music.163.com", 0x0A4EC132, 60) == 0);
char out[64];
CHECK(dns_cache_lookup_ip(0x0A4EC132, out, sizeof out) == 0);
CHECK(strcmp(out, "music.163.com") == 0);
CHECK(dns_cache_lookup_name("music.163.com") == 0);
CHECK(dns_cache_lookup_name("absent.com") != 0);
dns_cache_flush();
CHECK(dns_cache_lookup_name("music.163.com") != 0);
}
printf("== dns_cache_from_response(A 记录抓取)==\n");
{
dns_cache_flush();
int rl = make_a_response("music.163.com", "39.164.0.12", r);
dns_cache_from_response(r, rl);
char out[64];
CHECK(dns_cache_lookup_ip(0x27A4000C, out, sizeof out) == 0);
CHECK(strcmp(out, "music.163.com") == 0);
}
printf("== suffix_match / rules_load / dns_query_domain_denied ==\n");
{
CHECK(suffix_match("ads.example.com", "example.com") == 1);
CHECK(suffix_match("sub.ads.example.com", "ads.example.com") == 1);
CHECK(suffix_match("example.com", "ads.example.com") == 0);
snprintf(path, sizeof path, "%s/policy", T);
wf(path, "# test\napp:dev.player.ncm domain=-ads.example.com,-tracker.net\nproc:dropbear domain=+k,l\n\n");
g_net_policy[0] = 0; snprintf(g_net_policy, sizeof g_net_policy, "%s", path);
rules_load();
CHECK(g_rule_n >= 2);
CHECK(dns_query_domain_denied("app:dev.player.ncm", "ads.example.com") == 1);
CHECK(dns_query_domain_denied("app:dev.player.ncm", "sub.ads.example.com") == 1);
CHECK(dns_query_domain_denied("app:dev.player.ncm", "music.163.com") == 0);
CHECK(dns_query_domain_denied("proc:dropbear", "k") == 0); /* allow 覆盖 */
CHECK(dns_query_domain_denied("proc:dropbear", "l") == 1); /* deny */
CHECK(dns_query_domain_denied("proc:other", "ads.example.com") == 0); /* 无实体规则 → 默认 allow */
}
printf("== refresh_entity_maps(svcd 落盘状态文件 → svc: 实体)==\n");
{
snprintf(path, sizeof path, "%s/svcd.status", T);
wf(path, "# meow_svcd status\ncard_present=1 wifi_up=1 services=2\n"
"svc=meowpanel state=running pid=1038 restart=0 exit=0 trigger=boot backoff_ms=2000\n"
"svc=meow_mdns state=running pid=1006 restart=0 exit=0 trigger=boot backoff_ms=2000\n");
g_svcd_status[0] = 0; snprintf(g_svcd_status, sizeof g_svcd_status, "%s", path);
g_svcd_sock[0] = 0; snprintf(g_svcd_sock, sizeof g_svcd_sock, "%s/none.sock", T);
g_ams_sock[0] = 0; snprintf(g_ams_sock, sizeof g_ams_sock, "%s/none.sock", T);
g_pid_ent_n = 0;
refresh_entity_maps();
char ent[48];
entity_for_pid(1038, ent, sizeof ent);
CHECK(strcmp(ent, "svc:meowpanel") == 0);
entity_for_pid(1006, ent, sizeof ent);
CHECK(strcmp(ent, "svc:meow_mdns") == 0);
}
printf("== pid_ent_put / entity_for_pid(app/svc/proc:comm)==\n");
{
g_pid_ent_n = 0;
pid_ent_put(123, "app:dev.player.ncm");
pid_ent_put(456, "svc:meowpanel");
char ent[48];
entity_for_pid(123, ent, sizeof ent);
CHECK(strcmp(ent, "app:dev.player.ncm") == 0);
entity_for_pid(456, ent, sizeof ent);
CHECK(strcmp(ent, "svc:meowpanel") == 0);
/* proc:<comm>:造一个 /proc-format comm 不可能,直接读当前 pid comm(非空即入 proc:) */
int self = (int)getpid();
snprintf(path, sizeof path, "/proc/%d/comm", self);
if (access(path, F_OK) == 0) {
entity_for_pid(self, ent, sizeof ent);
CHECK(strncmp(ent, "proc:", 5) == 0);
}
}
printf("== parse_proc_net(IPv4 地址/端口/inode)==\n");
{
snprintf(path, sizeof path, "%s/tcp", T);
wf(path, " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n"
" 0: 32C14E0A:5137 0905CD8C:01BB 01 00000000:00000000 00:00000000 00000000 1000 0 12345 1\n"
" 1: 32C14E0A:0A00 00000000:0000 07 00000000:00000000 00:00000000 00000000 1000 0 67890 1\n");
g_proc_tcp[0] = 0; snprintf(g_proc_tcp, sizeof g_proc_tcp, "%s", path);
static conn_t cur[CONN_MAX];
int n = 0;
CHECK(parse_proc_net(g_proc_tcp, 0, cur, &n) == 2);
CHECK(cur[0].local_port == 0x5137 && cur[0].remote_port == 0x01BB);
CHECK(cur[0].inode == 12345 && cur[0].state_code == 1);
char ip[32];
ip_fmt(cur[0].local_ip, ip, sizeof ip);
CHECK(strcmp(ip, "10.78.193.50") == 0);
ip_fmt(cur[0].remote_ip, ip, sizeof ip);
CHECK(strcmp(ip, "140.205.5.9") == 0);
}
printf("== build_conns_text / build_dns_ctl_text ==\n");
{
g_conn_n = 0;
conn_t c;
memset(&c, 0, sizeof c);
c.local_ip = hex_ip("32C14E0A"); c.local_port = 443; /* 10.78.193.50 */
c.remote_ip = hex_ip("32C34E0A"); c.remote_port = 25565; /* 10.78.195.50 */
c.state_code = 1; c.type = 0; c.inode = 555; c.pid = 12345;
snprintf(c.entity, sizeof c.entity, "app:dev.player.ncm");
snprintf(c.domain, sizeof c.domain, "music.163.com");
c.mono_open = mono_now(); c.last_seen = mono_now();
g_conn[g_conn_n++] = c;
char out[512]; int tr = 0;
int len = build_conns_text(NULL, out, sizeof out, &tr);
CHECK(len > 0 && tr == 0);
CHECK(strstr(out, "app:dev.player.ncm|10.78.193.50:443|10.78.195.50:25565|ESTAB|0|music.163.com") != NULL);
char dout[256];
CHECK(build_dns_ctl_text("status", dout, sizeof dout) == MEOW_OK);
CHECK(strstr(dout, "dns mode=") != NULL);
CHECK(build_dns_ctl_text("flush", dout, sizeof dout) == MEOW_OK);
}
if (g_fail) { printf("\nNETMGR P2 TEST: %d FAIL\n", g_fail); return 1; }
printf("\nNETMGR P2 TEST: ALL PASS\n");
return 0;
}