/* 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//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 #include #include #include #include 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, NULL) == 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::造一个 /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; }