-
Notifications
You must be signed in to change notification settings - Fork 92
/
zoneresolver.py
193 lines (168 loc) · 6.99 KB
/
zoneresolver.py
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# -*- coding: utf-8 -*-
import logging
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnslog.settings")
import django
django.setup()
import copy
import re
import struct
import socket
import random
from dnslib import RR, QTYPE, RCODE, TXT, A
from dnslib.server import DNSServer, DNSHandler, BaseResolver, DNSLogger
from logview.models import *
from dnslog import settings
import queue
import threading
from django.db import close_old_connections
from django.utils import timezone
q_query = queue.Queue()
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S',
filename='dns_server.log', filemode='a')
def process_log():
global q_query
while True:
try:
user_domain, ip, domain, qtype, timestamp = q_query.get()
user = User.objects.filter(user_domain__exact=user_domain)
# 有需要也可以记录下不属于任何用戶的请求记录
# 你需要创建1个用户,然后把他的 user_domain 更新为 @
# if not user and domain.strip(".") != settings.ADMIN_DOMAIN:
# user = User.objects.filter(user_domain__exact='@')
if not user:
logger.error('No such user: %s' % str(e), exc_info=True)
# 由于顺序获取客户端的IP地理位置过于耗时,大约300ms,不再顺序获取
# try:
# doc = requests.get('https://whois.pconline.com.cn/ip.jsp?ip=%s' % ip, timeout=10.0).text.strip()
# city = doc.split(' ')[0]
# except Exception as e:
# city = ''
city = ''
try:
sub_name = domain.split('.')[-3-len(settings.DNS_DOMAIN.split('.'))]
except:
sub_name = ''
for _ in range(5):
try:
log = DNSLog(user=user[0], host=domain.strip('.'), sub_name=sub_name, type=QTYPE[qtype], ip=ip,
city=city, created_time=timestamp)
log.save()
break
except django.db.utils.OperationalError as e:
logger.error('process_log.exception.1: %s' % str(e), exc_info=True)
close_old_connections()
except Exception as e:
logger.error('process_log.exception.2: %s' % str(e), exc_info=True)
except django.db.utils.OperationalError as e:
close_old_connections()
logger.error('process_log.exception.3: %s' % str(e), exc_info=True)
except Exception as e:
logger.error('process_log.exception.4: %s' % str(e))
class MySQLLogger:
def __init__(self):
pass
def log_data(self, dnsobj):
pass
def log_error(self, handler, e):
pass
def log_pass(self, *args):
pass
def log_prefix(self, handler):
pass
def log_recv(self, handler, data):
pass
def log_reply(self, handler, reply):
pass
def log_request(self, handler, request):
global q_query
if QTYPE[request.q.qtype] == 'AAAA':
return
domain = request.q.qname.__str__().lower()
if not domain.endswith(settings.DNS_DOMAIN + '.'):
return
matches = re.search(r'\.?([^\.]+)\.%s\.' % settings.DNS_DOMAIN, domain)
if not matches:
return
user_domain = matches.group(1)
ip = handler.client_address[0]
qtype = request.q.qtype
item = (user_domain, ip, domain, qtype, timezone.now())
q_query.put(item)
def log_send(self, handler, data):
pass
def log_truncated(self, handler, reply):
pass
class ZoneResolver(BaseResolver):
def __init__(self, zone, glob=False):
self.zone = [(rr.rname, QTYPE[rr.rtype], rr) for rr in RR.fromZone(zone)]
self.glob = glob
self.eq = 'matchGlob' if glob else '__eq__'
def resolve(self, request, handler):
reply = request.reply()
qname = request.q.qname
qtype = QTYPE[request.q.qtype]
if qtype == 'TXT':
reply.add_answer(RR(qname, QTYPE.TXT, rdata=TXT('Nothing to response')))
# DNS rebind
try:
if qtype == 'A' and qname.__str__().endswith(settings.DNS_DOMAIN + '.'):
subs = qname.__str__().replace('.' + settings.DNS_DOMAIN + '.', '')
ret = subs.split('.')
if len(ret) == 4 and ret[-2].lower() == 'r':
valid_ip = int(ret[0], 16)
invalid_ip = int(ret[1], 16)
valid_ip = socket.inet_ntoa(struct.pack("!I", valid_ip))
invalid_ip = socket.inet_ntoa(struct.pack("!I", invalid_ip))
ip = random.choice((valid_ip, invalid_ip))
rr = RR(qname, request.q.qtype, rdata=A(ip), ttl=0)
reply.add_answer(rr)
return reply
except Exception as e:
logger.error('DNS rebind resolve.exception: %s' % str(e), exc_info=True)
#
for name, rtype, rr in self.zone:
# Check if label & type match
if getattr(qname, self.eq)(name) and (qtype == rtype or qtype == 'ANY' or rtype == 'CNAME'):
# If we have a glob match fix reply label
if self.glob:
a = copy.copy(rr)
a.rname = qname
if qname.__str__().rstrip('.') in settings.ADMIN_DOMAIN:
a.ttl = 1200
reply.add_answer(a)
else:
reply.add_answer(rr)
# Check for A/AAAA records associated with reply and
# add in additional section
if rtype in ['CNAME', 'NS', 'MX', 'PTR']:
for a_name, a_rtype, a_rr in self.zone:
if a_name == rr.rdata.label and a_rtype in ['A', 'AAAA']:
reply.add_ar(a_rr)
if not reply.rr:
reply.header.rcode = RCODE.NXDOMAIN
return reply
def main():
zone = '''
{dns_domain}. IN NS {ns1_domain}.
{dns_domain}. IN NS {ns2_domain}.
*.{dns_domain}. IN NS {ns1_domain}.
*.{dns_domain}. IN NS {ns2_domain}.
*.{dns_domain}. IN A {server_ip}
{dns_domain}. IN A {server_ip}
*.{dns_domain}. IN AAAA 2408:871a:2100:3:0:ff:b025:348d
{dns_domain}. IN AAAA 2408:871a:2100:3:0:ff:b025:348d
'''.format(
dns_domain=settings.DNS_DOMAIN,
ns1_domain=settings.NS1_DOMAIN,
ns2_domain=settings.NS2_DOMAIN,
server_ip=settings.SERVER_IP
)
resolver = ZoneResolver(zone, True)
threading.Thread(target=process_log).start()
udp_server = DNSServer(resolver, port=53, address='', logger=MySQLLogger())
udp_server.start()
logger.info("Zone Resolver started (%s:%d) [%s]" % ("*", 53, "UDP"))
if __name__ == '__main__':
main()