diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index 1003901..46d392d 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -633,4 +633,34 @@ typedef struct m_net_policy_result { char text[MEOW_IPC_MAX - 8]; } m_net_policy_result_t; +/* ---- 期 4:SDK 网络自愿通道(NETMGR_DESIGN §6.3/§4;0xEE/0xEF)---- + * 一、meow_net_policy_check(host):框架 App 联网前自查 host 是否被 netmgrd 策略放行—— + * 服务端以调用方 app_id(msg.app_id)→ 实体 app:,评估 policy_net_blocked(entity) + * || dns_query_domain_denied(entity, host),放行=allow 1 / 拒绝=block 0;netmgrd 不在 → SDK 侧放行。 + * 二、meow_net_report(rx,tx):App 自愿上报本进程收发字节数(内核无 per-socket/per-uid 计数, + * §4 诚实声明的「字节级记账走 SDK 自报」);netmgrd 记入 per-实体统计(status JSON entity_bytes)。 + * 读级/上报任意调用者(自愿自报,无副作用;block 判定由服务端策略引擎权威执行)。 + * (0x00EE 曾留作「事件推送 订阅制」,期 4 起改作 SDK 通道;事件推送期 2 从未实装。) */ +enum { /* 与主 enum 分列,追加式不改既有行 */ + MSG_NET_SDK = 0x00EE, /* ↑ op=0 策略检查 / op=1 字节上报 */ + MSG_NET_SDK_RESULT = 0x00EF /* ↓ code + (CHECK)allow + 状态文本 */ +}; +enum { NET_SDK_CHECK = 0, NET_SDK_REPORT = 1 }; + +typedef struct m_net_sdk { + uint32_t op; /* NET_SDK_CHECK / NET_SDK_REPORT */ + char app_id[MEOW_APP_ID_MAX]; /* 调用方 application(SDK 用 env MEOW_LOG_APP;CLI/测试可任意) */ + char host[64]; /* CHECK:待查域名 */ + uint32_t rx; /* REPORT:本进程累计 RX 字节 */ + uint32_t tx; /* REPORT:本进程累计 TX 字节 */ +} m_net_sdk_t; /* 4+32+64+4+4 = 108 B */ + +typedef struct m_net_sdk_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + int32_t allow; /* CHECK:1=allow 0=block(block 由服务端策略引擎判定) */ + uint16_t text_len; + uint16_t pad; + char text[MEOW_IPC_MAX - 12]; +} m_net_sdk_result_t; + #endif /* MEOW_IPC_H */ diff --git a/patch_v2/meow_netmgrd/meow_netmgrd b/patch_v2/meow_netmgrd/meow_netmgrd index 08fd43a..acedf09 100755 Binary files a/patch_v2/meow_netmgrd/meow_netmgrd and b/patch_v2/meow_netmgrd/meow_netmgrd differ diff --git a/patch_v2/meow_netmgrd/meow_netmgrd.c b/patch_v2/meow_netmgrd/meow_netmgrd.c index 445b2ba..3555499 100644 --- a/patch_v2/meow_netmgrd/meow_netmgrd.c +++ b/patch_v2/meow_netmgrd/meow_netmgrd.c @@ -327,6 +327,9 @@ typedef struct { long rx, tx; /* 累计字节(/proc/net/dev) */ } iface_t; static iface_t g_ifaces[IFACE_MAX]; +typedef struct { char entity[48]; uint32_t rx, tx; } ent_bytes_t; +static ent_bytes_t g_ent_bytes[ENTITY_MAX]; /* SDK 自愿上报(§4 字节级记账自报通道;声明须在 build_status_json 前) */ +static int g_ent_bytes_n = 0; static int g_iface_n = 0; typedef struct { @@ -592,6 +595,15 @@ static void wifi_status(wifi_t *w) if (!nl) break; ln = nl + 1; } + /* 期4:status 无 signal=(本机 wpa_cli status 不输出)→ 回退 wpa_cli signal_poll(RSSI=)。 */ + if (!w->has_rssi) { + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s signal_poll 2>/dev/null", g_wpa_cli); + char sbuf[256]; + if (run_cmd(sbuf, sizeof sbuf, RUNCMD_TMO_MS, cmd) > 0) { + char *s = strstr(sbuf, "RSSI="); + if (s) { w->rssi = atoi(s + 5); w->has_rssi = 1; } + } + } } /* ---------------- 蓝牙:hciconfig/bluetoothctl 包装 ---------------- */ @@ -896,8 +908,17 @@ static void build_status_json(char *out, size_t cap) if (g_net_rate_rx < 0) jadd(&w, "null,"); else jadd(&w, "%ld,", g_net_rate_rx); jadd(&w, "\"tx_bps\":"); - if (g_net_rate_tx < 0) jadd(&w, "null}}"); - else jadd(&w, "%ld}}", g_net_rate_tx); + if (g_net_rate_tx < 0) jadd(&w, "null},"); /* rate object 闭合 + 逗号(tx_bps 为 rate 末字段) */ + else jadd(&w, "%ld},", g_net_rate_tx); + + /* 期4:SDK 自愿上报的 per-实体字节统计(§4 字节级记账自报通道);空则 []。 */ + jadd(&w, "\"entity_bytes\":["); + for (int i = 0; i < g_ent_bytes_n; i++) { + if (i) jadd(&w, ","); + jadd(&w, "{\"entity\":\"%s\",\"rx\":%u,\"tx\":%u}", g_ent_bytes[i].entity, + g_ent_bytes[i].rx, g_ent_bytes[i].tx); + } + jadd(&w, "]}"); } /* 单/全接口详情 JSON。name 空=全部(数组);否则单对象。返回 MEOW_OK / MEOW_ERR_NOENT。 */ @@ -975,6 +996,7 @@ typedef struct { static rule_t g_rules[RULE_ENT_MAX]; static int g_rule_n = 0; static char g_policy_log_level[8] = "info"; /* global log_level= */ +static int g_dns_filter_aaaa = 0; /* global dns.filter_aaaa=:0=透传 AAAA(默认),1=过滤(纯 IPv4 设备弱化 IPv6 优先) */ /* ---- 期3 防自锁清单(NETMGR_DESIGN §14 红线):这些实体拒绝/强制置 allow,防断网变砖。 ---- */ static int _is_selflock_by_name(const char *name) @@ -1185,7 +1207,11 @@ static void rules_load(void) *sp = 0; char *entity = p, *rest = sp + 1; if (strcmp(entity, "global") == 0) { - /* global log_level= */ + /* global log_level= dns.filter_aaaa=0|1 + * 注意:log_level 解析会在 rest 中部写 NUL 截断,故 dns.filter_aaaa 必须先解析。 */ + char *fa = strstr(rest, "dns.filter_aaaa="); + if (fa) { char *v = fa + 16; while (*v==' '||*v=='\t') v++; + g_dns_filter_aaaa = atoi(v) ? 1 : 0; } char *lv = strstr(rest, "log_level="); if (lv) { char *v = lv + 10; while (*v==' '||*v=='\t'||*v=='"') v++; char *e2 = v; while (*e2 && *e2!=' ' && *e2!='\t' && *e2!='\n') e2++; *e2=0; @@ -1294,7 +1320,8 @@ static void policy_persist_entity(const char *entity) } /* 追加本实体当前态 */ if (strcmp(entity, "global") == 0) { - int l = snprintf(out + o, sizeof out - o, "global log_level=%s\n", g_policy_log_level); + int l = snprintf(out + o, sizeof out - o, "global log_level=%s dns.filter_aaaa=%d\n", + g_policy_log_level, g_dns_filter_aaaa); if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l; } else { rule_t *r = policy_find(entity); @@ -1684,7 +1711,7 @@ static void dns_proxy_stop(void) svc_log("dns proxy off"); } -static int dns_parse_query(const uint8_t *q, int qlen, char *domain, int domcap, int *qend) +static int dns_parse_query(const uint8_t *q, int qlen, char *domain, int domcap, int *qend, int *qtype) { if (qlen < 12) return -1; if (q[2] & 0x80) return -1; /* 不是 query 位 */ @@ -1703,6 +1730,7 @@ static int dns_parse_query(const uint8_t *q, int qlen, char *domain, int domcap, } if (off + 4 > qlen) return -1; domain[di] = 0; + if (qtype) *qtype = (q[off] << 8) | q[off + 1]; /* QTYPE:1=A 28=AAAA */ off += 4; /* qtype+qclass */ *qend = off; return di > 0 ? 0 : -1; @@ -1711,7 +1739,7 @@ static int dns_build_nxdomain(const uint8_t *q, int qlen, uint8_t *out, int outc { int qend = 0; char dom[DNS_QNAME_MAX]; - if (dns_parse_query(q, qlen, dom, sizeof dom, &qend) != 0) return -1; + if (dns_parse_query(q, qlen, dom, sizeof dom, &qend, NULL) != 0) return -1; int qsec = qend - 12; if (12 + qsec > outcap) return -1; memcpy(out, q, 12); @@ -1783,8 +1811,8 @@ static void dns_handle_packet(void) if (n < 12) return; g_dns_q++; char dom[DNS_QNAME_MAX]; - int qend = 0; - if (dns_parse_query(q, (int)n, dom, sizeof dom, &qend) != 0 || !dom[0]) return; + int qend = 0, qtype = 0; + if (dns_parse_query(q, (int)n, dom, sizeof dom, &qend, &qtype) != 0 || !dom[0]) return; const char *entity = dns_entity_for_port((uint16_t)ntohs(from.sin_port)); /* 域名规则:deny → NXDOMAIN + 日志 */ if (dns_query_domain_denied(entity, dom)) { @@ -1795,6 +1823,21 @@ static void dns_handle_packet(void) if (rl > 0) sendto(g_dns_fd, resp, (size_t)rl, 0, (struct sockaddr *)&from, fl); return; } + /* 期4 AAAA 过滤(dns.filter_aaaa=1,纯 IPv4 设备弱化 IPv6 优先):回「无 AAAA」空应答 → 客户端回落 A。 */ + if (g_dns_filter_aaaa && qtype == 28) { + int qsec = qend - 12; + if (qsec > 0 && 12 + qsec <= (int)sizeof q) { + uint8_t resp[1536]; + memcpy(resp, q, 12); + resp[2] = 0x81; resp[3] = 0x80; /* QR|RD|RA, rcode=0 */ + resp[4] = 0; resp[5] = 1; /* qdcount=1 */ + resp[6] = 0; resp[7] = 0; resp[8] = 0; resp[9] = 0; resp[10] = 0; resp[11] = 0; + memcpy(resp + 12, q + 12, (size_t)qsec); + sendto(g_dns_fd, resp, (size_t)(12 + qsec), 0, (struct sockaddr *)&from, fl); + net_log("info", "dns", "aaaa filtered entity=%s domain=%s", entity, dom); + } + return; + } /* 只记新域名首见(全量 debug 级,防日志爆炸) */ int cached = dns_cache_lookup_name(dom); if (cached != 0) { g_dns_first++; net_log("info", "dns", "first entity=%s domain=%s", entity, dom); } @@ -2257,6 +2300,22 @@ static void wifi_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r) ctl_sets(r, n >= 0 ? MEOW_OK : MEOW_ERR_NODEV, "wifi disconnect: 已下发"); } else if (!strcmp(cmd, "on") || !strcmp(cmd, "off")) { wifi_onoff(cmd, r); + } else if (!strcmp(cmd, "forget")) { + int nid = arg ? atoi(arg) : -1; + if (nid < 0) { ctl_sets(r, MEOW_ERR_INVAL, "wifi forget 需 "); return; } + char cmd2[192]; + snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s remove_network %d 2>/dev/null", + g_wpa_cli, nid); + char buf[128]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2); + /* 期4:移除后持久化(MeowOS 模式;HibyOS 只读由服务端门控)。 */ + if (g_mode_meow) { + snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s save_config 2>/dev/null", g_wpa_cli); + run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2); + } + char msg[64]; + snprintf(msg, sizeof msg, "wifi forget: network_id=%d 已移除%s", nid, g_mode_meow ? "+save_config" : ""); + ctl_sets(r, n >= 0 ? MEOW_OK : MEOW_ERR_NODEV, msg); } else if (!strcmp(cmd, "rfkill")) { rfkill_ctl(arg, r); } else if (!strcmp(cmd, "udhcpc")) { @@ -2576,7 +2635,57 @@ static void handle_conn(int c) send_frame(c, MSG_NET_POLICY_RESULT, req_id, &res, (uint32_t)(8 + tl + 1)); continue; } - send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1 STATUS/IFACE/WIFI_CTL/BT_CTL; 期2 CONNS/DNS_CTL; 期3 POLICY)"); + if (type == MSG_NET_SDK) { + m_net_sdk_t q; + memset(&q, 0, sizeof q); + if (rlen > sizeof q) rlen = sizeof q; + memcpy(&q, payload, rlen); + m_net_sdk_result_t res; + memset(&res, 0, sizeof res); + char ent[48]; + snprintf(ent, sizeof ent, "app:%s", q.app_id[0] ? q.app_id : "unknown"); + if (q.op == NET_SDK_CHECK) { + int blocked = policy_net_blocked(ent) || dns_query_domain_denied(ent, q.host); + res.allow = blocked ? 0 : 1; + net_log("info", "sdk", "check app=%s host=%s -> %s", q.app_id, q.host, blocked ? "block" : "allow"); + snprintf(res.text, sizeof res.text, "sdk check app=%s host=%s %s\n", q.app_id, q.host, + blocked ? "block" : "allow"); + res.code = MEOW_OK; + } else if (q.op == NET_SDK_REPORT) { + /* 自愿字节上报 → per-实体统计(§4 字节级记账自报)。 */ + for (int i = 0; i < g_ent_bytes_n; i++) + if (strcmp(g_ent_bytes[i].entity, ent) == 0) { + g_ent_bytes[i].rx += q.rx; g_ent_bytes[i].tx += q.tx; + res.code = MEOW_OK; + snprintf(res.text, sizeof res.text, "sdk report app=%s rx=%u tx=%u (累计 %u/%u)\n", + q.app_id, q.rx, q.tx, g_ent_bytes[i].rx, g_ent_bytes[i].tx); + goto sdk_send; + } + if (g_ent_bytes_n < ENTITY_MAX) { + snprintf(g_ent_bytes[g_ent_bytes_n].entity, sizeof g_ent_bytes[g_ent_bytes_n].entity, "%s", ent); + g_ent_bytes[g_ent_bytes_n].rx = q.rx; + g_ent_bytes[g_ent_bytes_n].tx = q.tx; + g_ent_bytes_n++; + res.code = MEOW_OK; + snprintf(res.text, sizeof res.text, "sdk report app=%s rx=%u tx=%u\n", q.app_id, q.rx, q.tx); + } else { + res.code = MEOW_ERR_NOMEM; + snprintf(res.text, sizeof res.text, "sdk report: entity 槽满\n"); + } + } else { + res.code = MEOW_ERR_INVAL; + snprintf(res.text, sizeof res.text, "sdk: 未知 op\n"); + } + sdk_send: + { + size_t tl = strlen(res.text); + if (tl > sizeof res.text - 1) tl = sizeof res.text - 1; + res.text_len = (uint16_t)tl; + send_frame(c, MSG_NET_SDK_RESULT, req_id, &res, (uint32_t)(12 + tl + 1)); + } + continue; + } + send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1-4: STATUS/IFACE/WIFI_CTL/BT_CTL/CONNS/DNS/POLICY/SDK)"); break; } } @@ -3070,6 +3179,10 @@ static int cli_run(int n, char **v) if (!arg2) { fprintf(stderr, "connect 需 [,psk]\n"); return 1; } return cli_cmd_result(MSG_NET_WIFI_CTL, "connect", arg2); } + if (!strcmp(arg, "forget")) { + if (!arg2) { fprintf(stderr, "forget 需 \n"); return 1; } + return cli_cmd_result(MSG_NET_WIFI_CTL, "forget", arg2); + } return cli_cmd_result(MSG_NET_WIFI_CTL, arg, NULL); } if (!strcmp(cmd, "bt")) { diff --git a/patch_v2/meow_netmgrd/meow_netmgrd_host b/patch_v2/meow_netmgrd/meow_netmgrd_host index d0e4d39..a585b33 100755 Binary files a/patch_v2/meow_netmgrd/meow_netmgrd_host and b/patch_v2/meow_netmgrd/meow_netmgrd_host differ diff --git a/patch_v2/meow_netmgrd/netmgrd_authtest b/patch_v2/meow_netmgrd/netmgrd_authtest index 7e0530c..f58cc61 100755 Binary files a/patch_v2/meow_netmgrd/netmgrd_authtest and b/patch_v2/meow_netmgrd/netmgrd_authtest differ diff --git a/patch_v2/meow_netmgrd/test_netmgrd.sh b/patch_v2/meow_netmgrd/test_netmgrd.sh index 3e6ba5d..96c32be 100755 --- a/patch_v2/meow_netmgrd/test_netmgrd.sh +++ b/patch_v2/meow_netmgrd/test_netmgrd.sh @@ -252,11 +252,56 @@ ck T16e "permission denied" "$OUT" OUT=$(env $ENVS $Q "$T/meow_net" policy del app:dev.player.ncm 2>&1) ck T16f "permission denied" "$OUT" +# ---- T17: SDK 自愿通道 end-to-end(meow_net_policy_check/report 对守护 A)---- +SDK_BIN="$T/test_net_ipc" +if [ -n "$Q" ]; then + "$HOME/Project/R3ProII/.tools/zig-mipsel-cc" -Os -Wall -Wextra -I../sdk -I../sdk/include -I../meow_launcherd \ + ../sdk/test_net_ipc.c ../sdk/meow_net_ipc.c -o "$SDK_BIN" 2>/dev/null +else + cc -Os -Wall -Wextra -I../sdk -I../sdk/include -I../meow_launcherd \ + ../sdk/test_net_ipc.c ../sdk/meow_net_ipc.c -o "$SDK_BIN" 2>/dev/null +fi +if [ -x "$SDK_BIN" ]; then + OUT=$(env MEOW_LOG_APP=dev.player.ncm MEOW_NET_SOCK="$T/net.sock" $Q "$SDK_BIN" 2>&1) + ck T17a "CHECK_GOOD=0" "$OUT" # music.163.com 放行 + ck T17b "CHECK_BLOCK=1" "$OUT" # ads.example.com 被策略 deny → block + ck T17c "REPORT_DONE" "$OUT" +else + FAIL=$((FAIL+1)); echo "FAIL T17 SDK test 构建失败" +fi + +# ---- T18: SDK report → status JSON entity_bytes 回读 ---- +JOUT2=$(python3 - "$T/net.sock" <<'PYEOF' +import socket,struct,sys,json +s=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM); s.connect(sys.argv[1]); s.settimeout(2) +def recvn(n): + d=b"" + while len(d)=0(signal_poll 桩已给)---- +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T18d "rssi=-45" "$OUT" + # ---- T14: DNS 代理 wire 实测(守护 B:高端口 + 桩上游 + 全局 deny;测 deny/forward/cache)---- HOSTIP=$(hostname -I 2>/dev/null | awk '{print $1}') [ -n "$HOSTIP" ] || HOSTIP="127.0.0.1" cat > "$T/policy_dns" <<'EOF' -global domain=-ads.example.com +global domain=-ads.example.com dns.filter_aaaa=1 EOF printf 'nameserver %s\n' "$HOSTIP" > "$T/resolv_dns" cat > "$T/dns_test.py" <<'PYEOF' @@ -279,17 +324,18 @@ def serve(): except Exception: pass threading.Thread(target=serve, daemon=True).start() -def query(name): +def query(name, qtype=1): s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.settimeout(3) hdr=b'\x12\x34'+b'\x01\x00'+b'\x00\x01\x00\x00\x00\x00\x00\x00' - q=b''.join(bytes([len(l)])+l.encode() for l in name.split('.'))+b'\x00'+b'\x00\x01\x00\x01' + q=b''.join(bytes([len(l)])+l.encode() for l in name.split('.'))+b'\x00'+struct.pack('>HH',qtype,1) s.sendto(hdr+q, ("127.0.0.1", 5300)) r,_=s.recvfrom(1536); rco=r[3]&0xf; an=struct.unpack('>H',r[6:8])[0] return rco,an try: d1,a1 = query("ads.example.com") # deny → NXDOMAIN (rcode 3) d2,a2 = query("music.163.com") # allow → forward → A 11.22.33.44 - sys.stdout.write("DENY_RCODE=%d\nALLOW_AN=%d\n" % (d1, a2)) + da,aa = query("music.163.com", 28) # AAAA + dns.filter_aaaa=1 → 空应答(回落 A) + sys.stdout.write("DENY_RCODE=%d\nALLOW_AN=%d\nAAAA_RCODE=%d AAAA_AN=%d\n" % (d1, a2, da, aa)) except Exception as e: sys.stdout.write("ERR=%s\n" % e) PYEOF @@ -306,8 +352,10 @@ ck T14a "enabled=1 upstream=$HOSTIP" "$OUT" WIRE=$(python3 "$T/dns_test.py" 2>&1) ck T14b "DENY_RCODE=3" "$WIRE" ck T14c "ALLOW_AN=1" "$WIRE" +ck T14c2 "AAAA_RCODE=0" "$WIRE" # 期4 AAAA 过滤:rcode 0 +ck T14c3 "AAAA_AN=0" "$WIRE" # 且 0 条应答(回落 A) OUT=$(env $DNSENV $Q "$T/meow_net" dns stats 2>&1) -ck T14d "q=2" "$OUT" +ck T14d "q=3" "$OUT" # deny + allow + AAAA = 3 查询 ck T14e "deny=1" "$OUT" kill $D2PID 2>/dev/null; wait $D2PID 2>/dev/null # 恢复守护 A 的 MEOW_RESOLV(其未被 DNS 触碰,此处确认) diff --git a/patch_v2/meow_netmgrd/test_netmgrd_p2 b/patch_v2/meow_netmgrd/test_netmgrd_p2 index 0a9ce02..4f6d04d 100755 Binary files a/patch_v2/meow_netmgrd/test_netmgrd_p2 and b/patch_v2/meow_netmgrd/test_netmgrd_p2 differ diff --git a/patch_v2/meow_netmgrd/test_netmgrd_p2.c b/patch_v2/meow_netmgrd/test_netmgrd_p2.c index d96ed23..06d0103 100644 --- a/patch_v2/meow_netmgrd/test_netmgrd_p2.c +++ b/patch_v2/meow_netmgrd/test_netmgrd_p2.c @@ -80,7 +80,7 @@ int main(void) { 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(dns_parse_query(q, ql, dom, sizeof dom, &qend, NULL) == 0); CHECK(strcmp(dom, "music.163.com") == 0); CHECK(qend == ql); } diff --git a/patch_v2/meow_netmgrd/test_netmgrd_p3 b/patch_v2/meow_netmgrd/test_netmgrd_p3 index 2a9f51e..a6e5e98 100755 Binary files a/patch_v2/meow_netmgrd/test_netmgrd_p3 and b/patch_v2/meow_netmgrd/test_netmgrd_p3 differ diff --git a/patch_v2/meow_netmgrd/test_netmgrd_p5 b/patch_v2/meow_netmgrd/test_netmgrd_p5 new file mode 100755 index 0000000..259a8a6 Binary files /dev/null and b/patch_v2/meow_netmgrd/test_netmgrd_p5 differ diff --git a/patch_v2/meow_netmgrd/test_netmgrd_p5.c b/patch_v2/meow_netmgrd/test_netmgrd_p5.c new file mode 100644 index 0000000..e910695 --- /dev/null +++ b/patch_v2/meow_netmgrd/test_netmgrd_p5.c @@ -0,0 +1,110 @@ +/* test_netmgrd_p5.c — meow_netmgrd 期4 纯逻辑宿主自测(非生产代码)。 + * + * 通过 #include 生产源,同一 TU 直接访问 FILE-STATIC:wifi_status / dns_parse_query / + * rules_load / wifi_ctl_execute / g_dns_filter_aaaa / g_policy_log_level / policy_cmd。 + * 覆盖:①wifi status 缺 signal= 时回退 signal_poll 取 RSSI(面板 rssi=null 缺口修复) + * ②dns_parse_query 解析 QTYPE(A=1/AAAA=28,供 AAAA 过滤)③global dns.filter_aaaa 配置加载 + * ④wifi forget(remove_network + save_config,MeowOS)⑤策略引擎回归(net/domain/防自锁)。 + * + * 用法:cc -Wall -Wextra -I../meow_launcherd test_netmgrd_p5.c -o test_netmgrd_p5 && ./test_netmgrd_p5 + */ +#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);} } + +int main(void) +{ + static const char *T = "/tmp/nmg_p5"; + char path[256]; + mkdir(T, 0755); + + printf("== wifi_status:status 无 signal= 回退 signal_poll ==\n"); + snprintf(path, sizeof path, "%s/wpa_st", T); + wf(path, "#!/bin/sh\n" + "if [ \"$1\" = \"status\" ]; then echo 'wpa_state=COMPLETED'; echo 'ssid=MyWiFi'; echo 'ip_address=10.78.193.50'; fi\n" + "if [ \"$1\" = \"signal_poll\" ]; then echo 'RSSI=-56'; echo 'LINKSPEED=54'; fi\n" + "exit 0\n"); + chmod(path, 0755); + g_wpa_cli = ""; { static char w[192]; snprintf(w,sizeof w,"%s",path); g_wpa_cli=w; } + wifi_t w; + wifi_status(&w); + CHECK(w.has_rssi == 1); + CHECK(w.rssi == -56); + CHECK(w.connected == 1 && strcmp(w.ssid, "MyWiFi") == 0); + + printf("== dns_parse_query:QTYPE 解析 ==\n"); + { + uint8_t q[256]; + int off = 12; + const char *name = "music.163.com"; + memset(q,0,sizeof q); q[2]=0x01; q[4]=0; q[5]=1; /* RD=1 qdcount=1 */ + for (const char *p=name; *p;) { + const char *d = strchr(p, '.'); + size_t l = d ? (size_t)(d - p) : strlen(p); + q[off++] = (uint8_t)l; memcpy(q+off, p, l); off += (int)l; + p = d ? d + 1 : p + l; + } + q[off++] = 0; /* qname 终止 */ + q[off++] = 0; q[off++] = 1; /* qtype A */ + q[off++] = 0; q[off++] = 1; /* qclass IN */ + char dom[DNS_QNAME_MAX]; int qend=0,qt=0; + CHECK(dns_parse_query(q,off,dom,sizeof dom,&qend,&qt)==0); + CHECK(strcmp(dom,"music.163.com")==0 && qt==1); /* A=1 */ + q[off-4] = 0; q[off-3] = 28; /* 改 QTYPE=AAAA */ + CHECK(dns_parse_query(q,off,dom,sizeof dom,&qend,&qt)==0 && qt==28); + } + + printf("== global dns.filter_aaaa 配置加载 + policy 回归 ==\n"); + snprintf(path, sizeof path, "%s/policy", T); + wf(path, "global log_level=debug dns.filter_aaaa=1\nproc:thttpd net=block\n"); + g_net_policy[0]=0; snprintf(g_net_policy,sizeof g_net_policy,"%s",path); + rules_load(); + CHECK(g_dns_filter_aaaa == 1); + CHECK(strcmp(g_policy_log_level, "debug") == 0); + CHECK(policy_net_blocked("proc:thttpd") == 1); + CHECK(dns_query_domain_denied("proc:thttpd", "anything.com") == 1); /* net=block → DNS 全 deny */ + CHECK(is_self_lock("proc:dropbear") == 1); + CHECK(policy_net_blocked("proc:dropbear") == 0); + + printf("== wifi forget(remove_network + save_config)==\n"); + { + char rm_path[192], sv_path[192]; + snprintf(rm_path, sizeof rm_path, "%s/rm.log", T); + snprintf(sv_path, sizeof sv_path, "%s/sv.log", T); + snprintf(path, sizeof path, "%s/wpa_fg", T); + char script[512]; + snprintf(script, sizeof script, + "#!/bin/sh\n" + "if [ \"$1\" = \"remove_network\" ]; then echo \"$@\" > \"%s\"; fi\n" + "if [ \"$1\" = \"save_config\" ]; then echo \"$@\" > \"%s\"; fi\n" + "exit 0\n", rm_path, sv_path); + wf(path, script); + chmod(path, 0755); + g_wpa_cli = ""; { static char w2[192]; snprintf(w2,sizeof w2,"%s",path); g_wpa_cli=w2; } + g_mode_meow = 1; + ctl_result_t r; + wifi_ctl_execute("forget", "7", &r); + CHECK(r.code == MEOW_OK); + CHECK(strstr(r.text, "network_id=7") != NULL); + CHECK(strstr(r.text, "save_config") != NULL); + char buf[64]; read_file_txt(rm_path, buf, sizeof buf); + CHECK(strstr(buf, "remove_network 7") != NULL); + read_file_txt(sv_path, buf, sizeof buf); + CHECK(strstr(buf, "save_config") != NULL); + } + + if (g_fail) { printf("\nNETMGR P5 TEST: %d FAIL\n", g_fail); return 1; } + printf("\nNETMGR P5 TEST: ALL PASS\n"); + return 0; +} diff --git a/patch_v2/sdk/build_sdk.sh b/patch_v2/sdk/build_sdk.sh index e900bd4..dc518fc 100755 --- a/patch_v2/sdk/build_sdk.sh +++ b/patch_v2/sdk/build_sdk.sh @@ -40,7 +40,7 @@ FT_INCS="-I$FT_BUILD -I$FT_BUILD/include -I$FT_SRC/include" rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str meow_ui_ft" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str meow_ui_ft meow_net_ipc" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/patch_v2/sdk/include/meow_ui_net.h b/patch_v2/sdk/include/meow_ui_net.h new file mode 100644 index 0000000..332d434 --- /dev/null +++ b/patch_v2/sdk/include/meow_ui_net.h @@ -0,0 +1,33 @@ +/* + * meow_ui_net.h — MeowOS SDK 网络自愿通道(NETMGR_DESIGN §6.3/§4;追加式 API,含入 meow_ui.h)。 + * + * 定位:框架 App 联网前的**自愿自治**通道 + **自愿字节上报**,二者都不强制(防君子不防小人)。 + * - meow_net_policy_check(host):联网前自查 host 是否被 netmgrd 管控策略放行。 + * 返回 0=allow(可联网);1=block(策略拒绝,App 应中止本次联网)。 + * netmgrd 不在 / 超时 → 0(乐观放行,绝不因监控不可用而误封业务)。 + * - meow_net_report(rx,tx):App 自愿上报本进程累计收发字节。内核无 per-socket/per-uid + * 字节计数器(NETMGR_DESIGN §4 表),§4 的「字节级精确记账走 SDK 自报通道」由此落地; + * netmgrd 记入 per-实体统计(status JSON `entity_bytes`)。best-effort,失败静默不影响业务。 + * 用法:App 在发起 HTTP/TLS 前调 meow_net_policy_check(host),若返回 1 则按产品语义处理(如提示被限)。 + */ +#ifndef MEOW_UI_NET_H +#define MEOW_UI_NET_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 检查 host 是否被策略放行。0=allow;1=block;netmgrd 不在/超时 → 0(乐观放行)。 + * host 为域名(如 "music.163.com");判定以当前 App(MEOW_LOG_APP)为实体,服务端策略引擎权威执行。 */ +int meow_net_policy_check(const char *host); + +/* 上报本进程累计收发字节(best-effort,失败静默)。 */ +void meow_net_report(uint32_t rx, uint32_t tx); + +#ifdef __cplusplus +} +#endif + +#endif /* MEOW_UI_NET_H */ diff --git a/patch_v2/sdk/meow_net_ipc.c b/patch_v2/sdk/meow_net_ipc.c new file mode 100644 index 0000000..5247867 --- /dev/null +++ b/patch_v2/sdk/meow_net_ipc.c @@ -0,0 +1,157 @@ +/* + * meow_net_ipc.c — MeowOS SDK 网络自愿通道 → netmgrd IPC 桥(期4;追加式协议 0x00EE/0x00EF)。 + * + * 与 meow_notify_ipc.c 同型:只做「一次性 connect + send + read」,不维护连接/订阅循环。 + * - meow_net_policy_check(host):MSG_NET_SDK op=NET_SDK_CHECK(app_id=MEOW_LOG_APP + host); + * 读到 allow=1 → 返回 0(allow),allow=0 → 返回 1(block);任何失败(无 socket/超时/非法应答) + * → 返回 0(乐观放行,绝不因监控不可用误封业务——NETMGR_DESIGN §6.3 自愿性质)。 + * - meow_net_report(rx,tx):MSG_NET_SDK op=NET_SDK_REPORT;send 后读一次应答头即关(不读文本), + * best-effort,失败静默(返回值 void)。 + * 映射层(SDK 入参 ↔ IPC m_net_sdk_t 字段)集中在本文件;app_id 取 env MEOW_LOG_APP(与 meow_ui_log.c + * 口径一致),socket 路径取 env MEOW_NET_SOCK(宿主/qemu 覆盖)缺省 /usr/data/meow_net.sock。 + * + * 线程安全:app_id pthread_once 缓存;本文件纯传输层不调 meow_log(与 meow_notify_ipc.c 同约定)。 + */ +#include "meow_net_ipc.h" +#include "meow_ipc.h" /* 共享协议:帧头/m_net_sdk_t/m_net_sdk_result_t/MSG_NET_SDK* —— 防漂移 */ + +#include /* snprintf */ +#include /* getenv */ +#include +#include /* EINTR */ +#include +#include +#include +#include +#include + +#define MEOW_NET_IPC_TIMEOUT_MS 500 /* 应答读超时:netmgrd 单线程 poll,>500ms 视为降级 */ + +static pthread_once_t g_once = PTHREAD_ONCE_INIT; +static char g_app_id[MEOW_APP_ID_MAX] = "app"; + +static void ipc_init(void) +{ + const char *s = getenv("MEOW_LOG_APP"); + if (s && s[0]) snprintf(g_app_id, sizeof g_app_id, "%s", s); +} + +const char *meow_net_ipc_app_id(void) +{ + pthread_once(&g_once, ipc_init); + return g_app_id; +} + +/* socket 路径:优先 env MEOW_NET_SOCK,缺省 /usr/data/meow_net.sock。 */ +static const char *ipc_sock_path(void) +{ + const char *s = getenv("MEOW_NET_SOCK"); + return (s && s[0]) ? s : "/usr/data/meow_net.sock"; +} + +/* 连接 socket;失败 -1(调用方降级)。 */ +static int conn_path(const char *path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } + return fd; +} +static int write_all(int fd, const uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, p + off, n - off); + if (w < 0) { if (errno == EINTR) continue; return 0; } + if (w == 0) return 0; + off += (size_t)w; + } + return 1; +} +static int read_all(int fd, uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t r = read(fd, p + off, n - off); + if (r < 0) { if (errno == EINTR) continue; return 0; } + if (r == 0) return 0; + off += (size_t)r; + } + return 1; +} +static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t magic = MEOW_IPC_MAGIC, rl = len; + uint16_t ver = MEOW_IPC_VERSION, t = type; + memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2); + memcpy(hdr + 8, &req_id, 4); memcpy(hdr + 12, &rl, 4); + if (!write_all(fd, hdr, sizeof hdr)) return 0; + if (len && payload && !write_all(fd, payload, len)) return 0; + return 1; +} +static void set_timeout(int fd) +{ + struct timeval tv; + tv.tv_sec = MEOW_NET_IPC_TIMEOUT_MS / 1000; + tv.tv_usec = (MEOW_NET_IPC_TIMEOUT_MS % 1000) * 1000; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); +} + +int meow_net_policy_check(const char *host) +{ + if (!host || !host[0]) return 0; + m_net_sdk_t q; + memset(&q, 0, sizeof q); + q.op = NET_SDK_CHECK; + snprintf(q.app_id, sizeof q.app_id, "%s", meow_net_ipc_app_id()); + snprintf(q.host, sizeof q.host, "%s", host); + + int fd = conn_path(ipc_sock_path()); + if (fd < 0) return 0; /* java socket:乐观放行 */ + set_timeout(fd); + int allow = 0; /* 默认放行 */ + if (send_frame(fd, MSG_NET_SDK, 0x4E455443u /* "NETC" */, &q, (uint32_t)sizeof q)) { + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + if (read_all(fd, rhdr, sizeof rhdr)) { + uint16_t rtype; uint32_t rlen; + memcpy(&rtype, rhdr + 6, 2); memcpy(&rlen, rhdr + 12, 4); + if (rtype == MSG_NET_SDK_RESULT && rlen >= 10) { + uint8_t pay[12]; + if (read_all(fd, pay, 12)) { /* code(4)+allow(4)+text_len(2)+pad(2) */ + m_net_sdk_result_t res; + memset(&res, 0, sizeof res); + memcpy(&res.code, pay, 4); + memcpy(&res.allow, pay + 4, 4); + memcpy(&res.text_len, pay + 8, 2); + if (res.text_len < sizeof res.text) + read_all(fd, (uint8_t *)res.text, res.text_len); + if (res.code == MEOW_OK) allow = (res.allow != 0) ? 1 : 0; + } + } + } + } + close(fd); + return allow ? 0 : 1; /* allow=1 → 返回 0(放行);allow=0 → 返回 1(block) */ +} + +void meow_net_report(uint32_t rx, uint32_t tx) +{ + m_net_sdk_t q; + memset(&q, 0, sizeof q); + q.op = NET_SDK_REPORT; + snprintf(q.app_id, sizeof q.app_id, "%s", meow_net_ipc_app_id()); + q.rx = rx; q.tx = tx; + int fd = conn_path(ipc_sock_path()); + if (fd < 0) return; /* 无 socket:静默 */ + set_timeout(fd); + if (send_frame(fd, MSG_NET_SDK, 0x4E455452u /* "NETR" */, &q, (uint32_t)sizeof q)) { + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + read_all(fd, rhdr, sizeof rhdr); /* best-effort:读一次应答头即关,不解析 */ + } + close(fd); +} diff --git a/patch_v2/sdk/meow_net_ipc.h b/patch_v2/sdk/meow_net_ipc.h new file mode 100644 index 0000000..98646c7 --- /dev/null +++ b/patch_v2/sdk/meow_net_ipc.h @@ -0,0 +1,28 @@ +/* + * meow_net_ipc.h — MeowOS SDK 网络自愿通道 → netmgrd IPC 桥(内部头,不随 meow_ui.h 发布)。 + * + * 定位(NETMGR_DESIGN §6.3/§4):让框架 App 的 meow_net_policy_check()/meow_net_report() 真正经 + * AF_UNIX IPC 到 netmgrd(/usr/data/meow_net.sock)。协议帧与负载结构复用共享 meow_ipc.h: + * - MSG_NET_SDK(0x00EE) ↑:op=NET_SDK_CHECK(app_id+host)/ NET_SDK_REPORT(app_id+rx+tx); + * - MSG_NET_SDK_RESULT(0x00EF) ↓:code + (CHECK)allow + 状态文本。 + * 约定:只做「一次性 connect + send + read」,不维护订阅循环(同 meow_notify_ipc.c 型); + * 全部失败路径优雅降级(无 socket/写失败/超时/非法应答 → check 返回 0 放行、report 静默)。 + */ +#ifndef MEOW_NET_IPC_H +#define MEOW_NET_IPC_H + +#include +#include "meow_ui.h" /* meow_net_policy_check / meow_net_report */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* 当前 app 的 app_id(env MEOW_LOG_APP,缺省 "app";pthread_once 缓存,线程安全)。 */ +const char *meow_net_ipc_app_id(void); + +#ifdef __cplusplus +} +#endif + +#endif /* MEOW_NET_IPC_H */ diff --git a/patch_v2/sdk/meow_ui.h b/patch_v2/sdk/meow_ui.h index 5a9f3ea..1c7c1a0 100644 --- a/patch_v2/sdk/meow_ui.h +++ b/patch_v2/sdk/meow_ui.h @@ -71,5 +71,6 @@ #include "include/meow_ui_anim.h" #include "include/meow_ui_scroll.h" #include "include/meow_ui_str.h" +#include "include/meow_ui_net.h" /* 期4 网络自愿通道:meow_net_policy_check / meow_net_report */ #endif /* MEOW_UI_H */ diff --git a/patch_v2/sdk/test_net_ipc.c b/patch_v2/sdk/test_net_ipc.c new file mode 100644 index 0000000..a15f6a7 --- /dev/null +++ b/patch_v2/sdk/test_net_ipc.c @@ -0,0 +1,27 @@ +/* test_net_ipc.c — MeowOS SDK 网络自愿通道 宿主自测(非生产代码)。 + * + * 通过链接 meow_net_ipc.c 调 meow_net_policy_check()/meow_net_report(),对运行中的 netmgrd + * (MEOW_NET_SOCK 指向测试 socket)验证: + * - meow_net_policy_check(host):按 MEOW_LOG_APP 实体判定 allow/block(netmgrd 决策权威); + * - meow_net_report(rx,tx):best-effort 上报(netmgrd 记入 entity_bytes)。 + * 用法:cc -Iinclude -I. -I../meow_launcherd test_net_ipc.c meow_net_ipc.c -o test_net_ipc + * 然后 MEOW_NET_SOCK= MEOW_LOG_APP= ./test_net_ipc + */ +#include "include/meow_ui_net.h" +#include + +int main(void) +{ + /* CHECK:域名放行/拒绝判定(netmgrd 决策)。 */ + int a = meow_net_policy_check("music.163.com"); + printf("CHECK_GOOD=%d\n", a); /* 期望 0(allow/放行) */ + int b = meow_net_policy_check("ads.example.com"); + printf("CHECK_BLOCK=%d\n", b); /* 期望 1(block/拒绝,若策略 deny 该域) */ + /* 空 host → 0 放行(防御)。 */ + int c = meow_net_policy_check(NULL); + printf("CHECK_NULL=%d\n", c); + /* REPORT:best-effort 上报。 */ + meow_net_report(12345, 67890); + printf("REPORT_DONE\n"); + return 0; +}