├── .gitignore ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── PRCDNS ├── __init__.py ├── domain_cache.py ├── package_data.dat ├── proxy_client.py └── server.py ├── README ├── README.md ├── data └── data_file ├── run.sh ├── setup.cfg ├── setup.py ├── tests ├── __init__.py └── test_query.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | # Created by .ignore support plugin (hsz.mobi) 4 | ### Python template 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv/ 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | 96 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.9 2 | 3 | ENV HOST "0.0.0.0" 4 | ENV PORT "3535" 5 | 6 | 7 | RUN apk update && \ 8 | apk add python3 alpine-sdk python3-dev libffi-dev curl && \ 9 | pip3 install 'aiohttp<3.0' PRCDNS && \ 10 | apk del alpine-sdk python3-dev libffi-dev 11 | 12 | ADD ./run.sh / 13 | WORKDIR / 14 | 15 | CMD ["sh", "-x", "/run.sh"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the license file 2 | include LICENSE 3 | 4 | # Include the data files 5 | recursive-include data * 6 | 7 | # If using Python 2.6 or less, then have to include package data, even though 8 | # it's already declared in setup.py 9 | # include sample/*.dat 10 | -------------------------------------------------------------------------------- /PRCDNS/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbp0200/PRCDNS/6dc6c2fd3c3a933b4a331fc55c77e62ae624ca17/PRCDNS/__init__.py -------------------------------------------------------------------------------- /PRCDNS/domain_cache.py: -------------------------------------------------------------------------------- 1 | class DomainCache: 2 | def read(self): 3 | pass 4 | -------------------------------------------------------------------------------- /PRCDNS/package_data.dat: -------------------------------------------------------------------------------- 1 | some data -------------------------------------------------------------------------------- /PRCDNS/proxy_client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiohttp 3 | 4 | 5 | class ProxyClient: 6 | """Google DNS Client""" 7 | proxy = None 8 | 9 | # def __init__(self, proxy=None): 10 | # self.proxy = proxy 11 | 12 | @staticmethod 13 | async def fetch(session, url, proxy=None): 14 | with aiohttp.Timeout(10): 15 | # http://127.0.0.1:8123 16 | async with session.get(url, proxy=proxy) as response: 17 | return await response.text() 18 | 19 | @staticmethod 20 | async def query_domain(url, proxy=None): 21 | async with aiohttp.ClientSession() as session: 22 | return await ProxyClient.fetch(session, url, proxy) 23 | 24 | @staticmethod 25 | async def get(loop, url): 26 | async with aiohttp.ClientSession(loop=loop) as session: 27 | return await ProxyClient.fetch(session, url) 28 | 29 | @staticmethod 30 | def get_url(url, proxy=None): 31 | loop = asyncio.get_event_loop() 32 | return loop.run_until_complete(ProxyClient.get(loop, url)) 33 | -------------------------------------------------------------------------------- /PRCDNS/server.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import functools 4 | import json 5 | 6 | from dnslib import * 7 | 8 | from PRCDNS.proxy_client import ProxyClient 9 | 10 | 11 | class DNSServerProtocol(asyncio.Protocol): 12 | args = None 13 | gFuncs = globals() 14 | peername = None 15 | loop = None 16 | request = None 17 | 18 | def __init__(self, args, loop): 19 | self.args = args 20 | self.loop = loop 21 | 22 | def get_data(self, data): 23 | l = len(data) 24 | if l == 2: 25 | return 26 | sz = struct.unpack(">H", data[:2])[0] 27 | if sz < l - 2: 28 | raise Exception("Wrong size of TCP packet") 29 | elif sz > l - 2: 30 | return data 31 | # raise Exception("Too big TCP packet") 32 | return data[2:] 33 | 34 | def connection_made(self, transport): 35 | self.peername = transport.get_extra_info('peername') 36 | if self.args.debug: 37 | print('Connection from {}'.format(self.peername)) 38 | self.transport = transport 39 | 40 | def data_received(self, data): 41 | data = self.get_data(data) 42 | if data is None: 43 | self.transport.close() 44 | return 45 | self.request = DNSRecord.parse(data) 46 | if self.args.debug: 47 | print('Data received: {!r}'.format(self.request)) 48 | 49 | from IPy import IP 50 | ip = IP(self.peername[0]) 51 | client_ip = self.peername[0] 52 | if ip.iptype() == 'PRIVATE': 53 | client_ip = self.args.myip 54 | 55 | url = 'https://dns.google.com/resolve?name={}&edns_client_subnet={}/24'.format(str(self.request.q.qname), 56 | client_ip) 57 | # client = ProxyClient() 58 | # google_dns_resp = client.query_domain(url, self.args.proxy) 59 | try: 60 | from asyncio import ensure_future 61 | except ImportError: 62 | from asyncio import async as ensure_future 63 | 64 | asyncio.ensure_future(ProxyClient.query_domain(url, self.args.proxy), loop=self.loop).add_done_callback( 65 | functools.partial(self.send_resp)) 66 | 67 | def send_resp(self, fut): 68 | google_dns_resp = fut.result() 69 | # google_dns_resp = '{"Status": 0,"TC": false,"RD": true,"RA": true,"AD": false,"CD": false,"Question":[ {"name": "img.alicdn.com.","type": 1}],"Answer":[ {"name": "img.alicdn.com.","type": 5,"TTL": 21557,"data": "img.alicdn.com.danuoyi.alicdn.com."},{"name": "img.alicdn.com.danuoyi.alicdn.com.","type": 1,"TTL": 59,"data": "111.32.130.109"},{"name": "img.alicdn.com.danuoyi.alicdn.com.","type": 1,"TTL": 59,"data": "111.32.130.108"}],"Additional":[],"edns_client_subnet": "223.72.90.0/24","Comment": "Response from danuoyinewns1.gds.alicdn.com.(121.43.18.33)"}' 70 | if self.args.debug: 71 | print('from: {};response: {}'.format(self.peername[0], google_dns_resp)) 72 | resp = json.loads(google_dns_resp) 73 | a = self.request.reply() 74 | if resp['Status'] == 0 and 'Answer' in resp: 75 | for answer in resp['Answer']: 76 | qTypeFunc = QTYPE[answer['type']] 77 | a.add_answer(RR(answer['name'], answer['type'], rdata=self.gFuncs[qTypeFunc](answer['data']), 78 | ttl=answer['TTL'])) 79 | # elif resp['Status'] == 3 and 'Authority' in resp: 80 | # for answer in resp['Authority']: 81 | # qTypeFunc = QTYPE[answer['type']] 82 | # a.add_answer(RR(answer['name'], answer['type'], rdata=self.gFuncs[qTypeFunc](answer['data']), 83 | # ttl=answer['TTL'])) 84 | else: 85 | self.transport.close() 86 | return 87 | if self.args.debug: 88 | print('Send: {!r}'.format(a)) 89 | b_resp = a.pack() 90 | b_resp = struct.pack(">H", b_resp.__len__()) + b_resp 91 | self.transport.write(b_resp) 92 | self.transport.close() 93 | 94 | 95 | def get_arg(): 96 | """解析参数""" 97 | parser = argparse.ArgumentParser(prog='prcdns', description='google dns proxy.') 98 | parser.add_argument('--debug', help='debug model,default NO', default=False) 99 | parser.add_argument('-l', '--listen', help='listening IP,default 0.0.0.0', default='0.0.0.0') 100 | parser.add_argument('-p', '--port', help='listening Port,default 3535', default=3535) 101 | parser.add_argument('-r', '--proxy', help='Used For Query Google DNS,default direct', default=None) 102 | parser.add_argument('-ip', '--myip', help='IP location', default=None) 103 | 104 | return parser.parse_args() 105 | 106 | 107 | def main(): 108 | args = get_arg() 109 | if args.proxy is None: # 无代理,在PRC局域网外 110 | myip = ProxyClient.get_url('http://ipinfo.io/json') 111 | myip = json.loads(myip) 112 | args.myip = myip['ip'] 113 | else: # 用代理,在PRC局域网内 114 | if args.myip is None: 115 | myip = ProxyClient.get_url('http://ip.taobao.com/service/getIpInfo.php?ip=myip') 116 | print(myip) 117 | myip = json.loads(myip) 118 | myip = myip['data']['ip'] 119 | args.myip = myip 120 | 121 | loop = asyncio.get_event_loop() 122 | loop.set_debug(args.debug) 123 | 124 | # Each client connection will create a new protocol instance 125 | coro = loop.create_server(lambda: DNSServerProtocol(args, loop), args.listen, args.port) 126 | server = loop.run_until_complete(coro) 127 | 128 | try: 129 | print("public ip is {}".format(args.myip)) 130 | print("listen on {0}:{1}".format(args.listen, args.port)) 131 | loop.run_forever() 132 | except KeyboardInterrupt: 133 | pass 134 | 135 | print('Close Server') 136 | server.close() 137 | loop.run_until_complete(server.wait_closed()) 138 | loop.close() 139 | 140 | 141 | if __name__ == "__main__": 142 | main() 143 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | # PRCDNS 2 | 这是一个Google DNS代理 3 | 不同之处在于CDN友好,根据你的IP返回最优的解析结果 4 | 5 | ### 原理 6 | [DNS-over-HTTPS API](https://developers.google.com/speed/public-dns/docs/dns-over-https) 7 | 它支持edns_client_subnet,把你的IP作为参数提交,它会返回最优的解析结果,所以说它 8 | 是我见过的最完美的DNS解决方案。 9 | 10 | ### 注意事项:PRCDNS前面一定要放pdnsd或者unbound 11 | 1. PRCDNS**只支持TCP查询** 12 | 2. PRCDNS**没有缓存** 13 | 14 | 很多二级运营商为了节省成本,减少外网之间的带宽结算费用,对DNS查询做了重点照顾, 15 | 防止用户使用114、百度、阿里的公共DNS,强制用户将某些流量大的域名指向它的缓存服务器, 16 | 于是UDP成了重灾区,目前TCP没事,114已经支持TCP,百度、阿里还不行。PRCDNS前面放 17 | pdnsd、unbound,即解决了缓存问题,又解决了UDP的问题 18 | 19 | ### 安装 20 | 基于Python 3.5 21 | ```bash 22 | sudo pip3 install PRCDNS 23 | ``` 24 | 25 | ### 使用 26 | 27 | 1. 如果你把PRCDNS部署到VPS上,和SS做邻居,这样在家、公司都能用 28 | ```python 29 | PRCDNS 30 | ``` 31 | 2. 如果把PRCDNS部署到自己本地的机器或者路由器上,请将SS通过polipo转为http类型,以便于PRCDNS可以访问https://developers.google.com 32 | ```python 33 | PRCDNS -r http://127.0.0.1:8123 34 | ``` 35 | 36 | ### 参数 37 | ```bash 38 | --debug 调试模式 选填 默认false 39 | -l 监听IP 选填 默认0.0.0.0 40 | -p 监听端口 选填 默认3535 41 | -r http_proxy 如果PRCDNS可以访问https://developers.google.com就不用填写 42 | ``` 43 | 欢迎通过Issue讨论、提问和给予指导 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PRCDNS 2 | 这是一个Google DNS代理 3 | 不同之处在于CDN友好,根据你的IP返回最优的解析结果 4 | 5 | 基于Python2.7另外开发了一个[prc-dns](https://github.com/lbp0200/prc-dns),新增IPV6、脱离必须使用代理的限制,支持UDP和TCP。 6 | 7 | ### 原理 8 | [DNS-over-HTTPS API](https://developers.google.com/speed/public-dns/docs/dns-over-https) 9 | 它支持edns_client_subnet,把你的IP作为参数提交,它会返回最优的解析结果,所以说它 10 | 是我见过的最完美的DNS解决方案。 11 | 12 | ### 测试 13 | ```bash 14 | #本人北京联通,对比OPENDNS进行测试,证明PRCDNS是CDN友好的 15 | #23.106.151.177:3535 是我搭建的测试地址,遇到攻击可能会关闭 16 | #seaof-153-125-234-242.jp-tokyo-12.arukascloud.io:31910 部署在日本樱花docker 17 | #208.67.222.222:443 OPENDNS 18 | dig @23.106.151.177 +tcp -p 3535 google.com.hk 19 | dig @208.67.222.222 +tcp -p 443 google.com.hk 20 | 21 | dig @23.106.151.177 +tcp -p 3535 img.alicdn.com #123.125.18.108北京联通 22 | dig @seaof-153-125-234-242.jp-tokyo-12.arukascloud.io +tcp -p 31910 img.alicdn.com #123.125.18.108北京联通 23 | dig @208.67.222.222 +tcp -p 443 img.alicdn.com #69.192.12.15香港 24 | ``` 25 | 26 | ### 注意事项:PRCDNS前面一定要放pdnsd或者unbound 27 | 1. PRCDNS**只支持TCP查询** 28 | 2. PRCDNS**没有缓存** 29 | 30 | 很多二级运营商为了节省成本,减少外网之间的带宽结算费用,对DNS查询做了重点照顾, 31 | 防止用户使用114、百度、阿里的公共DNS,强制用户将某些流量大的域名指向它的缓存服务器, 32 | 于是UDP成了重灾区,目前TCP没事,114已经支持TCP,百度、阿里还不行。PRCDNS前面放 33 | pdnsd、unbound,即解决了缓存问题,又解决了UDP的问题 34 | 35 | ### 安装 36 | 基于Python 3.5 37 | ```bash 38 | sudo pip3 install PRCDNS 39 | ``` 40 | 41 | ### 使用 42 | 43 | 如果你把PRCDNS部署到VPS上,和SS做邻居,这样在家、公司都能用 44 | ```bash 45 | PRCDNS 46 | ``` 47 | 如果把PRCDNS部署到自己本地的机器或者路由器上,请将SS通过polipo转为http类型,以便于PRCDNS可以访问https://developers.google.com 48 | ```bash 49 | PRCDNS -r http://127.0.0.1:8123 50 | ``` 51 | 请使用Supervisor保证PRCDNS一直运行 52 | 53 | ### 参数 54 | ```bash 55 | --debug 调试模式 选填 默认false 56 | -l 监听IP 选填 默认0.0.0.0 57 | -p 监听端口 选填 默认3535 58 | -r http_proxy 如果PRCDNS可以访问https://developers.google.com就不用填写 59 | ``` 60 | 欢迎通过Issue讨论、提问和给予指导 61 | 62 | ### 更多文档 63 | [wiki](https://github.com/lbp0200/PRCDNS/wiki) 64 | 65 | ### docker 方式运行 66 | 1. build: `docker build -t prcdns:latest .` 67 | 2. run: `docker run -p 3535:3535 -d --name prcdns prcdns:latest` 68 | -------------------------------------------------------------------------------- /data/data_file: -------------------------------------------------------------------------------- 1 | some data -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | 2 | PRCDNS -l $HOST -p $PORT 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | """ 3 | 4 | # Always prefer setuptools over distutils 5 | from codecs import open 6 | from os import path 7 | 8 | from setuptools import setup, find_packages 9 | 10 | here = path.abspath(path.dirname(__file__)) 11 | 12 | # Get the long description from the README file 13 | with open(path.join(here, 'README'), encoding='utf-8') as f: 14 | long_description = f.read() 15 | 16 | setup( 17 | name='PRCDNS', 18 | 19 | # Versions should comply with PEP440. For a discussion on single-sourcing 20 | # the version across setup.py and the project code, see 21 | # https://packaging.python.org/en/latest/single_source_version.html 22 | version='0.0.9', 23 | 24 | description='准确、CDN友好的DNS软件,使用DNS-Over-HTTP', 25 | long_description=long_description, 26 | 27 | # The project's main homepage. 28 | url='https://github.com/lbp0200/PRCDNS', 29 | 30 | # Author details 31 | author='lbp0200', 32 | author_email='lbp0408@gmail.com', 33 | 34 | # Choose your license 35 | license='unlicense', 36 | 37 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 38 | classifiers=[ 39 | # How mature is this project? Common values are 40 | # 3 - Alpha 41 | # 4 - Beta 42 | # 5 - Production/Stable 43 | 'Development Status :: 4 - Beta', 44 | 45 | # Indicate who your project is intended for 46 | 'Intended Audience :: Developers', 47 | 'Topic :: Software Development :: Build Tools', 48 | 49 | # Pick your license as you wish (should match "license" above) 50 | 'License :: OSI Approved :: Python Software Foundation License', 51 | 52 | # Specify the Python versions you support here. In particular, ensure 53 | # that you indicate whether you support Python 2, Python 3 or both. 54 | 'Programming Language :: Python :: 3.5', 55 | 'Programming Language :: Python :: 3.6', 56 | ], 57 | 58 | # What does your project relate to? 59 | keywords='DNS', 60 | 61 | # You can just specify the packages manually here if your project is 62 | # simple. Or you can use find_packages(). 63 | packages=find_packages(exclude=['contrib', 'docs', 'tests']), 64 | 65 | # Alternatively, if you want to distribute just a my_module.py, uncomment 66 | # this: 67 | # py_modules=["my_module"], 68 | 69 | # List run-time dependencies here. These will be installed by pip when 70 | # your project is installed. For an analysis of "install_requires" vs pip's 71 | # requirements files see: 72 | # https://packaging.python.org/en/latest/requirements.html 73 | install_requires=['asyncio', 'aiohttp', 'aiodns', 'cchardet', 'dnslib', 'argparse', 'IPy'], 74 | 75 | # List additional groups of dependencies here (e.g. development 76 | # dependencies). You can install these using the following syntax, 77 | # for example: 78 | # $ pip install -e .[dev,test] 79 | extras_require={ 80 | 'dev': ['check-manifest'], 81 | 'test': ['coverage'], 82 | }, 83 | 84 | # If there are data files included in your packages that need to be 85 | # installed, specify them here. If using Python 2.6 or less, then these 86 | # have to be included in MANIFEST.in as well. 87 | package_data={ 88 | 'prcdns': ['domain_data.dat'], 89 | }, 90 | 91 | # Although 'package_data' is the preferred approach, in some case you may 92 | # need to place data files outside of your packages. See: 93 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa 94 | # In this case, 'data_file' will be installed into '/my_data' 95 | data_files=[], 96 | 97 | # To provide executable scripts, use entry points in preference to the 98 | # "scripts" keyword. Entry points provide cross-platform support and allow 99 | # pip to create the appropriate form of executable for the target platform. 100 | entry_points={ 101 | 'console_scripts': [ 102 | 'PRCDNS=PRCDNS.server:main', 103 | ], 104 | }, 105 | ) 106 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # the inclusion of the tests module is not meant to offer best practices for 2 | # testing in general, but rather to support the `find_packages` example in 3 | # setup.py that excludes installing the "tests" package 4 | -------------------------------------------------------------------------------- /tests/test_query.py: -------------------------------------------------------------------------------- 1 | # the inclusion of the tests module is not meant to offer best practices for 2 | # testing in general, but rather to support the `find_packages` example in 3 | # setup.py that excludes installing the "tests" package 4 | import asyncio 5 | import unittest 6 | 7 | from dnslib import * 8 | 9 | 10 | class TestDict(unittest.TestCase): 11 | # def test_init(self): 12 | # self.assertEqual(1, 1) 13 | # def test_demo(self): 14 | 15 | def test_dig(self): 16 | loop = asyncio.get_event_loop() 17 | 18 | tasks = [ 19 | asyncio.ensure_future(self.tcp_echo_client(loop)), 20 | asyncio.ensure_future(self.tcp_echo_client(loop)), 21 | asyncio.ensure_future(self.tcp_echo_client(loop)), 22 | asyncio.ensure_future(self.tcp_echo_client(loop)), 23 | asyncio.ensure_future(self.tcp_echo_client(loop)), 24 | ] 25 | 26 | loop.run_until_complete(asyncio.gather(*tasks)) 27 | loop.close() 28 | 29 | @asyncio.coroutine 30 | def tcp_echo_client(self, loop): 31 | reader, writer = yield from asyncio.open_connection( 32 | '127.0.0.1', 3535, 33 | # '114.114.114.114', 53, 34 | loop=loop) 35 | 36 | d = DNSRecord.question("img.alicdn.com") 37 | q = d.pack() 38 | b_req = struct.pack(">H", q.__len__()) + q 39 | writer.write(b_req) 40 | 41 | data = yield from reader.read() 42 | resp = DNSRecord.parse(data[2:]) 43 | print('Received: %r' % resp) 44 | 45 | print('Close the socket') 46 | writer.close() 47 | 48 | 49 | if __name__ == '__main__': 50 | unittest.main() 51 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # this file is *not* meant to cover or endorse the use of tox or pytest or 2 | # testing in general, 3 | # 4 | # It's meant to show the use of: 5 | # 6 | # - check-manifest 7 | # confirm items checked into vcs are in your sdist 8 | # - python setup.py check (using the readme_renderer extension) 9 | # confirms your long_description will render correctly on pypi 10 | # 11 | # and also to help confirm pull requests to this project. 12 | 13 | [tox] 14 | envlist = py{35} 15 | 16 | [testenv] 17 | basepython = 18 | py34: python3.5 19 | deps = 20 | check-manifest 21 | {py35}: readme_renderer 22 | flake8 23 | pytest 24 | commands = 25 | check-manifest --ignore tox.ini,tests* 26 | # py26 doesn't have "setup.py check" 27 | {py35}: python setup.py check -m -r -s 28 | flake8 . 29 | py.test tests 30 | [flake8] 31 | exclude = .tox,*.egg,build,data 32 | select = E,W,F 33 | --------------------------------------------------------------------------------