Hosts绑定新思路之DNS代理服务器实现篇
背景
详见《Hosts绑定新思路之DNS代理篇》
核心内容
1. DNS协议解析
2. 启动UDP服务,监听53端口
3. 根据DB或者文本,进行Hosts解析
DNS协议
DNS Protocol Overview (推荐)
非强详细,但是不怎么看得懂的长篇大论
如果没有耐心的同学,可以看看我通过wireshark分析之后制作的两张gif图片。大概能知道DNS协议的内容。
Request数据包(图片可放大)
Response数据包(图片可放大)
代码
import
struct
2
from
cStringIO
import
StringIO
3
4
class
Header(object):
5
6
def
__init__
(self, id, flags, questions, answer_rrs, authority_rrs, additional_rrs):
7
self.id
=
id
8
self.flags
=
flags
9
self.questions
=
questions
10
self.answer_rrs
=
answer_rrs
11
self.authority_rrs
=
authority_rrs
12
self.additional_rrs
=
additional_rrs
13
14
@classmethod
15
def
parse(cls, data):
16
id, flags, questions, answer_rrs, authority_rrs, additional_rrs
=
struct.unpack(
'
!6H
'
, data.read(
12
))
17
return
Header(id, flags, questions, answer_rrs, authority_rrs, additional_rrs)
18
19
def
serialize(self, data):
20
data.write(struct.pack(
'
!6H
'
, self.id, self.flags, self.questions, self.answer_rrs, self.authority_rrs, self.additional_rrs))
21
22
def
__str__
(self):
23
return
'
{id: %s, flags: %s, questions: %s, answer_rrs: %s, authority_rrs: %s, additional_rrs: %s}
'
%
(
24
self.id, self.flags, self.questions, self.answer_rrs, self.authority_rrs, self.additional_rrs)
25
26
class
Query(object):
27
28
TYPE_A
=
1
29
TYPE_AAAA
=
28
30
CLASS_IN
=
1
31
32
def
__init__
(self, name, type, clazz):
33
self.name
=
name
34
self.type
=
type
35
self.clazz
=
clazz
36
37
@classmethod
38
def
parse(cls, data):
39
domain
=
cls._parse_domain_name(data)
40
type, clazz
=
struct.unpack(
'
!2H
'
, data.read(
4
))
41
return
Query(domain, type, clazz)
42
43
def
serialize(self, data):
44
self._serialize_domain_name(data)
45
data.write(struct.pack(
'
!2H
'
, self.type, self.clazz))
46
47
@staticmethod
48
def
_parse_domain_name(data):
49
list
=
[]
50
while
True:
51
tmp
=
data.read(
1
)
52
len
=
ord(tmp)
53
if
len
==
0:
54
break
55
list.append(data.read(len))
56
return
'
.
'
.join(list)
57
58
def
_serialize_domain_name(self, data):
59
list
=
self.name.split(
'
.
'
)
60
for
i
in
list:
61
data.write(struct.pack(
'
!B%ss
'
%
(len(i)), len(i), i))
62
data.write(struct.pack(
'
!B
'
, 0))
63
64
def
__str__
(self):
65
return
'
{name: %s, type: %s, class: %s}
'
%
(
66
self.name, self.type, self.clazz)
67
68
class
Answer(Query):
69
70
def
__init__
(self, name, type, clazz, ttl, data):
71
Query.
__init__
(self, name, type, clazz)
72
self.ttl
=
ttl
73
self.data
=
data
74
75
def
serialize(self, data):
76
data.write(struct.pack(
'
!2B
'
,
0xc0
,
0x0c
))
77
data.write(struct.pack(
'
!2HI
'
, self.type, self.clazz, self.ttl))
78
#
only support A
79
if
self.type
==
Answer.TYPE_A:
80
addr
=
self.data.split(
'
.
'
)
81
data.write(struct.pack(
'
!H
'
, len(addr)))
82
for
i
in
addr:
83
data.write(struct.pack(
'
!B
'
, int(i)))
84
85
def
__str__
(self):
86
return
'
{name: %s, type: %s, class: %s, ttl: %s, data_length: %s, data: %s}
'
%
(
87
self.name, self.type, self.clazz, self.ttl, self.data_length, self.data)
88
89
class
DnsRequest(object):
90
91
def
__init__
(self, header, queries
=
[]):
92
self.header
=
header
93
self.queries
=
queries
94
95
@classmethod
96
def
parse(cls, data):
97
header
=
Header.parse(data)
98
queries
=
[]
99
for
_
in
range(header.questions):
100
queries.append(Query.parse(data))
101
return
DnsRequest(header, queries)
102
103
def
serialize(self):
104
data
=
StringIO()
105
self.header.serialize(data)
106
for
i
in
range(len(self.queries)):
107
self.queries[i].serialize(data)
108
data.seek(0)
109
return
data.read()
110
111
def
__str__
(self):
112
return
'
{header: %s, queries: %s}
'
%
(
113
self.header, str(self.queries))
114
115
class
DnsResponse(object):
116
117
def
__init__
(self, header, queries
=
[], answers
=
[]):
118
self.header
=
header
119
self.queries
=
queries
120
self.answers
=
answers
121
122
def
serialize(self):
123
data
=
StringIO()
124
self.header.serialize(data)
125
for
q
in
self.queries:
126
q.serialize(data)
127
for
a
in
self.answers:
128
a.serialize(data)
129
data.seek(0)
130
return
data.read()
from
SocketServer
import
BaseRequestHandler, ThreadingUDPServer
2
from
cStringIO
import
StringIO
3
from
protocol
import
DnsRequest, Answer, DnsResponse
4
import
socket
5
import
time
6
7
DEBUG
=
True
8
9
class
Hosts:
10
11
def
__init__
(self, hosts_file):
12
self.hosts
=
[]
13
list
=
[line.strip()
for
line
in
open(hosts_file)
if
line.strip()
!=
''
and
not
line.strip().startswith(
'
#
'
)]
14
for
l
in
list:
15
info
=
l.split()
16
self.hosts.extend([(h, info[0])
for
h
in
info[
1
:]])
17
18
def
get_ip(self, domain):
19
for
host
in
self.hosts:
20
if
host[0].startswith(
'
*
'
):
21
if
domain.endswith(host[0][
2
:]):
22
return
host[
1
]
23
else
:
24
if
host[0]
==
domain:
25
return
host[
1
]
26
return
None
27
28
class
Dns:
29
def
__init__
(self, server):
30
self.server
=
server
31
32
def
query(self, data):
33
sock
=
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
34
sock.connect(self.server)
35
sock.sendall(data)
36
resp
=
sock.recv(
65535
)
37
sock.close()
38
return
resp
39
40
class
DnsProxyHandler(BaseRequestHandler):
41
def
handle(self):
42
data, sock
=
self.request
43
hosts
=
self.server.hosts
44
45
req
=
DnsRequest.parse(StringIO(data))
46
domain
=
req.queries[0].name
47
48
ip
=
hosts.get_ip(domain)
49
if
ip:
50
self.log(
'
%s -- [%s] %s %s %s
'
%
(self.client_address[0], time.ctime(), domain,
'
Found
'
, ip))
51
header
=
req.header
52
header.answer_rrs
=
1
53
query
=
req.queries[0]
54
answer
=
Answer(domain, Answer.TYPE_A, Answer.CLASS_IN,
60
, ip)
55
resp
=
DnsResponse(header, [query], [answer]).serialize()
56
else
:
57
self.log(
'
%s -- [%s] %s %s
'
%
(self.client_address[0], time.ctime(), domain,
'
Not Found
'
))
58
resp
=
Dns(self.server.dns_server).query(data)
59
sock.sendto(resp, self.client_address)
60
61
def
log(self, message):
62
global
DEBUG
63
if
DEBUG:
64
print
message
65
66
67
class
DnsProxyServer(ThreadingUDPServer):
68
def
__init__
(self, local_server, dns_server, hosts
=
'
/ets/hosts
'
):
69
self.local_server
=
local_server
70
self.dns_server
=
dns_server
71
self.hosts
=
Hosts(hosts)
72
ThreadingUDPServer.
__init__
(self, local_server, DnsProxyHandler)
73
74
DnsProxyServer((
'
127.0.0.1
'
,
53
), (
'
10.20.30.40
'
,
53
),
'
/home/stone/tmp/hosts
'
).serve_forever()
2 from cStringIO import StringIO
3
4 class Header(object):
5
6 def __init__ (self, id, flags, questions, answer_rrs, authority_rrs, additional_rrs):
7 self.id = id
8 self.flags = flags
9 self.questions = questions
10 self.answer_rrs = answer_rrs
11 self.authority_rrs = authority_rrs
12 self.additional_rrs = additional_rrs
13
14 @classmethod
15 def parse(cls, data):
16 id, flags, questions, answer_rrs, authority_rrs, additional_rrs = struct.unpack( ' !6H ' , data.read( 12 ))
17 return Header(id, flags, questions, answer_rrs, authority_rrs, additional_rrs)
18
19 def serialize(self, data):
20 data.write(struct.pack( ' !6H ' , self.id, self.flags, self.questions, self.answer_rrs, self.authority_rrs, self.additional_rrs))
21
22 def __str__ (self):
23 return ' {id: %s, flags: %s, questions: %s, answer_rrs: %s, authority_rrs: %s, additional_rrs: %s} ' % (
24 self.id, self.flags, self.questions, self.answer_rrs, self.authority_rrs, self.additional_rrs)
25
26 class Query(object):
27
28 TYPE_A = 1
29 TYPE_AAAA = 28
30 CLASS_IN = 1
31
32 def __init__ (self, name, type, clazz):
33 self.name = name
34 self.type = type
35 self.clazz = clazz
36
37 @classmethod
38 def parse(cls, data):
39 domain = cls._parse_domain_name(data)
40 type, clazz = struct.unpack( ' !2H ' , data.read( 4 ))
41 return Query(domain, type, clazz)
42
43 def serialize(self, data):
44 self._serialize_domain_name(data)
45 data.write(struct.pack( ' !2H ' , self.type, self.clazz))
46
47 @staticmethod
48 def _parse_domain_name(data):
49 list = []
50 while True:
51 tmp = data.read( 1 )
52 len = ord(tmp)
53 if len == 0:
54 break
55 list.append(data.read(len))
56 return ' . ' .join(list)
57
58 def _serialize_domain_name(self, data):
59 list = self.name.split( ' . ' )
60 for i in list:
61 data.write(struct.pack( ' !B%ss ' % (len(i)), len(i), i))
62 data.write(struct.pack( ' !B ' , 0))
63
64 def __str__ (self):
65 return ' {name: %s, type: %s, class: %s} ' % (
66 self.name, self.type, self.clazz)
67
68 class Answer(Query):
69
70 def __init__ (self, name, type, clazz, ttl, data):
71 Query. __init__ (self, name, type, clazz)
72 self.ttl = ttl
73 self.data = data
74
75 def serialize(self, data):
76 data.write(struct.pack( ' !2B ' , 0xc0 , 0x0c ))
77 data.write(struct.pack( ' !2HI ' , self.type, self.clazz, self.ttl))
78 # only support A
79 if self.type == Answer.TYPE_A:
80 addr = self.data.split( ' . ' )
81 data.write(struct.pack( ' !H ' , len(addr)))
82 for i in addr:
83 data.write(struct.pack( ' !B