fix(mdns): 启动竞态+运行期接口重检 (agent-194) — 无可用IPv4不再exit(2)改内部2s等待重试(SIGTERM可打断); 5s周期检测掉线/换IP→重新join组播+A记录实时刷新(修IP变更还带旧地址); 接口down时零应答守卫; MEOW_MDNS_IF 落地

meow_mdns b23de7ce→d6b0151b (+3.5KB); mipsel -Wall -Wextra 0 warning; 宿主netns实测等待/换IP/掉线守卫全过
注: 真机源码在 mips-repo/bins/meow_mdns/(非patch_v2); fw_content.conf 的 md5 行打包时更新
This commit is contained in:
MeowOS committed 2026-08-31 05:16:18 +08:00
1 parent 3fd4726198
commit b2e7cc8c70
3 files changed
+92 -29

No files matched your search

Binary file not shown.
+5 -2
View File
@@ -28,6 +28,9 @@
- RFC 1035 名压缩(owner 名若已在响应中出现则回写 `0xC0` 指针)。
- IP_PKTINFO 判断查询到组播还是单播:**组播查询→组播应答 + 单播冗余回源**;单播查询→单播回源。
- 线程/信号安全;SIGINT/SIGTERM 优雅退出(留组并关 socket)。
- **启动容错**:接口未就绪(无 IPv4/wlan0 未 up)时**内部等待重试**(每 2s,无限等,stderr 打 `waiting`),不再 exit;修复开机时 mdns 先于网络就绪的 5×exit=2 崩溃竞态。
- **运行期容错**:每 5s 周期检查设备 IP,**掉线(无 IPv4)或换 IP(WiFi 重连)时自动重新 drop/join 组播并刷新应答 IP**,避免应答携带旧地址。
- `MEOW_MDNS_IF`(已实现):限定要绑定/公布的接口名——既作为 IP 筛选,也决定 join 绑定接口;可用于宿主模拟"无接口"(设为不存在的接口名即进入等待循环)。
## 环境覆写
| 变量 | 默认 | 说明 |
@@ -51,8 +54,8 @@
ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, stripped
## 体积
- 部署版:**79,468 B ≈ 77.6 KB**(strip 后)
- MD5 :**b23de7ce54873e926e7601a3b21b66a9**
- 部署版:**82,972 B ≈ 81.0 KB**(strip 后;较旧版 +3.5KB,启动容错 + 运行期重检)
- MD5 :**d6b0151bda26768f19952dae1342610d**(旧 `b23de7ce54873e926e7601a3b21b66a9`)
## 构建命令
见同目录 build.sh(宿主 gcc `-O2` 可编译并在本机组播/单播自测;交叉 `zig-mipsel-cc -Os -static`)。
+87 -27
View File
@@ -13,6 +13,8 @@
* MEOW_MDNS_ADDR 缺省 = 第一块非回环 UP 接口的 IPv4 (可为 wlan0 10.109.49.50)
* MEOW_MDNS_IF 缺省 = 自动; 设定接口名, join 组播绑定到该接口
* MEOW_MDNS_PORT 缺省 = 5353; 可覆写(通常仅宿主单测用)
* 容错: 接口未就绪(无 IPv4)时内部等待重试(每 2s, 无限等, 打 waiting)而非退出;
* 运行期掉线/换 IP(WiFi 重连)每 5s 检测, 自动重新 join 并刷新应答 IP。
*
* 构建: 宿主机 cc -O2 -o meow_mdns meow_mdns.c
* 交叉 zig-mipsel-cc -Os -static -o meow_mdns meow_mdns.c
@@ -39,6 +41,7 @@
#include <sys/socket.h>
#include <sys/time.h>
#include <ifaddrs.h>
#include <time.h>
#define MDNS_PORT 5353
#define MDNS_GROUP "224.0.0.251"
@@ -55,8 +58,35 @@
static volatile sig_atomic_t g_running = 1;
static int g_cacheflush = 0; /* 唯一记录(A/SRV/TXT)是否置 cache-flush 位 (MEOW_MDNS_CF) */
static char g_ifname[IFNAMSIZ]; /* MEOW_MDNS_IF 指定的接口名, 空=自动 */
static void on_signal(int sig) { (void)sig; g_running = 0; }
/* 确定当前应公布的设备 IPv4 (网络序, 存 *out)。
* 优先 MEOW_MDNS_ADDR(有效且非 0); 其次 MEOW_MDNS_IF 指定接口; 否则第一块非回环 UP 的 IPv4。
* 返回 1=找到; 0=暂无可用地址(接口未就绪). */
static int find_device_ip(struct in_addr *out)
{
const char *ea = getenv("MEOW_MDNS_ADDR");
if (ea && *ea) {
if (inet_pton(AF_INET, ea, out) == 1 && out->s_addr) return 1;
/* 无效或 0.0.0.0: 落到接口扫描 */
}
struct ifaddrs *ifa, *i;
int found = 0;
if (getifaddrs(&ifa) == 0) {
for (i = ifa; i; i = i->ifa_next) {
if (!i->ifa_addr || i->ifa_addr->sa_family != AF_INET) continue;
if (!(i->ifa_flags & IFF_UP) || (i->ifa_flags & IFF_LOOPBACK)) continue;
if (g_ifname[0] && strcmp(i->ifa_name, g_ifname) != 0) continue;
*out = ((struct sockaddr_in *)i->ifa_addr)->sin_addr;
found = 1;
break;
}
freeifaddrs(ifa);
}
return found;
}
static void to_lower(char *s) { for (; *s; s++) *s = (char)tolower((unsigned char)*s); }
/* 把 "a.b.c" 编码为 DNS 名字(无压缩) ==> <len>a<len>b<len>c<0>。返回长度, -1 失败 */
@@ -206,28 +236,25 @@ int main(int argc, char **argv)
char svc[MAX_NAME] = "_ssh._tcp.local";
/* 地址全程保持网络字节序 (struct in_addr) */
const char *eif = getenv("MEOW_MDNS_IF");
if (eif && *eif) snprintf(g_ifname, sizeof(g_ifname), "%s", eif);
/* 信号先注册, 使等待循环可被 SIGTERM/SIGINT 打断 */
signal(SIGINT, on_signal);
signal(SIGTERM, on_signal);
signal(SIGPIPE, SIG_IGN);
/* ---- 等待网络接口就绪,内部重试(每 2s, 无限等): 接口未就绪不算崩 ---- */
struct in_addr ipin = {0};
const char *ea = getenv("MEOW_MDNS_ADDR");
if (ea && *ea) {
if (inet_pton(AF_INET, ea, &ipin) != 1) ipin.s_addr = 0;
}
if (!ipin.s_addr) {
/* 遍历接口取第一块非回环 UP 的 IPv4(sin_addr 已是网络序) */
struct ifaddrs *ifa, *i;
if (getifaddrs(&ifa) == 0) {
for (i = ifa; i; i = i->ifa_next) {
if (!i->ifa_addr || i->ifa_addr->sa_family != AF_INET) continue;
if (!(i->ifa_flags & IFF_UP) || (i->ifa_flags & IFF_LOOPBACK)) continue;
ipin = ((struct sockaddr_in *)i->ifa_addr)->sin_addr;
break;
}
freeifaddrs(ifa);
}
}
if (!ipin.s_addr) {
fprintf(stderr, "meow_mdns: no IPv4 address (set MEOW_MDNS_ADDR)\n");
return 2;
for (;;) {
if (find_device_ip(&ipin)) break;
fprintf(stderr, "meow_mdns: no usable IPv4 interface (%s)... waiting\n",
g_ifname[0] ? g_ifname : "auto");
fflush(stderr);
if (!g_running) { printf("meow_mdns: exiting\n"); return 0; }
sleep(2);
}
if (!g_running) { printf("meow_mdns: exiting\n"); return 0; }
char ips[32]; inet_ntop(AF_INET, &ipin, ips, sizeof(ips));
printf("meow_mdns: host=%s.local addr=%s swc=%s\n", host, ips, svc);
@@ -271,10 +298,6 @@ int main(int argc, char **argv)
struct timeval tv = { 1, 0 };
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
signal(SIGINT, on_signal);
signal(SIGTERM, on_signal);
signal(SIGPIPE, SIG_IGN);
struct in_addr mcast_addr;
inet_pton(AF_INET, MDNS_GROUP, &mcast_addr);
@@ -302,7 +325,36 @@ int main(int argc, char **argv)
char pkt[2048];
struct sockaddr_in src;
time_t last_chk = time(NULL);
while (g_running) {
/* 周期接口检查(5s): 检测掉线(无 IPv4)/换 IP(WiFi 重连), 重新 join 组播并刷新应答 IP */
time_t now = time(NULL);
if (now - last_chk >= 5) {
last_chk = now;
struct in_addr cur = {0};
int have = find_device_ip(&cur);
if (have != (ipin.s_addr != 0) || (have && cur.s_addr != ipin.s_addr)) {
if (ipin.s_addr)
setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
ipin = cur;
if (have) {
inet_ntop(AF_INET, &ipin, ips, sizeof(ips));
memset(&mreq, 0, sizeof(mreq));
inet_pton(AF_INET, MDNS_GROUP, &mreq.imr_multiaddr);
mreq.imr_interface.s_addr = ipin.s_addr;
if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
perror("IP_ADD_MEMBERSHIP");
if (set_mcastif && ipin.s_addr)
setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &ipin.s_addr, sizeof(ipin.s_addr));
printf("meow_mdns: interface ready addr=%s\n", ips);
} else {
ips[0] = 0;
printf("meow_mdns: interface lost, addr=0 (waiting)\n");
}
fflush(stdout);
}
}
/* recvmsg + IP_PKTINFO: 拿到查询的目的地址与到达接口, 判断组播/单播 */
memset(&src, 0, sizeof(src));
unsigned char ctrl[256];
@@ -327,11 +379,16 @@ int main(int argc, char **argv)
if (n < 12) continue;
uint32_t destip = mcast_addr.s_addr; /* 取不到就按组播处理 */
for (struct cmsghdr *cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
/* 手工迭代 control 消息, 规避 musl CMSG_NXTHDR 的 -Wsign-compare 警告 */
unsigned char *ctrl_end = (unsigned char *)msg.msg_control + msg.msg_controllen;
for (struct cmsghdr *cm = CMSG_FIRSTHDR(&msg); cm != NULL; ) {
if (cm->cmsg_level == IPPROTO_IP && cm->cmsg_type == IP_PKTINFO) {
struct in_pktinfo *pi = (struct in_pktinfo *)CMSG_DATA(cm);
if (pi->ipi_addr.s_addr) destip = pi->ipi_addr.s_addr;
}
unsigned char *next = (unsigned char *)cm + CMSG_ALIGN(cm->cmsg_len);
if (next + sizeof(struct cmsghdr) > ctrl_end) break;
cm = (struct cmsghdr *)next;
}
uint8_t *m = (uint8_t *)pkt;
@@ -369,6 +426,8 @@ int main(int argc, char **argv)
fflush(stdout);
}
if (!ipin.s_addr) continue; /* 接口未就绪: 无有效公布地址, 暂不应答 */
/* ---- 构建应答 ----
* RFC 6762: 应答 QDCOUNT 通常为 0, 但部分实现(avahi)接受、部分要求回显问题。
* MEOW_MDNS_QD 控制是否回显; MEOW_MDNS_CF 控制唯一记录 cache-flush 位。 */
@@ -467,10 +526,11 @@ int main(int argc, char **argv)
ssize_t su = sendto(s, resp, ro, 0, (struct sockaddr *)&dst, sizeof(dst));
(void)su;
if (verbose) {
char srcip[64];
inet_ntop(AF_INET, &src.sin_addr, srcip, sizeof(srcip));
printf(" -> replied %zu bytes to %s%s:%d (AN %zu)\n", ro,
to_mcast ? "mcast(224.0.0.251)" : "",
inet_ntop(AF_INET, &src.sin_addr, ips, sizeof(ips)),
ntohs(src.sin_port), ancount);
srcip, ntohs(src.sin_port), ancount);
fflush(stdout);
}
}