回家后发现家里的新拉的光纤是移动的,但是移动居然对53端口的udp实行完全拦截,也就是dns拦截。想起之前在宿舍使用的cloudflare的json格式的DoH还有DoT,所以决定搞一搞。
DoH 把RFC8484关于实现的那部分看了,并没有JSON API格式的,一查,原来还只是草案. DoH(Dns over HTTPS),cloudflare(1.1.1.1)和Google(8.8.8.8)的dns都支持JSON格式的,虽说还是草案,但是国内好几个私人的DoH供应商都有提供,所以讲一下
JSON API JSON格式的请求,草案里写了必须要有Qname(请求的查询的域名),Qtype(请求查询的类型,A,AAAA,CNAME等),Qclass(这个不知道是啥,CF和Google的实现都没有这个字段,所以无视就行了)
请求:
1 https://1.0.0.1/dns-query?ct=application/dns-json&name=baidu.com&type=A
响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 { "Status" : 0 , "TC" : false , "RD" : true , "RA" : true , "AD" : false , "CD" : false , "Question" : [ { "name" : "baidu.com" , "type" : 1 } ] , "Answer" : [ { "name" : "baidu.com" , "type" : 1 , "TTL" : 389 , "data" : "39.156.69.79" } , { "name" : "baidu.com" , "type" : 1 , "TTL" : 389 , "data" : "220.181.38.148" } ] }
好像很简单
格式按照RFC1035
,刚开始不知道,然后就开始按着文档手撸实现,越撸越不对劲,发现这就是一个普通的DNS的UDP包格式……
按照RFC8484,DoH需要支持GET和POST两种格式
GET请求,需要把DNS包进行base64转一下,去除结尾的=
后发送,如
1 https://1.0.0.1/dns-query?dns=AAMBAAABAAAAAAAABWJhaWR1A2NvbQAAAQAB
POST请求,DNS包不需要任何操作,当成body直接POST就可以了
返回数据也是一个标准的UDP的DNS报文,直接返回就可以了
所以按照RFC8484,用python的话,一个简单的本地DoH转UDP格式的本地服务器就可以搭建起来了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from socketserver import DatagramRequestHandler, ThreadingUDPServerimport requestsimport base64server_ip = "127.0.0.1" server_port = 53 server_name = "github.com/SodiumAzide" s = requests.session() class Server (DatagramRequestHandler ): def handle (self ): data = self .rfile.read() rs = s.post("https://223.5.5.5/dns-query" , data=data).content self .wfile.write(rs) if __name__ == '__main__' : server = ThreadingUDPServer((server_ip, server_port), Server) try : server.serve_forever() except KeyboardInterrupt: server.shutdown()
DoT 最近把之前缺的DoT的RFC也看了.根据RFC7858关于数据的部分,也是按照RFC1035的数据包.看来真的是dns over xxxx了,就是将之前UDP的dns通过https或tls进行传输.那就没啥好说了. 发送数据前,需要发送udp数据包的长度,编码为两字节大端格式
(All messages (requests and responses) in the established TLS session MUST use the two-octet length field described in Section 4.2.2 of [RFC1035]).
刚开始看漏了这几句,卡了好久.具体看代码吧.PS :cloudflare的dns,是把长度+响应数据一起发送,阿里的dns是先发长度,然后再发相应包.RFC里面也没说是那种,所以发现只收到两个字节的,记得把后面那段也收了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from socketserver import DatagramRequestHandler, ThreadingUDPServerimport requestsimport base64import socketimport sslserver_ip = "127.0.0.1" server_port = 53 server_name = "github.com/SodiumAzide" s = requests.session() ss = "one.one.one.one" port = 853 class Server (DatagramRequestHandler ): def handle (self ): data = self .rfile.read() context = ssl.create_default_context() with socket.create_connection((ss, 853 )) as sock: with context.wrap_socket(sock, server_hostname=ss) as ssock: dataLen = len (data) data = dataLen.to_bytes(2 , "big" ) + data ssock.send(data) rs = ssock.recv(1024 ) self .wfile.write(rs[2 :]) if __name__ == '__main__' : server = ThreadingUDPServer((server_ip, server_port), Server) try : server.serve_forever() except KeyboardInterrupt: server.shutdown()
参考文献
JSON format to represent DNS data
(RFC1035)DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
(RFC7858)Specification for DNS over Transport Layer Security (TLS)
(RFC8484)DNS Queries over HTTPS (DoH)