diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index e9172a9..9f673c6 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -504,4 +504,64 @@ typedef struct m_audio_volume { int32_t pct; /* -1=查询;0-100=设置 */ } m_audio_volume_t; /* 4 B */ +/* ================= meow_netmgrd 网络管理服务(devdocs/plan/NETMGR_DESIGN.md §3/§10)===== + * 语义:接口统一层(WiFi/BT/USB-eth/rfkill/IP/路由/DNS 只读)查询与控制的系统权威。 + * 独立 socket /usr/data/meow_net.sock(--sock/MEOW_NET_SOCK 覆盖)。进程形态对齐 powermgr: + * svcd 托管 trigger=boot + 单一二进制双形态(/usr/bin/meow_netmgrd 守护 + /usr/bin/meow_net CLI)。 + * 鉴权分级同 svc_power(§10/期1):读级(STATUS/IFACE、WIFI_CTL 内 status/networks、BT_CTL 内 + * status/paired)任意调用者;写级(WIFI_CTL 内 scan/connect/disconnect/on/off/rfkill_set/udhcpc、 + * BT_CTL 内 on/off)仅系统身份(SO_PEERCRED → /usr/bin/meow_* 或 os.meow.* 包)。 + * HibyOS 模式(/tmp/meow_boot_mode=hiby):读级可查,写级拒 + 记日志。 + * 期 1 实装 0x00E0-0x00E7;0x00E8-0x00EF 保留(期2:连接普查/DNS 代理;期3:管控策略/事件推送)。 */ +enum { /* 与主 enum 分列,追加式不改既有行 */ + MSG_NET_STATUS_REQ = 0x00E0, /* ↑ 概览查询(空负载=全部) */ + MSG_NET_STATUS_RESULT = 0x00E1, /* ↓ 概览 JSON */ + MSG_NET_IFACE_REQ = 0x00E2, /* ↑ 接口列表/详情(name 空=全部) */ + MSG_NET_IFACE_RESULT = 0x00E3, /* ↓ 接口 JSON */ + MSG_NET_WIFI_CTL = 0x00E4, /* ↑ 子命令字串(scan/connect/... + rfkill/udhcpc) */ + MSG_NET_WIFI_RESULT = 0x00E5, /* ↓ 结果/文本 */ + MSG_NET_BT_CTL = 0x00E6, /* ↑ 子命令字串(status/on/off/paired) */ + MSG_NET_BT_RESULT = 0x00E7 /* ↓ 结果/文本 */ + /* 0x00E8-0x00EF 保留:0xE8 连接表 / 0xEA 策略读写 / 0xEC DNS 控制 / 0xEE 事件推送(期 2/3) */ +}; + +/* MSG_NET_STATUS_REQ ↑(空负载=全部;预留 subset 位,期 1 恒 0)→ MSG_NET_STATUS_RESULT ↓: + * 概览 JSON(mode/ifaces[]/wifi/bt/rfkill[],见 NETMGR_DESIGN §3 统一模型)。*/ +typedef struct m_net_status_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_* */ + uint16_t json_len; /* json 有效字节(不含 NUL) */ + uint16_t pad; + char json[MEOW_IPC_MAX - 8]; /* 概览 JSON(.data 形状) */ +} m_net_status_result_t; + +#define NET_IFACE_NAME_MAX 32 /* 接口名上限(lo/wlan0/usb-eth) */ + +/* MSG_NET_IFACE_REQ ↑:name 空=全部接口;否则单接口详情。→ MSG_NET_IFACE_RESULT ↓。 */ +typedef struct m_net_iface_req { + char name[NET_IFACE_NAME_MAX]; +} m_net_iface_req_t; /* 32 B */ + +typedef struct m_net_iface_result { + uint32_t code; + uint16_t json_len; + uint16_t pad; + char json[MEOW_IPC_MAX - 8]; +} m_net_iface_result_t; + +/* MSG_NET_WIFI_CTL / MSG_NET_BT_CTL ↑:子命令字串 + 变长 arg(connect 的 ssid/psk)。→ *_RESULT ↓。 + * cmd(WIFI_CTL): status|scan|networks|connect|disconnect|on|off + rfkill/udhcpc 命名空间 + * (rfkill arg="list"|" ";udhcpc arg="renew "|"release ")。 + * cmd(BT_CTL) : status|on|off|paired。 */ +typedef struct m_net_cmd { + char cmd[24]; /* 子命令字串(大写不敏感;接收侧截断) */ + char arg[132]; /* connect: "[,]";rfkill: " ";udhcpc: "renew|release " */ +} m_net_cmd_t; /* 156 B */ + +typedef struct m_net_cmd_result { + uint32_t code; /* MEOW_OK / MEOW_ERR_PERM / MEOW_ERR_INVAL / MEOW_ERR_NODEV */ + uint16_t text_len; /* text 有效字节(不含 NUL) */ + uint16_t pad; + char text[MEOW_IPC_MAX - 8]; +} m_net_cmd_result_t; + #endif /* MEOW_IPC_H */ diff --git a/patch_v2/meow_netmgrd/build_netmgrd.sh b/patch_v2/meow_netmgrd/build_netmgrd.sh new file mode 100755 index 0000000..8679583 --- /dev/null +++ b/patch_v2/meow_netmgrd/build_netmgrd.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# build_netmgrd.sh — meow_netmgrd 交叉编译(mipsel 静态)+ 宿主编译可选。 +# +# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。netmgrd 仅 POSIX + Linux 基接口 +# (复用 meow_ipc.h 帧格式 + m_net_* 负载,无 launcher 依赖),单文件满足「固件内、不入卡」。 +# 单一二进制双形态:/usr/bin/meow_netmgrd(svcd 空参=守护)+ /usr/bin/meow_net(CLI,同一份 +# 文件两次安装,argv[0] basename 分形态;fw_content.conf 两行同 src)。 +# +# 用法: +# ./build_netmgrd.sh # 交叉编译 → patch_v2/meow_netmgrd/meow_netmgrd(mipsel 静态) +# ./build_netmgrd.sh host # 宿主编译 → patch_v2/meow_netmgrd/meow_netmgrd_host +set -e +cd "$(dirname "$0")" +ROOT="$HOME/Project/R3ProII" +CC="$ROOT/.tools/zig-mipsel-cc" +I_HDR="$ROOT/patch_v2/meow_launcherd" # meow_ipc.h(帧/MSG_NET_*/m_net_*) + +if [ "$1" = "host" ]; then + HOST_CC="${CC_HOST:-cc}" + # -Wno-format-truncation:宿主 glibc 对固定缓冲 snprintf 的「安全截断」误报(musl 交叉不报)。 + "$HOST_CC" -Os -Wall -Wextra -U_FORTIFY_SOURCE -Wno-format-truncation -I"$I_HDR" \ + meow_netmgrd.c -o meow_netmgrd_host + echo "== host build ==" + ls -la meow_netmgrd_host + file meow_netmgrd_host + exit 0 +fi + +"$CC" -Os -Wall -Wextra -static -I"$I_HDR" meow_netmgrd.c -o meow_netmgrd +echo "== meow_netmgrd (mipsel static) ==" +ls -la meow_netmgrd +md5sum meow_netmgrd +file meow_netmgrd diff --git a/patch_v2/meow_netmgrd/meow_netmgrd b/patch_v2/meow_netmgrd/meow_netmgrd new file mode 100755 index 0000000..4fa5774 Binary files /dev/null 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 new file mode 100644 index 0000000..9011264 --- /dev/null +++ b/patch_v2/meow_netmgrd/meow_netmgrd.c @@ -0,0 +1,1756 @@ +#define _GNU_SOURCE /* struct ucred / SO_PEERCRED、asprintf 等 GNU/musl 扩展 */ + +/* meow_netmgrd.c — MeowOS 网络管理专项服务(devdocs/plan/NETMGR_DESIGN.md,期 1 骨架)。 + * + * 定位(与 powermgr/audiod 同级的系统服务):接口统一层——WiFi/BT/USB-eth/rfkill/IP/路由/DNS + * 的查询与控制收口为一个系统权威;系统设置、MeowPanel、未来系统组件一律走它的 IPC,不再各自直调 + * wpa_cli/hciconfig(设计 §1/§3)。期 1 范围(设计 §13 表,→ M6.0):骨架 + 接口统一层 + + * CLI(status/iface/wifi/bt/rfkill) + IPC(E0-E7) + 日志框架 + svcd conf + HibyOS 只读降级。 + * + * 单一二进制双形态(同 powermgr/svc_power): + * - svcd 空参拉起(板载 services.d/meow_netmgrd.conf,trigger=boot/restart=on-fail)→ + * IPC 服务常驻:bind /usr/data/meow_net.sock(--sock/MEOW_NET_SOCK 覆盖),收 + * MSG_HELLO→MSG_ACK、MSG_NET_STATUS_REQ/IFACE_REQ/WIFI_CTL/BT_CTL。 + * - `meow_net `(argv[0]=meow_net 双形态)或 `meow_netmgrd ` → 一次 CLI(IPC 薄壳)。 + * CLI 是 IPC 客户端:查询经守护,写操作经守护鉴权;守护不可达 → 报错(help 除外,help 本地必可用)。 + * + * HibyOS 只读降级(用户定案 2026-09-01):进程内读 /tmp/meow_boot_mode,"meow"=全功能; + * 否则(hiby/缺失)=只读——读级查询放行,一切写操作(wifi/bt on-off 等)拒 + 记日志。 + * (与 audiod 的 exit(0) 门控不同:本服务读侧无害故常驻只读,每采样拍重判可热切换,同 powermgr。) + * + * 内核事实(NETWORK_HW_FACTS.md,期 0 实测):无 iptables/cgroup/conntrack;内存 available≈27MB → + * 本服务 RSS 预算 ≤1MB(单线程 poll、静态表、热路径禁 malloc;popen 有界收割防 wedge,257 教训)。 + * + * 日志(§8):TF `MeowOS/logs/net_.log`(无卡降级 /tmp),每次开机新文件(复用 /tmp/meow_bootts + * 共享 bootts,一次开机一份);接口状态变化/写操作必记。行格式 `bootts lvl tag kv...`。 + * + * 编译(mipsel 静态):build_netmgrd.sh;宿主:build_netmgrd.sh host。 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "meow_ipc.h" /* 帧格式/消息号 + MSG_NET_*(0x00E0-0x00E7) + m_net_*(号段 0x00E0-0x00EF) */ + +/* ---------------- 路径(全部可经 env 覆盖,宿主/qemu 桩测试用) ---------------- */ +static char g_net_dir[128] = "/sys/class/net"; /* MEOW_NET_DIR */ +static char g_netdev[160] = "/proc/net/dev"; /* MEOW_NETDEV */ +static char g_net_route[160] = "/proc/net/route"; /* MEOW_NET_ROUTE */ +static char g_fibtrie[160] = "/proc/net/fib_trie"; /* MEOW_FIBTRIE(无 ip 工具兜底) */ +static char g_resolv[160] = "/etc/resolv.conf"; /* MEOW_RESOLV */ +static char g_rfkill_dir[128] = "/sys/class/rfkill"; /* MEOW_RFKILL_DIR */ +static char g_bt_dir[128] = "/sys/class/bluetooth"; /* MEOW_BT_DIR */ +static char g_boot_mode[160] = "/tmp/meow_boot_mode"; /* MEOW_BOOT_MODE */ +static char g_log_dir[192] = "/usr/data/mnt/sd_0/MeowOS/logs"; /* MEOW_LOG_DIR(TF 卡日志根) */ +static char g_bootts_cache[160]= "/tmp/meow_bootts"; /* MEOW_BOOTTS_CACHE */ +static const char *g_sock = "/usr/data/meow_net.sock"; /* MEOW_NET_SOCK/--sock */ + +/* 后端命令路径(宿主测试用桩脚本覆盖;默认走设备 PATH 的真工具) */ +static const char *g_wpa_cli = "wpa_cli"; /* MEOW_WPA_CLI */ +static const char *g_hciconfig = "hciconfig"; /* MEOW_HCICONFIG */ +static const char *g_bluetoothctl= "bluetoothctl"; /* MEOW_BLUETOOTHCTL */ +static const char *g_ip = "ip"; /* MEOW_IP */ +static const char *g_wifi_if = "wlan0"; /* MEOW_WIFI_IF */ + +#define NET_POLL_SEC_DEF 2 /* 接口轮询/状态节拍(秒;MEOW_NET_POLL_SEC 覆盖,测试用) */ +#define RUNCMD_TMO_MS 1500 /* 后端命令(wpa_cli/hciconfig/etc)有界收割限时(ms) */ +#define IFACE_MAX 16 +#define RFKILL_MAX 8 +#define DNS_MAX 8 + +static int g_poll_sec = NET_POLL_SEC_DEF; + +static long mono_now(void) +{ + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + return tp.tv_sec; +} + +/* ---------------- 极简日志(stderr;svcd supervisor 捕获) ---------------- */ +static void svc_log(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "[meow_netmgrd] "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +/* ---------------- 文件小工具(同 svc_power) ---------------- */ +static int read_file_txt(const char *path, char *buf, int cap) +{ + int fd = open(path, O_RDONLY); + if (fd < 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; + while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r' || buf[n - 1] == ' ')) buf[--n] = 0; + return n; +} + +static int read_file_int_def(const char *path, int def) +{ + char buf[32]; + if (read_file_txt(path, buf, sizeof buf) <= 0) return def; + return atoi(buf); +} + +/* ---------------- 日志框架(§8:每次开机新文件,TF 优先降级 /tmp) ---------------- */ +static int g_logfd = -1; +static char g_logfile[192]; + +/* bootts 复用(与 launcher/audiod 共用 /tmp/meow_bootts 缓存,一次开机一份日志) */ +static void get_bootts(char *out, size_t n) +{ + int fd = open(g_bootts_cache, O_RDONLY); + if (fd >= 0) { + int r = read(fd, out, (int)n - 1); + close(fd); + if (r > 0) { out[r] = 0; return; } + } + time_t now = time(NULL), up = 0; + fd = open("/proc/uptime", O_RDONLY); + if (fd >= 0) { + char buf[64]; + int r = read(fd, buf, sizeof buf - 1); + close(fd); + if (r > 0) { buf[r] = 0; char *p = strchr(buf, ' '); if (p) *p = 0; up = (time_t)atof(buf); } + } + struct tm tm; + localtime_r(&now - up, &tm); + strftime(out, n, "meow_%Y%m%d_%H%M%S", &tm); + fd = open(g_bootts_cache, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { (void)write(fd, out, strlen(out)); close(fd); } +} + +static void log_open(void) +{ + char ts[64]; + get_bootts(ts, sizeof ts); + char path[260]; + int use_tf = 0; + if (access(g_log_dir, W_OK) == 0) { + snprintf(path, sizeof path, "%s/net_%s.log", g_log_dir, ts); + use_tf = 1; + } else { + snprintf(path, sizeof path, "/tmp/net_%s.log", ts); + } + snprintf(g_logfile, sizeof g_logfile, "%s", path); + g_logfd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0644); + svc_log("log: %s (%s)", g_logfile, use_tf ? "TF" : "/tmp fallback"); +} + +/* 日志行:`bootts lvl tag kv...`;stderr 与文件双写(svcd 捕获 stderr + 文件归档)。 */ +static void net_log(const char *lvl, const char *tag, const char *fmt, ...) +{ + char msg[512]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof msg, fmt, ap); + va_end(ap); + char bootts[64]; + get_bootts(bootts, sizeof bootts); + if (g_logfd >= 0) { + char line[600]; + int n = snprintf(line, sizeof line, "%s %s %s %s\n", bootts, lvl, tag, msg); + if (n > 0 && n < (int)sizeof line) (void)write(g_logfd, line, (size_t)n); + } + fprintf(stderr, "[net] %s %s %s\n", lvl, tag, msg); +} + +/* ---------------- 有界 popen 收割(257 教训:命令 wedge 时无条件 waitpid 会拖死调用线程) ---------------- + * 非阻塞读子进程 stdout(poll 限时);读完后 WNOHANG 优雅等 300ms,不死 SIGKILL 再等 300ms; + * 仍不死(D 态)放弃收割(泄漏一子进程换线程不卡死,已记日志)。返回读到的字节数(-1=起不来)。 + * cmd 为完整 shell 行;buf 由调用方提供(静态/栈),不 malloc——热路径禁 malloc。 */ +static void reap_bounded(pid_t pid, const char *cmd) +{ + for (int i = 0; i < 30; i++) { + if (waitpid(pid, NULL, WNOHANG) == pid) return; + struct timespec ts = { 0, 10 * 1000000L }; + nanosleep(&ts, NULL); + } + svc_log("run_cmd: child %ld not exit in 300ms, SIGKILL (%s)", (long)pid, cmd); + kill(pid, SIGKILL); + for (int i = 0; i < 30; i++) { + if (waitpid(pid, NULL, WNOHANG) == pid) return; + struct timespec ts = { 0, 10 * 1000000L }; + nanosleep(&ts, NULL); + } + svc_log("run_cmd: child %ld unkillable (kernel D-state?) — abandon reap (%s)", (long)pid, cmd); +} + +static int run_cmd(char *buf, int cap, int tmo_ms, const char *cmd) +{ + int p[2]; + if (pipe(p) < 0) return -1; + pid_t pid = fork(); + if (pid < 0) { close(p[0]); close(p[1]); return -1; } + if (pid == 0) { + dup2(p[1], 1); + int dn = open("/dev/null", O_WRONLY); + if (dn >= 0) { dup2(dn, 2); close(dn); } + close(p[0]); close(p[1]); + execl("/bin/sh", "sh", "-c", cmd, (char *)0); + _exit(127); + } + close(p[1]); + fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL, 0) | O_NONBLOCK); + int off = 0; + int remain = tmo_ms; + while (remain > 0 && off < cap - 1) { + struct pollfd pf = { p[0], POLLIN, 0 }; + int r = poll(&pf, 1, remain); + if (r < 0) { if (errno == EINTR) continue; break; } + if (r == 0) break; + ssize_t rd = read(p[0], buf + off, (size_t)(cap - 1 - off)); + if (rd == 0) break; + if (rd < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) continue; break; } + off += (int)rd; + } + close(p[0]); + buf[off] = 0; + reap_bounded(pid, cmd); + return off; +} + +/* ---------------- JSON 构建(vsnprintf 助手法,截断分析免疫;同 powermgr) ---------------- */ +typedef struct { char *buf; size_t cap, off; } jw_t; +static void jadd(jw_t *w, const char *fmt, ...) +{ + if (w->off >= w->cap - 1) return; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(w->buf + w->off, w->cap - w->off, fmt, ap); + va_end(ap); + if (n > 0) { + w->off += (size_t)n; + if (w->off >= w->cap) w->off = w->cap - 1; + } +} + +/* 带引号的字符串值(转义 `"`/`\`/换行)。空串 → ""(不当 null,调用方自决)。 */ +static void jadd_q(jw_t *w, const char *key, const char *val) +{ + jadd(w, "\"%s\":\"", key); + for (const char *p = val; *p && w->off < w->cap - 3; p++) { + if (*p == '"' || *p == '\\') jadd(w, "\\%c", *p); + else if (*p == '\n') jadd(w, "\\n"); + else if (*p == '\t') jadd(w, "\\t"); + else w->buf[w->off++] = *p; + } + jadd(w, "\","); +} + +static void jadd_int_or_null(jw_t *w, const char *key, int v) +{ + if (v < 0) jadd(w, "\"%s\":null,", key); + else jadd(w, "\"%s\":%d,", key, v); +} + +static void jadd_str_or_null(jw_t *w, const char *key, const char *s) +{ + if (!s || !s[0]) jadd(w, "\"%s\":null,", key); + else jadd_q(w, key, s); +} + +/* ---------------- 动作执行结果容器 ---------------- */ +typedef struct { + int code; + char text[MEOW_IPC_MAX - 8]; +} ctl_result_t; +static void ctl_reset(ctl_result_t *r, int code) { r->code = code; r->text[0] = 0; } +static void ctl_sets(ctl_result_t *r, int code, const char *s) +{ + r->code = code; + snprintf(r->text, sizeof r->text, "%s", s); +} + +/* ---------------- 接口统一层:模型(设计 §3 统一模型) ---------------- */ +typedef struct { + char name[32]; + char kind[8]; /* "lo" | "wifi" | "usb-eth" */ + int up; /* 1=up 0=down */ + char ip[40]; /* ""=null */ + char gw[40]; /* ""=null */ + long rx, tx; /* 累计字节(/proc/net/dev) */ +} iface_t; +static iface_t g_ifaces[IFACE_MAX]; +static int g_iface_n = 0; + +typedef struct { + int connected; /* 0/1 */ + char state[16]; /* wpa_state 值;""=unknown */ + char ssid[64]; + char ip[40]; + int rssi; /* 信号强度 dBm(可为负);has_rssi=0 时无效 */ + int has_rssi; /* wpa_cli 是否给出 signal= */ +} wifi_t; + +typedef struct { + int available; + int powered; + char address[32]; + int paired_n; /* -1=null */ +} bt_t; + +typedef struct { + char type[16]; /* wlan/bluetooth/... */ + int soft; /* 0/1/-1 */ + int hard; /* 0/1/-1 */ +} rfkill_t; +static rfkill_t g_rfkill[RFKILL_MAX]; + +static int boot_mode_is_meow(void) +{ + char buf[16] = ""; + if (read_file_txt(g_boot_mode, buf, sizeof buf) <= 0) return 0; /* 缺失=只读等落地 */ + return strcmp(buf, "meow") == 0; +} + +/* kind 判定:0=lo 1=wifi 2=usb-eth(热插拔出现即记录)。 */ +static int iface_parse_kind(const char *name) +{ + if (strcmp(name, "lo") == 0) return 0; + if (strcmp(name, g_wifi_if) == 0) return 1; + return 2; +} +static const char *kind_str(int k) +{ + return k == 0 ? "lo" : (k == 1 ? "wifi" : "usb-eth"); +} + +/* /proc/net/dev 单接口行 → rx(列1)/tx(列9) 累计字节;返回 0=找到。 */ +static int netdev_iface(const char *ifname, long *rx, long *tx) +{ + FILE *f = fopen(g_netdev, "r"); + if (!f) return -1; + char line[256]; + int found = -1; + while (fgets(line, sizeof line, f)) { + char *c = strchr(line, ':'); + if (!c) continue; + *c = 0; + char *n = line; + while (*n == ' ' || *n == '\t') n++; + if (strcmp(n, ifname) != 0) continue; + char *save = NULL; + char *tk = strtok_r(c + 1, " \t", &save); /* 列1 = rx bytes */ + long vr = tk ? strtol(tk, NULL, 10) : 0; + for (int i = 0; i < 8; i++) tk = strtok_r(NULL, " \t", &save); /* 推进到列9 */ + long vt = tk ? strtol(tk, NULL, 10) : 0; + if (rx) *rx = vr; + if (tx) *tx = vt; + found = 0; + break; + } + fclose(f); + return found; +} + +/* 全局 /proc/net/dev RX/TX 累计(所有接口求和)。 */ +static void netdev_totals(long *rx, long *tx) +{ + long trx = 0, ttx = 0; + FILE *f = fopen(g_netdev, "r"); + if (f) { + char line[256]; + while (fgets(line, sizeof line, f)) { + char *c = strchr(line, ':'); + if (!c) continue; + char *save = NULL; + char *tk = strtok_r(c + 1, " \t", &save); + long vr = 0; + if (tk) { + vr = strtol(tk, NULL, 10); + for (int i = 0; i < 8; i++) tk = strtok_r(NULL, " \t", &save); + if (tk) ttx += strtol(tk, NULL, 10); + } + trx += vr; + } + fclose(f); + } + if (rx) *rx = trx; + if (tx) *tx = ttx; +} + +/* iface IP:优先 `ip -o -4 addr show dev `;ip 缺失/无输出 → fib_trie 兜底(作用于主口)。 + * 内核不提供 per-iface 主机 IP 的 /proc 文件,fib_trie 兜底无法映射接口——仅作用于 wifi 主口。 */ +static int ip_parse_addr(char *out, size_t cap, const char *buf) +{ + const char *p = buf; + while (*p) { + while (*p == ' ' || *p == '\t' || *p == '\n') p++; + if (!*p) break; + const char *tk = p; + while (*p && *p != ' ' && *p != '\t' && *p != '\n') p++; + size_t tl = (size_t)(p - tk); + const char *dot = memchr(tk, '.', tl); + if (!dot) continue; + const char *sl = memchr(tk, '/', tl); + if (!sl) continue; + size_t len = (size_t)(sl - tk); + if (len >= cap) len = cap - 1; + memcpy(out, tk, len); out[len] = 0; + return 0; + } + return -1; +} + +static int fib_trie_first_ip(char *out, size_t cap) +{ + FILE *f = fopen(g_fibtrie, "r"); + if (!f) return -1; + char line[160]; + char last_local[48] = ""; + while (fgets(line, sizeof line, f)) { + char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (!isdigit((unsigned char)*p)) continue; + char ip[48]; int i = 0; + while (p[i] && p[i] != ' ' && p[i] != '\n' && i < 47) { ip[i] = p[i]; i++; } + ip[i] = 0; + if (strchr(ip, '.')) snprintf(last_local, sizeof last_local, "%s", ip); + } + fclose(f); + if (last_local[0]) { snprintf(out, cap, "%s", last_local); return 0; } + return -1; +} + +static void iface_ip(const char *ifname, int want_fallback, char *out, size_t cap) +{ + out[0] = 0; + char cmd[192]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s -o -4 addr show dev %s 2>/dev/null", + g_ip, ifname); + char buf[256]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0 && ip_parse_addr(out, cap, buf) == 0) return; + if (want_fallback) (void)fib_trie_first_ip(out, cap); +} + +/* /proc/net/route → 该接口网关(RTF_GATEWAY,优取默认路由)。 */ +static void iface_gw(const char *ifname, char *out, size_t cap) +{ + out[0] = 0; + FILE *f = fopen(g_net_route, "r"); + if (!f) return; + char line[256]; + char best[40] = ""; + while (fgets(line, sizeof line, f)) { + char ifc[32]; unsigned long dest = 0, gw = 0, flags = 0; + int n = sscanf(line, "%31s %lx %lx %lx", ifc, &dest, &gw, &flags); + if (n < 4) continue; + if (strcmp(ifc, ifname) != 0) continue; + if (!(flags & 0x2)) continue; /* RTF_GATEWAY */ + unsigned char b0 = (unsigned char)(gw & 0xff), b1 = (unsigned char)((gw >> 8) & 0xff); + unsigned char b2 = (unsigned char)((gw >> 16) & 0xff), b3 = (unsigned char)((gw >> 24) & 0xff); + snprintf(best, sizeof best, "%u.%u.%u.%u", b0, b1, b2, b3); + if (dest == 0) break; /* 默认路由:最佳 */ + } + fclose(f); + if (best[0]) snprintf(out, cap, "%s", best); +} + +/* resolv.conf → nameserver 列表(≤DNS_MAX)。 */ +static int dns_read(char out[DNS_MAX][40], int *n) +{ + FILE *f = fopen(g_resolv, "r"); + *n = 0; + if (!f) return -1; + char line[160]; + while (fgets(line, sizeof line, f) && *n < DNS_MAX) { + char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (strncmp(p, "nameserver", 10) != 0) continue; + p += 10; + while (*p == ' ' || *p == '\t') p++; + char ip[40]; int i = 0; + while (*p && *p != ' ' && *p != '\t' && *p != '\n' && i < 39) ip[i++] = *p++; + ip[i] = 0; + if (strchr(ip, '.')) { snprintf(out[*n], 40, "%s", ip); (*n)++; } + } + fclose(f); + return *n > 0 ? 0 : -1; +} + +/* 扫描 /sys/class/net。detail=1 补 ip/gw(触发 ip popen);detail=0 仅 name/kind/up/rx/tx(节拍用,无 popen)。 */ +static void enum_ifaces(int detail) +{ + DIR *d = opendir(g_net_dir); + g_iface_n = 0; + if (d) { + struct dirent *de; + while ((de = readdir(d)) && g_iface_n < IFACE_MAX) { + if (de->d_name[0] == '.') continue; + iface_t *f = &g_ifaces[g_iface_n]; + memset(f, 0, sizeof *f); + snprintf(f->name, sizeof f->name, "%s", de->d_name); + int ki = iface_parse_kind(de->d_name); + snprintf(f->kind, sizeof f->kind, "%s", kind_str(ki)); + char p[192]; + snprintf(p, sizeof p, "%s/%s/operstate", g_net_dir, de->d_name); + char os[16] = ""; + read_file_txt(p, os, sizeof os); + /* lo 的 operstate 常为 "unknown"(真机实测),但回环恒 up。 */ + f->up = (ki == 0 || strcmp(os, "up") == 0) ? 1 : 0; + long rx = -1, tx = -1; + netdev_iface(de->d_name, &rx, &tx); + f->rx = rx >= 0 ? rx : 0; + f->tx = tx >= 0 ? tx : 0; + if (detail) { + iface_ip(f->name, ki == 1, f->ip, sizeof f->ip); /* fib_trie 兜底仅 wifi 主口 */ + iface_gw(f->name, f->gw, sizeof f->gw); + } + g_iface_n++; + } + closedir(d); + } + /* 排序稳定:lo 首、wifi 次、usb-eth 末(冒泡,量小)。 */ + for (int i = 0; i < g_iface_n; i++) + for (int j = i + 1; j < g_iface_n; j++) { + if (iface_parse_kind(g_ifaces[i].name) > iface_parse_kind(g_ifaces[j].name)) { + iface_t t = g_ifaces[i]; g_ifaces[i] = g_ifaces[j]; g_ifaces[j] = t; + } + } +} + +/* ---------------- WiFi:wpa_cli 包装(popen 有界) ---------------- */ +static void wifi_status(wifi_t *w) +{ + memset(w, 0, sizeof *w); + w->rssi = -1; + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s status 2>/dev/null", g_wpa_cli); + char buf[512]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) <= 0) return; + char *ln = buf; + while (ln && *ln) { + char *nl = strchr(ln, '\n'); + if (nl) *nl = 0; + if (!strncmp(ln, "wpa_state=", 10)) { + snprintf(w->state, sizeof w->state, "%s", ln + 10); + if (!strcmp(ln + 10, "COMPLETED")) w->connected = 1; + } else if (!strncmp(ln, "ssid=", 5)) { + snprintf(w->ssid, sizeof w->ssid, "%s", ln + 5); + } else if (!strncmp(ln, "ip_address=", 11)) { + snprintf(w->ip, sizeof w->ip, "%s", ln + 11); + } else if (!strncmp(ln, "signal=", 7)) { + w->rssi = atoi(ln + 7); + w->has_rssi = 1; + } + if (!nl) break; + ln = nl + 1; + } +} + +/* ---------------- 蓝牙:hciconfig/bluetoothctl 包装 ---------------- */ +static void bt_status(bt_t *b) +{ + memset(b, 0, sizeof *b); + b->paired_n = -1; + char p[192]; + snprintf(p, sizeof p, "%s/hci0", g_bt_dir); + if (access(p, F_OK) == 0) b->available = 1; + if (!b->available) return; + /* powered:/sys/.../power(on/off)优先,回退 hciconfig 行首 UP/DOWN */ + snprintf(p, sizeof p, "%s/hci0/power", g_bt_dir); + char pw[8] = ""; + if (read_file_txt(p, pw, sizeof pw) > 0 && pw[0]) { + b->powered = !strcmp(pw, "on"); + } else { + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 2>/dev/null", g_hciconfig); + char buf[256]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) { + char *ln = buf; + while (ln && *ln) { + char *nl = strchr(ln, '\n'); + if (nl) *nl = 0; + char *q = ln; + while (*q == ' ' || *q == '\t') q++; + if (!strncmp(q, "UP", 2)) { b->powered = 1; break; } + if (!strncmp(q, "DOWN", 4)) { b->powered = 0; break; } + if (!nl) break; + ln = nl + 1; + } + } + } + /* address:/sys/.../address 优先,回退 hciconfig "BD Address:" */ + snprintf(p, sizeof p, "%s/hci0/address", g_bt_dir); + read_file_txt(p, b->address, sizeof b->address); + if (!b->address[0]) { + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 2>/dev/null", g_hciconfig); + char buf[256]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) { + char *q = strstr(buf, "BD Address:"); + if (q) { + q += 11; + while (*q == ' ') q++; + int i = 0; + while (*q && *q != ' ' && *q != '\n' && i < (int)sizeof b->address - 1) b->address[i++] = *q++; + b->address[i] = 0; + } + } + } + /* paired 数:bluetoothctl paired(BlueZ 5.54);失败/空 → -1。 */ + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s paired 2>/dev/null", g_bluetoothctl); + char buf[512]; + if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) { + int n = 0; + char *ln = buf; + while (ln && *ln) { + char *nl = strchr(ln, '\n'); + if (nl) *nl = 0; + char *q = ln; + while (*q == ' ' || *q == '\t') q++; + if (!strncmp(q, "Device", 6)) n++; + if (!nl) break; + ln = nl + 1; + } + b->paired_n = n; + } +} + +/* ---------------- rfkill:统一软开关 ---------------- */ +static void rfkill_list(void) +{ + int n = 0; + DIR *d = opendir(g_rfkill_dir); + if (d) { + struct dirent *de; + while ((de = readdir(d)) && n < RFKILL_MAX) { + if (strncmp(de->d_name, "rfkill", 6) != 0) continue; + char p[448]; + rfkill_t *r = &g_rfkill[n]; + memset(r, 0, sizeof *r); + snprintf(p, sizeof p, "%s/%s/type", g_rfkill_dir, de->d_name); + read_file_txt(p, r->type, sizeof r->type); + snprintf(p, sizeof p, "%s/%s/soft", g_rfkill_dir, de->d_name); + r->soft = read_file_int_def(p, -1); + snprintf(p, sizeof p, "%s/%s/hard", g_rfkill_dir, de->d_name); + r->hard = read_file_int_def(p, -1); + n++; + } + closedir(d); + } + for (int i = n; i < RFKILL_MAX; i++) memset(&g_rfkill[i], 0, sizeof(rfkill_t)); +} + +/* 找某类型 rfkill 设备并写 soft=block(1)/unblock(0)。返回 0=ok;-1=无该类型/写失败。 */ +static int rfkill_set_block(const char *type, int block) +{ + char norm[16] = ""; + if (!strcmp(type, "wifi")) snprintf(norm, sizeof norm, "%s", "wlan"); + else if (!strcmp(type, "bt")) snprintf(norm, sizeof norm, "%s", "bluetooth"); + else snprintf(norm, sizeof norm, "%s", type); + DIR *d = opendir(g_rfkill_dir); + int rc = -1; + if (d) { + struct dirent *de; + while ((de = readdir(d))) { + if (strncmp(de->d_name, "rfkill", 6) != 0) continue; + char p[448], t[16] = ""; + snprintf(p, sizeof p, "%s/%s/type", g_rfkill_dir, de->d_name); + if (read_file_txt(p, t, sizeof t) <= 0 || strcmp(t, norm) != 0) continue; + snprintf(p, sizeof p, "%s/%s/soft", g_rfkill_dir, de->d_name); + int fd = open(p, O_WRONLY); + if (fd < 0) continue; + char one = block ? '1' : '0'; + if (write(fd, &one, 1) == 1) rc = 0; + close(fd); + break; + } + closedir(d); + } + return rc; +} + +/* ---------------- udhcpc:续租/释放(SIGUSR1/SIGUSR2,不改启动路径) ---------------- */ +static int udhcpc_ctl(const char *op, const char *ifname) +{ + char pidfile[160]; + const char *iff = (ifname && ifname[0]) ? ifname : g_wifi_if; + snprintf(pidfile, sizeof pidfile, "/var/run/udhcpc.%s.pid", iff); + char buf[32]; + if (read_file_txt(pidfile, buf, sizeof buf) <= 0) return -1; + int pid = atoi(buf); + if (pid <= 0) return -1; + int sig = (strcmp(op, "release") == 0) ? SIGUSR2 : SIGUSR1; + return kill(pid, sig) == 0 ? 0 : -1; +} + +/* ---------------- IPC 帧读写(同 svc_power 约定,全小端) ---------------- */ +static int x_read_full(int fd, uint8_t *buf, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t r = read(fd, buf + off, n - off); + if (r <= 0) return -1; + off += (size_t)r; + } + return 0; +} +static int x_write_full(int fd, const uint8_t *buf, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, buf + off, n - off); + if (w <= 0) return -1; + off += (size_t)w; + } + return 0; +} + +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, rlen = 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, &rlen, 4); + if (x_write_full(fd, hdr, MEOW_IPC_HDR_LEN) < 0) return -1; + if (len && payload && x_write_full(fd, payload, len) < 0) return -1; + return 0; +} + +static int send_ack(int fd, uint32_t req_id, uint32_t code, uint32_t status) +{ + m_ack_t a; + memset(&a, 0, sizeof a); + a.code = code; + a.status = status; + return send_frame(fd, MSG_ACK, req_id, &a, sizeof a); +} + +static int send_err(int fd, uint32_t req_id, uint32_t code, const char *msg) +{ + m_err_t e; + memset(&e, 0, sizeof e); + e.code = code; + uint16_t ml = msg ? (uint16_t)strlen(msg) : 0; + if (ml > 63) ml = 63; + e.msg_len = ml; + if (ml) memcpy(e.msg, msg, ml); + return send_frame(fd, MSG_ERR, req_id, &e, sizeof e); +} + +/* json/text 结果通发(m_net_status_result_t / m_net_iface_result_t / m_net_cmd_result_t 布局同构 + * code(4)+len(2)+pad(2)+payload,payload ≤ MEOW_IPC_MAX-8)。 */ +static int send_payload_result(int fd, uint16_t type, uint32_t req_id, uint32_t code, + const char *payload, size_t pl) +{ + m_net_cmd_result_t r; /* 复用布局最大的那个(payload 区 4088B)。 */ + memset(&r, 0, sizeof r); + r.code = code; + if (pl > sizeof r.text - 1) pl = sizeof r.text - 1; + r.text_len = (uint16_t)pl; + memcpy(r.text, payload, pl); + r.text[pl] = 0; + return send_frame(fd, type, req_id, &r, (uint32_t)(8 + pl + 1)); +} + +static int send_net_status_result(int fd, uint32_t req_id, const char *json) +{ + return send_payload_result(fd, MSG_NET_STATUS_RESULT, req_id, MEOW_OK, json, strlen(json)); +} +static int send_net_iface_result(int fd, uint32_t req_id, uint32_t code, const char *json) +{ + return send_payload_result(fd, MSG_NET_IFACE_RESULT, req_id, code, json, strlen(json)); +} +static int send_net_wifi_result(int fd, uint32_t req_id, const ctl_result_t *r) +{ + return send_payload_result(fd, MSG_NET_WIFI_RESULT, req_id, (uint32_t)r->code, r->text, strlen(r->text)); +} +static int send_net_bt_result(int fd, uint32_t req_id, const ctl_result_t *r) +{ + return send_payload_result(fd, MSG_NET_BT_RESULT, req_id, (uint32_t)r->code, r->text, strlen(r->text)); +} + +/* ---------------- 守护进程态 ---------------- */ +static int g_mode_meow = 0; /* meow=1 全功能;0=只读降级 */ +static long g_net_last_rx = -1, g_net_last_tx = 0, g_net_last_mono = 0; +static long g_net_rate_rx = -1, g_net_rate_tx = -1; /* B/s;-1=未知 */ +static char g_prev_if[IFACE_MAX][32]; +static int g_prev_if_up[IFACE_MAX]; +static int g_prev_if_n = 0; + +/* ---------------- JSON 构建:status/iface ---------------- */ +static void iface_json_obj(jw_t *w, const iface_t *f) +{ + jadd(w, "{\"name\":\"%s\",\"kind\":\"%s\",\"up\":%s,", f->name, f->kind, + f->up ? "true" : "false"); + jadd_str_or_null(w, "ip", f->ip); + jadd_str_or_null(w, "gw", f->gw); + jadd(w, "\"rx_bytes\":%ld,\"tx_bytes\":%ld}", f->rx, f->tx); +} + +static void build_status_json(char *out, size_t cap) +{ + jw_t w = { out, cap, 0 }; + out[0] = 0; + jadd(&w, "{\"mode\":\"%s\",\"boot_ts\":\"", g_mode_meow ? "meow" : "readonly"); + { + char ts[64]; get_bootts(ts, sizeof ts); + for (const char *p = ts; *p; p++) w.buf[w.off++] = *p; + } + jadd(&w, "\",\"ts\":%ld,", (long)time(NULL)); + + enum_ifaces(1); + jadd(&w, "\"ifaces\":["); + for (int i = 0; i < g_iface_n; i++) { if (i) jadd(&w, ","); iface_json_obj(&w, &g_ifaces[i]); } + jadd(&w, "],"); + + wifi_t wi; + wifi_status(&wi); + jadd(&w, "\"wifi\":{"); + jadd_q(&w, "state", wi.state); + jadd(&w, "\"connected\":%s,", wi.connected ? "true" : "false"); + jadd_str_or_null(&w, "ssid", wi.ssid); + jadd_str_or_null(&w, "ip", wi.ip); + if (wi.has_rssi) jadd(&w, "\"rssi\":%d,", wi.rssi); + else jadd(&w, "\"rssi\":null,"); + jadd(&w, "\"networks\":null},"); + + bt_t bt; + bt_status(&bt); + jadd(&w, "\"bt\":{"); + jadd(&w, "\"available\":%s,\"powered\":%s,", bt.available ? "true" : "false", + bt.powered ? "true" : "false"); + jadd_str_or_null(&w, "address", bt.address); + jadd_int_or_null(&w, "paired", bt.paired_n); + jadd(&w, "\"dns\":["); + { + char ns[DNS_MAX][40]; + int n = 0; + dns_read(ns, &n); + for (int i = 0; i < n; i++) { if (i) jadd(&w, ","); jadd(&w, "\"%s\"", ns[i]); } + } + jadd(&w, "],"); + + rfkill_list(); + jadd(&w, "\"rfkill\":["); + for (int i = 0; i < RFKILL_MAX && g_rfkill[i].type[0]; i++) { + if (i) jadd(&w, ","); + jadd(&w, "{\"type\":\"%s\",\"soft\":%d,\"hard\":%d}", g_rfkill[i].type, + g_rfkill[i].soft, g_rfkill[i].hard); + } + jadd(&w, "],"); + + jadd(&w, "\"rate\":{\"rx_bps\":"); + 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); +} + +/* 单/全接口详情 JSON。name 空=全部(数组);否则单对象。返回 MEOW_OK / MEOW_ERR_NOENT。 */ +static int build_iface_json(const char *name, char *out, size_t cap) +{ + enum_ifaces(1); + jw_t w = { out, cap, 0 }; + out[0] = 0; + if (!name || !name[0]) { + jadd(&w, "["); + for (int i = 0; i < g_iface_n; i++) { + if (i) jadd(&w, ","); + iface_json_obj(&w, &g_ifaces[i]); + } + jadd(&w, "]"); + return MEOW_OK; + } + for (int i = 0; i < g_iface_n; i++) { + if (strcmp(g_ifaces[i].name, name) == 0) { + iface_json_obj(&w, &g_ifaces[i]); + /* 去掉尾 } 补 dns(期 1 不分接口,统一读 resolv.conf)。 */ + if (w.off >= 2 && w.buf[w.off - 1] == '}') w.buf[--w.off] = 0; + jadd(&w, ",\"dns\":["); + char ns[DNS_MAX][40]; + int n = 0; + dns_read(ns, &n); + for (int i2 = 0; i2 < n; i2++) { if (i2) jadd(&w, ","); jadd(&w, "\"%s\"", ns[i2]); } + jadd(&w, "]}"); + return MEOW_OK; + } + } + return MEOW_ERR_NOENT; +} + +/* ---------------- 节拍:模式热切换 + 接口变化检测(日志)+ 全局速率差分 ---------------- */ +static void daemon_tick(void) +{ + int fm = boot_mode_is_meow(); + if (fm != g_mode_meow) { + svc_log("mode -> %s", fm ? "full(meow)" : "readonly"); + g_mode_meow = fm; + } + /* 接口热插拔/状态变化:节拍检测 → 日志(读侧无 popen)。 */ + enum_ifaces(0); + int cur = g_iface_n; + for (int i = 0; i < cur; i++) { + int found = -1; + for (int j = 0; j < g_prev_if_n; j++) + if (strcmp(g_prev_if[j], g_ifaces[i].name) == 0) { found = j; break; } + if (found < 0) + net_log("info", "iface", "add kind=%s name=%s", g_ifaces[i].kind, g_ifaces[i].name); + else if (g_prev_if_up[found] != g_ifaces[i].up) + net_log("info", "iface", "state name=%s up=%d", g_ifaces[i].name, g_ifaces[i].up); + if (found >= 0) g_prev_if_up[found] = g_ifaces[i].up; + } + for (int j = 0; j < g_prev_if_n; j++) { + int gone = 1; + for (int i = 0; i < cur; i++) if (strcmp(g_prev_if[j], g_ifaces[i].name) == 0) { gone = 0; break; } + if (gone) net_log("info", "iface", "del name=%s", g_prev_if[j]); + } + for (int i = 0; i < cur; i++) { + snprintf(g_prev_if[i], sizeof g_prev_if[i], "%s", g_ifaces[i].name); + g_prev_if_up[i] = g_ifaces[i].up; + } + g_prev_if_n = cur; + + /* 全局速率差分(2s 拍):/proc/net/dev 累计。 */ + long rx = -1, tx = 0; + netdev_totals(&rx, &tx); + if (rx >= 0) { + long now = mono_now(); + if (g_net_last_rx >= 0 && now > g_net_last_mono) { + long dt = now - g_net_last_mono; + if (dt > 0) { + g_net_rate_rx = (rx - g_net_last_rx) / dt; + g_net_rate_tx = (tx - g_net_last_tx) / dt; + if (g_net_rate_rx < 0) g_net_rate_rx = 0; + if (g_net_rate_tx < 0) g_net_rate_tx = 0; + } + } + g_net_last_rx = rx; g_net_last_tx = tx; g_net_last_mono = now; + } +} + +/* ---------------- IPC 鉴权(连接级,MPK_SIGNATURE_DESIGN §6.2;同 svc_power) ---------------- */ +typedef enum { CALL_OTHER, CALL_PKG, CALL_SYS } caller_kind_t; +typedef struct { + caller_kind_t kind; + char pkg[MEOW_APP_ID_MAX]; + uid_t uid; +} caller_t; + +static int is_sys_identity(const caller_t *c) +{ + if (c->kind == CALL_SYS) return 1; + if (c->kind == CALL_PKG && !strncmp(c->pkg, "os.meow.", 8)) return 1; + return 0; +} + +static void classify_exe(const char *exe, caller_t *out) +{ + static const char *mp = "/usr/data/mnt/sd_0/MeowAPPs/"; + if (strncmp(exe, mp, strlen(mp)) == 0) { + const char *p = exe + strlen(mp); + if (*p == '/') p++; + const char *end = strchr(p, '/'); + int plen = end ? (int)(end - p) : (int)strlen(p); + if (plen > 0 && plen < MEOW_APP_ID_MAX) { + memcpy(out->pkg, p, (size_t)plen); out->pkg[plen] = 0; + out->kind = CALL_PKG; + return; + } + } + char tmp[MEOW_APP_ID_MAX]; + if (strcmp(exe, "/usr/bin/meow_launcherd") == 0) { + snprintf(tmp, sizeof tmp, "%s", "os.meow.launcher"); + } 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; + memcpy(tmp, exe + 9, l); tmp[l] = 0; + } else if (strncmp(exe, "/usr/data/meow/", 15) == 0) { + snprintf(tmp, sizeof tmp, "%s", "os.meow.core"); + } else { + return; /* CALL_OTHER */ + } + snprintf(out->pkg, sizeof out->pkg, "%s", tmp); + out->kind = CALL_SYS; +} + +static void classify_caller(int fd, caller_t *out) +{ + memset(out, 0, sizeof *out); + out->uid = (uid_t)-1; + struct ucred cred; + socklen_t cl = sizeof cred; + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cl) != 0) return; + out->uid = cred.uid; + char procpath[64], exe[256]; + snprintf(procpath, sizeof procpath, "/proc/%d/exe", (int)cred.pid); + ssize_t n = readlink(procpath, exe, sizeof exe - 1); + if (n <= 0) return; + exe[n] = 0; + classify_exe(exe, out); +} + +/* CTL 写级分类(设计 §10/HibyOS:写级仅系统身份 + MeowOS 模式才允许)。 */ +static int wifi_cmd_is_write(const char *cmd, const char *arg) +{ + if (!strcmp(cmd, "status") || !strcmp(cmd, "networks")) return 0; /* 读级 */ + if (!strcmp(cmd, "rfkill")) return arg && strcmp(arg, "list") == 0 ? 0 : 1; + return 1; /* scan/connect/disconnect/on/off/udhcpc */ +} +static int bt_cmd_is_write(const char *cmd) +{ + if (!strcmp(cmd, "status") || !strcmp(cmd, "paired")) return 0; + return 1; /* on/off */ +} + +/* 写级授权决策(纯函数,宿主自测用;handle_conn 复用):读级任意;写级仅系统身份 + MeowOS 模式。 + * 返回 MEOW_OK(放行)或 MEOW_ERR_PERM(拒绝)。 */ +static int wifi_write_authorize(const caller_t *c, int mode_meow, const char *cmd, const char *arg) +{ + if (!wifi_cmd_is_write(cmd, arg)) return MEOW_OK; + if (!is_sys_identity(c)) return MEOW_ERR_PERM; + if (!mode_meow) return MEOW_ERR_PERM; + return MEOW_OK; +} +static int bt_write_authorize(const caller_t *c, int mode_meow, const char *cmd) +{ + if (!bt_cmd_is_write(cmd)) return MEOW_OK; + if (!is_sys_identity(c)) return MEOW_ERR_PERM; + if (!mode_meow) return MEOW_ERR_PERM; + return MEOW_OK; +} + +/* ---------------- WiFi/BT/rfkill/udhcpc 动作执行(dæmon 侧;写级已在上游鉴权) ---------------- */ +static const char *str_capsafe(const char *s, char *tmp, size_t cap) +{ + size_t n = strlen(s); + while (n > 0 && (s[0] == ' ' || s[0] == '\t')) { s++; n--; } + while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\n')) n--; + if (n >= cap) n = cap - 1; + memcpy(tmp, s, n); tmp[n] = 0; + return tmp; +} + +static void wifi_scan(ctl_result_t *r) +{ + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s scan 2>/dev/null", g_wpa_cli); + char buf[128]; + run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd); + struct timespec ts = { 0, 300 * 1000000L }; /* 触发后稍候再读结果(有界)。 */ + nanosleep(&ts, NULL); + char bufout[MEOW_IPC_MAX - 16]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s scan_results 2>/dev/null", g_wpa_cli); + int n = run_cmd(bufout, sizeof bufout, RUNCMD_TMO_MS, cmd); + if (n <= 0) { ctl_sets(r, MEOW_ERR_NODEV, "scan: no results (wpa_cli 空/失败)"); return; } + ctl_sets(r, MEOW_OK, bufout); +} + +static void wifi_networks(ctl_result_t *r) +{ + char cmd[160]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s list_networks 2>/dev/null", g_wpa_cli); + char buf[MEOW_IPC_MAX - 16]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd); + if (n <= 0) { ctl_sets(r, MEOW_ERR_NODEV, "networks: no output (wpa_cli 空/失败)"); return; } + ctl_sets(r, MEOW_OK, buf); +} + +static void wifi_connect(const char *arg, ctl_result_t *r) +{ + char ssid[96] = "", psk[96] = ""; + if (arg && arg[0]) { + const char *comma = strchr(arg, ','); + if (comma) { + size_t sl = (size_t)(comma - arg); + if (sl >= sizeof ssid) sl = sizeof ssid - 1; + memcpy(ssid, arg, sl); ssid[sl] = 0; + snprintf(psk, sizeof psk, "%s", comma + 1); + } else { + snprintf(ssid, sizeof ssid, "%s", arg); + } + } + char tmp_ssid[96], tmp_psk[96]; + str_capsafe(ssid, tmp_ssid, sizeof tmp_ssid); + str_capsafe(psk, tmp_psk, sizeof tmp_psk); + if (!tmp_ssid[0]) { ctl_sets(r, MEOW_ERR_INVAL, "connect: ssid 缺失"); return; } + + /* 找既有网络:list_networks → 匹配 ssid(剥引号比较);命中则复用其 id。 */ + char cmd[200]; + char list[1024]; + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s list_networks 2>/dev/null", g_wpa_cli); + int n = run_cmd(list, sizeof list, RUNCMD_TMO_MS, cmd); + int id = -1; + if (n > 0) { + char *ln = strtok(list, "\n"); + while (ln) { + char *tab0 = strchr(ln, '\t'); + if (tab0) { + int this_id = atoi(ln); + char *ss = tab0 + 1; + char *tab1 = strchr(ss, '\t'); + if (tab1) *tab1 = 0; + char clean[128], norm_clean[128]; + size_t cl = strlen(ss); + if (cl >= 2 && ss[0] == '"' && ss[cl-1] == '"') { + memcpy(clean, ss + 1, cl - 2); clean[cl - 2] = 0; + } else { + snprintf(clean, sizeof clean, "%s", ss); + } + str_capsafe(clean, norm_clean, sizeof norm_clean); + if (strcmp(norm_clean, tmp_ssid) == 0) id = this_id; + } + ln = strtok(NULL, "\n"); + } + } + char out[128]; + if (id < 0) { + snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s add_network 2>/dev/null", g_wpa_cli); + if (run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd) <= 0) { + ctl_sets(r, MEOW_ERR_NODEV, "connect: add_network 失败 (wpa_cli 不可用?)"); + return; + } + id = atoi(out); + if (id < 0) { ctl_sets(r, MEOW_ERR_INVAL, "connect: add_network 无 id"); return; } + snprintf(cmd, sizeof cmd, + "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d ssid '\"%s\"' 2>/dev/null", + g_wpa_cli, id, tmp_ssid); + run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd); + if (tmp_psk[0]) { + snprintf(cmd, sizeof cmd, + "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d psk '\"%s\"' 2>/dev/null", + g_wpa_cli, id, tmp_psk); + } else { + snprintf(cmd, sizeof cmd, + "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d key_mgmt NONE 2>/dev/null", + g_wpa_cli, id); + } + run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd); + } + { /* enable + select 该网络(复用/新建皆用)。 */ + char en[256], sel[256]; + snprintf(en, sizeof en, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s enable_network %d 2>/dev/null", + g_wpa_cli, id); + run_cmd(out, sizeof out, RUNCMD_TMO_MS, en); + 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); + } + char msg[140]; + snprintf(msg, sizeof msg, "connect: ssid=%s network_id=%d (已 enable+select)", tmp_ssid, id); + ctl_sets(r, MEOW_OK, msg); +} + +static void wifi_onoff(const char *cmd, ctl_result_t *r) +{ + int block = strcmp(cmd, "off") == 0; + int rc = rfkill_set_block("wlan", block); + if (rc == 0) { + char msg[64]; + snprintf(msg, sizeof msg, "wifi %s: rfkill wlan %s", cmd, block ? "blocked" : "unblocked"); + ctl_sets(r, MEOW_OK, msg); + } else { + ctl_sets(r, MEOW_ERR_NODEV, "wifi on/off: 无 wlan rfkill 设备或写失败"); + } +} + +static void rfkill_ctl(const char *arg, ctl_result_t *r) +{ + char tmp[64]; + str_capsafe(arg ? arg : "", tmp, sizeof tmp); + char *sp = strchr(tmp, ' '); + if (!sp) { ctl_sets(r, MEOW_ERR_INVAL, "rfkill: 用法 (如 `rfkill wlan on`)"); return; } + *sp = 0; + char *type = tmp; + char *state = sp + 1; + int block = strcmp(state, "off") == 0 ? 1 : (strcmp(state, "on") == 0 ? 0 : -1); + if (block < 0) { ctl_sets(r, MEOW_ERR_INVAL, "rfkill: state 须 on/off"); return; } + int rc = rfkill_set_block(type, block); + if (rc == 0) { + char msg[64]; + snprintf(msg, sizeof msg, "rfkill %s %s: done", type, block ? "blocked" : "unblocked"); + ctl_sets(r, MEOW_OK, msg); + } else { + ctl_sets(r, MEOW_ERR_NODEV, "rfkill: 无该类型设备或写失败"); + } +} + +static void udhcpc_ctl_svc(const char *arg, ctl_result_t *r) +{ + char tmp[96]; + str_capsafe(arg ? arg : "", tmp, sizeof tmp); + char *sp = strchr(tmp, ' '); + char op[16], iff[32] = ""; + if (sp) { *sp = 0; snprintf(op, sizeof op, "%s", tmp); snprintf(iff, sizeof iff, "%s", sp + 1); } + else snprintf(op, sizeof op, "%s", tmp); + if (strcmp(op, "renew") != 0 && strcmp(op, "release") != 0) { + ctl_sets(r, MEOW_ERR_INVAL, "udhcpc: op 须 renew/release"); + return; + } + int rc = udhcpc_ctl(op, iff[0] ? iff : NULL); + if (rc == 0) { + char msg[80]; + snprintf(msg, sizeof msg, "udhcpc %s %s: sent SIGUSR%s", op, iff[0] ? iff : g_wifi_if, + strcmp(op, "release") == 0 ? "2" : "1"); + ctl_sets(r, MEOW_OK, msg); + } else { + ctl_sets(r, MEOW_ERR_NOENT, "udhcpc: 无运行进程 (pid 文件缺失)"); + } +} + +static void wifi_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r) +{ + ctl_reset(r, MEOW_OK); + if (!strcmp(cmd, "status")) { + wifi_t w; + wifi_status(&w); + char msg[256]; + snprintf(msg, sizeof msg, "wifi state=%s connected=%d ssid=%s ip=%s rssi=%d", + w.state[0] ? w.state : "unknown", w.connected, w.ssid[0] ? w.ssid : "-", + w.ip[0] ? w.ip : "-", w.rssi); + ctl_sets(r, MEOW_OK, msg); + } else if (!strcmp(cmd, "scan")) { + wifi_scan(r); + } else if (!strcmp(cmd, "networks")) { + wifi_networks(r); + } else if (!strcmp(cmd, "connect")) { + wifi_connect(arg, r); + } else if (!strcmp(cmd, "disconnect")) { + char cmd2[160]; + snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s disconnect 2>/dev/null", g_wpa_cli); + char buf[128]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2); + 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, "rfkill")) { + rfkill_ctl(arg, r); + } else if (!strcmp(cmd, "udhcpc")) { + udhcpc_ctl_svc(arg, r); + } else { + ctl_sets(r, MEOW_ERR_INVAL, "wifi: 未知子命令"); + } +} + +static void bt_onoff(const char *cmd, ctl_result_t *r) +{ + int up = strcmp(cmd, "on") == 0; + char cmd2[160]; + snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 %s 2>/dev/null", + g_hciconfig, up ? "up" : "down"); + char buf[128]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2); + rfkill_set_block("bluetooth", up ? 0 : 1); + ctl_sets(r, n >= 0 ? MEOW_OK : MEOW_ERR_NODEV, + up ? "bt on: hci0 up + rfkill unblocked" : "bt off: hci0 down + rfkill blocked"); +} + +static void bt_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r) +{ + (void)arg; + ctl_reset(r, MEOW_OK); + if (!strcmp(cmd, "status")) { + bt_t b; + bt_status(&b); + char msg[192]; + snprintf(msg, sizeof msg, "bt available=%d powered=%d address=%s paired=%d", + b.available, b.powered, b.address[0] ? b.address : "-", b.paired_n); + ctl_sets(r, MEOW_OK, msg); + } else if (!strcmp(cmd, "on") || !strcmp(cmd, "off")) { + bt_onoff(cmd, r); + } else if (!strcmp(cmd, "paired")) { + char cmd2[160]; + snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s paired 2>/dev/null", g_bluetoothctl); + char buf[MEOW_IPC_MAX - 16]; + int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2); + if (n <= 0) ctl_sets(r, MEOW_ERR_NODEV, "bt paired: 无输出 (bluetoothctl 不可用/未开)"); + else ctl_sets(r, MEOW_OK, buf); + } else { + ctl_sets(r, MEOW_ERR_INVAL, "bt: 未知子命令"); + } +} + +/* ---------------- 处理单条连接 ---------------- */ +static void handle_conn(int c) +{ + caller_t caller; + classify_caller(c, &caller); + for (;;) { + uint8_t hdr[MEOW_IPC_HDR_LEN]; + if (x_read_full(c, hdr, sizeof hdr) < 0) break; + uint32_t magic, rlen, req_id; + uint16_t ver, type; + memcpy(&magic, hdr, 4); + memcpy(&ver, hdr + 4, 2); + memcpy(&type, hdr + 6, 2); + memcpy(&req_id, hdr + 8, 4); + memcpy(&rlen, hdr + 12, 4); + if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { + send_err(c, req_id, MEOW_ERR_INVAL, "bad magic/ver"); break; + } + if (rlen > MEOW_IPC_MAX) { send_err(c, req_id, MEOW_ERR_INVAL, "payload too big"); break; } + uint8_t payload[MEOW_IPC_MAX]; + if (rlen && x_read_full(c, payload, rlen) < 0) break; + + if (type == MSG_HELLO) { send_ack(c, req_id, MEOW_OK, 0); continue; } + + if (type == MSG_NET_STATUS_REQ) { + char json[MEOW_IPC_MAX]; + build_status_json(json, sizeof json); + send_net_status_result(c, req_id, json); + continue; + } + if (type == MSG_NET_IFACE_REQ) { + m_net_iface_req_t q; + memset(&q, 0, sizeof q); + if (rlen > sizeof q) rlen = sizeof q; + memcpy(&q, payload, rlen); + char json[MEOW_IPC_MAX]; + int rc = build_iface_json(q.name, json, sizeof json); + send_net_iface_result(c, req_id, rc == MEOW_OK ? MEOW_OK : MEOW_ERR_NOENT, json); + continue; + } + if (type == MSG_NET_WIFI_CTL) { + m_net_cmd_t q; + memset(&q, 0, sizeof q); + if (rlen > sizeof q) rlen = sizeof q; + memcpy(&q, payload, rlen); + ctl_result_t r; + int wr = wifi_write_authorize(&caller, g_mode_meow, q.cmd, q.arg); + if (wr != MEOW_OK) { + ctl_sets(&r, MEOW_ERR_PERM, + !is_sys_identity(&caller) + ? "permission denied (wifi ctl write needs system identity)" + : "readonly (HibyOS mode): wifi write denied"); + net_log("warn", "write", "denied wifi cmd=%s arg=%s caller_kind=%d uid=%d", q.cmd, + q.arg, (int)caller.kind, (int)caller.uid); + send_net_wifi_result(c, req_id, &r); + continue; + } + wifi_ctl_execute(q.cmd, q.arg, &r); + if (wifi_cmd_is_write(q.cmd, q.arg)) + net_log("info", "write", "wifi cmd=%s arg=%s rc=%d", q.cmd, q.arg, r.code); + send_net_wifi_result(c, req_id, &r); + continue; + } + if (type == MSG_NET_BT_CTL) { + m_net_cmd_t q; + memset(&q, 0, sizeof q); + if (rlen > sizeof q) rlen = sizeof q; + memcpy(&q, payload, rlen); + ctl_result_t r; + int wr = bt_write_authorize(&caller, g_mode_meow, q.cmd); + if (wr != MEOW_OK) { + ctl_sets(&r, MEOW_ERR_PERM, + !is_sys_identity(&caller) + ? "permission denied (bt ctl write needs system identity)" + : "readonly (HibyOS mode): bt write denied"); + net_log("warn", "write", "denied bt cmd=%s caller_kind=%d uid=%d", q.cmd, + (int)caller.kind, (int)caller.uid); + send_net_bt_result(c, req_id, &r); + continue; + } + bt_ctl_execute(q.cmd, q.arg, &r); + if (bt_cmd_is_write(q.cmd)) net_log("info", "write", "bt cmd=%s rc=%d", q.cmd, r.code); + send_net_bt_result(c, req_id, &r); + continue; + } + send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1仅 STATUS/IFACE/WIFI_CTL/BT_CTL)"); + break; + } +} + +/* ---------------- 守护主循环(单线程 poll;节拍 2s) ---------------- */ +static volatile sig_atomic_t g_stop = 0; +static void on_sig(int sig) { (void)sig; g_stop = 1; } + +static int run_daemon(const char *sock) +{ + signal(SIGPIPE, SIG_IGN); + signal(SIGTERM, on_sig); + signal(SIGINT, on_sig); + int lfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (lfd < 0) { svc_log("socket fail: %s", strerror(errno)); return 1; } + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, sock, sizeof a.sun_path - 1); + unlink(sock); + if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0) { + svc_log("bind %s fail: %s", sock, strerror(errno)); + close(lfd); + return 1; + } + chmod(sock, 0666); + if (listen(lfd, 8) < 0) { svc_log("listen fail: %s", strerror(errno)); close(lfd); return 1; } + + g_mode_meow = boot_mode_is_meow(); + log_open(); + svc_log("meow_netmgrd listening on %s (mode=%s, poll=%ds, log=%s)", + sock, g_mode_meow ? "full" : "readonly", g_poll_sec, g_logfile); + + long next_tick = 0; + while (!g_stop) { + long now = mono_now(); + if (now >= next_tick) { + daemon_tick(); + next_tick = now + g_poll_sec; + } + int wait_ms = (int)((next_tick - mono_now()) * 1000); + if (wait_ms < 50) wait_ms = 50; + if (wait_ms > g_poll_sec * 1000) wait_ms = g_poll_sec * 1000; + struct pollfd pfd = { lfd, POLLIN, 0 }; + int pr = poll(&pfd, 1, wait_ms); + if (pr > 0 && (pfd.revents & POLLIN)) { + int c = accept(lfd, NULL, NULL); + if (c >= 0) { handle_conn(c); close(c); } + } + } + svc_log("meow_netmgrd exit"); + close(lfd); + unlink(sock); + return 0; +} + +/* ---------------- CLI(IPC 薄壳) ---------------- */ +static int ipc_connect(void) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + struct timeval tv = { 2, 0 }; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, g_sock, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } + return fd; +} + +/* 收发一轮:req(可有可无)+ resp type/code + payload 文本(json/text)。返回 0=OK。 */ +static int ipc_xchg(int fd, uint16_t req_type, const void *req, uint32_t req_len, + uint16_t *resp_type, uint32_t *resp_code, char *out, size_t outcap) +{ + if (send_frame(fd, req_type, 1, req, req_len) < 0) return -1; + uint8_t hdr[MEOW_IPC_HDR_LEN]; + if (x_read_full(fd, hdr, sizeof hdr) < 0) return -1; + uint32_t rl; uint16_t rt; + memcpy(&rt, hdr + 6, 2); + memcpy(&rl, hdr + 12, 4); + if (resp_type) *resp_type = rt; + if (rl > MEOW_IPC_MAX) return -1; + uint8_t pl[MEOW_IPC_MAX]; + if (rl && x_read_full(fd, pl, rl) < 0) return -1; + if (rl >= 8) { + uint32_t code; uint16_t jl; + memcpy(&code, pl, 4); + memcpy(&jl, pl + 4, 2); + if (resp_code) *resp_code = code; + if (out && outcap) { + if (jl >= outcap) jl = (uint16_t)(outcap - 1); + memcpy(out, pl + 8, jl); + out[jl] = 0; + } + return 0; + } + return -1; +} + +static int cli_ipc_json(uint16_t req_type, const void *req, uint32_t req_len, char *json, size_t cap) +{ + int fd = ipc_connect(); + if (fd < 0) return -1; + uint16_t rt; uint32_t code; + int rc = ipc_xchg(fd, req_type, req, req_len, &rt, &code, json, cap); + close(fd); + return rc == 0 ? 0 : -1; +} + +static int cli_ipc_cmd(uint16_t req_type, const char *cmd, const char *arg, char *out, size_t cap) +{ + m_net_cmd_t q; + memset(&q, 0, sizeof q); + snprintf(q.cmd, sizeof q.cmd, "%s", cmd); + if (arg) snprintf(q.arg, sizeof q.arg, "%s", arg); + int fd = ipc_connect(); + if (fd < 0) { snprintf(out, cap, "meow_netmgrd 守护不可达 (%s)", g_sock); return -1; } + uint16_t rt; uint32_t code; + int rc = ipc_xchg(fd, req_type, &q, sizeof q, &rt, &code, out, cap); + close(fd); + if (rc != 0) { snprintf(out, cap, "IPC 往返失败"); return -1; } + return (int)code; +} + +static int cli_cmd_result(uint16_t req_type, const char *cmd, const char *arg) +{ + char out[MEOW_IPC_MAX - 8]; + int code = cli_ipc_cmd(req_type, cmd, arg, out, sizeof out); + printf("%s\n", out); + return code == MEOW_OK ? 0 : 1; +} + +/* ---- 简版 JSON 字段提取(自产 JSON 固定形状,不做通用解析;同 powermgr) ---- */ +static const char *jfind(const char *js, const char *key) +{ + char pat[48]; + snprintf(pat, sizeof pat, "\"%s\":", key); + const char *p = strstr(js, pat); + return p ? p + strlen(pat) : NULL; +} +static int jint(const char *js, const char *key, int *out) +{ + const char *p = jfind(js, key); + if (!p || strncmp(p, "null", 4) == 0) { *out = -1; return 1; } + *out = atoi(p); + return 0; +} +static void jstr(const char *js, const char *key, char *out, size_t cap) +{ + const char *p = jfind(js, key); + if (!p || strncmp(p, "null", 4) == 0 || *p != '"') { out[0] = 0; return; } + p++; + size_t n = 0; + while (p[n] && p[n] != '"' && n < cap - 1) { out[n] = p[n]; n++; } + out[n] = 0; +} + +/* 定位 `"key":{` 的子对象并切到其闭合 `}`(扁平对象)。返回切片进 out。 */ +static int slice_obj(const char *js, const char *key, char *out, size_t cap) +{ + char pat[64]; + snprintf(pat, sizeof pat, "\"%s\":", key); + const char *p = strstr(js, pat); + if (!p) { out[0] = 0; return -1; } + const char *q = strchr(p, '}'); + if (!q) { out[0] = 0; return -1; } + size_t n = (size_t)(q - p + 1); + if (n >= cap) n = cap - 1; + memcpy(out, p, n); out[n] = 0; + return 0; +} + +/* 在 json 中迭代 `"":"` 开头的扁平条目,取每个对象切片(到 `}`)。首调 *ctx=NULL。 */ +static int next_obj(const char *json, const char *key, const char **ctx, char *out, size_t cap) +{ + char pat[64]; + snprintf(pat, sizeof pat, "\"%s\":\"", key); + const char *p = *ctx ? *ctx : json; + const char *q = strstr(p, pat); + if (!q) { *ctx = NULL; return -1; } + q += strlen(pat); + const char *e = strchr(q, '}'); + if (!e) { *ctx = NULL; return -1; } + size_t n = (size_t)(e - q + 1); + if (n >= cap) n = cap - 1; + memcpy(out, q, n); out[n] = 0; + *ctx = e + 1; + return 0; +} + +static int cli_status(void) +{ + char js[MEOW_IPC_MAX]; + if (cli_ipc_json(MSG_NET_STATUS_REQ, NULL, 0, js, sizeof js) != 0) { + fprintf(stderr, "meow_net status: 守护不可达(%s)\n", g_sock); + return 1; + } + char mode[16]; + jstr(js, "mode", mode, sizeof mode); + printf("mode=%s src=daemon\n", mode[0] ? mode : "?"); + /* ifaces 数组 */ + const char *ctx = NULL; + for (;;) { + char tmp[256]; + if (next_obj(js, "name", &ctx, tmp, sizeof tmp) != 0) break; + char name[32]; int i = 0; + while (tmp[i] && tmp[i] != '"' && i < 31) { name[i] = tmp[i]; i++; } + name[i] = 0; + char kind[12] = "", ip[40] = "", gw[40] = ""; + int up = strstr(tmp, "\"up\":true") != NULL; + jstr(tmp, "kind", kind, sizeof kind); + jstr(tmp, "ip", ip, sizeof ip); + jstr(tmp, "gw", gw, sizeof gw); + printf("iface:%s kind=%s up=%s ip=%s gw=%s\n", name, kind[0] ? kind : "?", + up ? "true" : "false", ip[0] ? ip : "null", gw[0] ? gw : "null"); + } + /* wifi */ + { + char obj[256]; + slice_obj(js, "wifi", obj, sizeof obj); + char ssid[64], wstate[16], wip[40]; + int wc = strstr(obj, "\"connected\":true") != NULL; + jstr(obj, "state", wstate, sizeof wstate); + jstr(obj, "ssid", ssid, sizeof ssid); + jstr(obj, "ip", wip, sizeof wip); + int rssi = -1; + jint(obj, "rssi", &rssi); + printf("wifi connected=%d state=%s ssid=%s ip=%s rssi=%d\n", + wc, wstate[0] ? wstate : "unknown", ssid[0] ? ssid : "-", + wip[0] ? wip : "-", rssi); + } + /* bt */ + { + char obj[192]; + slice_obj(js, "bt", obj, sizeof obj); + char baddr[32]; + int bavail = strstr(obj, "\"available\":true") != NULL; + int bpow = strstr(obj, "\"powered\":true") != NULL; + jstr(obj, "address", baddr, sizeof baddr); + int bpaired = -1; + jint(obj, "paired", &bpaired); + printf("bt available=%d powered=%d address=%s paired=%d\n", + bavail, bpow, baddr[0] ? baddr : "-", bpaired); + } + /* rfkill 数组 */ + { + const char *rf = strstr(js, "\"rfkill\":["); + if (rf) { + const char *ctx2 = NULL; + for (;;) { + char tmp[96]; + if (next_obj(rf, "type", &ctx2, tmp, sizeof tmp) != 0) break; + char type[16]; int i = 0; + while (tmp[i] && tmp[i] != '"' && i < 15) { type[i] = tmp[i]; i++; } + type[i] = 0; + int soft = -1, hard = -1; + jint(tmp, "soft", &soft); + jint(tmp, "hard", &hard); + printf("rfkill:%s soft=%d hard=%d\n", type, soft, hard); + } + } + } + return 0; +} + +static int cli_iface(const char *name) +{ + char js[MEOW_IPC_MAX]; + m_net_iface_req_t q; + memset(&q, 0, sizeof q); + if (name) snprintf(q.name, sizeof q.name, "%s", name); + if (cli_ipc_json(MSG_NET_IFACE_REQ, &q, sizeof q, js, sizeof js) != 0) { + fprintf(stderr, "meow_net iface: 守护不可达(%s)\n", g_sock); + return 1; + } + printf("%s\n", js); + return 0; +} + +static const char *usage(void) +{ + return "status | iface [name] | wifi scan|networks|connect [,psk]|disconnect|on|off" + " | bt status|on|off|paired | rfkill [ ] | udhcpc renew|release [if] | help"; +} + +static int cli_help(void) +{ + printf("meow_net — MeowOS 网络管理 CLI(meow_netmgrd 的 IPC 薄壳)\n"); + printf("usage: meow_net <%s>\n", usage()); + printf("\n读级任意调用者;写级(wifi/bt on|off、connect、rfkill set、udhcpc)仅系统身份,且 HibyOS 模式只读。\n"); + return 0; +} + +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; + 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(); + 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); + if (!strcmp(arg, "connect")) { + if (!arg2) { fprintf(stderr, "connect 需 [,psk]\n"); return 1; } + return cli_cmd_result(MSG_NET_WIFI_CTL, "connect", arg2); + } + return cli_cmd_result(MSG_NET_WIFI_CTL, arg, NULL); + } + if (!strcmp(cmd, "bt")) { + if (!arg) { fprintf(stderr, "bt 子命令缺失(%s)\n", usage()); return 1; } + return cli_cmd_result(MSG_NET_BT_CTL, arg, NULL); + } + if (!strcmp(cmd, "rfkill")) { + if (!arg) return cli_status(); /* 读 rfkill 表 → status JSON 的 rfkill[] */ + if (!arg2) { fprintf(stderr, "rfkill 需 \n"); return 1; } + char combined[64]; + snprintf(combined, sizeof combined, "%s %s", arg, arg2); + return cli_cmd_result(MSG_NET_WIFI_CTL, "rfkill", combined); + } + if (!strcmp(cmd, "udhcpc")) { + if (!arg) { fprintf(stderr, "udhcpc 需 renew|release [if]\n"); return 1; } + char combined[64]; + snprintf(combined, sizeof combined, "%s %s", arg, arg2 ? arg2 : ""); + return cli_cmd_result(MSG_NET_WIFI_CTL, "udhcpc", combined); + } + fprintf(stderr, "usage: meow_net <%s>\n", usage()); + return 1; +} + +/* ---------------- main:单一二进制双形态 ---------------- */ +int main(int argc, char **argv) +{ + const char *e; + if ((e = getenv("MEOW_NET_DIR")) && e[0]) snprintf(g_net_dir, sizeof g_net_dir, "%s", e); + if ((e = getenv("MEOW_NETDEV")) && e[0]) snprintf(g_netdev, sizeof g_netdev, "%s", e); + if ((e = getenv("MEOW_NET_ROUTE")) && e[0]) snprintf(g_net_route, sizeof g_net_route, "%s", e); + if ((e = getenv("MEOW_FIBTRIE")) && e[0]) snprintf(g_fibtrie, sizeof g_fibtrie, "%s", e); + if ((e = getenv("MEOW_RESOLV")) && e[0]) snprintf(g_resolv, sizeof g_resolv, "%s", e); + if ((e = getenv("MEOW_RFKILL_DIR")) && e[0]) snprintf(g_rfkill_dir, sizeof g_rfkill_dir, "%s", e); + if ((e = getenv("MEOW_BT_DIR")) && e[0]) snprintf(g_bt_dir, sizeof g_bt_dir, "%s", e); + if ((e = getenv("MEOW_BOOT_MODE")) && e[0]) snprintf(g_boot_mode, sizeof g_boot_mode, "%s", e); + if ((e = getenv("MEOW_LOG_DIR")) && e[0]) snprintf(g_log_dir, sizeof g_log_dir, "%s", e); + if ((e = getenv("MEOW_BOOTTS_CACHE")) && e[0]) snprintf(g_bootts_cache, sizeof g_bootts_cache, "%s", e); + if ((e = getenv("MEOW_WPA_CLI")) && e[0]) g_wpa_cli = e; + if ((e = getenv("MEOW_HCICONFIG")) && e[0]) g_hciconfig = e; + if ((e = getenv("MEOW_BLUETOOTHCTL")) && e[0]) g_bluetoothctl = e; + if ((e = getenv("MEOW_IP")) && e[0]) g_ip = e; + if ((e = getenv("MEOW_WIFI_IF")) && e[0]) g_wifi_if = e; + if ((e = getenv("MEOW_NET_SOCK")) && e[0]) g_sock = e; + if ((e = getenv("MEOW_NET_POLL_SEC")) && atoi(e) > 0) g_poll_sec = atoi(e); + + int i = 1; + for (; i < argc; i++) { + if (!strcmp(argv[i], "--sock") && i + 1 < argc) g_sock = argv[++i]; + else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + printf("meow_netmgrd / meow_net (%s)\n", usage()); + return 0; + } + else break; + } + + /* argv[0] basename=meow_net → 强制 CLI(双形态);meow_netmgrd 空参 → 守护常驻。 */ + const char *base = strrchr(argv[0], '/'); + base = base ? base + 1 : argv[0]; + if (!strcmp(base, "meow_net")) { + if (i >= argc) return cli_help(); + return cli_run(argc - i, argv + i); + } + if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */ + return cli_run(argc - i, argv + i); +} diff --git a/patch_v2/meow_netmgrd/meow_netmgrd.conf b/patch_v2/meow_netmgrd/meow_netmgrd.conf new file mode 100644 index 0000000..77193cb --- /dev/null +++ b/patch_v2/meow_netmgrd/meow_netmgrd.conf @@ -0,0 +1,16 @@ +# meow_netmgrd 板载服务配置(svcd 托管;由 S89meow_svcd 首启预置到 /usr/data/meow/services.d/) +# +# trigger=boot(NETMGR_DESIGN §1/§13,用户定案 2026-09-01):开机即常驻,接口统一层(WiFi/BT/ +# USB-eth/rfkill/IP/路由/DNS)的查询与控制权威。HibyOS 只读降级在进程内门控: +# /tmp/meow_boot_mode != meow(含开机早期文件缺失)→ 读级查询放行,一切写操作拒 + 记日志; +# 文件落地为 meow 后下一采样拍自动转全功能(热切换,无需重启)。 +# 与 audiod 的 trigger=manual+exit(0) 门控不同:本服务读侧天然无害,HibyOS 下挂只读查询器零干扰。 +# restart=on-fail:崩溃只丢接口层缓存,svcd 退避重拉,不级联(FRAMEWORK §10)。 +exec=/usr/bin/meow_netmgrd +args= +enabled=1 +restart=on-fail +backoff_ms=2000 +max_restart=5 +trigger=boot +cap= diff --git a/patch_v2/meow_netmgrd/meow_netmgrd_host b/patch_v2/meow_netmgrd/meow_netmgrd_host new file mode 100755 index 0000000..51522a4 Binary files /dev/null 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 new file mode 100755 index 0000000..0d0cb99 Binary files /dev/null and b/patch_v2/meow_netmgrd/netmgrd_authtest differ diff --git a/patch_v2/meow_netmgrd/netmgrd_authtest.c b/patch_v2/meow_netmgrd/netmgrd_authtest.c new file mode 100644 index 0000000..bedd661 --- /dev/null +++ b/patch_v2/meow_netmgrd/netmgrd_authtest.c @@ -0,0 +1,218 @@ +/* netmgrd_authtest.c — meow_netmgrd IPC 鉴权/写级决策 宿主自测(本程序非生产代码)。 + * + * 通过 #include 生产源文件(meow_netmgrd.c,用 #define main 改名避免入口冲突),在同一 TU 内直接 + * 访问其 FILE-STATIC 函数/全局:classify_exe / is_sys_identity / wifi_cmd_is_write / bt_cmd_is_write / + * wifi_write_authorize / bt_write_authorize / rfkill_set_block / wifi_networks / wifi_scan / bt_status, + * 验证鉴别/分级/授权决策表与接口层动作(假 sysfs/proc + 桩命令)。 + * 覆盖「真机需特权路径 /proc/pid/exe 判定」之外的所有纯逻辑分支;真实 pid→exe 解析留真机验证。 + * + * 用途:dev docs 复现级宿主自测;不进入固件/不部署。用法: + * cc -Wall -Wextra -I../meow_launcherd netmgrd_authtest.c -o netmgrd_authtest && ./netmgrd_authtest + */ +#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_authtest"; + char p[192]; + caller_t c; + + printf("== classify_exe ==\n"); + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/os.meow.tst/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "os.meow.tst") == 0); + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/com.example.tool/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "com.example.tool") == 0); + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/os.meow.tst/sub/meow", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "os.meow.tst") == 0); + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/meow_net", &c); + CHECK(c.kind == CALL_SYS && strcmp(c.pkg, "meow_net") == 0); /* /usr/bin/meow_* 前缀 → 系统 */ + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/meow_launcherd", &c); + CHECK(c.kind == CALL_SYS && strcmp(c.pkg, "os.meow.launcher") == 0); + memset(&c, 0, sizeof c); + classify_exe("/usr/bin/wpa_cli", &c); + CHECK(c.kind == CALL_OTHER); /* 非 meow_* 系统二进制 */ + memset(&c, 0, sizeof c); + classify_exe("/usr/data/mnt/sd_0/MeowAPPs/justadirty/k", &c); + CHECK(c.kind == CALL_PKG && strcmp(c.pkg, "justadirty") == 0); /* 第三方包(非 os.meow.*) */ + CHECK(is_sys_identity(&c) == 0); + + printf("== is_sys_identity ==\n"); + c.kind = CALL_PKG; snprintf(c.pkg, sizeof c.pkg, "%s", "os.meow.tst"); + CHECK(is_sys_identity(&c) == 1); + c.kind = CALL_PKG; snprintf(c.pkg, sizeof c.pkg, "%s", "com.example.tool"); + CHECK(is_sys_identity(&c) == 0); + c.kind = CALL_SYS; snprintf(c.pkg, sizeof c.pkg, "%s", "os.meow.launcher"); + CHECK(is_sys_identity(&c) == 1); + c.kind = CALL_OTHER; c.pkg[0] = 0; + CHECK(is_sys_identity(&c) == 0); + + printf("== wifi_cmd_is_write / bt_cmd_is_write ==\n"); + CHECK(wifi_cmd_is_write("status", NULL) == 0); + CHECK(wifi_cmd_is_write("networks", NULL) == 0); + CHECK(wifi_cmd_is_write("scan", NULL) == 1); + CHECK(wifi_cmd_is_write("connect", "ssid") == 1); + CHECK(wifi_cmd_is_write("disconnect", NULL) == 1); + CHECK(wifi_cmd_is_write("on", NULL) == 1); + CHECK(wifi_cmd_is_write("off", NULL) == 1); + CHECK(wifi_cmd_is_write("rfkill", "list") == 0); + CHECK(wifi_cmd_is_write("rfkill", "wlan on") == 1); + CHECK(wifi_cmd_is_write("udhcpc", "renew wlan0") == 1); + CHECK(bt_cmd_is_write("status") == 0); + CHECK(bt_cmd_is_write("paired") == 0); + CHECK(bt_cmd_is_write("on") == 1); + CHECK(bt_cmd_is_write("off") == 1); + + printf("== authorize 决策(系统身份 × MeowOS 模式)==\n"); + caller_t sys; memset(&sys, 0, sizeof sys); sys.kind = CALL_SYS; snprintf(sys.pkg, sizeof sys.pkg, "%s", "meow_net"); + caller_t pkgm; memset(&pkgm, 0, sizeof pkgm); pkgm.kind = CALL_PKG; snprintf(pkgm.pkg, sizeof pkgm.pkg, "%s", "os.meow.panel"); + caller_t third; memset(&third, 0, sizeof third); third.kind = CALL_PKG; snprintf(third.pkg, sizeof third.pkg, "%s", "com.example.tool"); + caller_t other; memset(&other, 0, sizeof other); other.kind = CALL_OTHER; other.uid = 1000; + /* 读级:任意放行,与身份/模式无关 */ + CHECK(wifi_write_authorize(&other, 0, "networks", NULL) == MEOW_OK); + CHECK(bt_write_authorize(&other, 0, "status") == MEOW_OK); + CHECK(wifi_write_authorize(&third, 1, "rfkill", "list") == MEOW_OK); + /* 写级:only 系统身份 + meow 模式 */ + CHECK(wifi_write_authorize(&sys, 1, "on", NULL) == MEOW_OK); + CHECK(wifi_write_authorize(&pkgm, 1, "connect", "ssid") == MEOW_OK); + CHECK(bt_write_authorize(&sys, 1, "off") == MEOW_OK); + CHECK(wifi_write_authorize(&third, 1, "scan", NULL) == MEOW_ERR_PERM); /* 第三方包 → 拒 */ + CHECK(wifi_write_authorize(&other, 1, "on", NULL) == MEOW_ERR_PERM); /* CALL_OTHER + uid → 拒 */ + CHECK(wifi_write_authorize(&sys, 0, "on", NULL) == MEOW_ERR_PERM); /* HibyOS 只读 → 拒 */ + CHECK(bt_write_authorize(&sys, 0, "on") == MEOW_ERR_PERM); /* HibyOS 只读 → 拒 */ + CHECK(bt_write_authorize(&pkgm, 1, "off") == MEOW_OK); + + printf("== 接口层动作(假 sysfs + 桩命令)==\n"); + /* 搭假 /sys/class/net + rfkill + bt(先建祖先目录) */ + mkdir(T, 0755); + snprintf(p, sizeof p, "%s/net", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/net/lo", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/net/wlan0", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/rfkill", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/rfkill/rfkill0", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/bt", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/bt/hci0", T); mkdir(p, 0755); + snprintf(p, sizeof p, "%s/net/lo/operstate", T); wf(p, "up\n"); + snprintf(p, sizeof p, "%s/net/wlan0/operstate", T); wf(p, "up\n"); + snprintf(p, sizeof p, "%s/rfkill/rfkill0/type", T); wf(p, "wlan\n"); + snprintf(p, sizeof p, "%s/rfkill/rfkill0/soft", T); wf(p, "0\n"); + snprintf(p, sizeof p, "%s/rfkill/rfkill0/hard", T); wf(p, "0\n"); + snprintf(p, sizeof p, "%s/bt/hci0/address", T); wf(p, "00:1A:7D:DA:71:13\n"); + snprintf(p, sizeof p, "%s/bt/hci0/power", T); wf(p, "on\n"); + snprintf(p, sizeof p, "%s/netdev", T); wf(p, "Inter-| Receive | Transmit\n lo: 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0\n wlan0: 5000000 5000 0 0 0 0 0 0 1000000 1000 0 0 0 0 0 0\n"); + snprintf(p, sizeof p, "%s/route", T); wf(p, "Iface\tDestination\tGateway \tFlags\nwlan0\t005EC20A\t6F3DC20A\t0003\nlo\t0100007F\t00000000\t0001\n"); + snprintf(p, sizeof p, "%s/resolv", T); wf(p, "nameserver 223.5.5.5\nnameserver 10.78.193.1\n"); + snprintf(p, sizeof p, "%s/boot_mode", T); wf(p, "meow\n"); + /* 桩工具 */ + snprintf(p, sizeof p, "%s/wpa_cli", T); wf(p, + "#!/bin/sh\n" + "case \"$1\" in\n" + " status) echo 'wpa_state=COMPLETED'; echo 'ssid=MyWiFi'; echo 'ip_address=10.78.193.50'; echo 'signal=-45';;\n" + " scan) echo OK;;\n" + " scan_results) echo 'bssid / frequency / signal / flags / ssid'; echo 'xx:xx / 2412 / -50 / WPA2 / TestAP';;\n" + " list_networks) echo 'network id / ssid / bssid / flags'; echo '0\tMyWiFi\tany\t[CURRENT]';;\n" + " add_network) echo 5;;\n" + " set_network) echo OK;;\n" + " enable_network) echo OK;;\n" + " select_network) echo OK;;\n" + " disconnect) echo OK;;\n" + "esac\n" + "exit 0\n"); + snprintf(p, sizeof p, "%s/hciconfig", T); wf(p, + "#!/bin/sh\n" + "echo 'hci0: Type: Primary Bus: USB'\n" + "echo ' BD Address: 00:1A:7D:DA:71:13 ACL MTU: 1021:8'\n" + "echo ' UP RUNNING PSCAN'\n"); + snprintf(p, sizeof p, "%s/bluetoothctl", T); wf(p, + "#!/bin/sh\n" + "echo 'Device 00:1A:7D:DA:71:13 My Headset'\n" + "echo 'Device 11:22:33:44:55:66 Speaker'\n"); + snprintf(p, sizeof p, "%s/ip", T); wf(p, + "#!/bin/sh\n" + "if [ \"$6\" = \"wlan0\" ]; then echo \"2: wlan0 inet 10.78.193.50/24 brd 255.255.255.255 scope global wlan0\"; fi\n" + "exit 0\n"); + snprintf(p, sizeof p, "%s/wpa_cli", T); chmod(p, 0755); + snprintf(p, sizeof p, "%s/hciconfig", T); chmod(p, 0755); + snprintf(p, sizeof p, "%s/bluetoothctl", T); chmod(p, 0755); + snprintf(p, sizeof p, "%s/ip", T); chmod(p, 0755); + + g_net_dir[0] = 0; snprintf(g_net_dir, sizeof g_net_dir, "%s/net", T); + g_netdev[0] = 0; snprintf(g_netdev, sizeof g_netdev, "%s/netdev", T); + g_net_route[0] = 0; snprintf(g_net_route, sizeof g_net_route, "%s/route", T); + g_resolv[0] = 0; snprintf(g_resolv, sizeof g_resolv, "%s/resolv", T); + g_rfkill_dir[0] = 0; snprintf(g_rfkill_dir, sizeof g_rfkill_dir, "%s/rfkill", T); + g_bt_dir[0] = 0; snprintf(g_bt_dir, sizeof g_bt_dir, "%s/bt", T); + g_boot_mode[0] = 0; snprintf(g_boot_mode, sizeof g_boot_mode, "%s/boot_mode", T); + g_wpa_cli = ""; { char x[160]; snprintf(x, sizeof x, "%s/wpa_cli", T); static char wc[160]; snprintf(wc, sizeof wc, "%s", x); g_wpa_cli = wc; } + g_hciconfig = ""; { char x[160]; snprintf(x, sizeof x, "%s/hciconfig", T); static char hc[160]; snprintf(hc, sizeof hc, "%s", x); g_hciconfig = hc; } + g_bluetoothctl = ""; { char x[160]; snprintf(x, sizeof x, "%s/bluetoothctl", T); static char bc[160]; snprintf(bc, sizeof bc, "%s", x); g_bluetoothctl = bc; } + g_ip = ""; { char x[160]; snprintf(x, sizeof x, "%s/ip", T); static char ip[160]; snprintf(ip, sizeof ip, "%s", x); g_ip = ip; } + + /* rfkill_set_block:wlan → soft=1 */ + CHECK(rfkill_set_block("wlan", 1) == 0); + snprintf(p, sizeof p, "%s/rfkill/rfkill0/soft", T); + char rb[8]; read_file_txt(p, rb, sizeof rb); + CHECK(strcmp(rb, "1") == 0); + + /* enum_ifaces(detail=1):ip/gw/rx/tx */ + enum_ifaces(1); + CHECK(g_iface_n >= 2); + int wlan = -1; + for (int i = 0; i < g_iface_n; i++) if (!strcmp(g_ifaces[i].name, "wlan0")) wlan = i; + CHECK(wlan >= 0); + CHECK(strcmp(g_ifaces[wlan].kind, "wifi") == 0); + CHECK(g_ifaces[wlan].up == 1); + CHECK(strcmp(g_ifaces[wlan].gw, "10.194.61.111") == 0); /* 0x6F3DC20A LE → 10.194.61.111 */ + CHECK(g_ifaces[wlan].rx == 5000000); + + /* wifi_networks(读) + wifi_scan(写执行路径) */ + ctl_result_t r; + wifi_networks(&r); + CHECK(r.code == MEOW_OK && strstr(r.text, "MyWiFi") != NULL); + wifi_scan(&r); + CHECK(r.code == MEOW_OK && strstr(r.text, "TestAP") != NULL); + + /* bt_status:available=1 powered=1 address=... paired=2 */ + bt_t b; + bt_status(&b); + CHECK(b.available == 1 && b.powered == 1); + CHECK(strcmp(b.address, "00:1A:7D:DA:71:13") == 0); + CHECK(b.paired_n == 2); + + /* 写级执行(wifi_onoff off → rfkill block wlan)已在 rfkill_set_block 覆盖;此处联调 wifi_ctl_execute */ + wifi_ctl_execute("networks", NULL, &r); + CHECK(r.code == MEOW_OK && strstr(r.text, "MyWiFi") != NULL); + /* connect:现有 ssid 复用(enable+select,network_id=0);新 ssid 走 add/set/enable/select(id=5)。 */ + wifi_ctl_execute("connect", "MyWiFi", &r); + CHECK(r.code == MEOW_OK && strstr(r.text, "ssid=MyWiFi") != NULL && strstr(r.text, "network_id=0") != NULL); + wifi_ctl_execute("connect", "NewAP,secret", &r); + CHECK(r.code == MEOW_OK && strstr(r.text, "ssid=NewAP") != NULL && strstr(r.text, "network_id=5") != NULL); + wifi_ctl_execute("disconnect", NULL, &r); + CHECK(r.code == MEOW_OK); + + if (g_fail) { printf("\nNETMGR AUTH TEST: %d FAIL\n", g_fail); return 1; } + printf("\nNETMGR AUTH TEST: ALL PASS\n"); + return 0; +} diff --git a/patch_v2/meow_netmgrd/test_netmgrd.sh b/patch_v2/meow_netmgrd/test_netmgrd.sh new file mode 100755 index 0000000..0ab5784 --- /dev/null +++ b/patch_v2/meow_netmgrd/test_netmgrd.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# test_netmgrd.sh — meow_netmgrd 宿主桩测试(假 sysfs/procfs 节点 + 桩后端命令,MEOW_* env 覆盖)。 +# 用法:./test_netmgrd.sh [host二进制路径](默认 ./meow_netmgrd_host;可传 mipsel 二进制 + QEMU 前缀) +# QEMU="qemu-mipsel" ./test_netmgrd.sh ./meow_netmgrd # qemu 验证 +set -u +cd "$(dirname "$0")" +BIN="${1:-./meow_netmgrd_host}" +Q="${QEMU:-}" +T=$(mktemp -d /tmp/netmgrdtest.XXXXXX) +trap 'kill $DPID 2>/dev/null; rm -rf "$T"' EXIT +PASS=0; FAIL=0 +ck() { # ck <名> <期望子串> <实际> + if echo "$3" | grep -qF "$2"; then PASS=$((PASS+1)); echo "PASS $1"; + else FAIL=$((FAIL+1)); echo "FAIL $1: 期望含 [$2] 实际=[$3]"; fi +} + +# ---- 搭假节点 ---- +NET="$T/net"; RF="$T/rfkill"; BT="$T/bt" +mkdir -p "$NET/lo" "$NET/wlan0" "$RF/rfkill0" "$RF/rfkill1" "$BT/hci0" +echo up > "$NET/lo/operstate" +echo up > "$NET/wlan0/operstate" +echo wlan > "$RF/rfkill0/type"; echo 0 > "$RF/rfkill0/soft"; echo 0 > "$RF/rfkill0/hard" +echo bluetooth > "$RF/rfkill1/type"; echo 1 > "$RF/rfkill1/soft"; echo 0 > "$RF/rfkill1/hard" +echo 00:1A:7D:DA:71:13 > "$BT/hci0/address" +echo on > "$BT/hci0/power" +cat > "$T/netdev" <<'EOF' +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + lo: 1000 10 0 0 0 0 0 0 1000 10 0 0 0 0 0 0 + wlan0: 5000000 5000 0 0 0 0 0 0 1000000 1000 0 0 0 0 0 0 +EOF +cat > "$T/route" <<'EOF' +Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +wlan0 005EC20A 6F3DC20A 0003 0 0 0 00FFFFFF 0 0 0 +lo 0100007F 00000000 0001 0 0 0 000000FF 0 0 0 +EOF +printf 'nameserver 223.5.5.5\nnameserver 10.78.193.1\n' > "$T/resolv" +echo meow > "$T/boot_mode" + +# 桩后端命令(popen stubbing) +cat > "$T/wpa_cli" <<'EOF' +#!/bin/sh +case "$1" in + status) echo "wpa_state=COMPLETED"; echo "ssid=MyWiFi"; echo "ip_address=10.78.193.50"; echo "signal=-45";; + scan) echo OK;; + scan_results) printf 'bssid / frequency / signal level / flags / ssid\nxx:xx / 2412 / -50 / WPA2 / TestAP\n';; + list_networks) printf 'network id / ssid / bssid / flags\n0\tMyWiFi\tany\t[CURRENT]\n';; + add_network) echo 1;; + set_network) echo OK;; + enable_network) echo OK;; + select_network) echo OK;; + disconnect) echo OK;; +esac +exit 0 +EOF +cat > "$T/hciconfig" <<'EOF' +#!/bin/sh +echo "hci0: Type: Primary Bus: USB" +echo " BD Address: 00:1A:7D:DA:71:13 ACL MTU: 1021:8 SCO MTU: 64:1" +echo " UP RUNNING PSCAN" +EOF +cat > "$T/bluetoothctl" <<'EOF' +#!/bin/sh +echo "Device 00:1A:7D:DA:71:13 My Headset" +echo "Device 11:22:33:44:55:66 Speaker" +EOF +cat > "$T/ip" <<'EOF' +#!/bin/sh +for a in "$@"; do + if [ "$a" = "wlan0" ]; then echo "2: wlan0 inet 10.78.193.50/24 brd 255.255.255.255 scope global wlan0"; fi + if [ "$a" = "lo" ]; then echo "1: lo inet 127.0.0.1/8 scope host lo"; fi +done +exit 0 +EOF +for f in wpa_cli hciconfig bluetoothctl ip; do chmod +x "$T/$f"; done + +mkdir -p "$T/logs" "$T/pid" +ENVS="MEOW_NET_DIR=$NET MEOW_NETDEV=$T/netdev MEOW_NET_ROUTE=$T/route MEOW_RESOLV=$T/resolv + MEOW_RFKILL_DIR=$RF MEOW_BT_DIR=$BT MEOW_BOOT_MODE=$T/boot_mode MEOW_BOOTTS_CACHE=$T/bootts + MEOW_IP=$T/ip MEOW_WPA_CLI=$T/wpa_cli MEOW_HCICONFIG=$T/hciconfig MEOW_BLUETOOTHCTL=$T/bluetoothctl + MEOW_NET_SOCK=$T/net.sock MEOW_NET_POLL_SEC=1 MEOW_LOG_DIR=$T/logs" +ln -sf "$(readlink -f "$BIN")" "$T/meow_net" # argv[0] 双形态 → CLI + +# ---- T0: 守护起 ---- +env $ENVS $Q "$BIN" --sock "$T/net.sock" >"$T/daemon.log" 2>&1 & +DPID=$! +for i in $(seq 1 30); do [ -S "$T/net.sock" ] && break; sleep 0.2; done +[ -S "$T/net.sock" ] || { echo "FAIL T0: daemon socket 未出现"; cat "$T/daemon.log"; exit 1; } +PASS=$((PASS+1)); echo "PASS T0 daemon up" + +# ---- T1: status(读级,任意调用者)---- +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T1a "mode=meow src=daemon" "$OUT" +ck T1b "iface:lo kind=lo up=true" "$OUT" +ck T1c "iface:wlan0 kind=wifi up=true" "$OUT" +ck T1d "ip=10.78.193.50" "$OUT" # 若 iface 行 ip 解析成功(ip 桩已修)——实际由 wlan gw 行给,见下 +ck T1e "gw=10.194.61.111" "$OUT" +ck T1f "wifi connected=1 state=COMPLETED ssid=MyWiFi" "$OUT" +ck T1g "bt available=1 powered=1 address=00:1A:7D:DA:71:13 paired=2" "$OUT" +ck T1h "rfkill:wlan soft=0 hard=0" "$OUT" +ck T1i "rfkill:bluetooth soft=1 hard=0" "$OUT" + +# ---- T2: iface 单/全 ---- +OUT=$(env $ENVS $Q "$T/meow_net" iface wlan0 2>&1) +ck T2a '"name":"wlan0"' "$OUT" +ck T2b '"kind":"wifi"' "$OUT" +ck T2c '"ip":"10.78.193.50"' "$OUT" +OUT=$(env $ENVS $Q "$T/meow_net" iface 2>&1) +ck T2d '"name":"lo"' "$OUT" + +# ---- T3: wifi networks(读级)---- +OUT=$(env $ENVS $Q "$T/meow_net" wifi networks 2>&1) +ck T3a "MyWiFi" "$OUT" + +# ---- T4: wifi scan(写级;宿主 CLI=CALL_OTHER → 拒)---- +OUT=$(env $ENVS $Q "$T/meow_net" wifi scan 2>&1) +ck T4a "permission denied" "$OUT" + +# ---- T5: bt status / bt paired(读级)---- +OUT=$(env $ENVS $Q "$T/meow_net" bt status 2>&1) +ck T5a "available=1 powered=1" "$OUT" +ck T5b "paired=2" "$OUT" +OUT=$(env $ENVS $Q "$T/meow_net" bt paired 2>&1) +ck T5c "My Headset" "$OUT" +ck T5d "Speaker" "$OUT" + +# ---- T6: HibyOS 只读降级(读可查,写拒)---- +echo hiby > "$T/boot_mode" +sleep 2 # 等守护下一拍热切换 +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T6a "mode=readonly" "$OUT" +ck T6b "iface:wlan0" "$OUT" # 读侧仍可查 +OUT=$(env $ENVS $Q "$T/meow_net" wifi networks 2>&1) +ck T6c "MyWiFi" "$OUT" # 读级子命令仍可用 +OUT=$(env $ENVS $Q "$T/meow_net" wifi on 2>&1); RC=$? +ck T6d "permission denied" "$OUT" +echo meow > "$T/boot_mode" +sleep 2 +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T6e "mode=meow" "$OUT" # 热切换回全功能 + +# ---- T7: 守护日志(每次开机新文件;接口 add/写拒绝 必记)---- +sleep 1 +LOGS=$(ls "$T/logs"/net_*.log 2>/dev/null | head -1) +[ -n "$LOGS" ] && { PASS=$((PASS+1)); echo "PASS T7a log file: $(basename "$LOGS")"; } || { FAIL=$((FAIL+1)); echo "FAIL T7a 无日志文件"; } +[ -n "$LOGS" ] && { grep -q "iface add kind=wifi name=wlan0" "$LOGS" && { PASS=$((PASS+1)); echo "PASS T7b iface add 已记"; } || { FAIL=$((FAIL+1)); echo "FAIL T7b 未记 iface add"; }; } + +# ---- T8: 守护停 → CLI help 本地可用;status 报守护不可达 ---- +kill $DPID 2>/dev/null; wait $DPID 2>/dev/null; DPID="" +sleep 0.5 +OUT=$(env $ENVS $Q "$T/meow_net" help 2>&1) +ck T8a "usage: meow_net" "$OUT" +OUT=$(env $ENVS $Q "$T/meow_net" status 2>&1) +ck T8b "守护不可达" "$OUT" + +# ---- T9: 无参 → help;未知子命令 → usage ---- +OUT=$(env $ENVS $Q "$T/meow_net" 2>&1) +ck T9a "usage: meow_net" "$OUT" +OUT=$(env $ENVS $Q "$T/meow_net" bogus 2>&1); RC=$? +ck T9b "usage: meow_net" "$OUT" + +echo "==============================" +echo "PASS=$PASS FAIL=$FAIL" +[ $FAIL -eq 0 ]