meow_netmgrd 期4:SDK 自愿通道 + wifi forget + RSSI signal_poll + AAAA 过滤 + iptables 评估

NETMGR_DESIGN.md §7/§13 期4 落地(只动 patch_v2/meow_netmgrd/ + patch_v2/sdk/ 追加式):
1. SDK 自愿通道(§6.3/§4):meow_ui.h 新增 meow_net_policy_check(host)(0=放行 1=block;
   netmgrd 不在/超时→0 乐观放行)+ meow_net_report(rx,tx)(自愿上报,§4 字节级记账自报通道)。
   meow_ipc.h 追加 0xEE MSG_NET_SDK(op=CHECK/REPORT)/ 0xEF RESULT。netmgrd 侧:CHECK 以
   app_id→实体 app:<pkg> 评估 policy_net_blocked||dns_query_domain_denied;REPORT 记入 per-实体
   g_ent_bytes(status JSON 新增 entity_bytes)。SDK 实现 meow_net_ipc.c(一次 connect+send+read,
   降级不崩);meow_ui.a 重编。
2. wifi forget <id>:wpa_cli remove_network+save_config(MeowOS;HibyOS 只读门控),CLI+IPC 接通
   (286 launcher 录入 UI 缺口)。
3. RSSI:wifi_status 在 status 无 signal= 时回退 wpa_cli signal_poll(真机 rssi=-38,修面板 rssi=null 缺口)。
4. DNS AAAA 过滤:global dns.filter_aaaa=0|1(默认 0 透传;纯 IPv4 设备可置 1 让 AAAA 查询回空答案回落 A)。
5. 静态 iptables 评估(devdocs/plan/NETMGR_IPTABLES_EVAL.md,本机不入库):iptables-legacy 1.8.9 交叉
   编译成功(~578KB,含 owner/cgroup/REDIRECT 等全部 extension),但内核实测 matches 仅
   quota/icmp/udp/tcp、targets 仅 NFQUEUE/NFLOG/ERROR、无 nat 表、全部进程 UID 0 → **per-app 管控
   与 REDIRECT 透明代理判不可行**(与 NETWORK_HW_FACTS 一致);正路为 per-app UID 框架改造(单独立项)。

验证:宿主 test_netmgrd.sh 70/70 + qemu 70/70;test_netmgrd_p2/p3/p5 宿主全过 + 交叉 0 warning;
netmgrd_authtest 全过;sdk meow_net_ipc 交叉 0 warning。真机(固件 netmgrd 共存,独立 socket+DNS off):
wifi status rssi=-38(signal_poll)、wifi forget 0(DISABLED 安全移除,id1 CURRENT 保留、wifi 未断)、
SDK policy check(CHECK_GOOD=0/CHECK_BLOCK=1/REPORT ok)、policy show 正常;resolv.conf 未触碰、
固件 meow_netmgrd(pid) running 未动;测试产物全清。

新 md5:meow_netmgrd 20243ea1a800b29badefebb395603aaf;meow_ui.a be24214e24faadba63577f3a98c2c9d1
(主会话更 fw_content.conf / 热更设备 / 记录新 meow_ui.a)。
This commit is contained in:
MeowOS committed 2026-09-02 16:19:40 +08:00
1 parent 4e0e7441fd
commit 8a43f17653
17 files changed
+563 -16

No files matched your search

+30
View File
@@ -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:<pkg>,评估 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 */
Binary file not shown.
+122 -9
View File
@@ -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=<val>)。 */
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=<lvl> */
/* global log_level=<lvl> 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 需 <network_id>"); 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 需 <ssid>[,psk]\n"); return 1; }
return cli_cmd_result(MSG_NET_WIFI_CTL, "connect", arg2);
}
if (!strcmp(arg, "forget")) {
if (!arg2) { fprintf(stderr, "forget 需 <network_id>\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")) {
Binary file not shown.
Binary file not shown.
+53 -5
View File
@@ -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)<n:
c=s.recv(n-len(d))
if not c: return d
d+=c
return d
s.sendall(struct.pack('<IHHII',0x4D4F5701,1,0x00E0,1,0))
H=struct.unpack('<IHHII', recvn(16)); body=recvn(H[4])
code,jlen=struct.unpack('<IH',body[:6]); js=body[8:8+jlen].decode('utf-8','replace')
o=json.loads(js)
eb=o.get("entity_bytes") or []
hit=[e for e in eb if e["entity"]=="app:dev.player.ncm"]
sys.stdout.write("EB_HIT=%d RX=%d TX=%d\n" % (len(hit), hit[0]["rx"] if hit else -1, hit[0]["tx"] if hit else -1))
PYEOF
)
ck T18a "EB_HIT=1" "$JOUT2"
ck T18b "RX=12345" "$JOUT2"
ck T18c "TX=67890" "$JOUT2"
# ---- RSSI 回读:status wifi rssi 应 >=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 触碰,此处确认)
Binary file not shown.
+1 -1
View File
@@ -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);
}
Binary file not shown.
Binary file not shown.
+110
View File
@@ -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 <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);} }
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;
}
+1 -1
View File
@@ -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 =="
+33
View File
@@ -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 <stdint.h>
#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 */
+157
View File
@@ -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 <stdio.h> /* snprintf */
#include <stdlib.h> /* getenv */
#include <string.h>
#include <errno.h> /* EINTR */
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#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);
}
+28
View File
@@ -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 <stdint.h>
#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 */
+1
View File
@@ -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 */
+27
View File
@@ -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=<sock> MEOW_LOG_APP=<pkg> ./test_net_ipc
*/
#include "include/meow_ui_net.h"
#include <stdio.h>
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;
}