apernet / OpenGFW

OpenGFW is a flexible, easy-to-use, open source implementation of GFW (Great Firewall of China) on Linux
https://gfw.dev/
Mozilla Public License 2.0
9.72k stars 733 forks source link

疑似 NTP 数据包被识别成 DNS 数据包 #126

Closed trantuan-20048607 closed 7 months ago

trantuan-20048607 commented 7 months ago

在 OpenWRT 上测试 DNS 规则时遇到了奇怪的报错:

Wed Apr 10 22:00:11 2024 user.notice OpenGFW: 2024-04-10T13:59:46Z  ERROR   ruleset match error {"name": "geosite_gfw_dns", "id": 1778060308602949632, "src": "192.168.1.195:123", "dst": "17.253.116.253:123", "error": "reflect: call of reflect.Value.Len on zero Value (1:26)\n | dns != nil && !dns.qr && any(dns.questions, {geosite(string(.name), \"gfw\")})\n | .........................^"}
Wed Apr 10 22:00:11 2024 user.notice OpenGFW: 2024-04-10T13:59:47Z  ERROR   ruleset match error {"name": "geosite_gfw_dns", "id": 1778060308602949632, "src": "192.168.1.195:123", "dst": "17.253.116.253:123", "error": "reflect: call of reflect.Value.Len on zero Value (1:26)\n | dns != nil && !dns.qr && any(dns.questions, {geosite(string(.name), \"gfw\")})\n | .........................^"}
Wed Apr 10 22:00:17 2024 user.notice OpenGFW: 2024-04-10T13:59:47Z  ERROR   ruleset match error {"name": "geosite_gfw_dns", "id": 1778060309060132864, "src": "192.168.1.195:123", "dst": "17.253.114.125:123", "error": "reflect: call of reflect.Value.Len on zero Value (1:26)\n | dns != nil && !dns.qr && any(dns.questions, {geosite(string(.name), \"gfw\")})\n | .........................^"}

报错对应的规则内容:

- name: geosite_gfw_dns
  action: block
  log: true
  expr: dns != nil && !dns.qr && any(dns.questions, {geosite(string(.name), "gfw")})

我认为这是将 NTP 数据包误识别为 DNS,使得dns != nil导致的,主要原因有两点:

  1. port.srcport.dst都是123,这是 NTP 协议所使用的端口
  2. 反向查询目标 IP,得到一个看上去是 NTP 服务的域名:
    
    $ nslookup -type=PTR 253.116.253.17.in-addr.arpa
    服务器:  OpenWRT.lan
    Address:  192.168.1.1

非权威应答: 253.116.253.17.in-addr.arpa name = twtpe2-ntp-002.aaplimg.com


由于不是很了解 NTP 协议,我并没有进一步抓包确认。
haruue commented 7 months ago

现有实现是直接用 gopacket 的 layer.DNS.DecodeFromBytes() 解析 UDP 包, 如果不返回 error 就视为是 DNS 。

构造一个让 gopacket 的 layer.DNS.DecodeFromBytes() 不返回 error 的 UDP 包挺简单的, 包大小不小于 12 且直接把 packet[4:12] 置 0 即可(这样可以跳过对后续更复杂结构的解析), 例如下面的脚本就可以发送一个 UDP 包, 让 OpenGFW 的 dns != nil

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

try:
    message = bytes(12)
    sock.sendto(message, ("192.0.2.1", 2333))
finally:
    sock.close()
trantuan-20048607 commented 7 months ago

这样啊,反正也不会崩溃,问题不大...