mirror of
https://github.com/lijiejie/eyes.sh.git
synced 2026-09-22 01:30:43 +08:00
194 lines
7.0 KiB
Python
194 lines
7.0 KiB
Python
# -*- 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()
|