Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
log/*
*.log

# process id file
*.pid

# swap
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ smartdns
========
## 使用场景
##### smartdns是python语言编写,基于twisted框架实现的dns server,能够支持针对不同的dns请求根据配置返回不同的解析结果。smartdns获取dns请求的源IP或者客户端IP(支持edns协议的请求可以获取客户端IP),根据本地的静态IP库获取请求IP的特性,包括所在的国家、省份、城市、ISP等,然后根据我们的调度配置返回解析结果。
##### smartdns的使用场景
##### smartdns的使用场景
1. 服务的多机房流量调度,比如电信流量调度到电信机房、联通流量调度到联通机房;
2. 用户访问控制,将用户调度到离用户最近或者链路质量最好的节点上。

Expand Down Expand Up @@ -76,7 +76,7 @@ git clone smartdns到本地路径,进入script目录,执行install_smartdns.

启动:

进入smartdns的bin路径下,执行sh run_dns.sh即可启动smartdns
进入smartdns的script路径下,执行sh run_dns.sh即可启动smartdns

## 测试

Expand Down
Binary file removed bin/.dnsserver.py.swp
Binary file not shown.
50 changes: 36 additions & 14 deletions bin/dnsserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ def __call__(self, failure):

class MapResolver(client.Resolver):
def __init__(self, Finder, Amapping, NSmapping, SOAmapping, servers):
logger.info("[MapResolver] Init.");
self.Finder = Finder
self.Amapping = Amapping
self.NSmapping = NSmapping
self.SOAmapping = SOAmapping
client.Resolver.__init__(self, servers=servers)

def query(self, query, timeout = None, addr = None, edns = None):
logger.info("[MapResolver] Check query type: [%s] and query name: [%s]" % (query.type, query.name))
try:
if typeToMethod[query.type] in smartType:
return self.typeToMethod[query.type](str(query.name), timeout, addr, edns)
Expand All @@ -71,30 +73,37 @@ def query(self, query, timeout = None, addr = None, edns = None):
return defer.fail(failure.Failure(NotImplementedError(str(self.__class__) + " " + str(query.type))))

def lookupAddress(self, name, timeout = None, addr = None, edns = None):
logger.info("[MapResolver] Lookup address name: [%s]" % (name))
if name in self.Amapping:
logger.info("[MapResolver] Lookup address name, found in Amapping: [%s]" % (self.Amapping[name]))
ttl = self.Amapping[name]['ttl']
# construct result
def packResult( value ):
ret = []
add = []
for x in value:
ret.append(dns.RRHeader(name, dns.A, dns.IN, ttl, dns.Record_A(x, ttl), True))

if edns is not None:
if edns.rdlength > 8:
add.append(dns.RRHeader('', dns.EDNS, 4096, edns.ttl, edns.payload, True))
else:
else:
add.append(dns.RRHeader('', dns.EDNS, 4096, 0, dns.Record_EDNS(None, 0), True))
return [ret, (), add]

result = self.Finder.FindIP(str(addr[0]), name)

result = self.Finder.FindIP2(str(addr[0]), name)
logger.info("[MapResolver] Found ip:[%s] and its information: [%s]" % (str(addr[0]), result))
#返回的IP数组乱序
random.shuffle(result)
return packResult(result)
else:
logger.info("[MapResolver] Lookup address name, not found in Amapping.")
return self._lookup(name, dns.IN, dns.A, timeout)

def lookupNameservers(self, name, timeout=None):
logger.info("[MapResolver] Lookup name server: [%s]" % (name))
if name in self.NSmapping:
logger.info("[MapResolver] Lookup name server, found in NSmapping: [%s]" % (self.NSmapping[name]))
result = self.NSmapping[name]
ttl = result['ttl']
record = re.split(ur',|\s+', result['record'])
Expand All @@ -105,41 +114,52 @@ def packResultNS(value):
return [ret, (), ()]
return packResultNS(record)
else:
logger.info("[MapResolver] Lookup name server, not found in NSmapping")
return self._lookup(name, dns.IN, dns.NS, timeout)

def lookupAuthority(self, name, timeout=None, addr = None, edns = None):
logger.info("[MapResolver] Lookup authority: [%s]" % (name))
if name in self.SOAmapping:
logger.info("[MapResolver] Lookup authority, found in SOAmapping: [%s]" % (self.SOAmapping[name]))
result = self.SOAmapping[name]
add = []
def packResultSOA(value):
if edns is not None:
if edns.rdlength > 8:
add.append(dns.RRHeader('', dns.EDNS, 4096, edns.ttl, edns.payload, True))
else:
else:
add.append(dns.RRHeader('', dns.EDNS, 4096, 0, dns.Record_EDNS(None, 0), True))
return [(dns.RRHeader(name, dns.SOA, dns.IN, value['ttl'], dns.Record_SOA(value['record'], value['email'], value['serial'], value['refresh'], value['retry'], value['expire'], value['ttl']), True),),
(),

return [(dns.RRHeader(name, dns.SOA, dns.IN, value['ttl'], dns.Record_SOA(value['record'], value['email'], value['serial'], value['refresh'], value['retry'], value['expire'], value['ttl']), True),),
(),
add
]
ret = packResultSOA(result)
logger.info("SOA\t[domain: %s]\t[return: %s]\t[additional: %s]" % \
(name, result, add))
return ret
else:
logger.info("[MapResolver] Lookup authority, not found in SOAmapping.")
return self._lookup(name, dns.IN, dns.SOA, timeout)

def lookupIPV6Address(self, name, timeout = None, addr = None):
logger.info("[MapResolver] Lookup IPV6 address, not implemented yet.")
return [(),(),()]

def lookupCanonicalName(self, name, timeout = None, addr = None):
logger.info("[MapResolver] Lookup Canonical name, not implemented yet.")
return [(), (), ()]

class SmartResolverChain(resolve.ResolverChain):

def __init__(self, resolvers):
logger.info("[SmartResolverChain] Init.")
#resolve.ResolverChain.__init__(self, resolvers)
common.ResolverBase.__init__(self)
self.resolvers = resolvers

def _lookup(self, name, cls, type, timeout, addr = None, edns = None):
logger.info("[SmartResolverChain] _lookup with name: [%s], cls: [%s], type: [%s], timeout: [%s]" % (name, cls, type, timeout))
q = dns.Query(name, type, cls)
#d = self.resolvers[0].query(q, timeout)
d = defer.fail(failure.Failure(dns.DomainError(name)))
Expand All @@ -150,6 +170,7 @@ def _lookup(self, name, cls, type, timeout, addr = None, edns = None):
return d

def query(self, query, timeout = None, addr = None, edns = None):
logger.info("[SmartResolverChain] query with query: [%s]" % (query))
try:
if typeToMethod[query.type] in smartType:
return self.typeToMethod[query.type](str(query.name), timeout, addr, edns)
Expand All @@ -166,12 +187,13 @@ def lookupAuthority(self, name, timeout=None, addr = None, edns = None):

def lookupIPV6Address(self, name, timeout = None, addr = None, edns = None):
return self._lookup(name, dns.IN, dns.AAAA, timeout, addr, edns)

def lookupNameservers(self, name, timeout = None, addr = None, edns = None):
return self._lookup(name, dns.IN, dns.NS, timeout, addr, edns)

class SmartDNSFactory(server.DNSServerFactory):
def handleQuery(self, message, protocol, address):
logger.info("[SmartDNSFactory] handleQuery with message: [%s], protocol: [%s] and address: [%s]" % (message.queries[0], protocol, address));
#if len(message.additional) > 0:
# print inspect.getmembers(message.additional[0]
# 可以支持多个query
Expand All @@ -186,15 +208,16 @@ def handleQuery(self, message, protocol, address):
and message.additional[0].rdlength > 8:
cliAddr = (message.additional[0].payload.dottedQuad(), 0)
edns = message.additional[0]
logger.info("[type: %s]\t[protocol: %s]\t[query: %s]\t[address: %s]\t[dns_server_addr: %s]\t[additional: %s]" % \
(typeToMethod[query.type], type(protocol), query, cliAddr[0], address[0], edns))
logger.info("[type: %s]\t[protocol: %s]\t[query: %s]\t[address: %s]\t[dns_server_addr: %s]\t[additional: %s]" % (typeToMethod[query.type], type(protocol), query, cliAddr[0], address[0], edns))

return self.resolver.query(query, addr = cliAddr, edns = edns).addCallback(
self.gotResolverResponse, protocol, message, address
).addErrback(
self.gotResolverError, protocol, message, address
)

def __init__(self, authorities = None, caches = None, clients = None, verbose = 0):
logger.info("[SmartDNSFactory] Init.")
resolvers = []
if authorities is not None:
resolvers.extend(authorities)
Expand All @@ -209,4 +232,3 @@ def __init__(self, authorities = None, caches = None, clients = None, verbose =
if caches:
self.cache = caches[-1]
self.connections = []

Binary file removed bin/dnsserver.pyc
Binary file not shown.
53 changes: 34 additions & 19 deletions bin/ippool.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import sys, os
import requests
from logger import logger
import time
import bisect
Expand All @@ -11,25 +12,25 @@
reload(sys)
sys.setdefaultencoding('utf8')


def ip2long(ip):
"convert decimal dotted quad string to long integer"
hexn = ''.join(["%02X" % long(i) for i in ip.split('.')])
"convert decimal dotted quad string to long integer"
hexn = ''.join(["%02X" % long(i) for i in ip.split('.')])
return long(hexn, 16)

def long2ip(n):
"convert long int to dotted quad string"
d = 256 * 256 * 256
def long2ip(n):
"convert long int to dotted quad string"
d = 256 * 256 * 256
q = []
while d > 0:
m,n = divmod(n,d)
q.append(str(m))
d = d/256
while d > 0:
m,n = divmod(n,d)
q.append(str(m))
d = d/256
return '.'.join(q)


class IPPool:
def __init__(self, ipfile, recordfile):
logger.info("[IPPool] Init.");
if not isfile(ipfile):
logger.warning("can't find ip data file: %s" % ipfile)
# 故意返回数据,另程序退出
Expand All @@ -47,25 +48,26 @@ def __init__(self, ipfile, recordfile):
self.iphash = {}

#初始化存储a.yaml配置
self.record = {}
self.record = {}
# 存储各个域名的地域对于ip信息
self.locmapip = {}

#load record data
self.LoadRecord()

#load ip data
self.LoadIP()

print 'Init IP pool finished !'

def LoadIP(self):
logger.info("[IPPool] LoadIP.");
f = open(self.ipfile, 'r')
logger.warning("before load: %s" % ( time.time() ) )
for eachline in f:
ipstart, ipend, country, province, city, sp = eachline.strip().split(',')
ipstart = long(ipstart)
ipend = long(ipend)
ipend = long(ipend)

#如果ip地址为0,忽略
if 0 == ipstart:
Expand All @@ -78,14 +80,15 @@ def LoadIP(self):
#ipstart, ipend, country, province, city, sp, domain ip hash
self.iphash[ipstart] = [ipstart, ipend, country, province, city, sp, {}]
# 最好合并后再计算
self.JoinIP(ipstart)
self.JoinIP(ipstart)
f.close()
logger.warning("after load: %s" % ( time.time() ) )
self.iplist.sort()
logger.warning("after sort: %s" % ( time.time() ) )

# 重写LoadRecord和JoinIP,提升启动效率
def LoadRecord(self):
logger.info("[IPPool] LoadRecord.");
Add = [8, 4, 2, 1]
f = open(self.recordfile, 'r')
self.record = yaml.load(f)
Expand All @@ -109,7 +112,7 @@ def LoadRecord(self):
match[num] = p[num]
if p[num] != "":
weight += Add[num]

if match[0] not in self.locmapip[fqdn]:
self.locmapip[fqdn][match[0]] = {}
self.locmapip[fqdn][match[0]][match[1]] = {}
Expand Down Expand Up @@ -166,12 +169,14 @@ def JoinIP(self, ip):
self.iphash[ip][6][fqdnk] = [self.record[fqdnk]['default'], 0]

def ListIP(self):
logger.info("[IPPool] ListIP.");
for key in self.iphash:
print "ipstart: %s ipend: %s country: %s province: %s city: %s sp: %s" % (key, self.iphash[key][1], self.iphash[key][2], self.iphash[key][3], self.iphash[key][4], self.iphash[key][5])
logger.info("ipstart: %s ipend: %s country: %s province: %s city: %s sp: %s" % (key, self.iphash[key][1], self.iphash[key][2], self.iphash[key][3], self.iphash[key][4], self.iphash[key][5]))
for i in self.iphash[key][6]:
print "[domain:%s ip: %s]" % (i, self.iphash[key][6][i][0])
logger.info("[domain:%s ip: %s]" % (i, self.iphash[key][6][i][0]))

def SearchLocation(self, ip):
logger.info("[IPPool] SearchLocation with ip: [%s]." % (ip));
ipnum = ip2long(ip)
ip_point = bisect.bisect_right(self.iplist, ipnum)
i = self.iplist[ip_point - 1]
Expand All @@ -183,6 +188,7 @@ def SearchLocation(self, ip):
return i, j, ipnum

def FindIP(self, ip, name):
logger.info("[IPPool] FindIP with ip: [%s] and name: [%s]." % (ip, name));
i, j, ipnum = self.SearchLocation(ip)

if i in self.iphash:
Expand All @@ -207,6 +213,15 @@ def FindIP(self, ip, name):
logger.warning("can't find ip in iphash, ip:[%s] domain:[%s] ip_list:%s" % (ip, name, ip_list))
return ip_list

def FindIP2(self, ip, name):
logger.info("[IPPool] FindIP2 with ip: [%s] and name: [%s]." % (ip, name))
response = requests.get("http://ipinfo.io/%s/json" % (ip))
if response.status_code == 200:
logger.info("[IPPool] FindIP2 found ip infomation: %s" % response.json())
else:
logger.error("[IPPool] Failed to find ip: [%s] from http://ipinfo.io." % ip)

return self.FindIP(ip, name)

if __name__ == '__main__':
ipcheck = IPPool('../data/ip.csv', '../conf/a.yaml')

Binary file removed bin/ippool.pyc
Binary file not shown.
Binary file removed bin/logger.pyc
Binary file not shown.
Binary file removed conf/.a.yaml.swp
Binary file not shown.
4 changes: 4 additions & 0 deletions conf/a.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ test.test.com:
美国,,,: 1.1.1.10 3.3.3.10
马来西亚,,,: 1.1.1.11 3.3.3.11
土耳其,,,: 1.1.1.12 3.3.3.12

a.b.com:
ttl: 2000
default: 11.11.11.11 12.12.12.12
1 change: 0 additions & 1 deletion conf/sdns.pid

This file was deleted.

Binary file removed lib/yaml/__init__.pyc
Binary file not shown.
Binary file removed lib/yaml/composer.pyc
Binary file not shown.
Binary file removed lib/yaml/constructor.pyc
Binary file not shown.
Binary file removed lib/yaml/cyaml.pyc
Binary file not shown.
Binary file removed lib/yaml/dumper.pyc
Binary file not shown.
Binary file removed lib/yaml/emitter.pyc
Binary file not shown.
Binary file removed lib/yaml/error.pyc
Binary file not shown.
Binary file removed lib/yaml/events.pyc
Binary file not shown.
Binary file removed lib/yaml/loader.pyc
Binary file not shown.
Binary file removed lib/yaml/nodes.pyc
Binary file not shown.
Binary file removed lib/yaml/parser.pyc
Binary file not shown.
Binary file removed lib/yaml/reader.pyc
Binary file not shown.
Binary file removed lib/yaml/representer.pyc
Binary file not shown.
Binary file removed lib/yaml/resolver.pyc
Binary file not shown.
Binary file removed lib/yaml/scanner.pyc
Binary file not shown.
Binary file removed lib/yaml/serializer.pyc
Binary file not shown.
Binary file removed lib/yaml/tokens.pyc
Binary file not shown.
Binary file added pkg/requests-v2.10.0-27.tar.gz
Binary file not shown.
9 changes: 6 additions & 3 deletions script/install_smartdns.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,29 @@ filepath=$(cd "$(dirname "$0")"; pwd)

cd $filepath/../pkg
tar zxf virtualenv-1.9.1.tar.gz
tar zxf requests-v2.10.0-27.tar.gz
tar zxf zope.interface-4.0.1.tar.gz
tar jxf Twisted-12.2.0.tar.bz2

cd virtualenv-1.9.1
python virtualenv.py $filepath/../../smartdns_env
. $filepath/../../smartdns_env/bin/activate

cd ../kennethreitz-requests-8813787
python setup.py install

cd ../zope.interface-4.0.1
python setup.py install

cd ../Twisted-12.2.0
python setup.py install

cd .. && rm -rf virtualenv-1.9.1 zope.interface-4.0.1 Twisted-12.2.0
cd .. && rm -rf virtualenv-1.9.1 kennethreitz-requests-8813787 zope.interface-4.0.1 Twisted-12.2.0

dnsfilenum=`find $filepath/../../smartdns_env/lib/python*/site-packages/Twisted-12.2.0*/twisted/names -name dns.py | wc -l`
if [ 1 -ne $dnsfilenum ]; then
echo "cannot find dns.py"
exit 2
fi
dnsfile=`find $filepath/../../smartdns_env/lib/python*/site-packages/Twisted-12.2.0*/twisted/names -name dns.py`
cp -f $filepath/dns_in_twisted.py $dnsfile