refactor(panel): 前后端按模块拆分+屏幕/控制合并页 — 后端meowpanel.c 6372行拆src/*.c+mp.h(235函数原型,20端点output byte-identical,0warning)+前端拆mp_shared/mp_pages/meowpanel三层;屏幕·遥控合并(左屏实时画面+右MD3 SVG矢量按钮无emoji);重编后热更TF卡
This commit is contained in:
1 parent
f3ae06d7cd
commit
2dc4cad454
26 files changed
+9317
-2755
No files matched your search
@@ -0,0 +1,440 @@
|
||||
/* mp_net.c — 网络管理域:meow_netmgrd IPC(状态/连接/DNS/ifaces/policy,期2/期3)。 */
|
||||
#include "mp.h"
|
||||
|
||||
#define NET_LOG_TAIL_MAX (256 * 1024) /* net 日志 tail 读取上限 */
|
||||
|
||||
|
||||
int net_ipc(uint16_t req_type, const void *req, uint32_t req_len,
|
||||
uint16_t *ret_type, uint32_t *code, uint16_t *meta,
|
||||
char *out, size_t cap)
|
||||
{
|
||||
int fd = ipc_connect_tmo(g_sock_net, IPC_TIMEOUT_MS);
|
||||
if (fd < 0) return -1;
|
||||
uint8_t buf[MEOW_IPC_MAX];
|
||||
uint16_t type = 0; uint32_t rid = 0;
|
||||
int rlen = -1;
|
||||
if (send_frame(fd, req_type, 0x1020, req, req_len) == 0)
|
||||
rlen = recv_frame(fd, &type, &rid, buf, sizeof buf);
|
||||
close(fd);
|
||||
if (rlen <= 0) return -1;
|
||||
if (ret_type) *ret_type = type;
|
||||
/* m_net_*_result 统一布局:code(u32)|len(u16)|meta/pad(u16)|content[len],content 偏移 8 */
|
||||
uint32_t c = 0; uint16_t ln = 0, mv = 0;
|
||||
memcpy(&c, buf, 4); memcpy(&ln, buf + 4, 2); memcpy(&mv, buf + 6, 2);
|
||||
if (code) *code = c;
|
||||
if (meta) *meta = mv;
|
||||
if ((size_t)ln >= cap) ln = (uint16_t)(cap - 1);
|
||||
if ((size_t)(8 + ln) < sizeof buf) buf[8 + ln] = 0;
|
||||
memcpy(out, buf + 8, ln);
|
||||
out[ln] = 0;
|
||||
return (int)ln;
|
||||
}
|
||||
|
||||
int api_net_status(int sock)
|
||||
{
|
||||
uint16_t t; uint32_t code; char json[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_STATUS_REQ, NULL, 0, &t, &code, NULL, json, sizeof json);
|
||||
if (n <= 0 || t != MSG_NET_STATUS_RESULT || code != MEOW_OK || !json[0])
|
||||
return send_err(sock, 503, "NET_UNAVAILABLE", "网络管理服务不可用");
|
||||
if (!utf8_valid_n(json, strlen(json))) /* 上游坏字节(含 ssid)→ 不返回,降级 */
|
||||
return send_err(sock, 503, "NET_UNAVAILABLE", "网络管理服务应答包含非法字节");
|
||||
return send_json(sock, 200, json);
|
||||
}
|
||||
|
||||
int net_conns_parse_body(sbuf_t *s, const char *txt)
|
||||
{
|
||||
const char *p = txt;
|
||||
int first = 1, n = 0;
|
||||
while (p && *p) {
|
||||
const char *nl = strchr(p, '\n');
|
||||
size_t ll = nl ? (size_t)(nl - p) : strlen(p);
|
||||
if (ll == 0) { p = nl ? nl + 1 : p + 1; continue; }
|
||||
char line[384];
|
||||
if (ll >= sizeof line) ll = sizeof line - 1;
|
||||
memcpy(line, p, ll); line[ll] = 0;
|
||||
char *f[6]; int fn = 0; char *cur = line;
|
||||
while (fn < 6) {
|
||||
f[fn++] = cur;
|
||||
char *bar = strchr(cur, '|');
|
||||
if (!bar) break;
|
||||
*bar = 0; cur = bar + 1;
|
||||
}
|
||||
if (fn >= 6) {
|
||||
if (!first) sA(s, ",");
|
||||
first = 0;
|
||||
sA(s, "{\"entity\":"); sA_str(s, f[0]);
|
||||
sA(s, ",\"local\":"); sA_str(s, f[1]);
|
||||
sA(s, ",\"remote\":"); sA_str(s, f[2]);
|
||||
sA(s, ",\"state\":"); sA_str(s, f[3]);
|
||||
sA(s, ",\"age\":%lld,\"domain\":", atoll(f[4]));
|
||||
sA_str(s, strcmp(f[5], "-") == 0 ? "" : f[5]);
|
||||
sA(s, "}");
|
||||
n++;
|
||||
}
|
||||
p = nl ? nl + 1 : p + ll;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int api_net_conns(int sock, const char *query)
|
||||
{
|
||||
m_net_conns_req_t q; memset(&q, 0, sizeof q);
|
||||
char ent[128];
|
||||
if (qparam(query, "entity", ent, sizeof ent))
|
||||
strncpy(q.entity, ent, sizeof q.entity - 1);
|
||||
uint16_t t; uint32_t code; uint16_t meta; char txt[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_CONNS_REQ, &q, sizeof q, &t, &code, &meta, txt, sizeof txt);
|
||||
if (n <= 0 || t != MSG_NET_CONNS_RESULT || code != MEOW_OK)
|
||||
return send_err(sock, 503, "NET_UNAVAILABLE", "网络管理服务不可用");
|
||||
sbuf_t a = {0};
|
||||
int count = net_conns_parse_body(&a, txt);
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"truncated\":%s,\"count\":%d,\"conns\":[", meta ? "true" : "false", count);
|
||||
if (a.n) sA_append(&s, a.b, a.n);
|
||||
sA(&s, "]}");
|
||||
sbuf_free(&a);
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int net_dns_obj_fill(sbuf_t *s)
|
||||
{
|
||||
m_net_dns_ctl_t q; memset(&q, 0, sizeof q);
|
||||
strcpy(q.cmd, "status");
|
||||
uint16_t t; uint32_t code; uint16_t meta; char txt[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_DNS_CTL, &q, sizeof q, &t, &code, &meta, txt, sizeof txt);
|
||||
if (n <= 0 || t != MSG_NET_DNS_RESULT) return -1;
|
||||
if (code != MEOW_OK) return -2;
|
||||
/* txt: `dns mode=%s enabled=%d upstream=%s cache=%d/%d q=%llu deny=%llu first=%llu resolv_modified=%d` */
|
||||
char mode[32] = "", upstream[64] = "";
|
||||
int enabled = 0, ch = 0, ct = 0, rmod = 0;
|
||||
unsigned long long qq = 0, deny = 0, first = 0;
|
||||
(void)sscanf(txt, "dns mode=%31s enabled=%d upstream=%63s cache=%d/%d q=%llu deny=%llu first=%llu resolv_modified=%d",
|
||||
mode, &enabled, upstream, &ch, &ct, &qq, &deny, &first, &rmod);
|
||||
sA(s, "{\"mode\":");
|
||||
sA_str(s, mode);
|
||||
sA(s, ",\"enabled\":%s,\"upstream\":", enabled ? "true" : "false");
|
||||
sA_str(s, upstream);
|
||||
sA(s, ",\"cache_hits\":%d,\"cache_total\":%d,\"queries\":%llu,\"denies\":%llu,\"first_seen\":%llu,\"resolv_modified\":%s}",
|
||||
ch, ct, qq, deny, first, rmod ? "true" : "false");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_net_dns(int sock)
|
||||
{
|
||||
sbuf_t s = {0};
|
||||
int rc = net_dns_obj_fill(&s);
|
||||
if (rc == -1) { sbuf_free(&s); return send_err(sock, 503, "NET_UNAVAILABLE", "网络管理服务不可用"); }
|
||||
if (rc == -2) { sbuf_free(&s); return send_err(sock, 503, "NET_NODEV", "DNS 代理不可用"); }
|
||||
int out = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return out;
|
||||
}
|
||||
|
||||
int net_block_fill(sbuf_t *s)
|
||||
{
|
||||
uint16_t nt; uint32_t ncode; char njson[MEOW_IPC_MAX];
|
||||
int nn = net_ipc(MSG_NET_STATUS_REQ, NULL, 0, &nt, &ncode, NULL, njson, sizeof njson);
|
||||
if (nn <= 0 || nt != MSG_NET_STATUS_RESULT || ncode != MEOW_OK || !njson[0]) return -1;
|
||||
/* 状态 JSON 为对象:剥外层大括号,拼入 DNS 子键(DNS 不可用则省略该键,仍回 net 状态,不谎报)。 */
|
||||
size_t l = strlen(njson);
|
||||
while (l > 0 && (njson[l - 1] == ' ' || njson[l - 1] == '\t' || njson[l - 1] == '\n' || njson[l - 1] == '\r')) njson[--l] = 0;
|
||||
if (l < 2 || njson[0] != '{' || njson[l - 1] != '}') return -1;
|
||||
if (!utf8_valid_n(njson, l)) return -1; /* 任一上游(netmgrd 含 ssid)产出非法 UTF-8 → 整块降级,不拖垮整页 */
|
||||
njson[l - 1] = 0; /* 去尾部 } */
|
||||
sbuf_t dns = {0};
|
||||
int drc = net_dns_obj_fill(&dns);
|
||||
sA(s, ",\"net\":{");
|
||||
sA(s, "%s", njson + 1); /* 去首 { 的状态内层 */
|
||||
if (drc == 0 && dns.b) sA(s, ",\"dns\":%s", dns.b);
|
||||
sA(s, "}"); /* net 块只开了一个 {,一个 } 收尾(批2 曾写 }} 多一括号,浏览器 Extra data 破页 2026-09-02) */
|
||||
sbuf_free(&dns);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_net_dns_flush(int sock)
|
||||
{
|
||||
m_net_dns_ctl_t q; memset(&q, 0, sizeof q);
|
||||
strcpy(q.cmd, "flush");
|
||||
uint16_t t; uint32_t code; uint16_t meta; char txt[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_DNS_CTL, &q, sizeof q, &t, &code, &meta, txt, sizeof txt);
|
||||
if (n <= 0 || t != MSG_NET_DNS_RESULT)
|
||||
return send_err(sock, 503, "NET_UNAVAILABLE", "网络管理服务不可用");
|
||||
if (code == (uint32_t)MEOW_ERR_PERM)
|
||||
return send_err(sock, 403, "FORBIDDEN", txt[0] ? txt : "DNS flush 需系统身份");
|
||||
if (code != MEOW_OK)
|
||||
return send_err(sock, 503, "NET_NODEV", txt[0] ? txt : "DNS flush 失败");
|
||||
/* txt: `dns cache flushed (N entries)` */
|
||||
int entries = 0;
|
||||
(void)sscanf(txt, "dns cache flushed (%d entries)", &entries);
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"flushed\":true,\"entries\":%d}", entries);
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int api_net_log(int sock)
|
||||
{
|
||||
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
|
||||
DIR *d = opendir(LOGS_DIR);
|
||||
if (!d) return send_json(sock, 200, "{\"file\":\"\",\"lines\":[],\"total\":0,\"truncated\":false}");
|
||||
char best[512] = ""; long best_mt = -1;
|
||||
struct dirent *e;
|
||||
while ((e = readdir(d))) {
|
||||
if (strncmp(e->d_name, "net_", 4) != 0) continue;
|
||||
if (e->d_name[0] == '.') continue;
|
||||
char full[1024];
|
||||
snprintf(full, sizeof full, "%s/%s", LOGS_DIR, e->d_name);
|
||||
struct stat st;
|
||||
if (stat(full, &st) < 0 || !S_ISREG(st.st_mode)) continue;
|
||||
if (st.st_mtime > best_mt) { best_mt = st.st_mtime; snprintf(best, sizeof best, "%s", e->d_name); }
|
||||
}
|
||||
closedir(d);
|
||||
if (!best[0])
|
||||
return send_json(sock, 200, "{\"file\":\"\",\"lines\":[],\"total\":0,\"truncated\":false}");
|
||||
char full[1024];
|
||||
snprintf(full, sizeof full, "%s/%s", LOGS_DIR, best);
|
||||
int fd = open(full, O_RDONLY);
|
||||
struct stat st;
|
||||
if (fd < 0 || fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) {
|
||||
if (fd >= 0) close(fd);
|
||||
return send_json(sock, 200, "{\"file\":\"\",\"lines\":[],\"total\":0,\"truncated\":false}");
|
||||
}
|
||||
off_t off = st.st_size > NET_LOG_TAIL_MAX ? st.st_size - NET_LOG_TAIL_MAX : 0;
|
||||
size_t buflen = (size_t)(st.st_size - off);
|
||||
if (buflen == 0) { close(fd); return send_json(sock, 200, "{\"file\":\"\",\"lines\":[],\"total\":0,\"truncated\":false}"); }
|
||||
if (off) (void)lseek(fd, off, SEEK_SET);
|
||||
char *buf = malloc(buflen + 1);
|
||||
if (!buf) { close(fd); return send_err(sock, 500, "INTERNAL", "内存不足"); }
|
||||
ssize_t n = read(fd, buf, buflen);
|
||||
close(fd);
|
||||
if (n <= 0) { free(buf); return send_json(sock, 200, "{\"file\":\"\",\"lines\":[],\"total\":0,\"truncated\":false}"); }
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"file\":");
|
||||
sA_str(&s, best);
|
||||
sA(&s, ",\"lines\":[");
|
||||
const char *p = buf, *end = buf + n;
|
||||
int linecnt = 0;
|
||||
for (const char *c = buf; c < end; c++) if (*c == '\n') linecnt++;
|
||||
if (linecnt > 0 && end[-1] != '\n') linecnt++;
|
||||
int maxlines = LOG_MAX_LINES;
|
||||
int startrow = linecnt - maxlines;
|
||||
if (startrow < 0) startrow = 0;
|
||||
int truncated = (off > 0 || startrow > 0) ? 1 : 0;
|
||||
int rowidx = 0, gotlines = 0;
|
||||
p = buf;
|
||||
while (p < end) {
|
||||
const char *nl = memchr(p, '\n', (size_t)(end - p));
|
||||
const char *line_end = nl ? nl : end;
|
||||
if (rowidx >= startrow) {
|
||||
if (gotlines) sA(&s, ",");
|
||||
/* sA_str_n:转义 + UTF-8 消毒(任意长度安全;勿 sA("%s")——单次超 511B 会截断坏 JSON) */
|
||||
sA_str_n(&s, p, (size_t)(line_end - p));
|
||||
gotlines++;
|
||||
}
|
||||
rowidx++;
|
||||
if (!nl) break;
|
||||
p = nl + 1;
|
||||
}
|
||||
free(buf);
|
||||
sA(&s, "],\"total\":%d,\"truncated\":%s}", linecnt, truncated ? "true" : "false");
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int api_net_ifaces(int sock)
|
||||
{
|
||||
m_net_iface_req_t q; memset(&q, 0, sizeof q);
|
||||
uint16_t t; uint32_t code; char arr[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_IFACE_REQ, &q, sizeof q, &t, &code, NULL, arr, sizeof arr);
|
||||
if (n <= 0 || t != MSG_NET_IFACE_RESULT || code != MEOW_OK || !arr[0])
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
/* 上游接口数组由 netmgrd iface_json_obj 直出(name/kind/ip/gw/rx/tx,ASCII,无 ssid),仍校验坏字节。 */
|
||||
if (!utf8_valid_n(arr, strlen(arr)))
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
|
||||
/* 取 rssi + rfkill + dns:用 0xE0 状态 JSON(单一权威,含 wifi.rssi/rfkill[]/bt.dns[]),jv 提取。 */
|
||||
uint16_t st; uint32_t scode; char sj[MEOW_IPC_MAX];
|
||||
int sn2 = net_ipc(MSG_NET_STATUS_REQ, NULL, 0, &st, &scode, NULL, sj, sizeof sj);
|
||||
long long rssi = -9999; int got_rssi = 0;
|
||||
sbuf_t rfkill = {0}, dns = {0};
|
||||
if (sn2 > 0 && st == MSG_NET_STATUS_RESULT && scode == MEOW_OK && utf8_valid_n(sj, strlen(sj))) {
|
||||
jv_t *jr = jv_parse(sj, (int)strlen(sj));
|
||||
if (jr) {
|
||||
jv_t *w = jv_obj_get(jr, "wifi");
|
||||
jv_t *r = w ? jv_obj_get(w, "rssi") : NULL;
|
||||
if (r && r->type == J_NUM) { rssi = r->inum; got_rssi = 1; }
|
||||
jv_t *rf = jv_obj_get(jr, "rfkill");
|
||||
if (rf && rf->type == J_ARR) jv_to_json(&rfkill, rf);
|
||||
jv_t *bt = jv_obj_get(jr, "bt");
|
||||
jv_t *dn = bt ? jv_obj_get(bt, "dns") : NULL;
|
||||
if (dn && dn->type == J_ARR) jv_to_json(&dns, dn);
|
||||
jv_free(jr);
|
||||
}
|
||||
}
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"available\":true,\"rssi\":");
|
||||
if (got_rssi) sA(&s, "%lld", rssi); else sA(&s, "null");
|
||||
sA(&s, ",\"rfkill\":%s,\"dns\":%s,\"ifaces\":%s}", rfkill.b ? rfkill.b : "[]", dns.b ? dns.b : "[]", arr);
|
||||
sbuf_free(&rfkill); sbuf_free(&dns);
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void net_policy_parse(const char *txt, sbuf_t *s)
|
||||
{
|
||||
char lvl[24] = "", enf[24] = "dns_only";
|
||||
const char *p = txt;
|
||||
sA(s, "{\"available\":true,\"enforce\":");
|
||||
/* 首行找 enforce=... */
|
||||
/* enforce 后面紧跟全角「(」多字节说明,须在非 ASCII 字节处停(否则会吞「(SOCK_DESTROY...)」)。 */
|
||||
const char *en = strstr(p, "enforce=");
|
||||
if (en) {
|
||||
en += 8;
|
||||
int i = 0; while (en[i] && en[i] != ' ' && en[i] != '\n' && (unsigned char)en[i] < 0x80 && i < (int)sizeof enf - 1) { enf[i] = en[i]; i++; }
|
||||
enf[i] = 0;
|
||||
}
|
||||
const char *lg = strstr(p, "log_level=");
|
||||
if (lg) {
|
||||
lg += 10;
|
||||
int i = 0; while (lg[i] && lg[i] != ' ' && lg[i] != '\n' && (unsigned char)lg[i] < 0x80 && i < (int)sizeof lvl - 1) { lvl[i] = lg[i]; i++; }
|
||||
lvl[i] = 0;
|
||||
}
|
||||
sA_str(s, enf);
|
||||
sA(s, ",\"log_level\":");
|
||||
sA_str(s, lvl);
|
||||
sA(s, ",\"rules\":[");
|
||||
int first = 1;
|
||||
const char *ln = txt;
|
||||
while (ln && *ln) {
|
||||
const char *nl = strchr(ln, '\n');
|
||||
size_t ll = nl ? (size_t)(nl - ln) : strlen(ln);
|
||||
if (ll == 0) { ln = nl ? nl + 1 : ln + ll; continue; }
|
||||
/* 跳注释行(以 # 开头)与空;数据行为 `<entity> net=... domain=...`(entity 含冒号无空格) */
|
||||
const char *c = ln;
|
||||
while (c < ln + ll && (*c == ' ' || *c == '\t')) c++;
|
||||
int is_comment = (c < ln + ll && *c == '#');
|
||||
int is_none = (strncmp(c, "(none)", 6) == 0 || strncmp(c, "(allow)", 7) == 0);
|
||||
if (!is_comment && !is_none && c < ln + ll) {
|
||||
/* entity = 首个空白符之前 */
|
||||
char entity[64] = ""; size_t ei = 0;
|
||||
const char *e = c;
|
||||
while (e < ln + ll && *e != ' ' && *e != '\t' && ei < sizeof entity - 1) { entity[ei++] = *e++; }
|
||||
entity[ei] = 0;
|
||||
/* net=block|allow 与 domain=+h1,-h2 从 entity 之后扫 */
|
||||
char net[8] = "allow"; int has_net = 0;
|
||||
char allow_h[512] = "", deny_h[512] = "";
|
||||
const char *k = e;
|
||||
if (k < ln + ll) {
|
||||
const char *seg = k;
|
||||
while (seg < ln + ll && (*seg == ' ' || *seg == '\t')) seg++;
|
||||
const char *nh = strstr(seg, "net=");
|
||||
if (nh) { nh += 4; int i2 = 0; while (nh[i2] && nh[i2] != ' ' && nh[i2] != '\n' && i2 < 7) { net[i2] = nh[i2]; i2++; } net[i2] = 0; has_net = 1; }
|
||||
const char *dh = strstr(seg, "domain=");
|
||||
if (dh) {
|
||||
dh += 7;
|
||||
/* 逗号分隔;+ 前缀=allow,- 前缀=deny。主机列表内部以 ',' 拼接(供拆分回数组)。 */
|
||||
size_t ao = 0, do2 = 0;
|
||||
const char *h = dh;
|
||||
while (*h && *h != ' ' && *h != '\n' && *h != '\r') {
|
||||
if (*h == '+') {
|
||||
h++;
|
||||
if (ao && ao < sizeof allow_h - 1) allow_h[ao++] = ',';
|
||||
while (*h && *h != ',' && *h != ' ' && *h != '\n' && ao < sizeof allow_h - 1) allow_h[ao++] = *h++;
|
||||
} else if (*h == '-') {
|
||||
h++;
|
||||
if (do2 && do2 < sizeof deny_h - 1) deny_h[do2++] = ',';
|
||||
while (*h && *h != ',' && *h != ' ' && *h != '\n' && do2 < sizeof deny_h - 1) deny_h[do2++] = *h++;
|
||||
} else h++;
|
||||
if (*h == ',') h++;
|
||||
}
|
||||
allow_h[ao] = 0; deny_h[do2] = 0;
|
||||
}
|
||||
if (!first) sA(s, ",");
|
||||
first = 0;
|
||||
sA(s, "{\"entity\":"); sA_str(s, entity);
|
||||
sA(s, ",\"net\":"); sA_str(s, has_net ? net : "");
|
||||
sA(s, ",\"has_net\":%s", has_net ? "true" : "false");
|
||||
sA(s, ",\"allow_hosts\":[");
|
||||
{ const char *hp = allow_h; int hf = 1;
|
||||
while (hp && *hp) { const char *comma = strchr(hp, ','); size_t hl = comma ? (size_t)(comma - hp) : strlen(hp);
|
||||
if (!hf) { sA(s, ","); }
|
||||
hf = 0; sA_str_n(s, hp, hl);
|
||||
hp = comma ? comma + 1 : hp + hl; } }
|
||||
sA(s, "],\"deny_hosts\":[");
|
||||
{ const char *hp = deny_h; int hf = 1;
|
||||
while (hp && *hp) { const char *comma = strchr(hp, ','); size_t hl = comma ? (size_t)(comma - hp) : strlen(hp);
|
||||
if (!hf) { sA(s, ","); }
|
||||
hf = 0; sA_str_n(s, hp, hl);
|
||||
hp = comma ? comma + 1 : hp + hl; } }
|
||||
sA(s, "]}");
|
||||
}
|
||||
}
|
||||
ln = nl ? nl + 1 : ln + ll;
|
||||
}
|
||||
sA(s, "]}");
|
||||
}
|
||||
|
||||
int api_net_policy_get(int sock)
|
||||
{
|
||||
m_net_policy_t q; memset(&q, 0, sizeof q);
|
||||
q.op = NET_POLICY_SHOW;
|
||||
uint16_t t; uint32_t code; char txt[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_POLICY, &q, sizeof q, &t, &code, NULL, txt, sizeof txt);
|
||||
if (n <= 0 || t != MSG_NET_POLICY_RESULT || code != MEOW_OK)
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
if (!utf8_valid_n(txt, strlen(txt))) /* 实体名/域名坏字节 → 降级 */
|
||||
return send_json(sock, 200, "{\"available\":false}");
|
||||
sbuf_t s = {0};
|
||||
net_policy_parse(txt, &s);
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int api_net_policy_set(int sock, const char *body, size_t body_len)
|
||||
{
|
||||
if (!body || !body[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 body");
|
||||
jv_t *root = jv_parse(body, (int)body_len);
|
||||
if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON");
|
||||
jv_t *jo = jv_obj_get(root, "op"), *je = jv_obj_get(root, "entity"), *ja = jv_obj_get(root, "arg");
|
||||
char op[16] = "set_net", entity[64] = "", arg[64] = "";
|
||||
if (jo && jo->type == J_STR && jo->str) snprintf(op, sizeof op, "%s", jo->str);
|
||||
if (je && je->type == J_STR && je->str) snprintf(entity, sizeof entity, "%s", je->str);
|
||||
if (ja && ja->type == J_STR && ja->str) snprintf(arg, sizeof arg, "%s", ja->str);
|
||||
jv_free(root);
|
||||
if (!entity[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 entity(app:<pkg>/svc:<svc>/proc:<comm>)");
|
||||
if (!entity[0] || strchr(entity, '/')) return send_err(sock, 422, "UNSUPPORTED", "entity 非法");
|
||||
int nop = 0;
|
||||
if (!strcmp(op, "set_net")) nop = NET_POLICY_SET_NET;
|
||||
else if (!strcmp(op, "set_domain")) nop = NET_POLICY_SET_DOMAIN;
|
||||
else if (!strcmp(op, "del")) nop = NET_POLICY_DEL;
|
||||
else return send_err(sock, 422, "UNSUPPORTED", "op 须为 set_net|set_domain|del");
|
||||
if (nop != NET_POLICY_DEL && !arg[0]) return send_err(sock, 422, "UNSUPPORTED", "缺 arg");
|
||||
|
||||
m_net_policy_t q; memset(&q, 0, sizeof q);
|
||||
q.op = (uint32_t)nop;
|
||||
snprintf(q.entity, sizeof q.entity, "%.47s", entity); /* entity[48] 留一个 NUL;防截断告警 */
|
||||
snprintf(q.arg, sizeof q.arg, "%.63s", arg); /* arg[64] 同理 */
|
||||
uint16_t t; uint32_t code; char txt[MEOW_IPC_MAX];
|
||||
int n = net_ipc(MSG_NET_POLICY, &q, sizeof q, &t, &code, NULL, txt, sizeof txt);
|
||||
if (n <= 0 || t != MSG_NET_POLICY_RESULT)
|
||||
return send_err(sock, 503, "NET_UNAVAILABLE", "网络管理服务不可用");
|
||||
if (code == (uint32_t)MEOW_ERR_PERM)
|
||||
return send_err(sock, 403, "FORBIDDEN", txt[0] ? txt : "策略写入需系统身份,面板未被白名单?");
|
||||
if (code == (uint32_t)MEOW_ERR_INVAL)
|
||||
return send_err(sock, 422, "UNSUPPORTED", txt[0] ? txt : "策略参数非法");
|
||||
if (code != MEOW_OK)
|
||||
return send_err(sock, 503, "NET_NODEV", txt[0] ? txt : "策略写入失败");
|
||||
sbuf_t s = {0};
|
||||
sA(&s, "{\"accepted\":true,\"message\":");
|
||||
sA_str(&s, txt);
|
||||
sA(&s, "}");
|
||||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||||
sbuf_free(&s);
|
||||
return rc;
|
||||
}
|
||||
Reference in new issue
Block a user