meow_netmgrd 期3:管控层(三实体策略引擎+防自锁+SOCK_DESTROY 实测→仅 DNS 层)

NETMGR_DESIGN.md §6/§13 期3 落地(全在 patch_v2/meow_netmgrd/ + meow_ipc.h 追加):
1. 策略引擎全量:/data/meow/netmgrd/net_policy.conf(/data=/usr/data)读改写单键+原子写
   (temp+rename,绝不 O_TRUNC 整文件重写——watchdog 毁设置教训);per-实体(app:/svc:/proc:)
   net=allow|block + domain 白/黑(接通 DNS 层执行)+ global log_level;默认 allow。
2. 防自锁硬编码:dropbear/wpa_supplicant/udhcpc/meow_netmgrd/meow_svcd/meow_sysui/init 拒绝 block
   (CLI/IPC 拒 + 加载校验把手工改坏的文件里这些实体的 net=block 同样强制复位 allow——§14 红线)。
3. SOCK_DESTROY 真机实测(sockdestroy_probe.c,root @内核4.4):**内核接受 SOCK_DESTROY 请求(rc=0)
   但未真正销毁 socket(cookie=0 与 dump-cookie 两法均 dead=0)→ 判定 no-op/不可用**。
   ⇒ 连接级执行降级为「仅 DNS 层生效」(net=block → dns_query_domain_denied 全量 deny,DNS 层落地),
   policy show 头 enfor=dns_only 明示。SDK 自愿通道/设置页迁移期外。
4. IPC 0x00EA MSG_NET_POLICY(op: show/set net/set domain/del)+ RESULT 0x00EB:读级任意、写级仅系统身份。
   CLI:policy show [entity] | set <entity> allow|block | domain <entity> +host|-host | del <entity>。
5. 两小修:①classify_exe 增认 MeowPanel 精确路径(修面板 dns flush 403;面板已有 PIN 门禁)
   ②连接未归因(inode=0/查不到)一律落 proc:unknown——修 apps 表出现「IP:port」原始端点行。
6. WiFi connect 持久化:MeowOS 模式 connect 后 wpa_cli save_config(HibyOS 只读由服务端门控)。

验证:宿主 test_netmgrd.sh 61/61 + qemu 61/61;test_netmgrd_p2/p3 宿主+qemu 全过;netmgrd_authtest 全过;
交叉 -Wall -Wextra 0 warning。真机只读实测(DNS-on 固件共存,用独立 socket+/tmp 临时 policy.json):
policy set/domain/del 全链 + 防自锁拒绝 proc:dropbear(rc=1) + 空文件 del(size=0) + apps 无原始端点 +
resolv.conf 未触碰(测后核验固件两行形式);Firmware meow_netmgrd(pid 8142) running 未动。

新 md5:meow_netmgrd 62369ebe29e7ef4b55067a0f14e240d0(主会话更 fw_content.conf/热更设备)。
This commit is contained in:
MeowOS committed 2026-09-01 22:08:10 +08:00
1 parent 7ba0fde3e6
commit b37dc23cd9
10 files changed
+737 -35

No files matched your search

