开源异步 DNS 解析库c-ares命令行工具鸿蒙化构建

本文记录使用命令 OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh 构建 c-ares 1.34.5 的完整过程,包括环境、构建链路、关键日志、常见问题与解决方案、产物验证与重建方法,便于复现与运维。

📖 c-ares 简介

c-ares(C Asynchronous DNS Requests)是一个 C 语言实现的异步 DNS 解析库。它提供了高性能、非阻塞的 DNS 查询功能,被广泛用于需要异步 DNS 解析的应用程序中,如 curl、Node.js 等。

🎯 c-ares 的作用与重要性

c-ares 是异步 DNS 解析的核心库,提供了:

  • 异步 DNS 解析:非阻塞的 DNS 查询,不会阻塞应用程序主线程
  • 高性能:高效的 DNS 查询实现,支持并发查询
  • 跨平台:支持多种操作系统和平台
  • 轻量级:库体积小,依赖少,易于集成
  • 网络应用支持:为 curl、Node.js 等网络工具提供 DNS 解析支持
  • DNS 协议支持:支持 A、AAAA、MX、TXT、SRV 等多种 DNS 记录类型

🔧 c-ares 核心特性

1. 异步 DNS 解析
  • 非阻塞查询:DNS 查询不会阻塞应用程序
  • 事件驱动:基于事件循环的异步处理
  • 并发支持:支持多个并发 DNS 查询
  • 回调机制:查询完成后通过回调函数通知
2. DNS 协议支持
  • 标准记录类型:A、AAAA、MX、TXT、SRV、NS、CNAME、PTR 等
  • DNS over HTTPS (DoH):支持 DNS over HTTPS 查询
  • DNS over TLS (DoT):支持 DNS over TLS 查询
  • EDNS0 支持:支持扩展 DNS 功能
3. 命令行工具
  • adig:异步 DNS 查询工具,类似 dig
  • ahost:异步主机名解析工具
  • 批量查询:支持批量查询多个域名
  • 多种输出格式:支持多种输出格式
4. 编程接口
  • C API:提供完整的 C 语言接口
  • 简单易用:简洁的 API 设计
  • 线程安全:线程安全的实现
  • 错误处理:完善的错误码和错误处理
5. 应用场景
  • Web 客户端:curl、wget 等工具的 DNS 解析
  • 网络应用:需要异步 DNS 解析的网络应用
  • 服务器应用:需要高性能 DNS 解析的服务器应用
  • 移动应用:移动设备上的 DNS 解析

