Files
MeowOS b2e7cc8c70 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 行打包时更新
2026-08-31 05:16:18 +08:00

544 lines
23 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* meow_mdns.c — 极简 IPv4-only mDNS 应答器(MeowOS / R3 Pro II)
*
* 目标: 让设备在纯 IPv4 下应答 <host>.local (A 记录) 与 _ssh._tcp.local (PTR/SRV/TXT),
* 供主机 avahi-resolve <host>.local 解析到设备 IP。
*
* 为什么不用 mDNSResponder/SAResponder: 两者均需 IPv6(socket AF_INET6 在内核无 IPv6 时 fatal),
* 且完整版 mdnsd 是 IPC 服务型守护进程,自身不公布主机名 A 记录;SAResponder 只注册单一服务。
* 本实现为自研,零 IPv6 依赖,线程/信号安全,优雅退。
*
* 用法: meow_mdns [-v] # 前台运行; -v 打印收到的查询/源地址
* 环境: MEOW_MDNS_HOST 缺省 = gethostname() 第一标签 (设备为"ingenic")
* 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
*
* 协议要点(RFC 6762):
* - join 224.0.0.251:5353 (IP_ADD_MEMBERSHIP), SO_REUSEADDR(+SO_REUSEPORT)
* - 应答所有收到的查询(单播/组播), 回发到查询源地址(单播), 规避组播回程丢失
* - 应答头 flags = 0x8400 (QR=1, AA=1), 回显查询的 Question 段
* - 记录均带 TTL 120; 唯一记录(A/SRV/TXT)置 cache-flush 位 0x8000, PTR 不置
* - QNAME 大小写不敏感; 应答中名字不压缩(合法, 由 avahi 解析)
* - 忽略 QR=1 包(避免自响应), 忽略非查询包
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#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"
#define TTL_DEFAULT 120
#define QCLASS_IN 1
#define QFLUSH 0x8000
#define TYPE_A 1
#define TYPE_PTR 12
#define TYPE_TXT 16
#define TYPE_SRV 33
#define TYPE_ANY 255
#define MAX_Q 16 /* 每包最多解析的 question 数 */
#define MAX_NAME 256
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 失败 */
static int encode_name(uint8_t *buf, size_t buflen, const char *name)
{
size_t pos = 0;
const char *p = name;
if (!name || !*name) return -1;
while (*p) {
const char *dot = strchr(p, '.');
size_t len = dot ? (size_t)(dot - p) : strlen(p);
if (len == 0 || len > 63) return -1;
if (pos + 1 + len >= buflen) return -1;
buf[pos++] = (uint8_t)len;
memcpy(buf + pos, p, len); pos += len;
if (!dot) break;
p = dot + 1;
}
if (pos + 1 >= buflen) return -1;
buf[pos++] = 0;
return (int)pos;
}
/* 解析 DNS 名字(支持 0xC0 压缩指针)。返回解码长度; 把 *off 移到名字结束处 */
static int parse_name(const uint8_t *msg, size_t msglen, size_t *off,
char *out, size_t outlen)
{
size_t pos = *off, olen = 0, jumps = 0;
int jumped = 0;
while (1) {
if (pos >= msglen) return -1;
uint8_t c = msg[pos];
if (c == 0) { pos++; break; }
if ((c & 0xC0) == 0xC0) {
if (pos + 1 >= msglen) return -1;
size_t ptr = ((c & 0x3F) << 8) | msg[pos + 1];
pos += 2;
if (!jumped) *off = pos;
jumped = 1;
if (++jumps > 64) return -1;
pos = ptr;
continue;
}
if ((c & 0xC0) != 0) return -1; /* 非法标签 */
pos++;
if (olen > 0) { if (olen + 1 >= outlen) return -1; out[olen++] = '.'; }
if (pos + c > msglen) return -1;
if (olen + c >= outlen) return -1;
memcpy(out + olen, msg + pos, c); olen += c; pos += c;
}
if (!jumped) *off = pos;
out[olen] = 0;
return (int)olen;
}
/* 小写入工具 */
static size_t w16(uint8_t *b, size_t o, uint16_t v){ b[o]=v>>8; b[o+1]=v&0xFF; return o+2; }
static size_t w32(uint8_t *b, size_t o, uint32_t v){ b[o]=v>>24; b[o+1]=v>>16; b[o+2]=v>>8; b[o+3]=v&0xFF; return o+4; }
struct question {
char name[MAX_NAME]; /* 小写 QNAME */
uint16_t qtype;
uint16_t qclass;
size_t raw_off; /* question 原始字节在消息中的起点 */
size_t raw_len; /* question 原始字节长度(用于回显) */
};
/* 编码一个 DNS 名字到 resp[*ro..];若能在 resp[0..*ro) 中找到完全相同的名字序列,
* 则改写为压缩指针(RFC 1035 §4.1.4)。返回 0 成功, -1 失败。 */
static int put_name(uint8_t *resp, size_t *ro, size_t cap, const char *name)
{
uint8_t tmp[256];
int tl = encode_name(tmp, sizeof(tmp), name);
if (tl < 0) return -1;
for (size_t i = 0; i + (size_t)tl <= *ro && i < 0x4000; i++) {
if (resp[i] == tmp[0] && memcmp(resp + i, tmp, (size_t)tl) == 0) {
if (*ro + 2 > cap) return -1;
resp[(*ro)++] = (uint8_t)(0xC0 | (i >> 8));
resp[(*ro)++] = (uint8_t)(i & 0xFF);
return 0;
}
}
if (*ro + (size_t)tl > cap) return -1;
memcpy(resp + *ro, tmp, (size_t)tl); *ro += (size_t)tl;
return 0;
}
/* 追加一条 answer 记录。owner=记录名(自带大小写, 小写即可), type, unique,
* rdata 指针+长度。返回 0 成功 */
static int put_answer(uint8_t *resp, size_t *ro, size_t cap,
const char *owner, uint16_t type, int unique,
const uint8_t *rdata, uint16_t rdlen)
{
int nl = put_name(resp, ro, cap, owner);
if (nl < 0 || (size_t)(*ro + 2 + 2 + 4 + 2 + rdlen) > cap) return -1;
*ro = w16(resp, *ro, type);
*ro = w16(resp, *ro, (uint16_t)(QCLASS_IN | ((unique && g_cacheflush) ? QFLUSH : 0)));
*ro = w32(resp, *ro, TTL_DEFAULT);
size_t rdlen_pos = *ro; *ro += 2; /* 占位 */
if (rdlen) { memcpy(resp + *ro, rdata, rdlen); *ro += rdlen; }
/* 回填 rdlen(注意:*ro 已越过 rdata,不能用 w16 的返回值覆盖 *ro) */
resp[rdlen_pos] = (uint8_t)(rdlen >> 8);
resp[rdlen_pos + 1] = (uint8_t)(rdlen & 0xFF);
return 0;
}
int main(int argc, char **argv)
{
int verbose = 0;
for (int i = 1; i < argc; i++)
if (!strcmp(argv[i], "-v")) verbose = 1;
uint16_t port = MDNS_PORT;
const char *ep = getenv("MEOW_MDNS_PORT");
if (ep && *ep) port = (uint16_t)strtoul(ep, NULL, 10);
if (port == 0) port = MDNS_PORT;
int qd_echo = 1; /* 是否回显问题段 (QDCOUNT) */
const char *eq = getenv("MEOW_MDNS_QD");
if (eq && *eq) qd_echo = atoi(eq);
g_cacheflush = 0;/* 唯一记录(A/SRV/TXT)是否置 cache-flush 位 */
const char *ecf = getenv("MEOW_MDNS_CF");
if (ecf && *ecf) g_cacheflush = atoi(ecf);
int reply_mcast = 1;/* 组播查询是否同时回一条组播应答; 0=只单播回源 */
const char *emc = getenv("MEOW_MDNS_MCAST");
if (emc && *emc) reply_mcast = atoi(emc);
int set_mcastif = 0;/* 是否显式设置 IP_MULTICAST_IF (0=走内核默认, 同 zeroconf, 更稳) */
const char *emi = getenv("MEOW_MDNS_MIF");
if (emi && *emi) set_mcastif = atoi(emi);
/* ---- 确定主机名 ---- */
char host[MAX_NAME];
const char *eh = getenv("MEOW_MDNS_HOST");
if (eh && *eh) { snprintf(host, sizeof(host), "%s", eh); }
else {
if (gethostname(host, sizeof(host) - 1) != 0 || !host[0])
snprintf(host, sizeof(host), "meowos");
host[sizeof(host) - 1] = 0;
char *d = strchr(host, '.'); if (d) *d = 0; /* 只取第一标签 */
}
if (!*host) snprintf(host, sizeof(host), "meowos");
to_lower(host);
/* ---- 确定 IP ---- */
char hostA[MAX_NAME]; /* "host.local" */
snprintf(hostA, sizeof(hostA), "%s.local", host);
char inst[MAX_NAME]; /* "host._ssh._tcp.local" */
snprintf(inst, sizeof(inst), "%s._ssh._tcp.local", host);
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};
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);
fflush(stdout);
/* ---- 打开 UDP 套接字 ---- */
int s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0) { perror("socket"); return 2; }
int one = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); /* 可能失败, 忽略 */
/* 接收时携带目的地址与接口 (用于判断查询是组播还是单播, 再决定应答方向) */
setsockopt(s, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
struct sockaddr_in bindaddr;
memset(&bindaddr, 0, sizeof(bindaddr));
bindaddr.sin_family = AF_INET;
bindaddr.sin_port = htons(port);
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) {
perror("bind"); return 2;
}
/* ---- 加入组播组 ---- */
struct ip_mreq mreq;
memset(&mreq, 0, sizeof(mreq));
inet_pton(AF_INET, MDNS_GROUP, &mreq.imr_multiaddr);
mreq.imr_interface.s_addr = ipin.s_addr; /* 绑定到设备 IP */
if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
perror("IP_ADD_MEMBERSHIP");
/* 不视为致命: 仍可应答单播查询 */
}
unsigned char ttl = 255;
const char *ettl = getenv("MEOW_MDNS_TTL");
if (ettl && *ettl) ttl = (unsigned char)atoi(ettl);
setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
/* IP_MULTICAST_LOOP 保持默认(1): 允许本机其它 socket(如 avahi)收到组播应答;
* 自身收到的应答会是 QR=1, 已被下方 flags&0x8000 过滤, 不会自响应。 */
/* 让 recv 有超时以便检查 g_running */
struct timeval tv = { 1, 0 };
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
struct in_addr mcast_addr;
inet_pton(AF_INET, MDNS_GROUP, &mcast_addr);
/* 找出持有设备 IP 的接口索引 (组播应答固定从该接口发出, 避免 rcv_ifindex 落到 lo/错接口) */
int dev_ifindex = 0;
{
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 &&
((struct sockaddr_in *)i->ifa_addr)->sin_addr.s_addr == ipin.s_addr) {
dev_ifindex = (int)if_nametoindex(i->ifa_name);
if (dev_ifindex) break;
}
}
freeifaddrs(ifa);
}
}
printf("meow_mdns: listening on %s:%d (multicast %s)\n", MDNS_GROUP, port, MDNS_GROUP);
if (verbose)
printf("meow_mdns: ip=%s dev_ifindex=%d\n", ips, dev_ifindex);
fflush(stdout);
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];
struct iovec iov;
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &src;
msg.msg_namelen = sizeof(src);
iov.iov_base = pkt;
iov.iov_len = sizeof(pkt);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = ctrl;
msg.msg_controllen = sizeof(ctrl);
ssize_t n = recvmsg(s, &msg, 0);
if (n < 0) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
if (!g_running) break;
perror("recvmsg"); continue;
}
if (n < 12) continue;
uint32_t destip = mcast_addr.s_addr; /* 取不到就按组播处理 */
/* 手工迭代 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;
size_t ml = (size_t)n;
uint16_t flags = (uint16_t)((m[2] << 8) | m[3]);
if (flags & 0x8000) continue; /* 是应答/响应, 忽略 */
uint16_t qd = (uint16_t)((m[4] << 8) | m[5]);
if (qd == 0 || qd > MAX_Q) continue;
struct question qs[MAX_Q];
size_t off = 12;
int nq = 0;
for (int qi = 0; qi < qd; qi++) {
size_t qstart = off;
if (parse_name(m, ml, &off, qs[nq].name, sizeof(qs[nq].name)) < 0)
{ nq = -1; break; }
if (off + 4 > ml) { nq = -1; break; }
qs[nq].qtype = (uint16_t)((m[off] << 8) | m[off + 1]);
qs[nq].qclass = (uint16_t)((m[off + 2] << 8) | m[off + 3]);
off += 4;
qs[nq].raw_off = qstart;
qs[nq].raw_len = off - qstart;
to_lower(qs[nq].name);
nq++;
}
if (nq < 0) continue;
if (verbose) {
char srcip[64];
inet_ntop(AF_INET, &src.sin_addr, srcip, sizeof(srcip));
printf("Q from %s:%d, %d question(s)\n", srcip, ntohs(src.sin_port), nq);
for (int qi = 0; qi < nq; qi++)
printf(" q[%d] %s type=%u class=%u\n",
qi, qs[qi].name, qs[qi].qtype, qs[qi].qclass);
fflush(stdout);
}
if (!ipin.s_addr) continue; /* 接口未就绪: 无有效公布地址, 暂不应答 */
/* ---- 构建应答 ----
* RFC 6762: 应答 QDCOUNT 通常为 0, 但部分实现(avahi)接受、部分要求回显问题。
* MEOW_MDNS_QD 控制是否回显; MEOW_MDNS_CF 控制唯一记录 cache-flush 位。 */
uint8_t resp[2048];
size_t ro = 0, ancount = 0;
size_t head_ro = ro; ro += 12; /* 预占 header */
uint16_t qdcount = 0;
if (qd_echo) {
for (int qi = 0; qi < nq; qi++) {
if (ro + qs[qi].raw_len > sizeof(resp)) { ro = 0; ancount = 0; break; }
memcpy(resp + ro, m + qs[qi].raw_off, qs[qi].raw_len);
ro += qs[qi].raw_len;
}
qdcount = (uint16_t)nq;
}
/* 依据每个 question 的 (qname, qtype) 追加相应记录 */
for (int qi = 0; qi < nq && ro; qi++) {
const char *qn = qs[qi].name;
uint16_t qt = qs[qi].qtype;
int wantA = (qt == TYPE_A || qt == TYPE_ANY);
int wantPTR = (qt == TYPE_PTR || qt == TYPE_ANY);
int wantSRV = (qt == TYPE_SRV || qt == TYPE_ANY);
int wantTXT = (qt == TYPE_TXT || qt == TYPE_ANY);
/* host.local -> A */
if (wantA && !strcmp(qn, hostA)) {
uint8_t rd[4];
memcpy(rd, &ipin.s_addr, 4); /* 网络序, 直接作为 A rdata */
if (put_answer(resp, &ro, sizeof(resp), hostA, TYPE_A, 1, rd, 4) == 0) ancount++;
}
/* _ssh._tcp.local -> PTR 实例 */
if (wantPTR && !strcmp(qn, svc)) {
uint8_t rd[256];
int rl = encode_name(rd, sizeof(rd), inst);
if (rl > 0 && put_answer(resp, &ro, sizeof(resp), svc, TYPE_PTR, 0, rd, (uint16_t)rl) == 0) ancount++;
}
/* host._ssh._tcp.local -> SRV + TXT */
if ((wantSRV || wantTXT) && !strcmp(qn, inst)) {
if (wantSRV) {
uint8_t rd[256];
size_t rp = 0;
rp = w16(rd, rp, 0); rp = w16(rd, rp, 0); /* priority=weight=0 */
rp = w16(rd, rp, 22); /* SSH port */
int hl = encode_name(rd + rp, sizeof(rd) - rp, hostA);
if (hl > 0 && put_answer(resp, &ro, sizeof(resp), inst, TYPE_SRV, 1, rd, (uint16_t)(rp + hl)) == 0) ancount++;
}
if (wantTXT) {
uint8_t rd[2] = { 0x00 }; /* 空 TXT */
if (put_answer(resp, &ro, sizeof(resp), inst, TYPE_TXT, 1, rd, 1) == 0) ancount++;
}
}
/* _services._dns-sd._udp.local -> PTR _ssh._tcp.local (便于 avahi-browse) */
if (wantPTR && !strcmp(qn, "_services._dns-sd._udp.local")) {
uint8_t rd[256];
int rl = encode_name(rd, sizeof(rd), svc);
if (rl > 0 && put_answer(resp, &ro, sizeof(resp), "_services._dns-sd._udp.local", TYPE_PTR, 0, rd, (uint16_t)rl) == 0) ancount++;
}
}
if (ro == 0) continue; /* 构建失败或没有记录, 放弃 */
if (ancount == 0) continue; /* 没有我们需要应答的记录 */
/* 组装 header */
memset(resp + head_ro, 0, 12);
resp[head_ro] = 0x00; resp[head_ro+1] = 0x00; /* ID */
resp[head_ro+2] = 0x84; resp[head_ro+3] = 0x00; /* QR+AA, no error */
resp[head_ro+4] = (uint8_t)(qdcount >> 8); resp[head_ro+5] = (uint8_t)qdcount;
resp[head_ro+6] = (uint8_t)(ancount >> 8); resp[head_ro+7] = (uint8_t)ancount;
resp[head_ro+8] = 0; resp[head_ro+9] = 0; /* ANCOUNT of NS */
resp[head_ro+10] = 0; resp[head_ro+11] = 0; /* ARCOUNT */
/* 选择应答方向:
* 查询到达组播组 -> 应答走组播(RFC 6762: 组播查询期待组播应答, avahi 才会接受),
* 并冗余单播一份给查询源(组播被热点丢弃时兜底)。
* 查询到达单播 -> 直接单播回源。 */
int to_mcast = (destip == mcast_addr.s_addr);
struct sockaddr_in dst;
memset(&dst, 0, sizeof(dst));
dst.sin_family = AF_INET;
if (to_mcast && reply_mcast) {
/* 组播应答固定从持有设备 IP 的接口发出 */
unsigned char mloop = 1;
/* IP_MULTICAST_IF 用设备接口的 IPv4 地址(网络序), 而非接口索引 */
if (set_mcastif && ipin.s_addr)
setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &ipin.s_addr, sizeof(ipin.s_addr));
setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &mloop, sizeof(mloop));
dst.sin_port = htons(MDNS_PORT);
dst.sin_addr = mcast_addr;
ssize_t sn = sendto(s, resp, ro, 0, (struct sockaddr *)&dst, sizeof(dst));
if (verbose)
printf(" -> mcast sendto ret=%zd errno=%s\n", sn, errno ? strerror(errno) : "ok");
}
/* 单播回源 (始终发送; 也作为组播应答的兜底/冗余) */
dst.sin_port = src.sin_port;
dst.sin_addr = src.sin_addr;
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)" : "",
srcip, ntohs(src.sin_port), ancount);
fflush(stdout);
}
}
/* 退出: 离开组播组 */
if (setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == 0)
close(s);
printf("meow_mdns: exiting\n");
return 0;
}