diff --git a/mips-repo/bins/meow_mdns/bin/meow_mdns b/mips-repo/bins/meow_mdns/bin/meow_mdns index 02bbf36..d55d896 100755 Binary files a/mips-repo/bins/meow_mdns/bin/meow_mdns and b/mips-repo/bins/meow_mdns/bin/meow_mdns differ diff --git a/mips-repo/bins/meow_mdns/manifest.md b/mips-repo/bins/meow_mdns/manifest.md index 4bc8051..e598510 100644 --- a/mips-repo/bins/meow_mdns/manifest.md +++ b/mips-repo/bins/meow_mdns/manifest.md @@ -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`)。 diff --git a/mips-repo/bins/meow_mdns/meow_mdns.c b/mips-repo/bins/meow_mdns/meow_mdns.c index 1f2320c..f095378 100644 --- a/mips-repo/bins/meow_mdns/meow_mdns.c +++ b/mips-repo/bins/meow_mdns/meow_mdns.c @@ -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 #include #include +#include #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 名字(无压缩) ==> abc<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); } }