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 测后核验原值未触碰。
214 lines
9.5 KiB
C
214 lines
9.5 KiB
C
/* 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;
|
||
}
|