├── .gitignore ├── .travis.yml ├── CHANGES ├── LICENSE ├── README.md ├── README.rst ├── config.json ├── setup.py ├── shadowdns ├── __init__.py └── dnsrelay.py ├── test.py └── tests ├── aes.json └── table.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | 29 | .DS_Store 30 | .idea 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.6 4 | - 2.7 5 | - pypy 6 | before_install: 7 | - sudo apt-get update -qq 8 | - sudo apt-get install -qq dnsutils build-essential libssl-dev swig python-m2crypto python-numpy 9 | - pip install m2crypto salsa20 shadowsocks 10 | script: 11 | - python test.py -c tests/table.json 12 | - python test.py -c tests/aes.json 13 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.1.3 2014-06-13 2 | - Update shadowsocks to 2.0.4 3 | 4 | 0.1.2 2014-06-12 5 | - Add TCP relay 6 | 7 | 0.1.1 2014-06-11 8 | - Fix table encryption 9 | 10 | 0.1 2014-06-11 11 | - Initial version 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Shadowsocks 2 | 3 | Copyright (c) 2014 clowwindy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ShadowDNS 2 | ========= 3 | 4 | [![PyPI version]][PyPI] [![Build Status]][Travis CI] 5 | 6 | A DNS forwarder using [Shadowsocks] as the server. 7 | 8 | ShadowDNS creates a DNS server at localhost. 9 | 10 | Experimental; use with caution. 11 | 12 | Install 13 | ------- 14 | 15 | #### OS X: 16 | 17 | brew install swig 18 | git clone https://github.com/clowwindy/M2Crypto.git 19 | cd M2Crypto 20 | pip install . 21 | pip install shadowdns 22 | 23 | #### Windows: 24 | 25 | Install M2Crypto (Google an M2Crypto Windows installer for your python version 26 | and install it. Might be complicated, need someone to write a help here). 27 | 28 | easy_install pip 29 | pip install shadowdns 30 | 31 | #### Debian / Ubuntu: 32 | 33 | apt-get install python-pip python-m2crypto 34 | pip install shadowdns 35 | 36 | #### CentOS: 37 | 38 | yum install m2crypto python-setuptools 39 | easy_install pip 40 | pip install shadowdns 41 | 42 | Usage 43 | ----- 44 | 45 | Create a config file `/etc/shadowdns.json` (or put it in other path). 46 | Example: 47 | 48 | { 49 | "server":"my_server_ip", 50 | "server_port":8388, 51 | "local_address": "127.0.0.1", 52 | "password":"mypassword", 53 | "method":"aes-256-cfb", 54 | "dns":"8.8.8.8" 55 | } 56 | 57 | Explanation of the fields: 58 | 59 | | Name | Explanation | 60 | | ------------- | ----------------------------------------------- | 61 | | server | the address your server listens | 62 | | server_port | server port | 63 | | local_address | the address your local listens | 64 | | password | password used for encryption | 65 | | method | encryption method, "aes-256-cfb" is recommended | 66 | | dns | DNS server to use | 67 | 68 | 69 | Run `sudo ssdns -c /etc/shadowdns.json` on your local machine. 70 | 71 | Set your DNS to 127.0.0.1. 72 | 73 | License 74 | ------- 75 | MIT 76 | 77 | Bugs and Issues 78 | ---------------- 79 | Please visit [Issue Tracker] 80 | 81 | Mailing list: http://groups.google.com/group/shadowsocks 82 | 83 | 84 | [Build Status]: https://img.shields.io/travis/clowwindy/ShadowDNS/master.svg?style=flat 85 | [Issue Tracker]: https://github.com/clowwindy/ShadowDNS/issues?state=open 86 | [PyPI]: https://pypi.python.org/pypi/shadowdns 87 | [PyPI version]: https://img.shields.io/pypi/v/shadowdns.svg?style=flat 88 | [Shadowsocks]: https://github.com/clowwindy/shadowsocks 89 | [Travis CI]: https://travis-ci.org/clowwindy/ShadowDNS 90 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ShadowDNS 2 | ========= 3 | 4 | |PyPI version| |Build Status| 5 | 6 | A DNS forwarder using 7 | `Shadowsocks `__ as the 8 | server. 9 | 10 | ShadowDNS creates a DNS server at localhost. 11 | 12 | Experimental; use with caution. 13 | 14 | Install 15 | ------- 16 | 17 | OS X: 18 | ^^^^^ 19 | 20 | :: 21 | 22 | brew install swig 23 | git clone https://github.com/clowwindy/M2Crypto.git 24 | cd M2Crypto 25 | pip install . 26 | pip install shadowdns 27 | 28 | Windows: 29 | ^^^^^^^^ 30 | 31 | Install M2Crypto (Google an M2Crypto Windows installer for your python 32 | version and install it. Might be complicated, need someone to write a 33 | help here). 34 | 35 | :: 36 | 37 | easy_install pip 38 | pip install shadowdns 39 | 40 | Debian / Ubuntu: 41 | ^^^^^^^^^^^^^^^^ 42 | 43 | :: 44 | 45 | apt-get install python-pip python-m2crypto 46 | pip install shadowdns 47 | 48 | CentOS: 49 | ^^^^^^^ 50 | 51 | :: 52 | 53 | yum install m2crypto python-setuptools 54 | easy_install pip 55 | pip install shadowdns 56 | 57 | Usage 58 | ----- 59 | 60 | Create a config file ``/etc/shadowdns.json`` (or put it in other path). 61 | Example: 62 | 63 | :: 64 | 65 | { 66 | "server":"my_server_ip", 67 | "server_port":8388, 68 | "local_address": "127.0.0.1", 69 | "password":"mypassword", 70 | "method":"aes-256-cfb", 71 | "dns":"8.8.8.8" 72 | } 73 | 74 | Explanation of the fields: 75 | 76 | +------------------+---------------------------------------------------+ 77 | | Name | Explanation | 78 | +==================+===================================================+ 79 | | server | the address your server listens | 80 | +------------------+---------------------------------------------------+ 81 | | server\_port | server port | 82 | +------------------+---------------------------------------------------+ 83 | | local\_address | the address your local listens | 84 | +------------------+---------------------------------------------------+ 85 | | password | password used for encryption | 86 | +------------------+---------------------------------------------------+ 87 | | method | encryption method, "aes-256-cfb" is recommended | 88 | +------------------+---------------------------------------------------+ 89 | | dns | DNS server to use | 90 | +------------------+---------------------------------------------------+ 91 | 92 | Run ``sudo ssdns -c /etc/shadowdns.json`` on your local machine. 93 | 94 | Set your DNS to 127.0.0.1. 95 | 96 | License 97 | ------- 98 | 99 | MIT 100 | 101 | Bugs and Issues 102 | --------------- 103 | 104 | Please visit `Issue 105 | Tracker `__ 106 | 107 | Mailing list: http://groups.google.com/group/shadowsocks 108 | 109 | .. |PyPI version| image:: https://img.shields.io/pypi/v/shadowdns.svg?style=flat 110 | :target: https://pypi.python.org/pypi/shadowdns 111 | .. |Build Status| image:: https://img.shields.io/travis/clowwindy/ShadowDNS/master.svg?style=flat 112 | :target: https://travis-ci.org/clowwindy/ShadowDNS 113 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"my_server_ip", 3 | "server_port":8388, 4 | "local_address": "127.0.0.1", 5 | "local_port":53, 6 | "password":"mypassword", 7 | "method":"aes-256-cfb", 8 | "dns":"8.8.8.8" 9 | } 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | with open('README.rst') as f: 5 | long_description = f.read() 6 | 7 | setup( 8 | name="shadowdns", 9 | version="0.1.3", 10 | license='MIT', 11 | description="A DNS forwarder using Shadowsocks as the server", 12 | author='clowwindy', 13 | author_email='clowwindy42@gmail.com', 14 | url='https://github.com/clowwindy/shadowdns', 15 | packages=['shadowdns'], 16 | package_data={ 17 | 'shadowdns': ['README.rst', 'LICENSE', 'config.json'] 18 | }, 19 | install_requires=[ 20 | 'shadowsocks==2.0.4' 21 | ], 22 | entry_points=""" 23 | [console_scripts] 24 | ssdns = shadowdns.dnsrelay:main 25 | """, 26 | classifiers=[ 27 | 'License :: OSI Approved :: MIT License', 28 | 'Programming Language :: Python :: 2', 29 | 'Programming Language :: Python :: 2.6', 30 | 'Programming Language :: Python :: 2.7', 31 | 'Topic :: Internet :: Proxy Servers', 32 | ], 33 | long_description=long_description, 34 | ) 35 | -------------------------------------------------------------------------------- /shadowdns/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | -------------------------------------------------------------------------------- /shadowdns/dnsrelay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import time 25 | import socket 26 | import struct 27 | import errno 28 | import logging 29 | from shadowsocks import eventloop, asyncdns, lru_cache, encrypt 30 | from shadowsocks import utils as shadowsocks_utils 31 | from shadowsocks.common import parse_header 32 | 33 | 34 | BUF_SIZE = 16384 35 | 36 | CACHE_TIMEOUT = 10 37 | 38 | 39 | class DNSRelay(object): 40 | 41 | def __init__(self, config): 42 | self._loop = None 43 | self._config = config 44 | self._last_time = time.time() 45 | 46 | self._local_addr = (config['local_address'], 53) 47 | self._remote_addr = (config['server'], config['server_port']) 48 | 49 | dns_addr = config['dns'] 50 | addrs = socket.getaddrinfo(dns_addr, 53, 0, 51 | socket.SOCK_DGRAM, socket.SOL_UDP) 52 | if not addrs: 53 | raise Exception("can't get addrinfo for DNS address") 54 | af, socktype, proto, canonname, sa = addrs[0] 55 | 56 | dns_port = struct.pack('>H', 53) 57 | if af == socket.AF_INET: 58 | self._address_to_send = '\x01' + socket.inet_aton(sa[0]) + dns_port 59 | else: 60 | self._address_to_send = '\x04' + socket.inet_pton(af, sa[0]) + \ 61 | dns_port 62 | 63 | def add_to_loop(self, loop): 64 | if self._loop: 65 | raise Exception('already add to loop') 66 | self._loop = loop 67 | loop.add_handler(self.handle_events) 68 | 69 | def handle_events(self, events): 70 | pass 71 | 72 | 73 | class UDPDNSRelay(DNSRelay): 74 | 75 | def __init__(self, config): 76 | DNSRelay.__init__(self, config) 77 | 78 | self._id_to_addr = lru_cache.LRUCache(CACHE_TIMEOUT) 79 | self._local_sock = None 80 | self._remote_sock = None 81 | 82 | sockets = [] 83 | for addr in (self._local_addr, self._remote_addr): 84 | addrs = socket.getaddrinfo(addr[0], addr[1], 0, 85 | socket.SOCK_DGRAM, socket.SOL_UDP) 86 | if len(addrs) == 0: 87 | raise Exception("can't get addrinfo for %s:%d" % addr) 88 | af, socktype, proto, canonname, sa = addrs[0] 89 | sock = socket.socket(af, socktype, proto) 90 | sock.setblocking(False) 91 | sockets.append(sock) 92 | 93 | self._local_sock, self._remote_sock = sockets 94 | self._local_sock.bind(self._local_addr) 95 | 96 | def add_to_loop(self, loop): 97 | DNSRelay.add_to_loop(self, loop) 98 | 99 | loop.add(self._local_sock, eventloop.POLL_IN) 100 | loop.add(self._remote_sock, eventloop.POLL_IN) 101 | 102 | def _handle_local(self, sock): 103 | data, addr = sock.recvfrom(BUF_SIZE) 104 | header = asyncdns.parse_header(data) 105 | if header: 106 | try: 107 | req_id = header[0] 108 | req = asyncdns.parse_response(data) 109 | self._id_to_addr[req_id] = addr 110 | data = self._address_to_send + data 111 | data = encrypt.encrypt_all(self._config['password'], 112 | self._config['method'], 1, data) 113 | self._remote_sock.sendto(data, self._remote_addr) 114 | logging.info('request %s', req.hostname) 115 | except Exception as e: 116 | import traceback 117 | traceback.print_exc() 118 | logging.error(e) 119 | 120 | def _handle_remote(self, sock): 121 | data, addr = sock.recvfrom(BUF_SIZE) 122 | if data: 123 | try: 124 | data = encrypt.encrypt_all(self._config['password'], 125 | self._config['method'], 0, data) 126 | header_result = parse_header(data) 127 | if header_result is None: 128 | return None, None 129 | addrtype, dest_addr, dest_port, header_length = header_result 130 | data = data[header_length:] 131 | header = asyncdns.parse_header(data) 132 | if header: 133 | req_id = header[0] 134 | res = asyncdns.parse_response(data) 135 | addr = self._id_to_addr.get(req_id, None) 136 | if addr: 137 | self._local_sock.sendto(data, addr) 138 | del self._id_to_addr[req_id] 139 | logging.info('response %s', res) 140 | except Exception as e: 141 | import traceback 142 | traceback.print_exc() 143 | logging.error(e) 144 | 145 | def handle_events(self, events): 146 | for sock, fd, event in events: 147 | if sock == self._local_sock: 148 | self._handle_local(sock) 149 | elif sock == self._remote_sock: 150 | self._handle_remote(sock) 151 | now = time.time() 152 | if now - self._last_time > CACHE_TIMEOUT / 2: 153 | self._id_to_addr.sweep() 154 | 155 | 156 | class TCPDNSRelay(DNSRelay): 157 | 158 | def __init__(self, config): 159 | DNSRelay.__init__(self, config) 160 | 161 | self._local_to_remote = {} 162 | self._remote_to_local = {} 163 | self._local_to_encryptor = {} 164 | 165 | addrs = socket.getaddrinfo(self._local_addr[0], self._local_addr[1], 0, 166 | socket.SOCK_STREAM, socket.SOL_TCP) 167 | if len(addrs) == 0: 168 | raise Exception("can't get addrinfo for %s:%d" % self._local_addr) 169 | af, socktype, proto, canonname, sa = addrs[0] 170 | self._listen_sock = socket.socket(af, socktype, proto) 171 | self._listen_sock.setblocking(False) 172 | self._listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 173 | self._listen_sock.bind(self._local_addr) 174 | self._listen_sock.listen(1024) 175 | 176 | def _handle_conn(self, sock): 177 | try: 178 | local, addr = sock.accept() 179 | addrs = socket.getaddrinfo(self._remote_addr[0], 180 | self._remote_addr[1], 0, 181 | socket.SOCK_STREAM, socket.SOL_TCP) 182 | if len(addrs) == 0: 183 | raise Exception("can't get addrinfo for %s:%d" % 184 | self._remote_addr) 185 | af, socktype, proto, canonname, sa = addrs[0] 186 | remote = socket.socket(af, socktype, proto) 187 | local.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) 188 | remote.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) 189 | self._local_to_remote[local] = remote 190 | self._remote_to_local[remote] = local 191 | 192 | self._loop.add(local, 0) 193 | self._loop.add(remote, eventloop.POLL_OUT) 194 | try: 195 | remote.connect(self._remote_addr) 196 | except (OSError, IOError) as e: 197 | if eventloop.errno_from_exception(e) in (errno.EINPROGRESS, 198 | errno.EAGAIN): 199 | pass 200 | else: 201 | raise 202 | except (OSError, IOError) as e: 203 | logging.error(e) 204 | 205 | def _destroy(self, local, remote): 206 | if local in self._local_to_remote: 207 | self._loop.remove(local) 208 | self._loop.remove(remote) 209 | del self._local_to_remote[local] 210 | del self._remote_to_local[remote] 211 | if local in self._local_to_encryptor: 212 | del self._local_to_encryptor[local] 213 | local.close() 214 | remote.close() 215 | else: 216 | logging.error('already destroyed') 217 | 218 | def _handle_local(self, local, event): 219 | remote = self._local_to_remote[local] 220 | encryptor = self._local_to_encryptor.get(local, None) 221 | if event & eventloop.POLL_ERR: 222 | self._destroy(local, remote) 223 | elif event & eventloop.POLL_IN: 224 | try: 225 | data = local.recv(BUF_SIZE) 226 | if not data: 227 | self._destroy(local, remote) 228 | else: 229 | if not encryptor: 230 | try: 231 | req = asyncdns.parse_response(data[2:]) 232 | if req: 233 | logging.info('request %s', req.hostname) 234 | except Exception as e: 235 | logging.error(e) 236 | encryptor = \ 237 | encrypt.Encryptor(self._config['password'], 238 | self._config['method']) 239 | self._local_to_encryptor[local] = encryptor 240 | data = self._address_to_send + data 241 | data = encryptor.encrypt(data) 242 | remote.send(data) 243 | except (OSError, IOError) as e: 244 | self._destroy(local, self._local_to_remote[local]) 245 | logging.error(e) 246 | 247 | def _handle_remote(self, remote, event): 248 | local = self._remote_to_local[remote] 249 | if event & eventloop.POLL_ERR: 250 | self._destroy(local, remote) 251 | elif event & eventloop.POLL_OUT: 252 | self._loop.modify(remote, eventloop.POLL_IN) 253 | self._loop.modify(local, eventloop.POLL_IN) 254 | elif event & eventloop.POLL_IN: 255 | try: 256 | data = remote.recv(BUF_SIZE) 257 | if not data: 258 | self._destroy(local, remote) 259 | else: 260 | encryptor = self._local_to_encryptor[local] 261 | data = encryptor.decrypt(data) 262 | try: 263 | res = asyncdns.parse_response(data[2:]) 264 | if res: 265 | logging.info('response %s', res) 266 | except Exception as e: 267 | logging.error(e) 268 | local.send(data) 269 | except (OSError, IOError) as e: 270 | self._destroy(local, remote) 271 | logging.error(e) 272 | 273 | def add_to_loop(self, loop): 274 | DNSRelay.add_to_loop(self, loop) 275 | loop.add(self._listen_sock, eventloop.POLL_IN) 276 | 277 | def handle_events(self, events): 278 | for sock, fd, event in events: 279 | if sock == self._listen_sock: 280 | self._handle_conn(sock) 281 | elif sock in self._local_to_remote: 282 | self._handle_local(sock, event) 283 | elif sock in self._remote_to_local: 284 | self._handle_remote(sock, event) 285 | # TODO implement timeout 286 | 287 | 288 | def main(): 289 | shadowsocks_utils.check_python() 290 | 291 | config = shadowsocks_utils.get_config(True) 292 | 293 | encrypt.init_table(config['password'], config['method']) 294 | 295 | logging.info("starting dns at %s:%d" % (config['local_address'], 53)) 296 | 297 | config['dns'] = config.get('dns', '8.8.8.8') 298 | 299 | loop = eventloop.EventLoop() 300 | 301 | udprelay = UDPDNSRelay(config) 302 | udprelay.add_to_loop(loop) 303 | tcprelay = TCPDNSRelay(config) 304 | tcprelay.add_to_loop(loop) 305 | 306 | loop.run() 307 | 308 | if __name__ == '__main__': 309 | main() 310 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | sys.path.insert(0, 'shadowsocks') 6 | import os 7 | import signal 8 | import select 9 | import time 10 | from subprocess import Popen, PIPE 11 | 12 | p1 = Popen(['sudo', sys.executable, 'shadowdns/dnsrelay.py', '-c', sys.argv[-1]], 13 | shell=False, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) 14 | p2 = Popen(['ssserver', '-c', sys.argv[-1]], shell=False, bufsize=0, stdin=PIPE, 15 | stdout=PIPE, stderr=PIPE, close_fds=True, env=os.environ) 16 | p3 = None 17 | 18 | try: 19 | local_ready = False 20 | server_ready = False 21 | fdset = [p1.stdout, p2.stdout, p1.stderr, p2.stderr] 22 | while True: 23 | r, w, e = select.select(fdset, [], fdset) 24 | if e: 25 | break 26 | 27 | for fd in r: 28 | line = fd.readline() 29 | sys.stdout.write(line) 30 | if line.find('starting dns') >= 0: 31 | local_ready = True 32 | if line.find('starting server') >= 0: 33 | server_ready = True 34 | 35 | if local_ready and server_ready and p3 is None: 36 | time.sleep(1) 37 | p3 = Popen(['dig', '@127.0.0.1', 'any', 'google.com'], 38 | shell=False, bufsize=0, close_fds=True) 39 | break 40 | 41 | if p3 is not None: 42 | r = p3.wait() 43 | if r == 0: 44 | print 'test passed' 45 | sys.exit(r) 46 | 47 | finally: 48 | for p in [p1, p2]: 49 | try: 50 | os.kill(p.pid, signal.SIGTERM) 51 | except OSError: 52 | pass 53 | 54 | sys.exit(-1) 55 | -------------------------------------------------------------------------------- /tests/aes.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/table.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"table_password", 6 | "timeout":60, 7 | "method":"table", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | --------------------------------------------------------------------------------