目录
前文
之前在php扫描同路由下的设备中用到php 内置的gethostbyaddr去反查地址名称,但速度太慢了。
如下数据,扫描加反查要22秒。
ip=192.168.2.1 name=RT-AC3100-C45B.lan send=1 recive=1 loss=0.00% min=2.53ms max=2.53ms avg=2.53ms
ip=192.168.2.13 name=BL-c0-74-68.lan send=1 recive=1 loss=0.00% min=49.27ms max=49.27ms avg=49.27ms
ip=192.168.2.50 name=192.168.2.50 send=1 recive=1 loss=0.00% min=2.40ms max=2.40ms avg=2.40ms
ip=192.168.2.78 name=TL-IPC42A-4.lan send=1 recive=1 loss=0.00% min=7.54ms max=7.54ms avg=7.54ms
ip=192.168.2.205 name=MI5-xiaomishouji.lan send=1 recive=1 loss=0.00% min=158.06ms max=158.06ms avg=158.06ms
ip=192.168.2.215 name=lwb-R5zen.lan send=1 recive=1 loss=0.00% min=0.23ms max=0.23ms avg=0.23ms
ip=192.168.2.240 name=192.168.2.240 send=1 recive=1 loss=0.00% min=3.25ms max=3.25ms avg=3.25ms
total time_ms 22011.496067047
total cnt 6
尝试
在我思考如何能加速度的过程中,我逛到了php 的官方文档。其中有个评论是自己构建了一个可以设定超时,向指定服务器查询的gethostbyaddr_timeout。一顿操作,试了下。效果还可以,同样的代码,扫描加反查只要3.8秒。
total time_ms 3872.8039264679
total cnt 6
单次查询对比
数据
-
没有host返回的
次序 |
方法 |
返回 |
花费时间(s) |
1 |
gethostbyaddr |
1.0.0.2 |
9.2134771347046 |
2 |
gethostbyaddr_timeout |
1.0.0.2 |
0.014039039611816 |
3 |
gethostbyaddr |
1.0.0.2 |
9.0230431556702 |
4 |
gethostbyaddr_timeout |
1.0.0.2 |
0.013981819152832 |
-
有host返回的
次序 |
方法 |
返回 |
花费时间(s) |
1 |
gethostbyaddr |
dns.google |
0.0071818828582764 |
2 |
gethostbyaddr_timeout |
dns.google |
0.0041348934173584 |
3 |
gethostbyaddr |
dns.google |
0.00032496452331543 |
4 |
gethostbyaddr_timeout |
dns.google |
0.0037519931793213 |
次序 |
方法 |
返回 |
花费时间(s) |
1 |
gethostbyaddr |
dns.google |
0.0039701461791992 |
2 |
gethostbyaddr_timeout |
dns.google |
0.0048518180847168 |
3 |
gethostbyaddr |
dns.google |
0.00065708160400391 |
4 |
gethostbyaddr_timeout |
dns.google |
0.0048589706420898 |
小结
- 无host返回,gethostbyaddr_timeout 优势很明显。
- 有host返回时
- 无本地dns缓存时
- gethostbyaddr_timeout 首次稍微快一倍
- 有本地dns缓存时,gethostbyaddr 和 gethostbyaddr 差距很小
这应该是本地查和到指定dns服务器查的差距
- gethostbyaddr 首次调用后之后的速度就很快了,似乎有进程内缓存
- gethostbyaddr_timeout 每次调用时间差不多
实例分析
首先我的脚本是扫描局域网内的设备,局域网内的路由器本身就是一个dns服务器。对于该场景下,明显通过gethostbyaddr_timeout向路由查询会就可以了。gethostbyaddr 会进行迭代查询,一层又一层,如果没有host返回,9s的阻塞不能接受的。并且,每个ip是运行没有host的,这并不是一个强制性的规范,如果直接使用,妥妥的九九八十一难了。正如开头,刚好2个设备就是没有host的,其22秒的组成也就是 9 + 9 + 4。
Obsoleting IQUERY
随着思路写,还以为是IQUERY。结果。。。
IQUERY 已过时, 且已被弃用。主要原因是:
- 概念不明确,
- 逆向查询处理会给服务器带来相当沉重的负担并且需要维护一个键值数据库
- 对于大型权威服务器会返回包过大容易被发起拒绝服务攻击
- 服务商通常会禁用这个功能
- 无法告知去那里获取全部记录
- 没有成型应用
- 有更好替代方案:指针记录(PTR)的反向解析
PTR反查报文
目标 192.168.2.215,PTR 发送一个查询报文,但查询内容为是 192.168.2.15 书写顺序相反的 215.2.168.192 作为前缀的 in-addr.arpa 子域名,即
215.2.168.192.in-addr.arpa
implode('.',array_reverse(explode('.','192.168.2.215')))
请求报文
## 头部
53 49 1 0 # 53 49 标识 1 RD=1 ,表示客户端希望服务器可以执行递归查询
0 1 0 0 # 问题记录 1个
0 0 0 0
## 问题
3 # 长度 3
50 49 53 # 215
1 # 长度 1
50 # 2
3 # 长度 3
49 54 56 # 168
3 # 长度 3
49 57 50 # 192
7 # 长度 7
105 110 45 97 100 100 114 # in-addr
4 # 长度 4
97 114 112 97 # arpa
0 # 表示 根域
0 12 0 1 # type 12 PTR反查, class 1 ipv4
标志
其中 53 49 1 0 的标志“1 0”,转为二进制为
0 0000 0 0 1 0 000 0000
qurey code |
opcode x4 |
AA |
TC |
RD |
RA |
保留位 x 3 |
response code x 4 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
查询请求 |
标准查询 |
- |
非截短 |
递归查询 |
- |
0 |
没有差错 |
返回报文
// 头部
55 53 133 128
0 1 0 1 ## 01 问题1 ,01 答案 1
0 0 0 0
// 问题
3 50 49 53
1 50
3 49 54 56
3 49 57 50
7 105 110 45 97 100 100 114
4 97 114 112 97
0
0 12 0 1
// 答案
192 12 ## 压缩域名
0 12 0 1 ## type ,class
0 0 0 0 ## TTL
0 15 ## 资源长度
9 ## 长度
108 119 98 45 82 53 122 101 110 ## 见 Resource Data
3 ## 长度
108 97 110 ## 见 Resource Data
0 ## 根域
标志
“55 53 133 128”中的标志“133 128”,转为二进制为
1 0000 1 0 1 1 000 0000
qurey code |
opcode x4 |
AA |
TC |
RD |
RA |
保留位 x 3 |
response code x 4 |
1 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
查询应答 |
标准查询 |
权威回答 |
非截短 |
递归查询 |
服务器支持递归查询 |
0 |
没有差错 |
答案
name
192 12 转为 二进制,这里并没有直接保存name,而是域名压缩,因为在问题里面已经包含了域名信息,所以这里采用字节偏移来表示。
11000000 1100
域名在报文中第二次出现时,只用两个字节来保存。第一个字节最高两位都是 1 ,余下部分和第二个字节组合在一起,表示域名第一次出现时在报文中的偏移量。通过这个偏移量,就可以找到对应的域名。
这里的12刚好是头部的长度,如果只有一个问题,这应该是个固定值,如果还有一个问题,那么第二个问题这个偏移就会再加上第一个问题的长度。
Resource Data
- 108 119 98 45 82 53 122 101 110
(('108 119 98 45 82 53 122 101 110'.split(' ')).map(function(item){return String.fromCharCode(item)})).join('')
(('108 97 110'.split(' ')).map(function(item){return String.fromCharCode(item)})).join('')
点拼接后就是
ip=192.168.2.215 name=lwb-R5zen.lan
收获
- 原本以为反查用的是dns中的IQUERY,甚至在看到IQUERY弃用前,我还以为PTR只是反查报文的一种格式,其实二者完全不一样。
- dns的报文头是最容易理解的,固定长度,一一对应即可。而到了问题和答案,除了特定的结构外,还需要通过计算长度去获取后续信息,即便在PNG格式中已经接触过一次,还是在阅读上花费了不少时间。结构的上设计还是让我学到不少。
- 应答的name压缩还是让我有点惊讶,最开始还理所应当的以为和问题一样,看半天怎么都对不上。直到发现是name压缩,不得不说,确实巧妙。
- 之前已经看过并摘录了DNS报文格式,但实际上,读抄远远不如上手操作一番,唯一的好处就是查的时候方便许多。
参考
gethostbyaddr_timeout
反向DNS
Obsoleting IQUERY
小菜学网络之DNS报文格式
[摘]DNS报文格式解析