🚀 构建入口与环境

  • 📝 执行命令OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh
  • 🔧 入口脚本create-hnp.sh
    • 检查必需的环境变量 OHOS_ARCHOHOS_ABI
    • 导出 LC_CTYPETOOL_HOMEOHOS_SDK_HOME
    • 执行 make -C build-hnp
  • 📦 顶层构建build-hnp/Makefile
    • PKGS 变量定义需要构建的包列表(包含 c-ares
    • 通过 check-pkgs 机制自动检测 PKGS 变化并触发重新构建
    • 自动合并 external-hnp 目录下的外部 HNP 包
    • base.hnp 依赖所有包的 .stamp 和外部 HNP 包
    • 总目标 all: copy,打包 base.hnp 并拷贝到 entry/hnp/$(OHOS_ABI)

⚙️ c-ares 包的构建配置

  • 📁 包目录build-hnp/c-ares/Makefile
    • 继承通用规则:include ../utils/Makefrag
    • 源地址:https://github.com/c-ares/c-ares/releases/download/v1.34.5/c-ares-1.34.5.tar.gz
    • 版本:1.34.5
  • ⚙️ CMake 配置参数
    • -DCMAKE_INSTALL_PREFIX=$(PREFIX) - 安装前缀(/data/app/base.org/base_1.0
    • -DCMAKE_SYSTEM_NAME=Linux - 目标系统
    • -DCMAKE_SYSTEM_PROCESSOR=$(OHOS_ARCH) - 目标架构
    • -DCARES_STATIC=OFF - 禁用静态库
    • -DCARES_SHARED=ON - 构建共享库
    • -DCMAKE_BUILD_TYPE=RelWithDebInfo - 构建类型
  • 🔨 构建流程
    1. 下载源码包(从 GitHub releases)
    2. 解包并进入 temp/c-ares-1.34.5 目录
    3. 创建 build 目录并运行 cmake 配置
    4. 使用 make -j $(nproc) 并行编译
    5. 使用 make install 安装
    6. 执行 ELF strip 减小体积
    7. 复制到 ../sysroot
  • 🔧 通用工具链与路径build-hnp/utils/Makefrag
    • CC/CXX/LD/AR/RANLIB/... 均指向 OHOS SDK 的 LLVM 工具链
    • CMake 通过 CMAKE_C_COMPILER 等变量指定交叉编译器
  • 📦 依赖关系
    • 前置依赖:无(c-ares 是独立的 DNS 解析库)
    • 后续依赖curl 的异步 DNS 支持通常依赖 c-ares
    • 建议构建顺序c-ares → curl(如果 curl 需要异步 DNS 支持)

📋 关键日志与过程节点

  • 📥 下载与解包
    • 从 GitHub releases 下载 c-ares-1.34.5.tar.gz
    • 完成解包并进入 temp/c-ares-1.34.5 目录
    • 创建 build 目录用于 CMake 构建
  • ⚙️ CMake 配置阶段
    • 运行 cmake -DCMAKE_INSTALL_PREFIX=... -DCARES_STATIC=OFF -DCARES_SHARED=ON ...
    • 检测 Clang 编译器与一系列编译警告标志
    • 部分 HAVE__W* 标志测试失败属正常(CMake 会仅启用可用标志)
    • CMAKE_SYSTEM_NAME=Linux 与三元组工具链生效
    • ABI 与编译特性探测通过
    • 配置成功,启用共享库构建
    • 生成 Makefile 和构建配置
  • 🔨 编译与安装
    • 使用 make -j $(nproc) 并行编译
    • 成功编译生成 libcares.so.2.19.4adigahost 命令行工具
    • 使用 make install 安装到临时前缀
    • 执行 llvm-strip 剥离共享库和二进制符号
    • 复制到 ../sysroot
  • 📦 打包
    • 完成 base.hnp 重打包,拷贝产物到 entry/hnp/arm64-v8a/
    • c-ares 库和工具已成功打包到 base.hnp

✅ 产物验证

📦 检查打包文件

ls build-hnp/base.hnp  # 应存在
ls entry/hnp/arm64-v8a/*.hnp  # 应包含 base.hnp 与 base-public.hnp

🔍 检查二进制和库文件

# 检查 adig 和 ahost 命令行工具
ls -lh build-hnp/sysroot/bin/adig build-hnp/sysroot/bin/ahost
file build-hnp/sysroot/bin/adig build-hnp/sysroot/bin/ahost

# 检查库文件
ls -lh build-hnp/sysroot/lib/libcares.so*
file build-hnp/sysroot/lib/libcares.so.2.19.4

# 检查头文件
ls -lh build-hnp/sysroot/include/ares.h build-hnp/sysroot/include/ares_dns.h

# 检查 pkg-config 文件
ls -lh build-hnp/sysroot/lib/pkgconfig/libcares.pc
cat build-hnp/sysroot/lib/pkgconfig/libcares.pc

✅ 构建验证结果

  • ✅ c-ares 命令行工具已成功安装:
    • adig (31K) - 异步 DNS 查询工具
    • ahost (11K) - 异步主机名解析工具
  • ✅ c-ares 库已安装:
    • libcares.so.2.19.4 (195K) - 主库文件
    • libcares.so.2 - 版本符号链接
    • libcares.so - 通用符号链接
  • ✅ 文件类型:ELF 64-bit LSB pie executable/shared object, ARM aarch64
  • ✅ 动态链接:interpreter /lib/ld-musl-aarch64.so.1
  • ✅ 已剥离符号:stripped
  • ✅ 头文件已安装:ares.h (47K)、ares_dns.h (5.7K)
  • ✅ pkg-config 文件已安装:libcares.pc (704 bytes)
  • ✅ 手册页面已安装:share/man/man1/adig.1share/man/man1/ahost.1share/man/man3/ares_*.3
  • ✅ 已打包到 base.hnp

💻 终端中执行的示例命令

🌐 adig 基本使用

image-20251124190340545

1. DNS 查询(A 记录)
# 查询 A 记录(IPv4 地址)
adig www.example.com

# 查询指定 DNS 服务器
adig @8.8.8.8 www.example.com

# 查询并显示详细信息
adig -x www.example.com

# 查询多个域名
adig www.example.com www.google.com
2. AAAA 记录查询(IPv6)
# 查询 AAAA 记录(IPv6 地址)
adig -t AAAA www.example.com

# 查询 IPv6 地址并显示详细信息
adig -t AAAA -x www.example.com

# 查询多个域名的 IPv6 地址
adig -t AAAA www.example.com www.google.com
3. MX 记录查询
# 查询 MX 记录(邮件交换记录)
adig -t MX example.com

# 查询 MX 记录并显示详细信息
adig -t MX -v example.com

# 查询多个域名的 MX 记录
adig -t MX example.com google.com
4. TXT 记录查询
# 查询 TXT 记录
adig -t TXT example.com

# 查询 TXT 记录并显示详细信息
adig -t TXT -v example.com

# 查询 SPF 记录
adig -t TXT example.com | grep spf

# 查询 DKIM 记录
adig -t TXT default._domainkey.example.com
5. SRV 记录查询
# 查询 SRV 记录
adig -t SRV _http._tcp.example.com

# 查询 SRV 记录并显示详细信息
adig -t SRV -v _http._tcp.example.com

# 查询 SIP SRV 记录
adig -t SRV _sip._tcp.example.com

# 查询 XMPP SRV 记录
adig -t SRV _xmpp-client._tcp.example.com

🌐 adig 高级用法

6. NS 和 CNAME 记录查询
# 查询 NS 记录(名称服务器)
adig -t NS example.com

# 查询 CNAME 记录(别名)
adig -t CNAME www.example.com

# 查询 PTR 记录(反向 DNS)
adig -t PTR 8.8.8.8.in-addr.arpa

# 查询 SOA 记录(起始授权机构)
adig -t SOA example.com
7. 批量查询
# 从文件读取域名并查询
echo -e "www.example.com\nwww.google.com" | xargs -n1 adig

# 批量查询 A 记录
cat domains.txt | while read domain; do
    echo "=== $domain ==="
    adig "$domain"
done

# 批量查询多个记录类型
for type in A AAAA MX TXT; do
    echo "=== $type ==="
    adig -t "$type" example.com
done

# 批量查询并保存结果
adig www.example.com > dns_result.txt
8. 输出格式控制
# 简洁输出(只显示 IP 地址)
adig -s www.example.com

# 详细输出
adig -v www.example.com

# 显示查询时间
adig -t www.example.com

# 显示统计信息
adig -S www.example.com

# 显示版本信息
adig -V

# 显示帮助信息
adig -h
9. DNS 服务器选择
# 使用 Google DNS
adig @8.8.8.8 www.example.com

# 使用 Cloudflare DNS
adig @1.1.1.1 www.example.com

# 使用本地 DNS 服务器
adig @127.0.0.1 www.example.com

# 使用 IPv6 DNS 服务器
adig @2001:4860:4860::8888 www.example.com

🌐 ahost 基本使用

image-20251124190715950

10. 主机名解析
# 解析主机名(A 记录)
ahost www.example.com

# 解析主机名(AAAA 记录)
ahost -t AAAA www.example.com

# 解析多个主机名
ahost www.example.com www.google.com

# 解析并显示详细信息
ahost -v www.example.com
11. 反向 DNS 查询
# 反向 DNS 查询(PTR 记录)
ahost -r 8.8.8.8

# 反向 DNS 查询 IPv6 地址
ahost -r 2001:4860:4860::8888

# 反向 DNS 查询并显示详细信息
ahost -r -v 8.8.8.8
12. 实际应用示例
# 检查域名解析是否正常
adig www.example.com && echo "DNS resolution OK" || echo "DNS resolution failed"

# 查询域名的所有记录类型
for type in A AAAA MX TXT NS SOA; do
    echo "=== $type ==="
    adig -t "$type" example.com
done

# 比较不同 DNS 服务器的响应
for dns in 8.8.8.8 1.1.1.1 208.67.222.222; do
    echo "=== DNS: $dns ==="
    adig @"$dns" www.example.com
done

# 查询邮件服务器配置
echo "=== MX Records ==="
adig -t MX example.com
echo "=== SPF Record ==="
adig -t TXT example.com | grep spf

# 检查 DNS 解析延迟
time adig www.example.com

# 批量检查域名解析
cat domains.txt | while read domain; do
    if adig "$domain" > /dev/null 2>&1; then
        echo "$domain: OK"
    else
        echo "$domain: FAILED"
    fi
done

# 查询 DNS 记录并保存到文件
adig -t A www.example.com > a_record.txt
adig -t AAAA www.example.com > aaaa_record.txt
adig -t MX example.com > mx_record.txt

# 监控 DNS 解析变化
while true; do
    echo "$(date): $(adig www.example.com)"
    sleep 60
done

# 查询并格式化输出
adig www.example.com | awk '{print "IP:", $1}'

# 检查 DNS 服务器响应时间
for dns in 8.8.8.8 1.1.1.1; do
    echo -n "DNS $dns: "
    time adig @"$dns" www.example.com > /dev/null 2>&1
done

🔧 编程接口示例(C 语言)

13. 基本 DNS 查询示例
// 示例:使用 c-ares 库进行 DNS 查询
#include <ares.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void callback(void *arg, int status, int timeouts, struct hostent *host) {
    if (status == ARES_SUCCESS) {
        printf("Hostname resolved successfully\n");
        struct in_addr **addr_list = (struct in_addr **)host->h_addr_list;
        for (int i = 0; addr_list[i] != NULL; i++) {
            printf("IP: %s\n", inet_ntoa(*addr_list[i]));
        }
    } else {
        printf("DNS query failed: %s\n", ares_strerror(status));
    }
}

int main() {
    ares_channel channel;
    int status;
    
    // 初始化 ares 库
    status = ares_library_init(ARES_LIB_INIT_ALL);
    if (status != ARES_SUCCESS) {
        fprintf(stderr, "ares_library_init failed: %s\n", ares_strerror(status));
        return 1;
    }
    
    // 创建 ares 通道
    status = ares_init(&channel);
    if (status != ARES_SUCCESS) {
        fprintf(stderr, "ares_init failed: %s\n", ares_strerror(status));
        ares_library_cleanup();
        return 1;
    }
    
    // 执行 DNS 查询
    ares_gethostbyname(channel, "www.example.com", AF_INET, callback, NULL);
    
    // 处理事件循环
    for (;;) {
        fd_set read_fds, write_fds;
        FD_ZERO(&read_fds);
        FD_ZERO(&write_fds);
        
        int nfds = ares_fds(channel, &read_fds, &write_fds);
        if (nfds == 0) {
            break;
        }
        
        struct timeval tv;
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        
        select(nfds, &read_fds, &write_fds, NULL, &tv);
        ares_process(channel, &read_fds, &write_fds);
    }
    
    // 清理资源
    ares_destroy(channel);
    ares_library_cleanup();
    
    return 0;
}

// 编译命令
// gcc -o test_ares test_ares.c -lcares
14. 使用 pkg-config
# 使用 pkg-config 获取编译和链接标志
pkg-config --cflags libcares
pkg-config --libs libcares

# 编译示例
gcc -o program program.c $(pkg-config --cflags --libs libcares)

# 检查库版本
pkg-config --modversion libcares

# 检查库是否存在
pkg-config --exists libcares && echo "Library found"

# 显示库信息
pkg-config --print-provides libcares

🧪 功能验证脚本

#!/bin/bash
# c-ares 工具验证脚本

CARES_BIN="build-hnp/sysroot/bin"
CARES_LIB="build-hnp/sysroot/lib"
CARES_INCLUDE="build-hnp/sysroot/include"

echo "=== c-ares 工具验证 ==="

# 检查 adig 二进制
if [ -f "$CARES_BIN/adig" ]; then
    echo "✓ adig: 存在"
    file "$CARES_BIN/adig"
    echo "文件大小: $(ls -lh "$CARES_BIN/adig" | awk '{print $5}')"
    echo "架构信息: $(file "$CARES_BIN/adig" | grep -o "ARM aarch64")"
else
    echo "✗ adig: 缺失"
fi

# 检查 ahost 二进制
if [ -f "$CARES_BIN/ahost" ]; then
    echo "✓ ahost: 存在"
    file "$CARES_BIN/ahost"
    echo "文件大小: $(ls -lh "$CARES_BIN/ahost" | awk '{print $5}')"
    echo "架构信息: $(file "$CARES_BIN/ahost" | grep -o "ARM aarch64")"
else
    echo "✗ ahost: 缺失"
fi

# 检查库文件
echo ""
echo "=== 库文件验证 ==="
if [ -f "$CARES_LIB/libcares.so.2.19.4" ]; then
    echo "✓ libcares.so.2.19.4: 存在"
    file "$CARES_LIB/libcares.so.2.19.4"
    echo "文件大小: $(ls -lh "$CARES_LIB/libcares.so.2.19.4" | awk '{print $5}')"
else
    echo "✗ libcares.so.2.19.4: 缺失"
fi

# 检查符号链接
for link in libcares.so libcares.so.2; do
    if [ -L "$CARES_LIB/$link" ]; then
        echo "✓ $link: 符号链接 -> $(readlink "$CARES_LIB/$link")"
    else
        echo "✗ $link: 缺失"
    fi
done

# 检查头文件
echo ""
echo "=== 头文件验证 ==="
for header in ares.h ares_dns.h ares_version.h; do
    if [ -f "$CARES_INCLUDE/$header" ]; then
        echo "✓ $header: 存在"
        echo "  文件大小: $(ls -lh "$CARES_INCLUDE/$header" | awk '{print $5}')"
    else
        echo "✗ $header: 缺失"
    fi
done

# 检查 pkg-config 文件
echo ""
echo "=== pkg-config 验证 ==="
if [ -f "build-hnp/sysroot/lib/pkgconfig/libcares.pc" ]; then
    echo "✓ libcares.pc: 存在"
    cat build-hnp/sysroot/lib/pkgconfig/libcares.pc
else
    echo "✗ libcares.pc: 缺失"
fi

# 测试基本功能(在目标设备上)
echo ""
echo "=== 功能测试(需要在目标设备上运行)==="
echo "测试 adig DNS 查询:"
echo "  $CARES_BIN/adig www.example.com"
echo ""
echo "测试 ahost 主机名解析:"
echo "  $CARES_BIN/ahost www.example.com"
echo ""
echo "测试 AAAA 记录查询:"
echo "  $CARES_BIN/adig -t AAAA www.example.com"
echo ""
echo "测试 MX 记录查询:"
echo "  $CARES_BIN/adig -t MX example.com"

🐛 常见问题与处理

❌ 问题 1:CMake 警告标志失败

  • 🔍 症状:配置阶段部分 HAVE__W* 标志测试失败
  • 🔎 原因:部分编译警告标志在目标平台上不可用
  • ✅ 解决方法
    • 这是正常现象,CMake 会仅启用可用标志
    • 不影响构建结果,可以忽略这些警告
    • 位置:CMake 配置阶段

❌ 问题 2:GitHub 下载失败

  • 🔍 症状:无法从 GitHub releases 下载源码包
  • 🔎 原因:网络问题或 GitHub 访问受限
  • ✅ 解决方法
    • 手动下载源码包放置到 build-hnp/c-ares/download/c-ares-1.34.5.tar.gz
    • 使用代理或镜像站点下载
    • 位置:build-hnp/c-ares/Makefile:3

❌ 问题 3:curl 异步 DNS 支持缺失

  • 🔍 症状:curl 构建时未启用 c-ares 支持
  • 🔎 原因c-ares 未在 curl 之前构建,或 curl 配置时未检测到
  • ✅ 解决方法
    • 确保 c-arescurl 之前构建
    • 检查 curl 配置是否启用了 c-ares 支持
    • 位置:构建顺序和 curl 配置

❌ 问题 4:静态/共享库选择

  • 🔍 症状:需要静态库但构建时使用了 -DCARES_STATIC=OFF
  • 🔎 原因:当前配置禁用了静态库以减小体积
  • ✅ 解决方法
    • 如果需要静态库,修改 CMAKE_ARGS-DCARES_STATIC=ON -DCARES_SHARED=OFF
    • 或同时启用:-DCARES_STATIC=ON -DCARES_SHARED=ON
    • 位置:build-hnp/c-ares/Makefile:6

❌ 问题 5:链接失败

  • 🔍 症状:链接错误,无法找到 libcares 符号
  • 🔎 原因:链接时未指定库或库路径不正确
  • ✅ 解决方法
    • 确保链接时添加 -lcares
    • 使用 pkg-config --libs libcares 获取正确的链接标志
    • 检查 LD_LIBRARY_PATH 是否包含库路径
    • 位置:编译和链接阶段

❌ 问题 6:头文件未找到

  • 🔍 症状:编译错误 ares.h: No such file or directory
  • 🔎 原因:头文件路径未正确设置
  • ✅ 解决方法
    • 确保编译时添加 -I$(prefix)/include
    • 使用 pkg-config --cflags libcares 获取正确的编译标志
    • 检查头文件是否已安装到 sysroot/include
    • 位置:编译阶段

❌ 问题 7:架构不支持

  • 🔍 症状Unsupported OHOS_ARCH=
  • 🔎 原因:传入的架构不在支持列表中
  • ✅ 解决方法
    • 确保传入支持架构(aarch64x86_64
    • 位置:build-hnp/Makefile:1-49

🔄 重建与扩展

  • 🔧 重建单包

    make -C build-hnp rebuild-c-ares  # 触发子包重新编译并刷新 .stamp
    
  • 🧹 清理

    make -C build-hnp clean  # 清理 sysroot、所有 .stamp 和 PKGS_MARKER
    
  • 📦 扩展:c-ares 是异步 DNS 解析的基础库,被 curl 等网络工具依赖

  • 🔄 自动重建机制

    • 修改 PKGS 后,check-pkgs 会自动检测变化并触发重新构建
    • 新增外部 HNP 包到 external-hnp 目录后,会自动合并到 base.hnp

💡 实践建议

  • 🔧 构建配置:使用共享库构建以减少体积,适合大多数场景
  • 🚀 依赖管理:c-ares 是独立的 DNS 解析库,无需前置依赖
  • 📦 网络应用:c-ares 为 curl 等网络工具提供异步 DNS 支持
  • 🔗 编程接口:使用 pkg-config 获取正确的编译和链接标志
  • 🌐 DNS 查询:使用 adigahost 命令行工具进行 DNS 查询和主机名解析

📝 结论与建议

  • ✅ 本次已在 aarch64 环境下完成 c-ares 1.34.5 的交叉编译与打包,adig、ahost 工具和库已安装到 sysroot 并纳入 HNP 包。
  • 💡 为保证构建稳定
    • c-ares 是独立的 DNS 解析库,无需前置依赖
    • 使用共享库构建以减少体积
    • 确保通过 create-hnp.sh 触发构建以获得完整环境变量
    • 利用 check-pkgs 机制自动检测包列表变化,无需手动清理
    • c-ares 为异步 DNS 解析提供了强大的工具和库支持
    • 常见陷阱包括 CMake 警告标志失败、静态/共享库选择、链接失败;当前已通过构建配置和参数处理
    • 建议在 curl 构建阶段启用 c-ares 支持,并结合 libidn2/libunistring/openssl/zlib 完成网络栈的功能验证与性能对比
    • 已完成 aarch64 目标下 c-ares 的交叉编译与打包,库与头文件进入 sysroot 并纳入 HNP 包

📚 以上为 c-ares 构建的深度解读与实践记录。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值