全屏滑条统一路径+对比色(112/115) + 字体索引+查列表+丢失降级(113) + 文档刷新(114) + svcd CLI文档+mips-repo拆bins/mpk(116)
This commit is contained in:
1 parent
b75ecce5de
commit
19a001f007
22 files changed
+666
-129
No files matched your search
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build.sh — 交叉编译 Apple mDNSResponder 的 POSIX 精简变体 SAResponder(mipsel-linux-muslsf,静态)
|
||||
#
|
||||
# 目标架构 : mipsel (MIPS32r2 little-endian), musl, 静态链接, 无 glibc 依赖
|
||||
# 交叉编译器: zig (目标 mips-linux-musl / mipsel-linux-musleabi),由 ~/Project/R3ProII/.tools/zig-mipsel-cc 包装
|
||||
# 产物 : bin/mdnsd = SAResponder (mDNSResponderPosix) —— 本脚本构建,无 UDS/IPC 的精简响应器
|
||||
# bin/mdnsd.bin = 完整 mdnsd 守护进程(上一轮产物,作为对照保留,需重编译见文件尾注释)
|
||||
# 用途 : MeowOS (R3 Pro II) 板载 mDNS 响应器,回应 .local 查询
|
||||
#
|
||||
# 用法 : ./build.sh # 默认构建 SAResponder → bin/mdnsd
|
||||
#
|
||||
# 可复现性 : 源码按固定 commit SHA 下载(codeload sha 方式),产物大小/校验值稳定。
|
||||
set -euo pipefail
|
||||
|
||||
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BIN_DIR="$REPO_DIR/bin"
|
||||
WORK="$REPO_DIR/.build"
|
||||
|
||||
# ---- 工具链(本机实际路径;zig-mipsel-cc 内部也用 $HOME/Project/R3ProII/.tools)----
|
||||
ZIGCC="${ZIGCC:-$HOME/Project/R3ProII/.tools/zig-mipsel-cc}"
|
||||
|
||||
# ---- 固定源码版本:apple-oss-distributions/mDNSResponder 2881.0.25 ----
|
||||
MDNS_SHA="${MDNS_SHA:-d4658af3f5f291311c6aee4210aa6d39bda82bbe}"
|
||||
MDNS_VER="${MDNS_VER:-2881.0.25}"
|
||||
TARBALL_URL="https://codeload.github.com/apple-oss-distributions/mDNSResponder/tar.gz/${MDNS_SHA}"
|
||||
TARBALL="$WORK/mDNSResponder-${MDNS_VER}.tar.gz"
|
||||
SRCDIR="$WORK/src"
|
||||
|
||||
mkdir -p "$BIN_DIR" "$WORK"
|
||||
|
||||
echo "==> [1/4] 获取源码 mDNSResponder-$MDNS_VER (commit ${MDNS_SHA:0:12})"
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
curl -fsSL --retry 4 --retry-all-errors -o "$TARBALL" "$TARBALL_URL"
|
||||
fi
|
||||
gzip -t "$TARBALL" >/dev/null 2>&1 || { echo "错误: 源码包校验失败"; exit 1; }
|
||||
rm -rf "$SRCDIR"
|
||||
mkdir -p "$SRCDIR"
|
||||
tar xzf "$TARBALL" -C "$SRCDIR" --strip-components=1
|
||||
|
||||
echo "==> [2/4] 交叉编译 SAResponder (zig: mipsel-linux-musleabi, -Os -static, tls=no)"
|
||||
cd "$SRCDIR/mDNSPosix"
|
||||
# 说明:
|
||||
# - os=linux : 选定 Linux 平台分支 (CFLAGS_OS)
|
||||
# - tls=no : 关闭 TLS 支持,避免链接 mbedtls (嵌入式设备不需要)
|
||||
# - CC/LD=$ZIGCC : 强制 zig 交叉编译器,不用宿主 gcc
|
||||
# - CFLAGS="-Os -static" : -Os 体积优化 + 静态链接;-Wno-date-time 关掉 zig/clang
|
||||
# 默认的 -Werror -Wdate-time (源码用了 __DATE__/__TIME__ 宏)
|
||||
# - LINKOPTS="-s" : 链接时直接 strip 符号表 + 调试信息,产出部署版。
|
||||
# 注意 SAResponder 的链接规则里没有 $(STRIP) $@ 这一行,
|
||||
# 唯一的 strip 来源就是这个 -s(勿依赖 STRIP=true)
|
||||
# - SAResponder 目标 : 无 UDS/IPC 的独立响应器,产物 build/prod/mDNSResponderPosix
|
||||
make os=linux tls=no \
|
||||
CC="$ZIGCC" CFLAGS="-Os -static -Wno-date-time" \
|
||||
LD="$ZIGCC" LINKOPTS="-s" STRIP=true SAResponder
|
||||
|
||||
echo "==> [3/4] 放置产物 $BIN_DIR/mdnsd (SAResponder)"
|
||||
install -m 755 build/prod/mDNSResponderPosix "$BIN_DIR/mdnsd"
|
||||
|
||||
echo "==> [4/4] 校验"
|
||||
file "$BIN_DIR/mdnsd"
|
||||
echo "--- 体积 (静态部署版, strip 后) ---"
|
||||
ls -l "$BIN_DIR/mdnsd"
|
||||
echo "字节数: $(stat -c %s "$BIN_DIR/mdnsd")"
|
||||
echo "MD5 : $(md5sum "$BIN_DIR/mdnsd" | cut -d' ' -f1)"
|
||||
echo "构建完成: $BIN_DIR/mdnsd"
|
||||
echo
|
||||
echo "提示: bin/mdnsd.bin 为上一轮的完整 mdnsd 守护进程(1,018,144 B, md5 fcb0a8ae5ad5696253a6ec56b3e585e6),"
|
||||
echo " 仅作对照保留。如需重编译完整 mdnsd 放到 mdnsd.bin,改目标为 Daemon 即可:"
|
||||
echo " make os=linux tls=no CC=\"\$ZIGCC\" CFLAGS=\"-Os -static -Wno-date-time\" LD=\"\$ZIGCC\" LINKOPTS=\"-s\" Daemon"
|
||||
echo " install -m 755 build/prod/mdnsd \"$BIN_DIR/mdnsd.bin\""
|
||||
@@ -0,0 +1,76 @@
|
||||
# mdnsd — Apple mDNSResponder 的 POSIX 精简变体 SAResponder(mipsel-linux-muslsf 静态)
|
||||
|
||||
> 本目录现以 **SAResponder(mDNSResponderPosix,无 UDS/IPC 的精简响应器)** 为部署目标。
|
||||
> 其目标名对应上游 `mDNSPosix/Makefile` 的 `SAResponder`,产物 `build/prod/mDNSResponderPosix`。
|
||||
> 完整 mdnsd 守护进程(上一轮产物)保留为 `bin/mdnsd.bin`(见下"与 mdnsd 对比")。
|
||||
|
||||
## 来源
|
||||
- 项目 : apple-oss-distributions/mDNSResponder
|
||||
- 版本 : mDNSResponder-2881.0.25
|
||||
- commit : d4658af3f5f291311c6aee4210aa6d39bda82bbe
|
||||
- 许可 : Apache-2.0
|
||||
- 获取 : https://codeload.github.com/apple-oss-distributions/mDNSResponder/tar.gz/<SHA>
|
||||
- 源码 : mDNSPosix/(Responder.c,独立响应器 main;非 PosixDaemon.c),tls=no(关闭 mbedtls)
|
||||
|
||||
## ARCH / 运行库
|
||||
- ARCH : mipsel(MIPS32r2 little-endian)
|
||||
- 编译目标 : mipsel-linux-musleabi(soft-float,对应 MeowOS 的 muslsf)
|
||||
- 运行库 : musl,静态链接(-static),无 glibc 依赖
|
||||
|
||||
## file 结果(部署版 = bin/mdnsd)
|
||||
ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, stripped
|
||||
- readelf 复核:无 `.debug_*` / `.symtab` / `.strtab`(真正 strip;仅剩 MIPS 必有的 .mdebug.abi32 / .pdr ABI 元数据段)。
|
||||
|
||||
## 体积(关键)
|
||||
- 未 strip(含 -g 调试信息,make 标准链接 `LINKOPTS=""`) : 3,197,588 字节 ≈ 3122.6 KB ≈ 3.05 MB
|
||||
(file 判:`with debug_info, not stripped`;md5 33f8fcef0a4df5e907f75b6913d2fe23)
|
||||
- **strip 后部署版(正式部署体积,`LINKOPTS=-s`)** : **906,864 字节 ≈ 885.6 KB ≈ 0.865 MB ← 以此为准**
|
||||
- md5 (strip 后) : **3361b1a062d39372a30eff0a5a1f1725**
|
||||
|
||||
## 构建命令摘要
|
||||
见同目录 build.sh。要点(目标由上一轮的 `Daemon` 改为 `SAResponder`):
|
||||
make os=linux tls=no \
|
||||
CC="$HOME/Project/R3ProII/.tools/zig-mipsel-cc" \
|
||||
CFLAGS="-Os -static -Wno-date-time" \
|
||||
LD="$HOME/Project/R3ProII/.tools/zig-mipsel-cc" \
|
||||
LINKOPTS="-s" STRIP=true SAResponder
|
||||
- 目标 `SAResponder`:构造对象 = SPECIALOBJ(13 个) + mDNS.c.o + Responder.c.o,**不含** UDS/IPC 相关对象
|
||||
(去掉了 PosixDaemon.c.o、uds_daemon.c.o、dnssd_ipc.c.o、ClientRequests.c.o;以 Responder.c.o 作 main)。
|
||||
- os=linux : 平台分支
|
||||
- tls=no : 关 mbedtls(嵌入式不需要)
|
||||
- CFLAGS -Os -static: 体积优化 + 静态链接
|
||||
- -Wno-date-time : 关掉 zig/clang 默认的 -Werror -Wdate-time(源码用了 __DATE__/__TIME__)
|
||||
- LINKOPTS=-s : 链接时直接 strip,产出部署版。
|
||||
注意 `mDNSResponderPosix` 的链接规则里**没有** `$(STRIP) $@` 这一行,唯一 strip 来源就是这个 `-s`(勿依赖 STRIP=true)。
|
||||
- 一次跑通,仅 Responder.c 报告 6 条无害 warning(unused parameter / set-but-not-used,非错误)。
|
||||
|
||||
## 功能差异(关键,勿当 mdnsd 的等价替代)
|
||||
- SAResponder 是**单服务注册型**独立响应器:必须用 `-n <服务名>` 或 `-f <服务文件>` 指定要发布/回应的服务,
|
||||
默认服务类型 `_afpovertcp._tcp.`、端口 548。它注册**一个**服务并回应针对该服务的 .local 查询。
|
||||
- mdnsd(bin/mdnsd.bin)是**通用宿主响应器**:读网络接口、监听组播 224.0.0.251:5353,回应宿主下任意 .local 查询。
|
||||
- 用途选择:若只需"板载发布单服务"→ 用 SAResponder;若需"通用 .local 解析"→ 用 mdnsd(bin/mdnsd.bin)。
|
||||
|
||||
## 与 mdnsd 对比(994 KB → 885.6 KB)
|
||||
| 变体 | strip 后 | 未 strip | file |
|
||||
|---|---|---|---|
|
||||
| SAResponder(bin/mdnsd) | **906,864 B = 885.6 KB**(md5 3361b1a0…) | 3,197,588 B = 3122.6 KB | stripped, static |
|
||||
| 完整 mdnsd(bin/mdnsd.bin) | 1,018,144 B = 994 KB(md5 fcb0a8ae…) | 3,549,496 B = 3466 KB | stripped, static |
|
||||
- 差值仅 **~109 KB(约 11%)**。去掉 UDS/IPC 并未显著变小,因为体积大头在 mDNS 核心
|
||||
(mDNS.c、DNSCommon.c、uDNS.c、dso 系),而非 IPC 层。
|
||||
|
||||
## 部署建议(结论:TF,不进固件)
|
||||
- **885.6 KB 远超 <200KB 阈值 → 放 TF 卡 `MeowOpt/mdnsd/mdnsd`**(若只需单服务),由 MeowOS 启动时从卡加载(拔卡降级不崩)。
|
||||
- 若需通用 .local 回应,部署 `mdnsd.bin`(原完整守护进程,同样放 TF)。
|
||||
- 启动/参数(SAResponder):`mdnsd -b -n <name> -t _xxxx._tcp. -p <port>`;`-b` 后台,`-P` pidfile,
|
||||
`-v 0..2` 调试,`-r` 同时绑 53,`-f <file>` 读服务清单。
|
||||
- 权限:需 root(或 setcap cap_net_bind_service,cap_net_raw)以绑定 5353/53 并加组播。
|
||||
- 验证:`qemu-mipsel bin/mdnsd -h` → 打印 Usage 后以 exit 1 退出(预期用法退出,程序可运行)。
|
||||
|
||||
## bin/ 内容
|
||||
- `bin/mdnsd` = SAResponder(mDNSResponderPosix)strip 后部署版(906,864 B)
|
||||
- `bin/mdnsd.bin` = 完整 mdnsd 守护进程 strip 后(1,018,144 B),对照保留
|
||||
|
||||
## 备选(评估)
|
||||
- 若 885 KB 仍偏重:改用 OpenWrt linkd / libmdns 或自研极简响应器(仅响应 .local 查询)。
|
||||
理由:纯 mDNS 查询响应是轻需求,完整 Bonjour 栈(DNSCommon/uDNS/dso/GenLinkedList 等核心)即占 ~880KB,
|
||||
在无动态链接的静态 musl 下已很难再压到 <200KB。
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build.sh — 自研极简 IPv4-only mDNS 应答器 meow_mdns(mipsel-linux-muslsf,静态)
|
||||
#
|
||||
# 背景: 完整 mDNSResponder / SAResponder 两者均需 IPv6(socket AF_INET6 在内核无 IPv6 时 fatal),
|
||||
# 且完整版 mdnsd 是 IPC 服务型守护进程, 自身不公布主机名 A 记录。
|
||||
# 故自研本应答器: join 224.0.0.251:5353, 应答 <host>.local A + _ssh._tcp.local PTR/SRV/TXT。
|
||||
#
|
||||
# 目标架构 : mipsel (MIPS32r2 little-endian), musl, 静态
|
||||
# 交叉编译器: ~/Project/R3ProII/.tools/zig-mipsel-cc
|
||||
# 产物 : bin/meow_mdns
|
||||
#
|
||||
# 用法 : ./build.sh # 构建 mipsel 静态(部署版)
|
||||
# 宿主单测 gcc -O2 -o meow_mdns meow_mdns.c && MEOW_MDNS_PORT=55353 MEOW_MDNS_HOST=ingenic MEOW_MDNS_ADDR=127.0.0.1 ./meow_mdns -v
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ZIGCC="${ZIGCC:-$HOME/Project/R3ProII/.tools/zig-mipsel-cc}"
|
||||
|
||||
echo "==> [1/2] 交叉编译 meow_mdns (zig: mipsel-linux-musleabi, -Os -static)"
|
||||
"$ZIGCC" -Os -static -o "$DIR/meow_mdns.tmp" "$DIR/meow_mdns.c"
|
||||
|
||||
echo "==> [2/2] 放置产物 $DIR/bin/meow_mdns"
|
||||
mkdir -p "$DIR/bin"
|
||||
install -m 755 "$DIR/meow_mdns.tmp" "$DIR/bin/meow_mdns"
|
||||
rm -f "$DIR/meow_mdns.tmp"
|
||||
|
||||
file "$DIR/bin/meow_mdns"
|
||||
echo "字节数: $(stat -c %s "$DIR/bin/meow_mdns")"
|
||||
echo "MD5 : $(md5sum "$DIR/bin/meow_mdns" | cut -d' ' -f1)"
|
||||
echo "构建完成: $DIR/bin/meow_mdns"
|
||||
@@ -0,0 +1,69 @@
|
||||
# meow_mdns — 自研极简 IPv4-only mDNS 应答器(mipsel-linux-muslsf 静态)
|
||||
|
||||
> 目的:让 R3 Pro II / MeowOS 设备在**纯 IPv4**(内核无 IPv6)下应答 `*.local`,
|
||||
> 使主机 `avahi-resolve <host>.local` / `avahi-browse` 能识别设备。
|
||||
> 产出为自研 C 代码,零 IPv6 依赖,覆盖路径 A(mDNSResponder HAVE_IPV6=0)无法满足的功能缺口。
|
||||
|
||||
## 为什么不用 mDNSResponder / SAResponder
|
||||
| 原因 | 说明 |
|
||||
|---|---|
|
||||
| 需 IPv6 | 两者都在 `socket(AF_INET6)` 处失败:`Address family not supported by protocol`。mDNSResponder 无 `NOT_HAVE_IPV6` 宏(0 处),IPv6 是运行时硬编码;即便 `make HAVE_IPV6=0` 编译通过(见下“路径 A 试验”),仍不解决下一条 |
|
||||
| 不公布主机名 A 记录 | 完整版 `mdnsd` 是 **IPC 服务型守护进程**:它等客户端经 UDS(`/var/run/mdnsd`) 注册名字,自身**不自动登记主机名**。`-debug` 日志证实它只注册接口 wlan0 + `_dns-sd` 浏览记录,从无 `ingenic.local A`。应答记录需客户端调用 `DNSServiceRegister`,没有现成客户端 |
|
||||
| SAResponder 单服务 | 只用 `mDNS_RegisterService` 注册**单一服务**,不注册主机 A 记录,`ingenic.local` 无法按名解析 |
|
||||
|
||||
## 路径 A 试验结论(编译层面 MVP,但功能不达标)
|
||||
- 源码无 `NOT_HAVE_IPV6`;开关是 Makefile Linux 分支的 `-DHAVE_IPV6`(第 113 行)+ 第 196-200 行 `HAVE_IPV6=0/1` 可覆盖。
|
||||
- `make os=linux tls=no HAVE_IPV6=0 CC=zig-mipsel-cc CFLAGS="-Os -static -Wno-date-time" LD=... LINKOPTS="-s" Daemon` **能编译出 1015792B 的 mdnsd**(md5 `7ee68d626d718d0572d4087c5a584d7f`),
|
||||
`SetupSocket`/接口枚举/unicast v6 socket 均被 `#if HAVE_IPV6` 移除;仅硬件地址那段 `socket(AF_INET6)` 被 `if(sockfd>=0)` 守护(无 IPv6 内核返回 -1,无害)。
|
||||
- 该 mdnsd 在设备上**可运行**(绑 0.0.0.0:5353 + 加入 224.0.0.251),但**不发布主机名**(见上),故不满足 `ingenic.local` 解析。→ 转路径 B。
|
||||
|
||||
## 功能
|
||||
- join IPv4 组播 `224.0.0.251:5353`(`IP_ADD_MEMBERSHIP`/`SO_REUSEADDR`/`SO_REUSEPORT`)。
|
||||
- 应答(QNAME 大小写不敏感):
|
||||
- `<host>.local` **A** → 设备 wlan0 IPv4。
|
||||
- `_ssh._tcp.local` **PTR** → `<host>._ssh._tcp.local`。
|
||||
- `<host>._ssh._tcp.local` **SRV**(port 22,target `<host>.local`)+ **TXT**(空)。
|
||||
- `_services._dns-sd._udp.local` **PTR** → `_ssh._tcp.local`(便于 avahi-browse)。
|
||||
- 支持 QTYPE=ANY(255)。
|
||||
- RFC 1035 名压缩(owner 名若已在响应中出现则回写 `0xC0` 指针)。
|
||||
- IP_PKTINFO 判断查询到组播还是单播:**组播查询→组播应答 + 单播冗余回源**;单播查询→单播回源。
|
||||
- 线程/信号安全;SIGINT/SIGTERM 优雅退出(留组并关 socket)。
|
||||
|
||||
## 环境覆写
|
||||
| 变量 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `MEOW_MDNS_HOST` | `gethostname()` 首标签 | 公布的主机名(设备上=内核 hostname `meowos`,`/etc/hostname` 残留 `ingenic`) |
|
||||
| `MEOW_MDNS_ADDR` | 首块非回环 UP 接口 IPv4 | 应答的 A 记录 IP(设备=wlan0 10.109.49.50) |
|
||||
| `MEOW_MDNS_PORT` | 5353 | 监听端口(通常仅宿主单测用) |
|
||||
| `MEOW_MDNS_TTL` | 255 | IP_MULTICAST_TTL |
|
||||
| `MEOW_MDNS_CF` | 0 | 唯一记录(A/SRV/TXT)是否置 cache-flush 位 |
|
||||
| `MEOW_MDNS_QD` | 1 | 应答是否回显问题段(QDCOUNT) |
|
||||
| `MEOW_MDNS_MCAST`| 1 | 组播查询是否同时回组播应答(0=只单播回源) |
|
||||
| `MEOW_MDNS_MIF` | 0 | 是否显式设 IP_MULTICAST_IF 为设备接口 IP |
|
||||
| `MEOW_MDNS_DBG` | - | (调试用) |
|
||||
|
||||
## ARCH / 运行库
|
||||
- ARCH: mipsel (MIPS32r2 little-endian)
|
||||
- 目标: mipsel-linux-musleabi (soft-float)
|
||||
- 静态链接 musl,无 glibc 依赖。
|
||||
|
||||
## file 结果(bin/meow_mdns)
|
||||
ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, stripped
|
||||
|
||||
## 体积
|
||||
- 部署版:**79,468 B ≈ 77.6 KB**(strip 后)
|
||||
- MD5 :**b23de7ce54873e926e7601a3b21b66a9**
|
||||
|
||||
## 构建命令
|
||||
见同目录 build.sh(宿主 gcc `-O2` 可编译并在本机组播/单播自测;交叉 `zig-mipsel-cc -Os -static`)。
|
||||
|
||||
## 部署
|
||||
- 设备:`/usr/data/mnt/sd_0/MeowOS/opt/mdnsd/mdnsd`(已部署, 755)。启动 `./mdnsd -v`(或经 meow_svcd 托管)。
|
||||
- 验证:设备 `netstat -uln | grep 5353`(0.0.0.0:5353)、`cat /proc/net/igmp`(wlan0 含 `FB0000E0`=224.0.0.251)、主机单播 `dig @<设备IP> -p 5353 <host>.local A` 或 python 探测。
|
||||
|
||||
## 已知限制(重要)
|
||||
- **手机热点丢弃客户端间组播**:设备能收到主机发来的组播查询并组播+单播应答,但**设备→主机组播被热点丢弃**(主机加入 224.0.0.251 组后 5s 收不到设备组播应答)。avahi 又**只接受组播应答**(对组播查询的单播应答, avahi 拒绝, 已实测)。故此刻 `avahi-resolve <host>.local` **超时**——这是**网络层热点隔离**,非应答器缺陷。主机需 DNS 兜底(如 `getent hosts meowos.local` 走库, 或主机 `/etc/hosts` 静态条目, 或设备侧起一个 UDP:53 应答器)。
|
||||
- 本机(多接口 phone-Linux host)自测时, 应答器组播发送在同机环回也偶发不达(与接口/多默认路由相关),但单播回源始终正确;单接口设备上的组播发送不受此影响。
|
||||
|
||||
## bin/ 内容
|
||||
- `bin/meow_mdns` = 部署版(mipsel 静态, 79,468 B, md5 b23de7ce…)
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* 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; 可覆写(通常仅宿主单测用)
|
||||
*
|
||||
* 构建: 宿主机 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>
|
||||
|
||||
#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 void on_signal(int sig) { (void)sig; g_running = 0; }
|
||||
|
||||
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) */
|
||||
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;
|
||||
}
|
||||
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));
|
||||
|
||||
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);
|
||||
|
||||
/* 找出持有设备 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;
|
||||
|
||||
while (g_running) {
|
||||
/* 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; /* 取不到就按组播处理 */
|
||||
for (struct cmsghdr *cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* ---- 构建应答 ----
|
||||
* 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) {
|
||||
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);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
/* 退出: 离开组播组 */
|
||||
if (setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == 0)
|
||||
close(s);
|
||||
printf("meow_mdns: exiting\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user