+388 -35
View File
@@ -46,8 +46,11 @@
#include <sys/wait.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/sock_diag.h>
#include <linux/inet_diag.h>
#include "meow_ipc.h" /* 帧格式/消息号 + MSG_NET_*(0x00E0-0x00ED) + m_net_*(号段 0x00E0-0x00EF) */
#include "meow_ipc.h" /* 帧格式/消息号 + MSG_NET_*(0x00E0-0x00EF) + m_net_*(号段 0x00E0-0x00EF) */
/* ---------------- 路径(全部可经 env 覆盖,宿主/qemu 桩测试用) ---------------- */
static char g_net_dir[128] = "/sys/class/net"; /* MEOW_NET_DIR */
@@ -75,7 +78,7 @@ static char g_proc_udp[160] = "/proc/net/udp"; /* MEOW_UDP *
static char g_ams_sock[128] = "/usr/data/meow_ams.sock"; /* MEOW_AMS_SOCK(amsd 注册表 pid→pkg) */
static char g_svcd_sock[128] = "/tmp/meow_svcd.sock"; /* MEOW_SVCD_SOCK(svcd 服务表 pid→svc,socket 查询兜底) */
static char g_svcd_status[160]= "/tmp/meow_svcd.status"; /* MEOW_SVCD_STATUS(svcd 落盘状态,机器可读 `svc=<name> ... pid=`) */
static char g_net_policy[160]= "/usr/data/meow/net_policy.conf"; /* MEOW_NET_POLICY(期2 只读静态域名规则) */
static char g_net_policy[160]= "/data/meow/netmgrd/net_policy.conf"; /* MEOW_NET_POLICY(期3 读改写;/data=/usr/data 符号链接) */
#define NET_POLL_SEC_DEF 2 /* 接口轮询/状态节拍(秒;MEOW_NET_POLL_SEC 覆盖,测试用) */
#define RUNCMD_TMO_MS 1500 /* 后端命令(wpa_cli/hciconfig/etc)有界收割限时(ms) */
@@ -966,9 +969,29 @@ typedef struct {
char entity[48];
char allow[RULE_HOST_MAX][48]; int allow_n;
char deny[RULE_HOST_MAX][48]; int deny_n;
int net_block; /* net=block(默认 allow;防自锁实体强制 0) */
int has_net; /* 显式 net= 出现过 */
} rule_t;
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= */
/* ---- 期3 防自锁清单(NETMGR_DESIGN §14 红线):这些实体拒绝/强制置 allow,防断网变砖。 ---- */
static int _is_selflock_by_name(const char *name)
{
static const char *const k[] = {
"dropbear", "wpa_supplicant", "udhcpc",
"meow_netmgrd", "meow_svcd", "meow_sysui", "init", NULL
};
for (int i = 0; k[i]; i++) if (strcmp(name, k[i]) == 0) return 1;
return 0;
}
static int is_self_lock(const char *entity)
{
const char *ns = strchr(entity, ':');
const char *name = ns ? ns + 1 : entity;
return _is_selflock_by_name(name);
}
typedef struct { char key[96]; long mono; } evt_dup_t;
static evt_dup_t g_evt_dup[EVT_DUP_MAX];
@@ -1088,7 +1111,7 @@ static void dns_cache_flush(void)
g_dns_n = 0;
}
/* ---- 域名规则(期2 静态 conf 只读;期3 策略引擎再接管) ---- */
/* ---- 策略引擎(§6:per-entity net=allow|block + domain 白/黑 + global log_level;默认 allow) ---- */
static int suffix_match(const char *host, const char *rule)
{
if (!host[0] || !rule[0]) return 0;
@@ -1097,23 +1120,43 @@ static int suffix_match(const char *host, const char *rule)
if (strcmp(host + hl - rl, rule) != 0) return 0;
return hl == rl || host[hl - rl - 1] == '.';
}
static int entity_rules_match(const rule_t *r, const char *entity)
static rule_t *entity_rules_match(rule_t *r, const char *entity)
{
return r->entity[0] && strcmp(r->entity, entity) == 0;
if (!r || !r->entity[0] || strcmp(r->entity, entity) != 0) return NULL;
if (strcmp(r->entity, "global") == 0) return r;
return r;
}
/* deny 判定:命中最具体的实体规则(entity+global),allow 覆盖 deny;默认 allow。 */
static rule_t *policy_find(const char *entity)
{
for (int i = 0; i < g_rule_n; i++)
if (entity_rules_match(&g_rules[i], entity)) return &g_rules[i];
return NULL;
}
static rule_t *policy_find_global(void)
{
for (int i = 0; i < g_rule_n; i++)
if (strcmp(g_rules[i].entity, "global") == 0) return &g_rules[i];
return NULL;
}
/* net 层 block 判定:防自锁实体恒 allow;实体 net=block → block;global net=block → block;默认 allow。 */
static int policy_net_blocked(const char *entity)
{
if (is_self_lock(entity)) return 0;
rule_t *e = policy_find(entity);
if (e && e->has_net) return e->net_block;
rule_t *g = policy_find_global();
return (g && g->has_net) ? g->net_block : 0;
}
/* domain 层 deny 判定:①网被 block 的实体 → 全量 deny(DNS 层是落地保障,无 SOCK_DESTROY 也生效)
* ②实体规则中最具体(entity → global)的 domain= 白/黑;allow 覆盖 deny;默认 allow。 */
static int dns_query_domain_denied(const char *entity, const char *domain)
{
const rule_t *g = NULL, *e = NULL;
for (int i = 0; i < g_rule_n; i++) {
if (strcmp(g_rules[i].entity, "global") == 0) g = &g_rules[i];
else if (!e && entity_rules_match(&g_rules[i], entity)) e = &g_rules[i];
}
const rule_t *r = e ? e : g;
if (policy_net_blocked(entity)) return 1;
rule_t *e = policy_find(entity);
const rule_t *r = e ? e : policy_find_global();
if (r && r->deny_n) {
for (int i = 0; i < r->deny_n; i++)
if (suffix_match(domain, r->deny[i])) {
/* allow 白名单可覆盖 */
for (int j = 0; j < r->allow_n; j++)
if (suffix_match(domain, r->allow[j])) return 0;
return 1;
@@ -1121,9 +1164,12 @@ static int dns_query_domain_denied(const char *entity, const char *domain)
}
return 0;
}
/* 规则加载(启动 + 每次写后重载以自愈)。防自锁:文件被手工改坏(net=block on 命根实体)→ 加载校验强制 allow。 */
static void rules_load(void)
{
g_rule_n = 0;
snprintf(g_policy_log_level, sizeof g_policy_log_level, "%s", "info");
FILE *f = fopen(g_net_policy, "r");
if (!f) return;
char line[256];
@@ -1135,33 +1181,152 @@ static void rules_load(void)
char *sp = strchr(p, ' ');
char *tab = strchr(p, '\t');
if (!sp || (tab && tab < sp)) sp = tab;
char *entity; char *rest;
if (sp) { *sp = 0; entity = p; rest = sp + 1; }
else continue;
rule_t *r = &g_rules[g_rule_n];
memset(r, 0, sizeof *r);
snprintf(r->entity, sizeof r->entity, "%s", entity);
/* rest 形如 "domain=+a,-b,c" 或 "net=block";逐 token 解析 */
if (!sp) continue;
*sp = 0;
char *entity = p, *rest = sp + 1;
if (strcmp(entity, "global") == 0) {
/* global log_level=<lvl> */
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;
snprintf(g_policy_log_level, sizeof g_policy_log_level, "%s", v); }
}
rule_t *r = policy_find(entity);
if (!r) {
if (g_rule_n >= RULE_ENT_MAX) continue;
r = &g_rules[g_rule_n++];
memset(r, 0, sizeof *r);
snprintf(r->entity, sizeof r->entity, "%s", entity);
}
char *save = NULL;
for (char *tok = strtok_r(rest, " \t", &save); tok; tok = strtok_r(NULL, " \t", &save)) {
if (strncmp(tok, "domain=", 7) != 0) continue;
char *sub = tok + 7;
char *ssave = NULL;
for (char *h = strtok_r(sub, ",", &ssave); h; h = strtok_r(NULL, ",", &ssave)) {
if (h[0] == '+') {
if (r->allow_n < RULE_HOST_MAX) snprintf(r->allow[r->allow_n++], 48, "%s", h + 1);
} else if (h[0] == '-') {
if (r->deny_n < RULE_HOST_MAX) snprintf(r->deny[r->deny_n++], 48, "%s", h + 1);
} else if (h[0]) {
if (r->deny_n < RULE_HOST_MAX) snprintf(r->deny[r->deny_n++], 48, "%s", h);
if (strncmp(tok, "domain=", 7) == 0) {
char *sub = tok + 7, *ssave = NULL;
for (char *h = strtok_r(sub, ",", &ssave); h; h = strtok_r(NULL, ",", &ssave)) {
if (h[0] == '+') { if (r->allow_n < RULE_HOST_MAX) snprintf(r->allow[r->allow_n++], 48, "%s", h+1); }
else if (h[0] == '-') { if (r->deny_n < RULE_HOST_MAX) snprintf(r->deny[r->deny_n++], 48, "%s", h+1); }
else if (h[0]) { if (r->deny_n < RULE_HOST_MAX) snprintf(r->deny[r->deny_n++], 48, "%s", h); }
}
} else if (strncmp(tok, "net=", 4) == 0) {
int blocked = (strcmp(tok + 4, "block") == 0);
/* 防自锁:加载校验把命根实体的 net=block 强制复位为 allow(§14 红线)。 */
if (blocked && is_self_lock(entity)) {
blocked = 0;
net_log("warn", "policy", "load: self-lock entity %s forced allow (net=block 被剔除)", entity);
}
r->net_block = blocked;
r->has_net = 1;
}
}
if (r->deny_n || r->allow_n) g_rule_n++;
/* 空规则(无 net/domain)且为新建末槽 → 移除(防占槽)。 */
if (r->deny_n == 0 && r->allow_n == 0 && !r->has_net &&
r == &g_rules[g_rule_n - 1]) {
memset(r, 0, sizeof *r);
g_rule_n--;
}
}
fclose(f);
}
/* ---- 策略持久化:读改写单键(只替换目标实体行,其余逐字保留)+ 原子写(watchdog 毁设置教训) ---- */
static int policy_read_text(char *buf, int cap)
{
int fd = open(g_net_policy, O_RDONLY);
if (fd < 0) { buf[0] = 0; return -1; }
int n = 0;
while (n < cap - 1) {
ssize_t r = read(fd, buf + n, (size_t)(cap - 1 - n));
if (r <= 0) break;
n += (int)r;
}
close(fd);
buf[n] = 0;
return n;
}
/* 原子写:写临时文件 + rename(避免半截文件)。 */
static int policy_write_atomic(const char *text)
{
char tmp[192];
snprintf(tmp, sizeof tmp, "%s.tmp", g_net_policy);
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
/* 尝试建目录(/data/meow/netmgrd/) */
char dir[160]; snprintf(dir, sizeof dir, "%s", g_net_policy);
char *s = strrchr(dir, '/'); if (s) *s = 0;
mkdir(dir, 0755);
fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
if (fd < 0) return -1;
size_t tl = strlen(text);
ssize_t w = write(fd, text, tl);
close(fd);
if (w < 0 || (size_t)w != tl) { unlink(tmp); return -1; } /* 空文本 write 返回 0 == tl,视为成功 */
if (rename(tmp, g_net_policy) < 0) { unlink(tmp); return -1; }
return 0;
}
/* 只重写 entity 的行(删旧 + 按 g_rules 的当前态重发),其余行逐字保留。 */
static void policy_persist_entity(const char *entity)
{
char text[2048];
int n = policy_read_text(text, sizeof text);
char out[2048]; size_t o = 0;
char line[256];
int start = 0;
int elen = (int)strlen(entity);
/* 复制非本实体的行 */
while (start < n) {
int i = 0;
while (start + i < n && text[start + i] != '\n') i++;
if (i >= (int)sizeof line - 1) i = (int)sizeof line - 2;
memcpy(line, text + start, (size_t)i); line[i] = 0;
start += i;
if (start < n && text[start] == '\n') { start++; }
/* 去掉行首空白判断 */
char *q = line; while (*q == ' ' || *q == '\t') q++;
int is_target = (strncmp(q, entity, (size_t)elen) == 0 &&
(q[elen] == ' ' || q[elen] == '\t' || q[elen] == 0));
int is_blank = (q[0] == 0);
if (is_target) continue; /* 本实体的旧行将被替换/删除 */
if (is_blank) continue; /* 空行丢弃(避免双空行) */
/* 注释与其它实体行逐字保留(读改写单键:只动目标实体)。 */
int l = snprintf(out + o, sizeof out - o, "%s\n", line);
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
}
/* 追加本实体当前态 */
if (strcmp(entity, "global") == 0) {
int l = snprintf(out + o, sizeof out - o, "global log_level=%s\n", g_policy_log_level);
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
} else {
rule_t *r = policy_find(entity);
if (r) {
if (r->has_net) {
int l = snprintf(out + o, sizeof out - o, "%s net=%s\n", entity, r->net_block ? "block" : "allow");
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
}
if (r->deny_n || r->allow_n) {
int l = snprintf(out + o, sizeof out - o, "%s domain=", entity);
if (l > 0 && o + (size_t)l < sizeof out) o += (size_t)l;
int first = 1;
for (int i = 0; i < r->allow_n; i++) {
int la = snprintf(out + o, sizeof out - o, "%s+%s", first ? "" : ",", r->allow[i]);
if (la > 0 && o + (size_t)la < sizeof out) o += (size_t)la;
first = 0;
}
for (int i = 0; i < r->deny_n; i++) {
int ld = snprintf(out + o, sizeof out - o, "%s-%s", first ? "" : ",", r->deny[i]);
if (ld > 0 && o + (size_t)ld < sizeof out) o += (size_t)ld;
first = 0;
}
int ln = snprintf(out + o, sizeof out - o, "\n");
if (ln > 0 && o + (size_t)ln < sizeof out) o += (size_t)ln;
}
}
}
out[o] = 0;
policy_write_atomic(out);
rules_load(); /* 写后重载(自愈:也剔除任何被手工改回的自锁 block) */
}
/* ---- 实体归因:pid→(app:<pkg> / svc:<svc> / proc:<comm>) ---- */
static void pid_ent_put(int pid, const char *entity)
{
@@ -1368,7 +1533,8 @@ static void census_run(void)
for (int i = 0; i < cn; i++) { cur[i].pid = 0; cur[i].entity[0] = 0; }
attribute_inodes(cur, cn);
for (int i = 0; i < cn; i++) {
if (cur[i].inode && !cur[i].entity[0]) snprintf(cur[i].entity, sizeof cur[i].entity, "proc:unknown");
/* 任何未归因的连接一律落 proc:unknown(防 apps 表出现空 entity→被 strtok 当端点显示成 IP:port)。 */
if (!cur[i].entity[0]) snprintf(cur[i].entity, sizeof cur[i].entity, "proc:unknown");
if (cur[i].inode && cur[i].remote_ip != 0)
dns_cache_lookup_ip(cur[i].remote_ip, cur[i].domain, sizeof cur[i].domain);
}
@@ -1820,6 +1986,8 @@ static void classify_exe(const char *exe, caller_t *out)
char tmp[MEOW_APP_ID_MAX];
if (strcmp(exe, "/usr/bin/meow_launcherd") == 0) {
snprintf(tmp, sizeof tmp, "%s", "os.meow.launcher");
} else if (strcmp(exe, "/usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/meowpanel") == 0) {
snprintf(tmp, sizeof tmp, "%s", "os.meow.panel"); /* MeowPanel 精确登记(面板 PIN 已有门禁;dns flush 等动作需系统身份) */
} else if (strncmp(exe, "/usr/bin/meow_", 14) == 0) {
size_t l = strlen(exe + 9); /* "/usr/bin/" 长 9 → 取 "meow_xyz" */
if (l >= sizeof tmp) l = sizeof tmp - 1;
@@ -1994,9 +2162,16 @@ static void wifi_connect(const char *arg, ctl_result_t *r)
snprintf(sel, sizeof sel, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s select_network %d 2>/dev/null",
g_wpa_cli, id);
run_cmd(out, sizeof out, RUNCMD_TMO_MS, sel);
/* 期3:MeowOS 模式 connect 后持久化 wpa_supplicant.conf(HibyOS 只读由服务端门控,这里仅 MeowOS 生效)。 */
if (g_mode_meow) {
char sc[160];
snprintf(sc, sizeof sc, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s save_config 2>/dev/null", g_wpa_cli);
run_cmd(out, sizeof out, RUNCMD_TMO_MS, sc);
}
}
char msg[140];
snprintf(msg, sizeof msg, "connect: ssid=%s network_id=%d (已 enable+select)", tmp_ssid, id);
snprintf(msg, sizeof msg, "connect: ssid=%s network_id=%d (已 enable+select%s)", tmp_ssid, id,
g_mode_meow ? "+save_config" : "");
ctl_sets(r, MEOW_OK, msg);
}
@@ -2129,6 +2304,116 @@ static void bt_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r)
}
}
/* ---------------- 期3 策略命令(IPC 0xEA / CLI policy 共用;写级已在 handle_conn 鉴权) ---------------- */
/* 从 g_rules 移除某实体(shift 数组),返回 0=存在已删。 */
static int policy_remove(const char *entity)
{
for (int i = 0; i < g_rule_n; i++) {
if (strcmp(g_rules[i].entity, entity) == 0) {
for (int j = i; j < g_rule_n - 1; j++) g_rules[j] = g_rules[j + 1];
memset(&g_rules[g_rule_n - 1], 0, sizeof(rule_t));
g_rule_n--;
return 0;
}
}
return -1;
}
/* 输出某实体当前行;无规则输出 (none)。 */
static void policy_print_entity(const rule_t *r, char *out, size_t cap)
{
out[0] = 0;
if (!r) { snprintf(out, cap, "(none)\n"); return; }
size_t o = 0;
int l = snprintf(out + o, cap - o, "%s ", r->entity);
if (l > 0 && o + (size_t)l < cap) o += (size_t)l;
int any = 0;
if (r->has_net) { l = snprintf(out + o, cap - o, "net=%s ", r->net_block ? "block" : "allow"); if (l > 0 && o + (size_t)l < cap) o += (size_t)l; any = 1; }
if (r->deny_n || r->allow_n) {
l = snprintf(out + o, cap - o, "domain="); if (l > 0 && o + (size_t)l < cap) o += (size_t)l;
int first = 1;
for (int i = 0; i < r->allow_n; i++) { int la = snprintf(out + o, cap - o, "%s+%s", first ? "" : ",", r->allow[i]); if (la > 0 && o + (size_t)la < cap) o += (size_t)la; first = 0; }
for (int i = 0; i < r->deny_n; i++) { int ld = snprintf(out + o, cap - o, "%s-%s", first ? "" : ",", r->deny[i]); if (ld > 0 && o + (size_t)ld < cap) o += (size_t)ld; first = 0; }
any = 1;
}
if (!any) { l = snprintf(out + o, cap - o, "(allow)"); if (l > 0 && o + (size_t)l < cap) o += (size_t)l; }
l = snprintf(out + o, cap - o, "\n"); if (l > 0 && o + (size_t)l < cap) o += (size_t)l;
}
static int policy_cmd(int op, const char *entity, const char *arg, char *out, size_t cap)
{
out[0] = 0;
if (op == NET_POLICY_SHOW) {
snprintf(out, cap, "# 策略(NETMGR_DESIGN §6)log_level=%s enforce=dns_only(SOCK_DESTROY 在本内核为 no-op)\n",
g_policy_log_level);
size_t o = strlen(out);
if (entity && entity[0]) {
rule_t *r = policy_find(entity);
policy_print_entity(r, out + o, cap - o);
} else {
for (int i = 0; i < g_rule_n; i++) {
char l[160]; policy_print_entity(&g_rules[i], l, sizeof l);
int n = snprintf(out + o, cap - o, "%s", l);
if (n > 0 && o + (size_t)n < cap) o += (size_t)n;
}
}
return MEOW_OK;
}
if (op == NET_POLICY_SET_NET) {
if (!entity || !entity[0] || !arg || (strcmp(arg, "allow") != 0 && strcmp(arg, "block") != 0))
{ snprintf(out, cap, "policy set 需 <entity> allow|block\n"); return MEOW_ERR_INVAL; }
if (strcmp(arg, "block") == 0 && is_self_lock(entity)) {
snprintf(out, cap, "policy: %s 是防自锁实体(SSH/WiFi/自身命根),拒绝置 block(NETMGR_DESIGN §14 红线)\n", entity);
net_log("warn", "policy", "self-lock entity %s tried block — 拒", entity);
return MEOW_ERR_PERM;
}
rule_t *r = policy_find(entity);
if (!r) {
if (g_rule_n >= RULE_ENT_MAX) { snprintf(out, cap, "policy: 规则槽满\n"); return MEOW_ERR_NOMEM; }
r = &g_rules[g_rule_n++]; memset(r, 0, sizeof *r);
snprintf(r->entity, sizeof r->entity, "%s", entity);
}
r->net_block = strcmp(arg, "block") == 0;
r->has_net = 1;
policy_persist_entity(entity);
net_log("info", "policy", "set %s net=%s", entity, arg);
snprintf(out, cap, "policy set %s net=%s ok\n", entity, arg);
return MEOW_OK;
}
if (op == NET_POLICY_SET_DOMAIN) {
if (!entity || !entity[0] || !arg || (arg[0] != '+' && arg[0] != '-') || arg[1] == 0)
{ snprintf(out, cap, "policy domain 需 <entity> +host|-host\n"); return MEOW_ERR_INVAL; }
rule_t *r = policy_find(entity);
if (!r) {
if (g_rule_n >= RULE_ENT_MAX) { snprintf(out, cap, "policy: 规则槽满\n"); return MEOW_ERR_NOMEM; }
r = &g_rules[g_rule_n++]; memset(r, 0, sizeof *r);
snprintf(r->entity, sizeof r->entity, "%s", entity);
}
if (arg[0] == '+') {
if (r->allow_n < RULE_HOST_MAX) { snprintf(r->allow[r->allow_n++], 48, "%s", arg + 1); snprintf(out, cap, "policy domain %s +%s ok\n", entity, arg + 1); }
else { snprintf(out, cap, "policy: allow 槽满\n"); return MEOW_ERR_NOMEM; }
} else {
if (r->deny_n < RULE_HOST_MAX) { snprintf(r->deny[r->deny_n++], 48, "%s", arg + 1); snprintf(out, cap, "policy domain %s -%s ok\n", entity, arg + 1); }
else { snprintf(out, cap, "policy: deny 槽满\n"); return MEOW_ERR_NOMEM; }
}
policy_persist_entity(entity);
net_log("info", "policy", "domain %s %s", entity, arg);
return MEOW_OK;
}
if (op == NET_POLICY_DEL) {
if (!entity || !entity[0]) { snprintf(out, cap, "policy del 需 <entity>\n"); return MEOW_ERR_INVAL; }
if (policy_remove(entity) == 0) {
policy_persist_entity(entity);
net_log("info", "policy", "del %s", entity);
snprintf(out, cap, "policy del %s ok\n", entity);
} else {
snprintf(out, cap, "policy del %s: 无此实体\n", entity);
}
return MEOW_OK;
}
snprintf(out, cap, "policy: 未知 op\n");
return MEOW_ERR_INVAL;
}
/* ---------------- 处理单条连接 ---------------- */
static void handle_conn(int c)
{
@@ -2263,7 +2548,35 @@ static void handle_conn(int c)
send_frame(c, MSG_NET_DNS_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)");
if (type == MSG_NET_POLICY) {
m_net_policy_t q;
memset(&q, 0, sizeof q);
if (rlen > sizeof q) rlen = sizeof q;
memcpy(&q, payload, rlen);
int is_write = (q.op != NET_POLICY_SHOW); /* SHOW=读级;SET_NET/SET_DOMAIN/DEL=写级 */
m_net_policy_result_t res;
memset(&res, 0, sizeof res);
int rc = MEOW_OK;
if (is_write && !is_sys_identity(&caller)) {
rc = MEOW_ERR_PERM;
snprintf(res.text, sizeof res.text, "permission denied (policy write needs system identity)");
net_log("warn", "write", "denied policy op=%d entity=%s caller_kind=%d uid=%d",
(int)q.op, q.entity, (int)caller.kind, (int)caller.uid);
} else if (is_write && !g_mode_meow) {
rc = MEOW_ERR_PERM;
snprintf(res.text, sizeof res.text, "readonly (HibyOS mode): policy write denied");
net_log("warn", "write", "denied policy op=%d (HibyOS readonly)", (int)q.op);
} else {
rc = policy_cmd((int)q.op, q.entity, q.arg, res.text, sizeof res.text);
}
res.code = (uint32_t)rc;
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_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)");
break;
}
}
@@ -2680,11 +2993,32 @@ static int cli_dns(const char *cmd)
return code == MEOW_OK ? 0 : 1;
}
/* 期3 policy:show/set net/domain/del(IPC 0xEA;写级在服务端鉴权系统身份)。 */
static int cli_policy(int op, const char *entity, const char *arg)
{
m_net_policy_t q;
memset(&q, 0, sizeof q);
q.op = (uint32_t)op;
if (entity) snprintf(q.entity, sizeof q.entity, "%s", entity);
if (arg) snprintf(q.arg, sizeof q.arg, "%s", arg);
char out[MEOW_IPC_MAX - 8];
int fd = ipc_connect();
if (fd < 0) { fprintf(stderr, "meow_net 守护不可达(%s)\n", g_sock); return 1; }
uint16_t rt; uint32_t code;
int rc = ipc_xchg(fd, MSG_NET_POLICY, &q, sizeof q, &rt, &code, out, sizeof out);
close(fd);
if (rc != 0) { fprintf(stderr, "meow_net policy: IPC 往返失败\n"); return 1; }
printf("%s", out);
return code == MEOW_OK ? 0 : 1;
}
static const char *usage(void)
{
return "status | iface [name] | wifi scan|networks|connect <ssid>[,psk]|disconnect|on|off"
" | bt status|on|off|paired | rfkill [<type> <on|off>] | udhcpc renew|release [if]"
" | apps | conns [entity] | top | dns status|stats|flush | help";
" | apps | conns [entity] | top | dns status|stats|flush"
" | policy show [entity] | policy set <entity> allow|block | policy domain <entity> +host|-host | policy del <entity>"
" | help";
}
static int cli_help(void)
@@ -2700,6 +3034,7 @@ static int cli_run(int n, char **v)
const char *cmd = v[0];
const char *arg = (n > 1) ? v[1] : NULL;
const char *arg2 = (n > 2) ? v[2] : NULL;
const char *arg3 = (n > 3) ? v[3] : NULL;
if (!strcmp(cmd, "status")) return cli_status();
if (!strcmp(cmd, "iface")) return cli_iface(arg);
if (!strcmp(cmd, "help") || !strcmp(cmd, "-h") || !strcmp(cmd, "--help")) return cli_help();
@@ -2710,6 +3045,24 @@ static int cli_run(int n, char **v)
if (!arg) { fprintf(stderr, "dns 子命令缺失(status|stats|flush)\n"); return 1; }
return cli_dns(arg);
}
if (!strcmp(cmd, "policy")) {
if (!arg) { fprintf(stderr, "policy 子命令缺失(show [entity] | set <entity> allow|block | domain <entity> +host|-host | del <entity>)\n"); return 1; }
if (!strcmp(arg, "show")) return cli_policy(NET_POLICY_SHOW, arg2, NULL);
if (!strcmp(arg, "set")) {
if (!arg2 || !arg3) { fprintf(stderr, "policy set 需 <entity> allow|block\n"); return 1; }
return cli_policy(NET_POLICY_SET_NET, arg2, arg3);
}
if (!strcmp(arg, "domain")) {
if (!arg2 || !arg3) { fprintf(stderr, "policy domain 需 <entity> +host|-host\n"); return 1; }
return cli_policy(NET_POLICY_SET_DOMAIN, arg2, arg3);
}
if (!strcmp(arg, "del")) {
if (!arg2) { fprintf(stderr, "policy del 需 <entity>\n"); return 1; }
return cli_policy(NET_POLICY_DEL, arg2, NULL);
}
fprintf(stderr, "policy 子命令未知(%s)\n", arg);
return 1;
}
if (!strcmp(cmd, "wifi")) {
if (!arg) { fprintf(stderr, "wifi 子命令缺失(%s)\n", usage()); return 1; }
if (!strcmp(arg, "on") || !strcmp(arg, "off")) return cli_cmd_result(MSG_NET_WIFI_CTL, arg, NULL);