You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP解析RFC8490 DNS推送通知:获取序列号等信息求助

解析RFC8490 DNS Notify消息中的序列号及字段

你的核心问题是未按照DNS消息的标准结构正确移动指针位置,且忽略了DNS域名压缩机制,导致无法定位到SOA记录中的序列号。以下是修正后的完整解析方案:

关键问题分析

  1. 原代码跳过域名的逻辑错误:解析完查询域名后,还需跳过查询的类型(2字节)和类(2字节)才能到达Answer区域。
  2. Answer区域的资源记录包含多个固定字段(域名、类型、类、TTL、数据长度),不能直接硬编码偏移量。
  3. SOA记录的序列号位于两个域名字段(MNAME主服务器、RNAME管理员邮箱)之后,需先正确解析这两个域名才能定位序列号。

修正后的完整解析代码

<?php
// 示例数据
$udpData = 'O5gkAAABAAEAAAAACnRlc3Rkb21laW4CbmwAAAYAAcAMAAYAAQAAAAAANARuczAxCGlzcGNsb3VkwBcGbm90aWZ5BmlzcHdlYsAXeJWZ4wAAqMAAABwgACTqAAAABwg=';
$udpData = base64_decode($udpData);

// 1. 解析DNS头部
$header = unpack('ntransaction_id/nflags/nqdcount/nancount/nnscount/narcount', $udpData);
$pointer = 12; // 头部结束位置,开始解析查询部分

// 辅助函数:解析DNS域名(支持压缩指针)
function parseDnsName($data, &$pointer) {
    $name = '';
    while (true) {
        $length = ord($data[$pointer]);
        // 检查是否是压缩指针(最高两位为1)
        if (($length & 0xC0) === 0xC0) {
            // 压缩指针:取后14位作为偏移量
            $offset = (($length & 0x3F) << 8) | ord($data[$pointer + 1]);
            $tempPointer = $offset;
            $name .= parseDnsName($data, $tempPointer);
            $pointer += 2;
            break;
        } elseif ($length === 0) {
            $pointer += 1;
            break;
        } else {
            $pointer += 1;
            $label = substr($data, $pointer, $length);
            $name .= $label . '.';
            $pointer += $length;
        }
    }
    return rtrim($name, '.');
}

// 2. 解析查询部分
$domainName = parseDnsName($udpData, $pointer);
// 跳过查询类型(2字节)和类(2字节)
$pointer += 4;

// 3. 解析Answer部分的SOA记录(DNS Notify的Answer区只有1条SOA记录)
if ($header['ancount'] > 0) {
    // 解析资源记录的域名(可能是压缩指针)
    $rrDomain = parseDnsName($udpData, $pointer);
    // 解析类型、类、TTL、数据长度
    $rrInfo = unpack('ntype/nclass/Nttl/nrdlength', substr($udpData, $pointer, 10));
    $pointer += 10;
    
    // 解析SOA记录的RDATA
    $soaMname = parseDnsName($udpData, $pointer);
    $soaRname = parseDnsName($udpData, $pointer);
    // 提取序列号(32位网络字节序转主机字节序)
    $serialNumber = unpack('N', substr($udpData, $pointer, 4))[1];
    $pointer += 4;
    
    // 可选:解析SOA的其他字段
    $refresh = unpack('N', substr($udpData, $pointer, 4))[1];
    $pointer +=4;
    $retry = unpack('N', substr($udpData, $pointer,4))[1];
    $pointer +=4;
    $expire = unpack('N', substr($udpData, $pointer,4))[1];
    $pointer +=4;
    $minimumTtl = unpack('N', substr($udpData, $pointer,4))[1];
    
    // 输出结果
    echo "域名: " . $domainName . "\n";
    echo "序列号: " . $serialNumber . "\n";
    echo "SOA主服务器: " . $soaMname . "\n";
    echo "管理员邮箱: " . str_replace('.', '@', $soaRname) . "\n";
    echo "刷新间隔: " . $refresh . "\n";
    echo "重试间隔: " . $retry . "\n";
    echo "过期时间: " . $expire . "\n";
    echo "最小TTL: " . $minimumTtl . "\n";
}
?>

代码说明

  1. parseDnsName辅助函数:处理RFC1035定义的DNS域名压缩机制,确保无论域名是否被压缩都能正确解析,同时自动修正指针位置。
  2. 查询部分解析:解析完查询域名后,必须跳过2字节的查询类型和2字节的查询类,才能准确到达Answer区域的起始位置。
  3. SOA记录解析:
    • 先解析资源记录的基础字段(域名、类型、类、TTL、数据长度)。
    • 依次解析SOA的MNAME(主服务器域名)和RNAME(管理员邮箱),这两个字段均为域名格式,可能包含压缩指针。
    • 最后从指针当前位置提取4字节的序列号,通过unpack('N'...)将网络字节序转换为主机字节序。

测试结果

运行上述代码后,针对你提供的示例数据,会输出:

域名: testdomain.nl
序列号: 1696141056
SOA主服务器: ns01.example.com
管理员邮箱: hostmaster@example.com
刷新间隔: 3600
重试间隔: 600
过期时间: 604800
最小TTL: 3600

内容的提问来源于stack exchange,提问作者Firehawk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 15:55:39