From 0edae7069f6087e6c25b9809ee0a5f651866f154 Mon Sep 17 00:00:00 2001 From: Yifu Yu Date: Sun, 21 Jun 2015 00:56:55 +0800 Subject: [PATCH 01/47] Add fail2ban filter. Please put the shadowsocks.conf into your filter.d directory, and using `filter = shadowsocks` to use the filter. --- utils/fail2ban/shadowsocks.conf | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 utils/fail2ban/shadowsocks.conf diff --git a/utils/fail2ban/shadowsocks.conf b/utils/fail2ban/shadowsocks.conf new file mode 100644 index 000000000..9b1c7ec7a --- /dev/null +++ b/utils/fail2ban/shadowsocks.conf @@ -0,0 +1,5 @@ +[Definition] + +_daemon = shadowsocks + +failregex = ^\s+ERROR\s+can not parse header when handling connection from :\d+$ From 1a62694a3b1d7693ae625e2b6a50a34c84d355eb Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 15:02:33 +0800 Subject: [PATCH 02/47] add udp source port test --- .travis.yml | 2 +- tests/jenkins.sh | 4 +++- tests/test_udp_src.py | 31 +++++++++++++++++++++++++++++++ tests/test_udp_src.sh | 23 +++++++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/test_udp_src.py create mode 100755 tests/test_udp_src.sh diff --git a/.travis.yml b/.travis.yml index f29cb9607..535bc9aeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_install: - sudo dd if=/dev/urandom of=/usr/share/nginx/www/file bs=1M count=10 - sudo sh -c "echo '127.0.0.1 localhost' > /etc/hosts" - sudo service nginx restart - - pip install pep8 pyflakes nose coverage + - pip install pep8 pyflakes nose coverage PySocks - sudo tests/socksify/install.sh - sudo tests/libsodium/install.sh - sudo tests/setup_tc.sh diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 71d5b1cab..5e4e61fe0 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -24,6 +24,8 @@ function run_test { return 0 } +pip install PySocks + python --version coverage erase mkdir tmp @@ -69,7 +71,7 @@ if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then fi run_test tests/test_large_file.sh - +run_test tests/test_udp_src.sh run_test tests/test_command.sh coverage combine && coverage report --include=shadowsocks/* diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py new file mode 100644 index 000000000..83c615dda --- /dev/null +++ b/tests/test_udp_src.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + +import socket +import socks + +if __name__ == '__main__': + sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.bind(('127.0.0.1', 9000)) + + sock_in1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_in2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + + sock_in1.bind(('127.0.0.1', 9001)) + sock_in2.bind(('127.0.0.1', 9002)) + + sock_out.sendto('data', ('127.0.0.1', 9001)) + result1 = sock_in1.recvfrom(8) + + sock_out.sendto('data', ('127.0.0.1', 9002)) + result2 = sock_in2.recvfrom(8) + + sock_out.close() + sock_in1.close() + sock_in2.close() + + # make sure they're from the same source port + assert result1 == result2 diff --git a/tests/test_udp_src.sh b/tests/test_udp_src.sh new file mode 100755 index 000000000..876f24252 --- /dev/null +++ b/tests/test_udp_src.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +PYTHON="coverage run -p -a" + +mkdir -p tmp + +$PYTHON shadowsocks/local.py -c tests/aes.json & +LOCAL=$! + +$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" & +SERVER=$! + +sleep 3 + +python tests/test_udp_src.py +r=$? + +kill -s SIGINT $LOCAL +kill -s SIGINT $SERVER + +sleep 2 + +exit $r From c34c99450f7d2410f005e6c81c1955a0c49d7c2e Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 16:58:04 +0800 Subject: [PATCH 03/47] fix UDP source port issue --- shadowsocks/udprelay.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 98bfaaa74..ed6937fed 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -76,8 +76,8 @@ BUF_SIZE = 65536 -def client_key(a, b, c, d): - return '%s:%s:%s:%s' % (a, b, c, d) +def client_key(source_addr, dest_addr): + return '%s:%s' % (source_addr[0], source_addr[1]) class UDPRelay(object): @@ -169,7 +169,7 @@ def _handle_server(self): else: server_addr, server_port = dest_addr, dest_port - key = client_key(r_addr[0], r_addr[1], dest_addr, dest_port) + key = client_key(r_addr, (dest_addr, dest_port)) client = self._cache.get(key, None) if not client: # TODO async getaddrinfo From 99b4121fd93f38e89354bca915e3ed620632d6fe Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 17:32:31 +0800 Subject: [PATCH 04/47] fix problem when UDP client requesting both IPv4 and IPv6 --- shadowsocks/udprelay.py | 43 ++++++++++++++++++---------------- tests/jenkins.sh | 2 -- tests/test_udp_src.py | 51 +++++++++++++++++++++++++++++++++++++++-- tests/test_udp_src.sh | 4 ++-- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index ed6937fed..888b036c7 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -76,8 +76,9 @@ BUF_SIZE = 65536 -def client_key(source_addr, dest_addr): - return '%s:%s' % (source_addr[0], source_addr[1]) +def client_key(source_addr, server_af): + # notice this is server af, not dest af + return '%s:%s:%d' % (source_addr[0], source_addr[1], server_af) class UDPRelay(object): @@ -169,27 +170,29 @@ def _handle_server(self): else: server_addr, server_port = dest_addr, dest_port - key = client_key(r_addr, (dest_addr, dest_port)) + addrs = socket.getaddrinfo(server_addr, server_port, 0, + socket.SOCK_DGRAM, socket.SOL_UDP) + if not addrs: + # drop + return + + af, socktype, proto, canonname, sa = addrs[0] + key = client_key(r_addr, af) + logging.debug(key) client = self._cache.get(key, None) if not client: # TODO async getaddrinfo - addrs = socket.getaddrinfo(server_addr, server_port, 0, - socket.SOCK_DGRAM, socket.SOL_UDP) - if addrs: - af, socktype, proto, canonname, sa = addrs[0] - if self._forbidden_iplist: - if common.to_str(sa[0]) in self._forbidden_iplist: - logging.debug('IP %s is in forbidden list, drop' % - common.to_str(sa[0])) - # drop - return - client = socket.socket(af, socktype, proto) - client.setblocking(False) - self._cache[key] = client - self._client_fd_to_server_addr[client.fileno()] = r_addr - else: - # drop - return + if self._forbidden_iplist: + if common.to_str(sa[0]) in self._forbidden_iplist: + logging.debug('IP %s is in forbidden list, drop' % + common.to_str(sa[0])) + # drop + return + client = socket.socket(af, socktype, proto) + client.setblocking(False) + self._cache[key] = client + self._client_fd_to_server_addr[client.fileno()] = r_addr + self._sockets.add(client.fileno()) self._eventloop.add(client, eventloop.POLL_IN) diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 5e4e61fe0..ea5c1630b 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -24,8 +24,6 @@ function run_test { return 0 } -pip install PySocks - python --version coverage erase mkdir tmp diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py index 83c615dda..840b8f676 100644 --- a/tests/test_udp_src.py +++ b/tests/test_udp_src.py @@ -4,6 +4,7 @@ import socks if __name__ == '__main__': + # Test 1: same source port IPv4 sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) @@ -17,10 +18,10 @@ sock_in1.bind(('127.0.0.1', 9001)) sock_in2.bind(('127.0.0.1', 9002)) - sock_out.sendto('data', ('127.0.0.1', 9001)) + sock_out.sendto(b'data', ('127.0.0.1', 9001)) result1 = sock_in1.recvfrom(8) - sock_out.sendto('data', ('127.0.0.1', 9002)) + sock_out.sendto(b'data', ('127.0.0.1', 9002)) result2 = sock_in2.recvfrom(8) sock_out.close() @@ -29,3 +30,49 @@ # make sure they're from the same source port assert result1 == result2 + + # Test 2: same source port IPv6 + # try again from the same port but IPv6 + sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.bind(('127.0.0.1', 9000)) + + sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_in2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, + socket.SOL_UDP) + + sock_in1.bind(('::1', 9001)) + sock_in2.bind(('::1', 9002)) + + sock_out.sendto(b'data', ('::1', 9001)) + result1 = sock_in1.recvfrom(8) + + sock_out.sendto(b'data', ('::1', 9002)) + result2 = sock_in2.recvfrom(8) + + sock_out.close() + sock_in1.close() + sock_in2.close() + + # make sure they're from the same source port + assert result1 == result2 + + # Test 3: different source ports IPv6 + sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.bind(('127.0.0.1', 9003)) + + sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, + socket.SOL_UDP) + sock_in1.bind(('::1', 9001)) + sock_out.sendto(b'data', ('::1', 9001)) + result3 = sock_in1.recvfrom(8) + + # make sure they're from different source ports + assert result1 != result3 + + sock_out.close() + sock_in1.close() diff --git a/tests/test_udp_src.sh b/tests/test_udp_src.sh index 876f24252..d356581cf 100755 --- a/tests/test_udp_src.sh +++ b/tests/test_udp_src.sh @@ -4,10 +4,10 @@ PYTHON="coverage run -p -a" mkdir -p tmp -$PYTHON shadowsocks/local.py -c tests/aes.json & +$PYTHON shadowsocks/local.py -c tests/aes.json -v & LOCAL=$! -$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" & +$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" -v & SERVER=$! sleep 3 From 13a6bb007c14fb1068f2b2c67d26725359cb35e5 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 17:39:24 +0800 Subject: [PATCH 05/47] cache DNS results in UDPRelay --- shadowsocks/udprelay.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 888b036c7..b90e36981 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -103,6 +103,7 @@ def __init__(self, config, dns_resolver, is_local): close_callback=self._close_client) self._client_fd_to_server_addr = \ lru_cache.LRUCache(timeout=config['timeout']) + self._dns_cache = lru_cache.LRUCache(timeout=300) self._eventloop = None self._closed = False self._last_time = time.time() @@ -170,11 +171,15 @@ def _handle_server(self): else: server_addr, server_port = dest_addr, dest_port - addrs = socket.getaddrinfo(server_addr, server_port, 0, - socket.SOCK_DGRAM, socket.SOL_UDP) - if not addrs: - # drop - return + addrs = self._dns_cache.get(server_addr, None) + if addrs is None: + addrs = socket.getaddrinfo(server_addr, server_port, 0, + socket.SOCK_DGRAM, socket.SOL_UDP) + if not addrs: + # drop + return + else: + self._dns_cache[server_addr] = addrs af, socktype, proto, canonname, sa = addrs[0] key = client_key(r_addr, af) From f55bd0302f0f997f6151b8fd1a10f8413625a21c Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 17:59:52 +0800 Subject: [PATCH 06/47] update CHANGES --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 4db142a67..ada189399 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.6.11 2015-07-10 +- Fix a compatibility issue in UDP Relay + 2.6.10 2015-06-08 - Optimize LRU cache - Refine logging From f7d69db6d15864f0910717f4fd677ae34d936073 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Fri, 10 Jul 2015 18:01:34 +0800 Subject: [PATCH 07/47] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 07ea2db0b..689dd7365 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="shadowsocks", - version="2.6.11", + version="2.6.12", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 1bb0e51e8e2fa31f526caa3fa81c62296dc414a8 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 11 Jul 2015 13:05:37 +0800 Subject: [PATCH 08/47] refine tests --- tests/test_udp_src.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py index 840b8f676..e8fa5057e 100644 --- a/tests/test_udp_src.py +++ b/tests/test_udp_src.py @@ -3,11 +3,16 @@ import socket import socks + +SERVER_IP = '127.0.0.1' +SERVER_PORT = 1081 + + if __name__ == '__main__': # Test 1: same source port IPv4 sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) sock_out.bind(('127.0.0.1', 9000)) sock_in1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, @@ -35,7 +40,7 @@ # try again from the same port but IPv6 sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) sock_out.bind(('127.0.0.1', 9000)) sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, @@ -62,7 +67,7 @@ # Test 3: different source ports IPv6 sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, '127.0.0.1', 1081) + sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) sock_out.bind(('127.0.0.1', 9003)) sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, From 2555aa8e2bf81e2d2ffd0bbd8a386be6fd1282e9 Mon Sep 17 00:00:00 2001 From: Christopher Meng Date: Mon, 27 Jul 2015 00:38:23 -0400 Subject: [PATCH 09/47] PEP8 indent A tiny change to perfect the indent. --- shadowsocks/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/common.py b/shadowsocks/common.py index fc03d556d..db4beea10 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -157,7 +157,7 @@ def parse_header(data): if len(data) >= 2 + addrlen: dest_addr = data[2:2 + addrlen] dest_port = struct.unpack('>H', data[2 + addrlen:4 + - addrlen])[0] + addrlen])[0] header_length = 4 + addrlen else: logging.warn('header is too short') From 4a8d0774b462841cd8de3b4cf13a365c03fda339 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 1 Aug 2015 18:22:35 +0800 Subject: [PATCH 10/47] optimize performance for multiple ports UDPRelay is broken now --- setup.py | 2 +- shadowsocks/asyncdns.py | 56 +- shadowsocks/eventloop.py | 119 +- shadowsocks/tcprelay.py | 95 +- shadowsocks/udprelay.py | 40 +- tests/gen_multiple_passwd.py | 19 + tests/server-multi-passwd-performance.json | 2008 ++++++++++++++++++++ 7 files changed, 2171 insertions(+), 168 deletions(-) create mode 100644 tests/gen_multiple_passwd.py create mode 100644 tests/server-multi-passwd-performance.json diff --git a/setup.py b/setup.py index 689dd7365..195b2bba2 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="shadowsocks", - version="2.6.12", + version="2.7", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 7e4a4ed7f..e60a3833b 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -256,7 +256,6 @@ def __init__(self): self._hostname_to_cb = {} self._cb_to_hostname = {} self._cache = lru_cache.LRUCache(timeout=300) - self._last_time = time.time() self._sock = None self._servers = None self._parse_resolv() @@ -304,7 +303,7 @@ def _parse_hosts(self): except IOError: self._hosts['localhost'] = '127.0.0.1' - def add_to_loop(self, loop, ref=False): + def add_to_loop(self, loop): if self._loop: raise Exception('already add to loop') self._loop = loop @@ -312,8 +311,8 @@ def add_to_loop(self, loop, ref=False): self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) self._sock.setblocking(False) - loop.add(self._sock, eventloop.POLL_IN) - loop.add_handler(self.handle_events, ref=ref) + loop.add(self._sock, eventloop.POLL_IN, self) + loop.add_periodic(self.handle_periodic) def _call_callback(self, hostname, ip, error=None): callbacks = self._hostname_to_cb.get(hostname, []) @@ -354,30 +353,27 @@ def _handle_data(self, data): self._call_callback(hostname, None) break - def handle_events(self, events): - for sock, fd, event in events: - if sock != self._sock: - continue - if event & eventloop.POLL_ERR: - logging.error('dns socket err') - self._loop.remove(self._sock) - self._sock.close() - # TODO when dns server is IPv6 - self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.SOL_UDP) - self._sock.setblocking(False) - self._loop.add(self._sock, eventloop.POLL_IN) - else: - data, addr = sock.recvfrom(1024) - if addr[0] not in self._servers: - logging.warn('received a packet other than our dns') - break - self._handle_data(data) - break - now = time.time() - if now - self._last_time > CACHE_SWEEP_INTERVAL: - self._cache.sweep() - self._last_time = now + def handle_event(self, sock, fd, event): + if sock != self._sock: + return + if event & eventloop.POLL_ERR: + logging.error('dns socket err') + self._loop.remove(self._sock, self) + self._sock.close() + # TODO when dns server is IPv6 + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + self._sock.setblocking(False) + self._loop.add(self._sock, eventloop.POLL_IN, self) + else: + data, addr = sock.recvfrom(1024) + if addr[0] not in self._servers: + logging.warn('received a packet other than our dns') + return + self._handle_data(data) + + def handle_periodic(self): + self._cache.sweep() def remove_callback(self, callback): hostname = self._cb_to_hostname.get(callback) @@ -385,7 +381,7 @@ def remove_callback(self, callback): del self._cb_to_hostname[callback] arr = self._hostname_to_cb.get(hostname, None) if arr: - arr.remove(callback) + arr.remove(callback, self) if not arr: del self._hostname_to_cb[hostname] if hostname in self._hostname_status: @@ -430,6 +426,7 @@ def resolve(self, hostname, callback): def close(self): if self._sock: + self._loop.remove(self._sock, self) self._sock.close() self._sock = None @@ -451,7 +448,6 @@ def callback(result, error): print(result, error) counter += 1 if counter == 9: - loop.remove_handler(dns_resolver.handle_events) dns_resolver.close() a_callback = callback return a_callback diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 42f9205bf..09e97611e 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -22,6 +22,7 @@ with_statement import os +import time import socket import select import errno @@ -51,23 +52,8 @@ POLL_NVAL: 'POLL_NVAL', } - -class EpollLoop(object): - - def __init__(self): - self._epoll = select.epoll() - - def poll(self, timeout): - return self._epoll.poll(timeout) - - def add_fd(self, fd, mode): - self._epoll.register(fd, mode) - - def remove_fd(self, fd): - self._epoll.unregister(fd) - - def modify_fd(self, fd, mode): - self._epoll.modify(fd, mode) +# we check timeouts every TIMEOUT_PRECISION seconds +TIMEOUT_PRECISION = 10 class KqueueLoop(object): @@ -100,17 +86,17 @@ def poll(self, timeout): results[fd] |= POLL_OUT return results.items() - def add_fd(self, fd, mode): + def register(self, fd, mode): self._fds[fd] = mode self._control(fd, mode, select.KQ_EV_ADD) - def remove_fd(self, fd): + def unregister(self, fd): self._control(fd, self._fds[fd], select.KQ_EV_DELETE) del self._fds[fd] - def modify_fd(self, fd, mode): - self.remove_fd(fd) - self.add_fd(fd, mode) + def modify(self, fd, mode): + self.unregister(fd) + self.register(fd, mode) class SelectLoop(object): @@ -129,7 +115,7 @@ def poll(self, timeout): results[fd] |= p[1] return results.items() - def add_fd(self, fd, mode): + def register(self, fd, mode): if mode & POLL_IN: self._r_list.add(fd) if mode & POLL_OUT: @@ -137,7 +123,7 @@ def add_fd(self, fd, mode): if mode & POLL_ERR: self._x_list.add(fd) - def remove_fd(self, fd): + def unregister(self, fd): if fd in self._r_list: self._r_list.remove(fd) if fd in self._w_list: @@ -145,16 +131,15 @@ def remove_fd(self, fd): if fd in self._x_list: self._x_list.remove(fd) - def modify_fd(self, fd, mode): - self.remove_fd(fd) - self.add_fd(fd, mode) + def modify(self, fd, mode): + self.unregister(fd) + self.register(fd, mode) class EventLoop(object): def __init__(self): - self._iterating = False if hasattr(select, 'epoll'): - self._impl = EpollLoop() + self._impl = select.epoll() model = 'epoll' elif hasattr(select, 'kqueue'): self._impl = KqueueLoop() @@ -166,48 +151,50 @@ def __init__(self): raise Exception('can not find any available functions in select ' 'package') self._fd_to_f = {} - self._handlers = [] - self._ref_handlers = [] - self._handlers_to_remove = [] + self._fd_to_handler = {} + self._last_time = time.time() + self._periodic_callbacks = [] + self._stopping = False logging.debug('using event model: %s', model) def poll(self, timeout=None): events = self._impl.poll(timeout) return [(self._fd_to_f[fd], fd, event) for fd, event in events] - def add(self, f, mode): + def add(self, f, mode, handler): fd = f.fileno() self._fd_to_f[fd] = f - self._impl.add_fd(fd, mode) + self._impl.register(fd, mode) + self._fd_to_handler[fd] = handler - def remove(self, f): + def remove(self, f, handler): fd = f.fileno() del self._fd_to_f[fd] - self._impl.remove_fd(fd) + self._impl.unregister(fd) + if handler is not None: + del self._fd_to_handler[fd] + + def add_periodic(self, callback): + self._periodic_callbacks.append(callback) + + def remove_periodic(self, callback): + self._periodic_callbacks.remove(callback) - def modify(self, f, mode): + def modify(self, f, mode, handler): fd = f.fileno() - self._impl.modify_fd(fd, mode) - - def add_handler(self, handler, ref=True): - self._handlers.append(handler) - if ref: - # when all ref handlers are removed, loop stops - self._ref_handlers.append(handler) - - def remove_handler(self, handler): - if handler in self._ref_handlers: - self._ref_handlers.remove(handler) - if self._iterating: - self._handlers_to_remove.append(handler) - else: - self._handlers.remove(handler) + self._impl.modify(fd, mode) + if handler is not None: + self._fd_to_handler[fd] = handler + + def stop(self): + self._stopping = True def run(self): events = [] - while self._ref_handlers: + while not self._stopping: + now = time.time() try: - events = self.poll(1) + events = self.poll(TIMEOUT_PRECISION) except (OSError, IOError) as e: if errno_from_exception(e) in (errno.EPIPE, errno.EINTR): # EPIPE: Happens when the client closes the connection @@ -219,18 +206,18 @@ def run(self): import traceback traceback.print_exc() continue - self._iterating = True - for handler in self._handlers: - # TODO when there are a lot of handlers - try: - handler(events) - except (OSError, IOError) as e: - shell.print_exception(e) - if self._handlers_to_remove: - for handler in self._handlers_to_remove: - self._handlers.remove(handler) - self._handlers_to_remove = [] - self._iterating = False + + for sock, fd, event in events: + handler = self._fd_to_handler.get(fd, None) + if handler is not None: + try: + handler.handle_event(sock, fd, event) + except (OSError, IOError) as e: + shell.print_exception(e) + if now - self._last_time >= TIMEOUT_PRECISION: + for callback in self._periodic_callbacks: + callback() + self._last_time = now # from tornado diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 4834883af..3300df3c2 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -32,9 +32,6 @@ # we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time TIMEOUTS_CLEAN_SIZE = 512 -# we check timeouts every TIMEOUT_PRECISION seconds -TIMEOUT_PRECISION = 4 - MSG_FASTOPEN = 0x20000000 # SOCKS command definition @@ -126,7 +123,8 @@ def __init__(self, server, fd_to_handlers, loop, local_sock, config, fd_to_handlers[local_sock.fileno()] = self local_sock.setblocking(False) local_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) - loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR) + loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR, + self._server) self.last_activity = 0 self._update_activity() @@ -175,14 +173,14 @@ def _update_stream(self, stream, status): event |= eventloop.POLL_OUT if self._upstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN - self._loop.modify(self._local_sock, event) + self._loop.modify(self._local_sock, event, self._server) if self._remote_sock: event = eventloop.POLL_ERR if self._downstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN if self._upstream_status & WAIT_STATUS_WRITING: event |= eventloop.POLL_OUT - self._loop.modify(self._remote_sock, event) + self._loop.modify(self._remote_sock, event, self._server) def _write_to_sock(self, data, sock): # write data to sock @@ -238,7 +236,7 @@ def _handle_stage_connecting(self, data): remote_sock = \ self._create_remote_socket(self._chosen_server[0], self._chosen_server[1]) - self._loop.add(remote_sock, eventloop.POLL_ERR) + self._loop.add(remote_sock, eventloop.POLL_ERR, self._server) data = b''.join(self._data_to_write_to_remote) l = len(data) s = remote_sock.sendto(data, MSG_FASTOPEN, self._chosen_server) @@ -375,7 +373,8 @@ def _handle_dns_resolved(self, result, error): errno.EINPROGRESS: pass self._loop.add(remote_sock, - eventloop.POLL_ERR | eventloop.POLL_OUT) + eventloop.POLL_ERR | eventloop.POLL_OUT, + self._server) self._stage = STAGE_CONNECTING self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) @@ -535,13 +534,13 @@ def destroy(self): logging.debug('destroy') if self._remote_sock: logging.debug('destroying remote') - self._loop.remove(self._remote_sock) + self._loop.remove(self._remote_sock, self._server) del self._fd_to_handlers[self._remote_sock.fileno()] self._remote_sock.close() self._remote_sock = None if self._local_sock: logging.debug('destroying local') - self._loop.remove(self._local_sock) + self._loop.remove(self._local_sock, self._server) del self._fd_to_handlers[self._local_sock.fileno()] self._local_sock.close() self._local_sock = None @@ -557,7 +556,6 @@ def __init__(self, config, dns_resolver, is_local): self._closed = False self._eventloop = None self._fd_to_handlers = {} - self._last_time = time.time() self._timeout = config['timeout'] self._timeouts = [] # a list for all the handlers @@ -598,10 +596,9 @@ def add_to_loop(self, loop): if self._closed: raise Exception('already closed') self._eventloop = loop - loop.add_handler(self._handle_events) - self._eventloop.add(self._server_socket, - eventloop.POLL_IN | eventloop.POLL_ERR) + eventloop.POLL_IN | eventloop.POLL_ERR, self) + self._eventloop.add_periodic(self.handle_periodic) def remove_handler(self, handler): index = self._handler_to_timeouts.get(hash(handler), -1) @@ -613,7 +610,7 @@ def remove_handler(self, handler): def update_activity(self, handler): # set handler to active now = int(time.time()) - if now - handler.last_activity < TIMEOUT_PRECISION: + if now - handler.last_activity < eventloop.TIMEOUT_PRECISION: # thus we can lower timeout modification frequency return handler.last_activity = now @@ -659,51 +656,49 @@ def _sweep_timeout(self): pos = 0 self._timeout_offset = pos - def _handle_events(self, events): + def handle_event(self, sock, fd, event): # handle events and dispatch to handlers - for sock, fd, event in events: + if sock: + logging.log(shell.VERBOSE_LEVEL, 'fd %d %s', fd, + eventloop.EVENT_NAMES.get(event, event)) + if sock == self._server_socket: + if event & eventloop.POLL_ERR: + # TODO + raise Exception('server_socket error') + try: + logging.debug('accept') + conn = self._server_socket.accept() + TCPRelayHandler(self, self._fd_to_handlers, + self._eventloop, conn[0], self._config, + self._dns_resolver, self._is_local) + except (OSError, IOError) as e: + error_no = eventloop.errno_from_exception(e) + if error_no in (errno.EAGAIN, errno.EINPROGRESS, + errno.EWOULDBLOCK): + return + else: + shell.print_exception(e) + if self._config['verbose']: + traceback.print_exc() + else: if sock: - logging.log(shell.VERBOSE_LEVEL, 'fd %d %s', fd, - eventloop.EVENT_NAMES.get(event, event)) - if sock == self._server_socket: - if event & eventloop.POLL_ERR: - # TODO - raise Exception('server_socket error') - try: - logging.debug('accept') - conn = self._server_socket.accept() - TCPRelayHandler(self, self._fd_to_handlers, - self._eventloop, conn[0], self._config, - self._dns_resolver, self._is_local) - except (OSError, IOError) as e: - error_no = eventloop.errno_from_exception(e) - if error_no in (errno.EAGAIN, errno.EINPROGRESS, - errno.EWOULDBLOCK): - continue - else: - shell.print_exception(e) - if self._config['verbose']: - traceback.print_exc() + handler = self._fd_to_handlers.get(fd, None) + if handler: + handler.handle_event(sock, event) else: - if sock: - handler = self._fd_to_handlers.get(fd, None) - if handler: - handler.handle_event(sock, event) - else: - logging.warn('poll removed fd') + logging.warn('poll removed fd') - now = time.time() - if now - self._last_time > TIMEOUT_PRECISION: - self._sweep_timeout() - self._last_time = now + def handle_periodic(self): + self._sweep_timeout() if self._closed: if self._server_socket: - self._eventloop.remove(self._server_socket) + self._eventloop.remove(self._server_socket, self) + self._eventloop.remove_periodic(self.handle_periodic) self._server_socket.close() self._server_socket = None logging.info('closed listen port %d', self._listen_port) if not self._fd_to_handlers: - self._eventloop.remove_handler(self._handle_events) + self._eventloop.stop() def close(self, next_tick=False): self._closed = True diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index b90e36981..7e5e2c3dd 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -106,7 +106,6 @@ def __init__(self, config, dns_resolver, is_local): self._dns_cache = lru_cache.LRUCache(timeout=300) self._eventloop = None self._closed = False - self._last_time = time.time() self._sockets = set() if 'forbidden_ip' in config: self._forbidden_iplist = config['forbidden_ip'] @@ -137,7 +136,7 @@ def _get_a_server(self): def _close_client(self, client): if hasattr(client, 'close'): self._sockets.remove(client.fileno()) - self._eventloop.remove(client) + self._eventloop.remove(client, self) client.close() else: # just an address @@ -199,7 +198,7 @@ def _handle_server(self): self._client_fd_to_server_addr[client.fileno()] = r_addr self._sockets.add(client.fileno()) - self._eventloop.add(client, eventloop.POLL_IN) + self._eventloop.add(client, eventloop.POLL_IN, self) if self._is_local: data = encrypt.encrypt_all(self._password, self._method, 1, data) @@ -257,34 +256,33 @@ def add_to_loop(self, loop): if self._closed: raise Exception('already closed') self._eventloop = loop - loop.add_handler(self._handle_events) server_socket = self._server_socket self._eventloop.add(server_socket, - eventloop.POLL_IN | eventloop.POLL_ERR) + eventloop.POLL_IN | eventloop.POLL_ERR, self) + loop.add_periodic(self.handle_periodic) - def _handle_events(self, events): - for sock, fd, event in events: - if sock == self._server_socket: - if event & eventloop.POLL_ERR: - logging.error('UDP server_socket err') - self._handle_server() - elif sock and (fd in self._sockets): - if event & eventloop.POLL_ERR: - logging.error('UDP client_socket err') - self._handle_client(sock) - now = time.time() - if now - self._last_time > 3: - self._cache.sweep() - self._client_fd_to_server_addr.sweep() - self._last_time = now + def handle_event(self, sock, fd, event): + if sock == self._server_socket: + if event & eventloop.POLL_ERR: + logging.error('UDP server_socket err') + self._handle_server() + elif sock and (fd in self._sockets): + if event & eventloop.POLL_ERR: + logging.error('UDP client_socket err') + self._handle_client(sock) + + def handle_periodic(self): + self._cache.sweep() + self._client_fd_to_server_addr.sweep() if self._closed: self._server_socket.close() for sock in self._sockets: sock.close() - self._eventloop.remove_handler(self._handle_events) + self._eventloop.remove_periodic(self.handle_periodic) def close(self, next_tick=False): self._closed = True if not next_tick: + self._eventloop.remove(self._server_socket, self) self._server_socket.close() diff --git a/tests/gen_multiple_passwd.py b/tests/gen_multiple_passwd.py new file mode 100644 index 000000000..a058582b8 --- /dev/null +++ b/tests/gen_multiple_passwd.py @@ -0,0 +1,19 @@ +#!/usr/bin/python + +import os +import json + +with open('server-multi-passwd-performance.json', 'wb') as f: + r = { + 'server': '127.0.0.1', + 'local_port': 1081, + 'timeout': 60, + 'method': 'aes-256-cfb' + } + ports = {} + for i in range(7000, 9000): + ports[str(i)] = 'aes_password' + + r['port_password'] = ports + print(r) + f.write(json.dumps(r, indent=4).encode('utf-8')) diff --git a/tests/server-multi-passwd-performance.json b/tests/server-multi-passwd-performance.json new file mode 100644 index 000000000..c9fbc37b5 --- /dev/null +++ b/tests/server-multi-passwd-performance.json @@ -0,0 +1,2008 @@ +{ + "server": "127.0.0.1", + "local_port": 1081, + "port_password": { + "7582": "aes_password", + "7672": "aes_password", + "8923": "aes_password", + "8502": "aes_password", + "8282": "aes_password", + "8871": "aes_password", + "7732": "aes_password", + "8671": "aes_password", + "7018": "aes_password", + "8492": "aes_password", + "7748": "aes_password", + "8992": "aes_password", + "8246": "aes_password", + "7127": "aes_password", + "7775": "aes_password", + "8542": "aes_password", + "8488": "aes_password", + "7515": "aes_password", + "7659": "aes_password", + "8892": "aes_password", + "8028": "aes_password", + "7276": "aes_password", + "7959": "aes_password", + "7457": "aes_password", + "8635": "aes_password", + "7592": "aes_password", + "8764": "aes_password", + "8861": "aes_password", + "8842": "aes_password", + "8135": "aes_password", + "8140": "aes_password", + "8376": "aes_password", + "7733": "aes_password", + "8174": "aes_password", + "7265": "aes_password", + "8314": "aes_password", + "8772": "aes_password", + "8991": "aes_password", + "7183": "aes_password", + "7067": "aes_password", + "7730": "aes_password", + "8694": "aes_password", + "7629": "aes_password", + "7041": "aes_password", + "8507": "aes_password", + "8112": "aes_password", + "8491": "aes_password", + "7273": "aes_password", + "8811": "aes_password", + "8947": "aes_password", + "8612": "aes_password", + "8134": "aes_password", + "8422": "aes_password", + "8970": "aes_password", + "7051": "aes_password", + "8158": "aes_password", + "8934": "aes_password", + "7579": "aes_password", + "7140": "aes_password", + "8448": "aes_password", + "8536": "aes_password", + "7554": "aes_password", + "8168": "aes_password", + "8307": "aes_password", + "8946": "aes_password", + "7872": "aes_password", + "7330": "aes_password", + "8208": "aes_password", + "7955": "aes_password", + "8597": "aes_password", + "7025": "aes_password", + "7086": "aes_password", + "7534": "aes_password", + "7311": "aes_password", + "7758": "aes_password", + "7103": "aes_password", + "8408": "aes_password", + "7688": "aes_password", + "7073": "aes_password", + "8963": "aes_password", + "8578": "aes_password", + "7735": "aes_password", + "7657": "aes_password", + "7763": "aes_password", + "7680": "aes_password", + "8627": "aes_password", + "8205": "aes_password", + "7188": "aes_password", + "8743": "aes_password", + "8472": "aes_password", + "8823": "aes_password", + "7167": "aes_password", + "7008": "aes_password", + "7601": "aes_password", + "8603": "aes_password", + "8467": "aes_password", + "8803": "aes_password", + "7014": "aes_password", + "7233": "aes_password", + "7199": "aes_password", + "7192": "aes_password", + "7329": "aes_password", + "8031": "aes_password", + "8584": "aes_password", + "8041": "aes_password", + "8962": "aes_password", + "8824": "aes_password", + "8079": "aes_password", + "8049": "aes_password", + "7743": "aes_password", + "8035": "aes_password", + "8212": "aes_password", + "8452": "aes_password", + "8484": "aes_password", + "8232": "aes_password", + "8444": "aes_password", + "8410": "aes_password", + "7110": "aes_password", + "7505": "aes_password", + "8856": "aes_password", + "8293": "aes_password", + "7967": "aes_password", + "8267": "aes_password", + "7772": "aes_password", + "8864": "aes_password", + "8518": "aes_password", + "7520": "aes_password", + "7976": "aes_password", + "8407": "aes_password", + "8971": "aes_password", + "7389": "aes_password", + "7510": "aes_password", + "7373": "aes_password", + "8013": "aes_password", + "8310": "aes_password", + "7028": "aes_password", + "7874": "aes_password", + "7356": "aes_password", + "7729": "aes_password", + "7427": "aes_password", + "8312": "aes_password", + "7721": "aes_password", + "7020": "aes_password", + "8231": "aes_password", + "8188": "aes_password", + "8869": "aes_password", + "8595": "aes_password", + "8022": "aes_password", + "8911": "aes_password", + "7957": "aes_password", + "7141": "aes_password", + "7157": "aes_password", + "8471": "aes_password", + "8157": "aes_password", + "8795": "aes_password", + "7087": "aes_password", + "7470": "aes_password", + "7266": "aes_password", + "8072": "aes_password", + "8346": "aes_password", + "7163": "aes_password", + "8954": "aes_password", + "7046": "aes_password", + "7856": "aes_password", + "7883": "aes_password", + "8198": "aes_password", + "8443": "aes_password", + "8496": "aes_password", + "8900": "aes_password", + "8354": "aes_password", + "8758": "aes_password", + "8287": "aes_password", + "7574": "aes_password", + "8316": "aes_password", + "7539": "aes_password", + "8460": "aes_password", + "7616": "aes_password", + "8599": "aes_password", + "7795": "aes_password", + "7079": "aes_password", + "8468": "aes_password", + "8462": "aes_password", + "8645": "aes_password", + "8347": "aes_password", + "8776": "aes_password", + "7072": "aes_password", + "8781": "aes_password", + "7765": "aes_password", + "8048": "aes_password", + "7401": "aes_password", + "8718": "aes_password", + "8712": "aes_password", + "7801": "aes_password", + "8673": "aes_password", + "8791": "aes_password", + "7567": "aes_password", + "7003": "aes_password", + "7358": "aes_password", + "8916": "aes_password", + "7021": "aes_password", + "7487": "aes_password", + "7499": "aes_password", + "7108": "aes_password", + "7501": "aes_password", + "7313": "aes_password", + "8887": "aes_password", + "8724": "aes_password", + "7376": "aes_password", + "7153": "aes_password", + "7377": "aes_password", + "8426": "aes_password", + "8831": "aes_password", + "7380": "aes_password", + "7958": "aes_password", + "8250": "aes_password", + "8155": "aes_password", + "8435": "aes_password", + "7630": "aes_password", + "8026": "aes_password", + "7533": "aes_password", + "8704": "aes_password", + "8411": "aes_password", + "7645": "aes_password", + "7937": "aes_password", + "7488": "aes_password", + "8750": "aes_password", + "7196": "aes_password", + "8714": "aes_password", + "8677": "aes_password", + "7475": "aes_password", + "7625": "aes_password", + "8234": "aes_password", + "8870": "aes_password", + "7147": "aes_password", + "8417": "aes_password", + "7362": "aes_password", + "7341": "aes_password", + "8896": "aes_password", + "8423": "aes_password", + "8884": "aes_password", + "7220": "aes_password", + "8615": "aes_password", + "8719": "aes_password", + "8575": "aes_password", + "8891": "aes_password", + "8210": "aes_password", + "8289": "aes_password", + "7406": "aes_password", + "7692": "aes_password", + "7518": "aes_password", + "7244": "aes_password", + "8561": "aes_password", + "7325": "aes_password", + "7306": "aes_password", + "8266": "aes_password", + "8136": "aes_password", + "7991": "aes_password", + "8844": "aes_password", + "8259": "aes_password", + "7749": "aes_password", + "7238": "aes_password", + "7952": "aes_password", + "8528": "aes_password", + "8477": "aes_password", + "7555": "aes_password", + "7544": "aes_password", + "7478": "aes_password", + "7112": "aes_password", + "8931": "aes_password", + "8082": "aes_password", + "8189": "aes_password", + "8461": "aes_password", + "7740": "aes_password", + "8633": "aes_password", + "8322": "aes_password", + "7093": "aes_password", + "8415": "aes_password", + "8093": "aes_password", + "8682": "aes_password", + "7860": "aes_password", + "8580": "aes_password", + "8503": "aes_password", + "7794": "aes_password", + "8394": "aes_password", + "8487": "aes_password", + "8053": "aes_password", + "7277": "aes_password", + "7241": "aes_password", + "8430": "aes_password", + "8428": "aes_password", + "7805": "aes_password", + "8393": "aes_password", + "7711": "aes_password", + "7807": "aes_password", + "7600": "aes_password", + "8403": "aes_password", + "8141": "aes_password", + "8937": "aes_password", + "7559": "aes_password", + "7834": "aes_password", + "8837": "aes_password", + "7823": "aes_password", + "8928": "aes_password", + "7083": "aes_password", + "7590": "aes_password", + "8806": "aes_password", + "7130": "aes_password", + "7929": "aes_password", + "8684": "aes_password", + "8195": "aes_password", + "8706": "aes_password", + "7044": "aes_password", + "7403": "aes_password", + "8890": "aes_password", + "8364": "aes_password", + "8206": "aes_password", + "7882": "aes_password", + "8522": "aes_password", + "8958": "aes_password", + "7429": "aes_password", + "8586": "aes_password", + "8330": "aes_password", + "8922": "aes_password", + "7940": "aes_password", + "7379": "aes_password", + "8955": "aes_password", + "7168": "aes_password", + "7294": "aes_password", + "7949": "aes_password", + "7384": "aes_password", + "8832": "aes_password", + "7423": "aes_password", + "8763": "aes_password", + "7148": "aes_password", + "7029": "aes_password", + "7969": "aes_password", + "8190": "aes_password", + "8807": "aes_password", + "8889": "aes_password", + "7750": "aes_password", + "7348": "aes_password", + "7193": "aes_password", + "7459": "aes_password", + "7507": "aes_password", + "7536": "aes_password", + "8734": "aes_password", + "7174": "aes_password", + "8400": "aes_password", + "8630": "aes_password", + "7128": "aes_password", + "7261": "aes_password", + "7527": "aes_password", + "7232": "aes_password", + "7843": "aes_password", + "7326": "aes_password", + "8639": "aes_password", + "7830": "aes_password", + "7981": "aes_password", + "8404": "aes_password", + "8888": "aes_password", + "7920": "aes_password", + "7410": "aes_password", + "7204": "aes_password", + "8382": "aes_password", + "8355": "aes_password", + "7700": "aes_password", + "7606": "aes_password", + "7372": "aes_password", + "8106": "aes_password", + "8160": "aes_password", + "7511": "aes_password", + "8204": "aes_password", + "8732": "aes_password", + "8751": "aes_password", + "7727": "aes_password", + "7137": "aes_password", + "8311": "aes_password", + "8587": "aes_password", + "7336": "aes_password", + "7674": "aes_password", + "8009": "aes_password", + "7230": "aes_password", + "7383": "aes_password", + "8867": "aes_password", + "7260": "aes_password", + "7497": "aes_password", + "7390": "aes_password", + "8821": "aes_password", + "7274": "aes_password", + "7285": "aes_password", + "7857": "aes_password", + "8137": "aes_password", + "7114": "aes_password", + "7979": "aes_password", + "8726": "aes_password", + "7227": "aes_password", + "7714": "aes_password", + "8012": "aes_password", + "7613": "aes_password", + "8876": "aes_password", + "7622": "aes_password", + "8582": "aes_password", + "7120": "aes_password", + "7104": "aes_password", + "8785": "aes_password", + "8096": "aes_password", + "8129": "aes_password", + "8481": "aes_password", + "8695": "aes_password", + "7473": "aes_password", + "8163": "aes_password", + "8357": "aes_password", + "8501": "aes_password", + "7177": "aes_password", + "7931": "aes_password", + "8220": "aes_password", + "7399": "aes_password", + "7956": "aes_password", + "8801": "aes_password", + "7719": "aes_password", + "8042": "aes_password", + "7433": "aes_password", + "7827": "aes_password", + "8377": "aes_password", + "7745": "aes_password", + "7302": "aes_password", + "8399": "aes_password", + "7766": "aes_password", + "8720": "aes_password", + "8685": "aes_password", + "8558": "aes_password", + "7796": "aes_password", + "7319": "aes_password", + "7170": "aes_password", + "7342": "aes_password", + "7191": "aes_password", + "8747": "aes_password", + "7231": "aes_password", + "7817": "aes_password", + "7352": "aes_password", + "7057": "aes_password", + "8177": "aes_password", + "7221": "aes_password", + "7297": "aes_password", + "7686": "aes_password", + "7082": "aes_password", + "8414": "aes_password", + "8529": "aes_password", + "7257": "aes_password", + "7300": "aes_password", + "7159": "aes_password", + "8901": "aes_password", + "7578": "aes_password", + "8479": "aes_password", + "7225": "aes_password", + "8286": "aes_password", + "7182": "aes_password", + "8194": "aes_password", + "8850": "aes_password", + "7847": "aes_password", + "7665": "aes_password", + "8011": "aes_password", + "7702": "aes_password", + "8638": "aes_password", + "7116": "aes_password", + "7301": "aes_password", + "8936": "aes_password", + "8661": "aes_password", + "8333": "aes_password", + "8025": "aes_password", + "7368": "aes_password", + "8634": "aes_password", + "7154": "aes_password", + "8365": "aes_password", + "8736": "aes_password", + "8478": "aes_password", + "7436": "aes_password", + "7411": "aes_password", + "7913": "aes_password", + "8236": "aes_password", + "8854": "aes_password", + "8722": "aes_password", + "8227": "aes_password", + "7757": "aes_password", + "8835": "aes_password", + "8651": "aes_password", + "7417": "aes_password", + "7877": "aes_password", + "7200": "aes_password", + "8622": "aes_password", + "7004": "aes_password", + "8845": "aes_password", + "8159": "aes_password", + "8741": "aes_password", + "7106": "aes_password", + "8897": "aes_password", + "7968": "aes_password", + "7047": "aes_password", + "8860": "aes_password", + "8777": "aes_password", + "7597": "aes_password", + "8859": "aes_password", + "7117": "aes_password", + "8178": "aes_password", + "8642": "aes_password", + "7246": "aes_password", + "7557": "aes_password", + "7965": "aes_password", + "7699": "aes_password", + "8658": "aes_password", + "7442": "aes_password", + "8272": "aes_password", + "7821": "aes_password", + "7893": "aes_password", + "8665": "aes_password", + "8499": "aes_password", + "7897": "aes_password", + "7173": "aes_password", + "7007": "aes_password", + "8219": "aes_password", + "8040": "aes_password", + "7571": "aes_password", + "7526": "aes_password", + "8203": "aes_password", + "7810": "aes_password", + "8974": "aes_password", + "8200": "aes_password", + "7778": "aes_password", + "7987": "aes_password", + "7701": "aes_password", + "7443": "aes_password", + "7798": "aes_password", + "8995": "aes_password", + "8473": "aes_password", + "7132": "aes_password", + "7262": "aes_password", + "7720": "aes_password", + "7282": "aes_password", + "8066": "aes_password", + "7006": "aes_password", + "7197": "aes_password", + "7815": "aes_password", + "7933": "aes_password", + "8138": "aes_password", + "8418": "aes_password", + "7365": "aes_password", + "7786": "aes_password", + "7891": "aes_password", + "8317": "aes_password", + "8207": "aes_password", + "8416": "aes_password", + "7448": "aes_password", + "8843": "aes_password", + "7371": "aes_password", + "8780": "aes_password", + "7989": "aes_password", + "8043": "aes_password", + "7363": "aes_password", + "7550": "aes_password", + "8678": "aes_password", + "7837": "aes_password", + "8302": "aes_password", + "7907": "aes_password", + "8865": "aes_password", + "8153": "aes_password", + "8090": "aes_password", + "7268": "aes_password", + "8292": "aes_password", + "7919": "aes_password", + "8131": "aes_password", + "8815": "aes_password", + "8154": "aes_password", + "7777": "aes_password", + "8369": "aes_password", + "8929": "aes_password", + "7670": "aes_password", + "7484": "aes_password", + "8353": "aes_password", + "8017": "aes_password", + "8833": "aes_password", + "8001": "aes_password", + "8058": "aes_password", + "7918": "aes_password", + "7694": "aes_password", + "7485": "aes_password", + "8592": "aes_password", + "7584": "aes_password", + "8527": "aes_password", + "8285": "aes_password", + "8264": "aes_password", + "8879": "aes_password", + "7944": "aes_password", + "7000": "aes_password", + "7187": "aes_password", + "7039": "aes_password", + "7769": "aes_password", + "8063": "aes_password", + "8701": "aes_password", + "7432": "aes_password", + "8594": "aes_password", + "7052": "aes_password", + "7369": "aes_password", + "8474": "aes_password", + "7709": "aes_password", + "8296": "aes_password", + "8278": "aes_password", + "8395": "aes_password", + "8674": "aes_password", + "8733": "aes_password", + "7890": "aes_password", + "7080": "aes_password", + "7528": "aes_password", + "7782": "aes_password", + "7466": "aes_password", + "7903": "aes_password", + "8105": "aes_password", + "7144": "aes_password", + "8069": "aes_password", + "8254": "aes_password", + "8573": "aes_password", + "7787": "aes_password", + "7577": "aes_password", + "8918": "aes_password", + "8767": "aes_password", + "7611": "aes_password", + "8233": "aes_password", + "7802": "aes_password", + "7129": "aes_password", + "8968": "aes_password", + "8217": "aes_password", + "8176": "aes_password", + "8027": "aes_password", + "7941": "aes_password", + "8070": "aes_password", + "8697": "aes_password", + "8798": "aes_password", + "8553": "aes_password", + "8510": "aes_password", + "7254": "aes_password", + "7386": "aes_password", + "8800": "aes_password", + "7712": "aes_password", + "7844": "aes_password", + "7535": "aes_password", + "8273": "aes_password", + "8875": "aes_password", + "8675": "aes_password", + "7396": "aes_password", + "7649": "aes_password", + "8623": "aes_password", + "7186": "aes_password", + "8125": "aes_password", + "7734": "aes_password", + "8275": "aes_password", + "7521": "aes_password", + "7135": "aes_password", + "8546": "aes_password", + "7867": "aes_password", + "8989": "aes_password", + "8787": "aes_password", + "8225": "aes_password", + "8457": "aes_password", + "7405": "aes_password", + "7588": "aes_password", + "7854": "aes_password", + "7789": "aes_password", + "8133": "aes_password", + "7641": "aes_password", + "8535": "aes_password", + "7849": "aes_password", + "7594": "aes_password", + "8313": "aes_password", + "8097": "aes_password", + "8030": "aes_password", + "8737": "aes_password", + "8260": "aes_password", + "8950": "aes_password", + "7249": "aes_password", + "7644": "aes_password", + "7912": "aes_password", + "8979": "aes_password", + "8998": "aes_password", + "8731": "aes_password", + "8662": "aes_password", + "7983": "aes_password", + "7035": "aes_password", + "7841": "aes_password", + "8600": "aes_password", + "7228": "aes_password", + "7071": "aes_password", + "8080": "aes_password", + "8713": "aes_password", + "7210": "aes_password", + "7935": "aes_password", + "8057": "aes_password", + "8242": "aes_password", + "7084": "aes_password", + "7070": "aes_password", + "7494": "aes_password", + "8451": "aes_password", + "8626": "aes_password", + "8618": "aes_password", + "7741": "aes_password", + "7118": "aes_password", + "7387": "aes_password", + "7715": "aes_password", + "8143": "aes_password", + "7668": "aes_password", + "7716": "aes_password", + "8101": "aes_password", + "7234": "aes_password", + "8021": "aes_password", + "7156": "aes_password", + "8392": "aes_password", + "7900": "aes_password", + "7055": "aes_password", + "8566": "aes_password", + "7869": "aes_password", + "7864": "aes_password", + "8265": "aes_password", + "8216": "aes_password", + "7738": "aes_password", + "7467": "aes_password", + "8447": "aes_password", + "8564": "aes_password", + "7767": "aes_password", + "7811": "aes_password", + "7898": "aes_password", + "8182": "aes_password", + "8065": "aes_password", + "7561": "aes_password", + "8545": "aes_password", + "7253": "aes_password", + "8173": "aes_password", + "7922": "aes_password", + "7951": "aes_password", + "7612": "aes_password", + "7324": "aes_password", + "7549": "aes_password", + "7858": "aes_password", + "8655": "aes_password", + "8211": "aes_password", + "8469": "aes_password", + "7298": "aes_password", + "8380": "aes_password", + "7394": "aes_password", + "7089": "aes_password", + "8060": "aes_password", + "7591": "aes_password", + "7542": "aes_password", + "7540": "aes_password", + "7456": "aes_password", + "7768": "aes_password", + "8489": "aes_password", + "8089": "aes_password", + "7838": "aes_password", + "8644": "aes_password", + "8344": "aes_password", + "7739": "aes_password", + "7984": "aes_password", + "7909": "aes_password", + "8517": "aes_password", + "8056": "aes_password", + "8297": "aes_password", + "8647": "aes_password", + "8334": "aes_password", + "7056": "aes_password", + "7164": "aes_password", + "8878": "aes_password", + "7816": "aes_password", + "7444": "aes_password", + "8996": "aes_password", + "8359": "aes_password", + "7901": "aes_password", + "8127": "aes_password", + "7424": "aes_password", + "8116": "aes_password", + "7354": "aes_password", + "8919": "aes_password", + "7214": "aes_password", + "7589": "aes_password", + "8982": "aes_password", + "8244": "aes_password", + "8295": "aes_password", + "7669": "aes_password", + "7562": "aes_password", + "8470": "aes_password", + "7474": "aes_password", + "7345": "aes_password", + "7799": "aes_password", + "8269": "aes_password", + "8213": "aes_password", + "8786": "aes_password", + "7482": "aes_password", + "8261": "aes_password", + "8755": "aes_password", + "8882": "aes_password", + "7166": "aes_password", + "7428": "aes_password", + "8766": "aes_password", + "7458": "aes_password", + "8372": "aes_password", + "8045": "aes_password", + "8185": "aes_password", + "7602": "aes_password", + "8373": "aes_password", + "7826": "aes_password", + "8249": "aes_password", + "8881": "aes_password", + "8830": "aes_password", + "8044": "aes_password", + "7563": "aes_password", + "7509": "aes_password", + "7290": "aes_password", + "7019": "aes_password", + "8454": "aes_password", + "8637": "aes_password", + "7876": "aes_password", + "8500": "aes_password", + "8226": "aes_password", + "7744": "aes_password", + "7753": "aes_password", + "7017": "aes_password", + "8362": "aes_password", + "7642": "aes_password", + "8091": "aes_password", + "7512": "aes_password", + "7708": "aes_password", + "8335": "aes_password", + "7292": "aes_password", + "8281": "aes_password", + "8132": "aes_password", + "7683": "aes_password", + "7048": "aes_password", + "7316": "aes_password", + "7011": "aes_password", + "8299": "aes_password", + "8440": "aes_password", + "8147": "aes_password", + "8280": "aes_password", + "8130": "aes_password", + "8303": "aes_password", + "8610": "aes_password", + "8361": "aes_password", + "8339": "aes_password", + "8037": "aes_password", + "8102": "aes_password", + "7845": "aes_password", + "7307": "aes_password", + "8607": "aes_password", + "8523": "aes_password", + "7839": "aes_password", + "7279": "aes_password", + "7321": "aes_password", + "8032": "aes_password", + "8894": "aes_password", + "8166": "aes_password", + "7381": "aes_password", + "8113": "aes_password", + "8139": "aes_password", + "8290": "aes_password", + "7990": "aes_password", + "7388": "aes_password", + "8571": "aes_password", + "8730": "aes_password", + "8441": "aes_password", + "8074": "aes_password", + "7813": "aes_password", + "8555": "aes_password", + "8978": "aes_password", + "7835": "aes_password", + "7323": "aes_password", + "7293": "aes_password", + "8550": "aes_password", + "7617": "aes_password", + "8071": "aes_password", + "7998": "aes_password", + "8115": "aes_password", + "7419": "aes_password", + "8825": "aes_password", + "8412": "aes_password", + "8019": "aes_password", + "8142": "aes_password", + "8186": "aes_password", + "8909": "aes_password", + "8078": "aes_password", + "8952": "aes_password", + "8360": "aes_password", + "8336": "aes_password", + "7953": "aes_password", + "7005": "aes_password", + "8663": "aes_password", + "8866": "aes_password", + "7950": "aes_password", + "7248": "aes_password", + "8519": "aes_password", + "8099": "aes_password", + "8151": "aes_password", + "8959": "aes_password", + "7042": "aes_password", + "7939": "aes_password", + "8064": "aes_password", + "8165": "aes_password", + "8836": "aes_password", + "8965": "aes_password", + "7431": "aes_password", + "7223": "aes_password", + "7999": "aes_password", + "8913": "aes_password", + "7921": "aes_password", + "8883": "aes_password", + "8169": "aes_password", + "7441": "aes_password", + "7469": "aes_password", + "7666": "aes_password", + "8547": "aes_password", + "7993": "aes_password", + "7705": "aes_password", + "8103": "aes_password", + "8524": "aes_password", + "8240": "aes_password", + "7779": "aes_password", + "7344": "aes_password", + "7395": "aes_password", + "8420": "aes_password", + "7287": "aes_password", + "7926": "aes_password", + "8100": "aes_password", + "8874": "aes_password", + "7496": "aes_password", + "7626": "aes_password", + "7784": "aes_password", + "7880": "aes_password", + "7226": "aes_password", + "8405": "aes_password", + "7910": "aes_password", + "8693": "aes_password", + "7997": "aes_password", + "8585": "aes_password", + "8383": "aes_password", + "8429": "aes_password", + "7878": "aes_password", + "8098": "aes_password", + "7288": "aes_password", + "8509": "aes_password", + "8809": "aes_password", + "7973": "aes_password", + "7620": "aes_password", + "7115": "aes_password", + "8445": "aes_password", + "8977": "aes_password", + "8341": "aes_password", + "7859": "aes_password", + "8256": "aes_password", + "7119": "aes_password", + "8442": "aes_password", + "8606": "aes_password", + "7992": "aes_password", + "7270": "aes_password", + "8988": "aes_password", + "7375": "aes_password", + "7747": "aes_password", + "7100": "aes_password", + "7639": "aes_password", + "7296": "aes_password", + "7435": "aes_password", + "7889": "aes_password", + "7636": "aes_password", + "7946": "aes_password", + "7819": "aes_password", + "7978": "aes_password", + "7728": "aes_password", + "8152": "aes_password", + "7660": "aes_password", + "7464": "aes_password", + "8398": "aes_password", + "8804": "aes_password", + "7450": "aes_password", + "8020": "aes_password", + "7988": "aes_password", + "7398": "aes_password", + "7171": "aes_password", + "8315": "aes_password", + "7871": "aes_password", + "8513": "aes_password", + "7250": "aes_password", + "8181": "aes_password", + "7793": "aes_password", + "7414": "aes_password", + "7179": "aes_password", + "7445": "aes_password", + "7259": "aes_password", + "8779": "aes_password", + "7695": "aes_password", + "7149": "aes_password", + "8973": "aes_password", + "8258": "aes_password", + "7291": "aes_password", + "8301": "aes_password", + "7447": "aes_password", + "8276": "aes_password", + "8279": "aes_password", + "7572": "aes_password", + "7146": "aes_password", + "7764": "aes_password", + "7504": "aes_password", + "7604": "aes_password", + "7465": "aes_password", + "7565": "aes_password", + "8976": "aes_password", + "8016": "aes_password", + "7707": "aes_password", + "7627": "aes_password", + "8379": "aes_password", + "7523": "aes_password", + "7145": "aes_password", + "7971": "aes_password", + "8790": "aes_password", + "8475": "aes_password", + "7718": "aes_password", + "7481": "aes_password", + "7650": "aes_password", + "7808": "aes_password", + "7142": "aes_password", + "8816": "aes_password", + "7676": "aes_password", + "7873": "aes_password", + "7885": "aes_password", + "7049": "aes_password", + "8994": "aes_password", + "8863": "aes_password", + "7178": "aes_password", + "8625": "aes_password", + "7237": "aes_password", + "7936": "aes_password", + "7575": "aes_password", + "7673": "aes_password", + "7689": "aes_password", + "7455": "aes_password", + "7780": "aes_password", + "8687": "aes_password", + "7675": "aes_password", + "8431": "aes_password", + "7030": "aes_password", + "8756": "aes_password", + "8318": "aes_password", + "7088": "aes_password", + "8792": "aes_password", + "7454": "aes_password", + "7776": "aes_password", + "8531": "aes_password", + "8829": "aes_password", + "8350": "aes_password", + "7198": "aes_password", + "7438": "aes_password", + "8543": "aes_password", + "8981": "aes_password", + "8915": "aes_password", + "8446": "aes_password", + "7812": "aes_password", + "7570": "aes_password", + "7054": "aes_password", + "8903": "aes_password", + "7213": "aes_password", + "8670": "aes_password", + "7211": "aes_password", + "7327": "aes_password", + "7121": "aes_password", + "8926": "aes_password", + "7430": "aes_password", + "8413": "aes_password", + "7337": "aes_password", + "7771": "aes_password", + "7350": "aes_password", + "7284": "aes_password", + "8252": "aes_password", + "7138": "aes_password", + "7491": "aes_password", + "8774": "aes_password", + "8828": "aes_password", + "7069": "aes_password", + "7545": "aes_password", + "7360": "aes_password", + "8771": "aes_password", + "7915": "aes_password", + "8485": "aes_password", + "7002": "aes_password", + "8838": "aes_password", + "7040": "aes_password", + "8820": "aes_password", + "8033": "aes_password", + "7053": "aes_password", + "8640": "aes_password", + "8552": "aes_password", + "8180": "aes_password", + "7361": "aes_password", + "7560": "aes_password", + "8277": "aes_password", + "7243": "aes_password", + "8999": "aes_password", + "7391": "aes_password", + "8930": "aes_password", + "8957": "aes_password", + "8504": "aes_password", + "7122": "aes_password", + "7139": "aes_password", + "8789": "aes_password", + "8245": "aes_password", + "7493": "aes_password", + "8810": "aes_password", + "8521": "aes_password", + "7317": "aes_password", + "7476": "aes_password", + "8562": "aes_password", + "7724": "aes_password", + "8652": "aes_password", + "7434": "aes_password", + "8110": "aes_password", + "8533": "aes_password", + "7970": "aes_password", + "8745": "aes_password", + "7585": "aes_password", + "7085": "aes_password", + "8421": "aes_password", + "8370": "aes_password", + "7289": "aes_password", + "7996": "aes_password", + "8840": "aes_password", + "7194": "aes_password", + "7256": "aes_password", + "8609": "aes_password", + "8604": "aes_password", + "7062": "aes_password", + "7252": "aes_password", + "8076": "aes_password", + "7647": "aes_password", + "8271": "aes_password", + "7684": "aes_password", + "8539": "aes_password", + "8588": "aes_password", + "8611": "aes_password", + "8654": "aes_password", + "7836": "aes_password", + "8351": "aes_password", + "7962": "aes_password", + "8230": "aes_password", + "7332": "aes_password", + "7573": "aes_password", + "7825": "aes_password", + "7934": "aes_password", + "8572": "aes_password", + "8700": "aes_password", + "8328": "aes_password", + "8046": "aes_password", + "7425": "aes_password", + "8729": "aes_password", + "8886": "aes_password", + "7806": "aes_password", + "7239": "aes_password", + "8601": "aes_password", + "8717": "aes_password", + "7697": "aes_password", + "8352": "aes_password", + "8123": "aes_password", + "7205": "aes_password", + "8298": "aes_password", + "7490": "aes_password", + "7023": "aes_password", + "7964": "aes_password", + "8986": "aes_password", + "7101": "aes_password", + "7634": "aes_password", + "7269": "aes_password", + "8000": "aes_password", + "8172": "aes_password", + "7172": "aes_password", + "8107": "aes_password", + "8486": "aes_password", + "8716": "aes_password", + "8961": "aes_password", + "7685": "aes_password", + "7667": "aes_password", + "7359": "aes_password", + "7804": "aes_password", + "7012": "aes_password", + "8975": "aes_password", + "7598": "aes_password", + "7662": "aes_password", + "7043": "aes_password", + "8331": "aes_password", + "8617": "aes_password", + "7015": "aes_password", + "8390": "aes_password", + "8608": "aes_password", + "7346": "aes_password", + "7331": "aes_password", + "8984": "aes_password", + "7339": "aes_password", + "8465": "aes_password", + "7184": "aes_password", + "7851": "aes_password", + "7868": "aes_password", + "7663": "aes_password", + "8849": "aes_password", + "8667": "aes_password", + "7506": "aes_password", + "7351": "aes_password", + "7109": "aes_password", + "7426": "aes_password", + "7453": "aes_password", + "7737": "aes_password", + "7870": "aes_password", + "8308": "aes_password", + "7543": "aes_password", + "7299": "aes_password", + "8340": "aes_password", + "8243": "aes_password", + "7357": "aes_password", + "8641": "aes_password", + "7553": "aes_password", + "8464": "aes_password", + "7966": "aes_password", + "8581": "aes_password", + "7343": "aes_password", + "8759": "aes_password", + "7631": "aes_password", + "7181": "aes_password", + "7415": "aes_password", + "8187": "aes_password", + "8924": "aes_password", + "7452": "aes_password", + "8797": "aes_password", + "8525": "aes_password", + "7026": "aes_password", + "7322": "aes_password", + "7982": "aes_password", + "8906": "aes_password", + "8342": "aes_password", + "7977": "aes_password", + "7059": "aes_password", + "7986": "aes_password", + "7513": "aes_password", + "7134": "aes_password", + "7462": "aes_password", + "7800": "aes_password", + "8966": "aes_password", + "8453": "aes_password", + "7760": "aes_password", + "7155": "aes_password", + "8632": "aes_password", + "7679": "aes_password", + "8808": "aes_password", + "8508": "aes_password", + "7479": "aes_password", + "7263": "aes_password", + "8648": "aes_password", + "7972": "aes_password", + "8215": "aes_password", + "8948": "aes_password", + "7894": "aes_password", + "7364": "aes_password", + "8520": "aes_password", + "8754": "aes_password", + "7773": "aes_password", + "7255": "aes_password", + "7413": "aes_password", + "7524": "aes_password", + "8511": "aes_password", + "8914": "aes_password", + "8557": "aes_password", + "8085": "aes_password", + "7938": "aes_password", + "8549": "aes_password", + "8839": "aes_password", + "7628": "aes_password", + "7852": "aes_password", + "8788": "aes_password", + "8680": "aes_password", + "8371": "aes_password", + "8378": "aes_password", + "7525": "aes_password", + "7338": "aes_password", + "8969": "aes_password", + "8895": "aes_password", + "7280": "aes_password", + "8827": "aes_password", + "7502": "aes_password", + "8126": "aes_password", + "7208": "aes_password", + "8643": "aes_password", + "7175": "aes_password", + "8183": "aes_password", + "7314": "aes_password", + "8818": "aes_password", + "8039": "aes_password", + "7392": "aes_password", + "7902": "aes_password", + "7576": "aes_password", + "8537": "aes_password", + "7618": "aes_password", + "7203": "aes_password", + "8306": "aes_password", + "7855": "aes_password", + "8778": "aes_password", + "7928": "aes_password", + "7866": "aes_password", + "8483": "aes_password", + "7152": "aes_password", + "7309": "aes_password", + "7222": "aes_password", + "8023": "aes_password", + "7619": "aes_password", + "8985": "aes_password", + "7643": "aes_password", + "7440": "aes_password", + "7646": "aes_password", + "7892": "aes_password", + "7061": "aes_password", + "8941": "aes_password", + "7846": "aes_password", + "8683": "aes_password", + "7829": "aes_password", + "8711": "aes_password", + "8235": "aes_password", + "8463": "aes_password", + "7899": "aes_password", + "8602": "aes_password", + "8114": "aes_password", + "7593": "aes_password", + "7385": "aes_password", + "7624": "aes_password", + "8659": "aes_password", + "7460": "aes_password", + "7917": "aes_password", + "8705": "aes_password", + "7713": "aes_password", + "7875": "aes_password", + "7788": "aes_password", + "7548": "aes_password", + "7076": "aes_password", + "8770": "aes_password", + "8506": "aes_password", + "8769": "aes_password", + "8197": "aes_password", + "7655": "aes_password", + "7247": "aes_password", + "8432": "aes_password", + "7783": "aes_password", + "8327": "aes_password", + "8120": "aes_password", + "7131": "aes_password", + "8268": "aes_password", + "7696": "aes_password", + "8783": "aes_password", + "8406": "aes_password", + "8433": "aes_password", + "8024": "aes_password", + "8005": "aes_password", + "7097": "aes_password", + "8554": "aes_password", + "8967": "aes_password", + "8943": "aes_password", + "7792": "aes_password", + "8003": "aes_password", + "8698": "aes_password", + "7229": "aes_password", + "7687": "aes_password", + "8532": "aes_password", + "7304": "aes_password", + "8122": "aes_password", + "8202": "aes_password", + "7833": "aes_password", + "8679": "aes_password", + "7580": "aes_password", + "8498": "aes_password", + "8345": "aes_password", + "8696": "aes_password", + "7422": "aes_password", + "8650": "aes_password", + "8905": "aes_password", + "7495": "aes_password", + "7075": "aes_password", + "8912": "aes_password", + "8570": "aes_password", + "8847": "aes_password", + "8613": "aes_password", + "7551": "aes_password", + "7150": "aes_password", + "7218": "aes_password", + "7514": "aes_password", + "7195": "aes_password", + "8775": "aes_password", + "8757": "aes_password", + "8251": "aes_password", + "7209": "aes_password", + "7162": "aes_password", + "7564": "aes_password", + "8121": "aes_password", + "7960": "aes_password", + "7656": "aes_password", + "7397": "aes_password", + "8699": "aes_password", + "7421": "aes_password", + "8263": "aes_password", + "8598": "aes_password", + "7009": "aes_password", + "7861": "aes_password", + "7001": "aes_password", + "8744": "aes_password", + "8368": "aes_password", + "7975": "aes_password", + "7165": "aes_password", + "8530": "aes_password", + "8614": "aes_password", + "7258": "aes_password", + "8034": "aes_password", + "7449": "aes_password", + "7932": "aes_password", + "7906": "aes_password", + "8124": "aes_password", + "8526": "aes_password", + "8568": "aes_password", + "7754": "aes_password", + "8228": "aes_password", + "8904": "aes_password", + "8161": "aes_password", + "7547": "aes_password", + "7310": "aes_password", + "7558": "aes_password", + "8338": "aes_password", + "8799": "aes_password", + "8224": "aes_password", + "8563": "aes_password", + "7863": "aes_password", + "7682": "aes_password", + "8944": "aes_password", + "7914": "aes_password", + "7541": "aes_password", + "8953": "aes_password", + "7318": "aes_password", + "8560": "aes_password", + "8972": "aes_password", + "7608": "aes_password", + "8455": "aes_password", + "7468": "aes_password", + "7037": "aes_password", + "7690": "aes_password", + "8814": "aes_password", + "8664": "aes_password", + "7756": "aes_password", + "7569": "aes_password", + "7102": "aes_password", + "8119": "aes_password", + "7161": "aes_password", + "7378": "aes_password", + "7790": "aes_password", + "7638": "aes_password", + "7678": "aes_password", + "7483": "aes_password", + "7063": "aes_password", + "8348": "aes_password", + "7974": "aes_password", + "8596": "aes_password", + "8653": "aes_password", + "8319": "aes_password", + "8933": "aes_password", + "8877": "aes_password", + "7886": "aes_password", + "8459": "aes_password", + "8008": "aes_password", + "8631": "aes_password", + "8482": "aes_password", + "8868": "aes_password", + "7031": "aes_password", + "8036": "aes_password", + "7530": "aes_password", + "7498": "aes_password", + "8497": "aes_password", + "8505": "aes_password", + "8323": "aes_password", + "7566": "aes_password", + "7038": "aes_password", + "7640": "aes_password", + "8690": "aes_password", + "8150": "aes_password", + "7235": "aes_password", + "8921": "aes_password", + "8196": "aes_password", + "8938": "aes_password", + "7531": "aes_password", + "8002": "aes_password", + "7439": "aes_password", + "7451": "aes_password", + "7583": "aes_password", + "7400": "aes_password", + "7242": "aes_password", + "8589": "aes_password", + "8826": "aes_password", + "8050": "aes_password", + "8960": "aes_password", + "7409": "aes_password", + "8873": "aes_password", + "8710": "aes_password", + "8932": "aes_password", + "7751": "aes_password", + "8846": "aes_password", + "7420": "aes_password", + "8817": "aes_password", + "7884": "aes_password", + "7124": "aes_password", + "8047": "aes_password", + "8081": "aes_password", + "8144": "aes_password", + "7477": "aes_password", + "7066": "aes_password", + "7862": "aes_password", + "8389": "aes_password", + "8321": "aes_password", + "8239": "aes_password", + "8987": "aes_password", + "7032": "aes_password", + "7522": "aes_password", + "7691": "aes_password", + "8567": "aes_password", + "8425": "aes_password", + "8128": "aes_password", + "8853": "aes_password", + "7092": "aes_password", + "8624": "aes_password", + "7416": "aes_password", + "7461": "aes_password", + "7283": "aes_password", + "7463": "aes_password", + "8956": "aes_password", + "8574": "aes_password", + "8872": "aes_password", + "8480": "aes_password", + "8384": "aes_password", + "8540": "aes_password", + "8381": "aes_password", + "7064": "aes_password", + "8095": "aes_password", + "8349": "aes_password", + "8855": "aes_password", + "7881": "aes_password", + "7519": "aes_password", + "7961": "aes_password", + "8728": "aes_password", + "8222": "aes_password", + "8326": "aes_password", + "8218": "aes_password", + "8723": "aes_password", + "7068": "aes_password", + "7202": "aes_password", + "7923": "aes_password", + "7347": "aes_password", + "8794": "aes_password", + "7264": "aes_password", + "7437": "aes_password", + "8494": "aes_password", + "7335": "aes_password", + "7717": "aes_password", + "7295": "aes_password", + "8061": "aes_password", + "8702": "aes_password", + "7824": "aes_password", + "8111": "aes_password", + "7176": "aes_password", + "8332": "aes_password", + "8556": "aes_password", + "7818": "aes_password", + "8583": "aes_password", + "8920": "aes_password", + "8006": "aes_password", + "8108": "aes_password", + "7308": "aes_password", + "8708": "aes_password", + "7587": "aes_password", + "7219": "aes_password", + "7367": "aes_password", + "8681": "aes_password", + "8945": "aes_password", + "8337": "aes_password", + "7586": "aes_password", + "7095": "aes_password", + "8880": "aes_password", + "8388": "aes_password", + "7065": "aes_password", + "7045": "aes_password", + "7111": "aes_password", + "8762": "aes_password", + "7703": "aes_password", + "8686": "aes_password", + "8367": "aes_password", + "8796": "aes_password", + "7840": "aes_password", + "7945": "aes_password", + "8329": "aes_password", + "7107": "aes_password", + "8167": "aes_password", + "8862": "aes_password", + "8951": "aes_password", + "7963": "aes_password", + "8073": "aes_password", + "8068": "aes_password", + "7759": "aes_password", + "8175": "aes_password", + "7704": "aes_password", + "8083": "aes_password", + "7206": "aes_password", + "8054": "aes_password", + "7160": "aes_password", + "8727": "aes_password", + "7333": "aes_password", + "7094": "aes_password", + "8753": "aes_password", + "7755": "aes_password", + "8577": "aes_password", + "7099": "aes_password", + "7334": "aes_password", + "8055": "aes_password", + "8397": "aes_password", + "8214": "aes_password", + "8366": "aes_password", + "8541": "aes_password", + "7312": "aes_password", + "8283": "aes_password", + "8052": "aes_password", + "7653": "aes_password", + "7402": "aes_password", + "7224": "aes_password", + "7217": "aes_password", + "7126": "aes_password", + "7710": "aes_password", + "8668": "aes_password", + "8401": "aes_password", + "8628": "aes_password", + "8424": "aes_password", + "7658": "aes_password", + "7471": "aes_password", + "8813": "aes_password", + "7374": "aes_password", + "8067": "aes_password", + "8703": "aes_password", + "7681": "aes_password", + "7382": "aes_password", + "8538": "aes_password", + "8676": "aes_password", + "7033": "aes_password", + "7480": "aes_password", + "8841": "aes_password", + "8834": "aes_password", + "8666": "aes_password", + "8084": "aes_password", + "7925": "aes_password", + "7742": "aes_password", + "8669": "aes_password", + "7532": "aes_password", + "8657": "aes_password", + "8358": "aes_password", + "8590": "aes_password", + "7746": "aes_password", + "7605": "aes_password", + "7905": "aes_password", + "8294": "aes_password", + "8980": "aes_password", + "8735": "aes_password", + "8179": "aes_password", + "8760": "aes_password", + "7036": "aes_password", + "7404": "aes_password", + "8062": "aes_password", + "8201": "aes_password", + "7320": "aes_password", + "8964": "aes_password", + "8402": "aes_password", + "7303": "aes_password", + "8765": "aes_password", + "7803": "aes_password", + "8247": "aes_password", + "8449": "aes_password", + "7278": "aes_password", + "7189": "aes_password", + "8284": "aes_password", + "8848": "aes_password", + "7809": "aes_password", + "7503": "aes_password", + "8419": "aes_password", + "7723": "aes_password", + "7446": "aes_password", + "7556": "aes_password", + "8739": "aes_password", + "8514": "aes_password", + "7486": "aes_password", + "7143": "aes_password", + "8709": "aes_password", + "7105": "aes_password", + "7654": "aes_password", + "8436": "aes_password", + "8761": "aes_password", + "7595": "aes_password", + "8209": "aes_password", + "8029": "aes_password", + "7603": "aes_password", + "8257": "aes_password", + "8649": "aes_password", + "7370": "aes_password", + "8534": "aes_password", + "7267": "aes_password", + "8565": "aes_password", + "7664": "aes_password", + "8551": "aes_password", + "7888": "aes_password", + "8576": "aes_password", + "8893": "aes_password", + "8857": "aes_password", + "7599": "aes_password", + "7568": "aes_password", + "7791": "aes_password", + "7916": "aes_password", + "8885": "aes_password", + "7418": "aes_password", + "7245": "aes_password", + "8170": "aes_password", + "8466": "aes_password", + "7098": "aes_password", + "7489": "aes_password", + "8997": "aes_password", + "8014": "aes_password", + "8805": "aes_password", + "7774": "aes_password", + "8325": "aes_password", + "8262": "aes_password", + "8038": "aes_password", + "8512": "aes_password", + "7546": "aes_password", + "7614": "aes_password", + "8309": "aes_password", + "7123": "aes_password", + "8324": "aes_password", + "8591": "aes_password", + "8255": "aes_password", + "7623": "aes_password", + "8270": "aes_password", + "8715": "aes_password", + "8515": "aes_password", + "8088": "aes_password", + "8375": "aes_password", + "7693": "aes_password", + "8752": "aes_password", + "8059": "aes_password", + "7271": "aes_password", + "8917": "aes_password", + "7500": "aes_password", + "8656": "aes_password", + "7022": "aes_password", + "8812": "aes_password", + "8858": "aes_password", + "7980": "aes_password", + "7090": "aes_password", + "7637": "aes_password", + "7671": "aes_password", + "7904": "aes_password", + "8629": "aes_password", + "7034": "aes_password", + "7207": "aes_password", + "7096": "aes_password", + "7621": "aes_password", + "7770": "aes_password", + "8221": "aes_password", + "7077": "aes_password", + "7661": "aes_password", + "7994": "aes_password", + "7632": "aes_password", + "7848": "aes_password", + "8822": "aes_password", + "7328": "aes_password", + "8438": "aes_password", + "8939": "aes_password", + "8636": "aes_password", + "7995": "aes_password", + "8363": "aes_password", + "7010": "aes_password", + "7212": "aes_password", + "8559": "aes_password", + "8548": "aes_password", + "7275": "aes_password", + "8819": "aes_password", + "7927": "aes_password", + "8291": "aes_password", + "7652": "aes_password", + "7954": "aes_password", + "7832": "aes_password", + "8087": "aes_password", + "8646": "aes_password", + "7736": "aes_password", + "7581": "aes_password", + "7216": "aes_password", + "7190": "aes_password", + "7180": "aes_password", + "7412": "aes_password", + "8569": "aes_password", + "8010": "aes_password", + "7529": "aes_password", + "8118": "aes_password", + "8162": "aes_password", + "8925": "aes_password", + "8490": "aes_password", + "7762": "aes_password", + "7610": "aes_password", + "8439": "aes_password", + "7698": "aes_password", + "7648": "aes_password", + "8742": "aes_password", + "8993": "aes_password", + "7785": "aes_password", + "7609": "aes_password", + "7896": "aes_password", + "8851": "aes_password", + "7731": "aes_password", + "8248": "aes_password", + "8386": "aes_password", + "8621": "aes_password", + "7349": "aes_password", + "7366": "aes_password", + "7169": "aes_password", + "7942": "aes_password", + "7633": "aes_password", + "7781": "aes_password", + "8387": "aes_password", + "8117": "aes_password", + "8782": "aes_password", + "8192": "aes_password", + "8104": "aes_password", + "7615": "aes_password", + "7050": "aes_password", + "7315": "aes_password", + "7725": "aes_password", + "7058": "aes_password", + "7865": "aes_password", + "7133": "aes_password", + "8458": "aes_password", + "7552": "aes_password", + "7761": "aes_password", + "8229": "aes_password", + "7677": "aes_password", + "8619": "aes_password", + "8193": "aes_password", + "8164": "aes_password", + "8493": "aes_password", + "7943": "aes_password", + "8476": "aes_password", + "8427": "aes_password", + "7240": "aes_password", + "8773": "aes_password", + "8749": "aes_password", + "8983": "aes_password", + "8910": "aes_password", + "7722": "aes_password", + "8092": "aes_password", + "8086": "aes_password", + "7911": "aes_password", + "8793": "aes_password", + "8725": "aes_password", + "7201": "aes_password", + "7538": "aes_password", + "7016": "aes_password", + "8396": "aes_password", + "7726": "aes_password", + "8495": "aes_password", + "8300": "aes_password", + "7948": "aes_password", + "8237": "aes_password", + "8802": "aes_password", + "7074": "aes_password", + "8077": "aes_password", + "7516": "aes_password", + "8908": "aes_password", + "8616": "aes_password", + "7814": "aes_password", + "8688": "aes_password", + "7236": "aes_password", + "7537": "aes_password", + "8691": "aes_password", + "8109": "aes_password", + "8223": "aes_password", + "7895": "aes_password", + "8018": "aes_password", + "7820": "aes_password", + "7407": "aes_password", + "8544": "aes_password", + "7651": "aes_password", + "8288": "aes_password", + "7930": "aes_password", + "8004": "aes_password", + "8935": "aes_password", + "7272": "aes_password", + "8707": "aes_password", + "8907": "aes_password", + "8146": "aes_password", + "7850": "aes_password", + "8579": "aes_password", + "7125": "aes_password", + "8692": "aes_password", + "7828": "aes_password", + "7113": "aes_password", + "7027": "aes_password", + "8148": "aes_password", + "7078": "aes_password", + "7508": "aes_password", + "8434": "aes_password", + "8374": "aes_password", + "8356": "aes_password", + "8942": "aes_password", + "8437": "aes_password", + "8902": "aes_password", + "7887": "aes_password", + "7281": "aes_password", + "8593": "aes_password", + "7853": "aes_password", + "8898": "aes_password", + "8927": "aes_password", + "8184": "aes_password", + "7879": "aes_password", + "8409": "aes_password", + "8241": "aes_password", + "7013": "aes_password", + "8145": "aes_password", + "8094": "aes_password", + "8450": "aes_password", + "7353": "aes_password", + "7286": "aes_password", + "7393": "aes_password", + "8852": "aes_password", + "8456": "aes_password", + "8660": "aes_password", + "7908": "aes_password", + "7408": "aes_password", + "8784": "aes_password", + "8990": "aes_password", + "8899": "aes_password", + "8748": "aes_password", + "8385": "aes_password", + "7151": "aes_password", + "7355": "aes_password", + "8051": "aes_password", + "7842": "aes_password", + "7985": "aes_password", + "7081": "aes_password", + "8620": "aes_password", + "7924": "aes_password", + "7305": "aes_password", + "8949": "aes_password", + "7091": "aes_password", + "7947": "aes_password", + "8075": "aes_password", + "8721": "aes_password", + "8605": "aes_password", + "8305": "aes_password", + "7185": "aes_password", + "7831": "aes_password", + "8672": "aes_password", + "8253": "aes_password", + "8274": "aes_password", + "8746": "aes_password", + "8738": "aes_password", + "8015": "aes_password", + "8007": "aes_password", + "8768": "aes_password", + "7492": "aes_password", + "7752": "aes_password", + "8391": "aes_password", + "7596": "aes_password", + "7797": "aes_password", + "8940": "aes_password", + "8304": "aes_password", + "7706": "aes_password", + "7060": "aes_password", + "7251": "aes_password", + "7472": "aes_password", + "7340": "aes_password", + "7607": "aes_password", + "7215": "aes_password", + "7136": "aes_password", + "8171": "aes_password", + "8740": "aes_password", + "8343": "aes_password", + "7024": "aes_password", + "7635": "aes_password", + "8689": "aes_password", + "7158": "aes_password", + "8320": "aes_password", + "8516": "aes_password", + "8238": "aes_password", + "8156": "aes_password", + "8149": "aes_password", + "8199": "aes_password", + "7822": "aes_password", + "7517": "aes_password", + "8191": "aes_password" + }, + "method": "aes-256-cfb", + "timeout": 60 +} \ No newline at end of file From 80102f38995de506a1ca6c18b01303fbdf6910ee Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 1 Aug 2015 18:57:44 +0800 Subject: [PATCH 11/47] fix pyflakes --- shadowsocks/asyncdns.py | 1 - shadowsocks/udprelay.py | 1 - tests/gen_multiple_passwd.py | 1 - 3 files changed, 3 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index e60a3833b..ea705b23b 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -18,7 +18,6 @@ from __future__ import absolute_import, division, print_function, \ with_statement -import time import os import socket import struct diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 7e5e2c3dd..b67770acc 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -62,7 +62,6 @@ from __future__ import absolute_import, division, print_function, \ with_statement -import time import socket import logging import struct diff --git a/tests/gen_multiple_passwd.py b/tests/gen_multiple_passwd.py index a058582b8..62586c202 100644 --- a/tests/gen_multiple_passwd.py +++ b/tests/gen_multiple_passwd.py @@ -1,6 +1,5 @@ #!/usr/bin/python -import os import json with open('server-multi-passwd-performance.json', 'wb') as f: From d319fab5cac62ac4676fa271700e287f27bcf84e Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 1 Aug 2015 18:58:57 +0800 Subject: [PATCH 12/47] fix asyncdns unit test --- shadowsocks/asyncdns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index ea705b23b..f5d398a98 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -433,7 +433,7 @@ def close(self): def test(): dns_resolver = DNSResolver() loop = eventloop.EventLoop() - dns_resolver.add_to_loop(loop, ref=True) + dns_resolver.add_to_loop(loop) global counter counter = 0 @@ -448,6 +448,7 @@ def callback(result, error): counter += 1 if counter == 9: dns_resolver.close() + loop.stop() a_callback = callback return a_callback From e8b29469993e0038befc0625f4ccc9c750b43a6d Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 1 Aug 2015 19:09:29 +0800 Subject: [PATCH 13/47] fix workers --- shadowsocks/asyncdns.py | 4 +++- shadowsocks/tcprelay.py | 3 +++ shadowsocks/udprelay.py | 5 +++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index f5d398a98..d807caf03 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -425,7 +425,9 @@ def resolve(self, hostname, callback): def close(self): if self._sock: - self._loop.remove(self._sock, self) + if self._loop: + self._loop.remove_periodic(self.handle_periodic) + self._loop.remove(self._sock, self) self._sock.close() self._sock = None diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 3300df3c2..38d4101eb 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -703,4 +703,7 @@ def handle_periodic(self): def close(self, next_tick=False): self._closed = True if not next_tick: + if self._eventloop: + self._eventloop.remove_periodic(self.handle_periodic) + self._eventloop.remove(self._server_socket, self) self._server_socket.close() diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index b67770acc..c2386d9e5 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -278,10 +278,11 @@ def handle_periodic(self): self._server_socket.close() for sock in self._sockets: sock.close() - self._eventloop.remove_periodic(self.handle_periodic) def close(self, next_tick=False): self._closed = True if not next_tick: - self._eventloop.remove(self._server_socket, self) + if self._eventloop: + self._eventloop.remove_periodic(self.handle_periodic) + self._eventloop.remove(self._server_socket, self) self._server_socket.close() From 111acf66c1dc3baacaf0004ed0b72a9c74102423 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 14:37:44 +0800 Subject: [PATCH 14/47] fix graceful restart and add unit test --- shadowsocks/eventloop.py | 6 ++-- shadowsocks/tcprelay.py | 7 ++-- shadowsocks/udprelay.py | 12 ++++--- tests/graceful.json | 10 ++++++ tests/graceful_cli.py | 18 ++++++++++ tests/graceful_server.py | 13 +++++++ tests/jenkins.sh | 1 + tests/test_graceful_restart.sh | 63 ++++++++++++++++++++++++++++++++++ 8 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 tests/graceful.json create mode 100644 tests/graceful_cli.py create mode 100644 tests/graceful_server.py create mode 100755 tests/test_graceful_restart.sh diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 09e97611e..05927d3ca 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -192,7 +192,7 @@ def stop(self): def run(self): events = [] while not self._stopping: - now = time.time() + asap = False try: events = self.poll(TIMEOUT_PRECISION) except (OSError, IOError) as e: @@ -200,6 +200,7 @@ def run(self): # EPIPE: Happens when the client closes the connection # EINTR: Happens when received a signal # handles them as soon as possible + asap = True logging.debug('poll:%s', e) else: logging.error('poll:%s', e) @@ -214,7 +215,8 @@ def run(self): handler.handle_event(sock, fd, event) except (OSError, IOError) as e: shell.print_exception(e) - if now - self._last_time >= TIMEOUT_PRECISION: + now = time.time() + if asap or now - self._last_time >= TIMEOUT_PRECISION: for callback in self._periodic_callbacks: callback() self._last_time = now diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 38d4101eb..516da328c 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -689,18 +689,19 @@ def handle_event(self, sock, fd, event): logging.warn('poll removed fd') def handle_periodic(self): - self._sweep_timeout() if self._closed: if self._server_socket: self._eventloop.remove(self._server_socket, self) - self._eventloop.remove_periodic(self.handle_periodic) self._server_socket.close() self._server_socket = None - logging.info('closed listen port %d', self._listen_port) + logging.info('closed TCP port %d', self._listen_port) if not self._fd_to_handlers: + logging.info('stopping') self._eventloop.stop() + self._sweep_timeout() def close(self, next_tick=False): + logging.debug('TCP close') self._closed = True if not next_tick: if self._eventloop: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index c2386d9e5..99e9ed341 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -272,14 +272,18 @@ def handle_event(self, sock, fd, event): self._handle_client(sock) def handle_periodic(self): + if self._closed: + if self._server_socket: + logging.info('closed UDP port %d', self._listen_port) + self._server_socket.close() + self._server_socket = None + for sock in self._sockets: + sock.close() self._cache.sweep() self._client_fd_to_server_addr.sweep() - if self._closed: - self._server_socket.close() - for sock in self._sockets: - sock.close() def close(self, next_tick=False): + logging.debug('UDP close') self._closed = True if not next_tick: if self._eventloop: diff --git a/tests/graceful.json b/tests/graceful.json new file mode 100644 index 000000000..4b95434f2 --- /dev/null +++ b/tests/graceful.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8387, + "local_port":1081, + "password":"aes_password", + "timeout":15, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/graceful_cli.py b/tests/graceful_cli.py new file mode 100644 index 000000000..ef6110e98 --- /dev/null +++ b/tests/graceful_cli.py @@ -0,0 +1,18 @@ +#!/usr/bin/python + +import socket +import socks +import time + + +SERVER_IP = '127.0.0.1' +SERVER_PORT = 8001 + + +if __name__ == '__main__': + s = socks.socksocket() + s.set_proxy(socks.SOCKS5, SERVER_IP, 1081) + s.connect((SERVER_IP, SERVER_PORT)) + s.send(b'test') + time.sleep(30) + s.close() diff --git a/tests/graceful_server.py b/tests/graceful_server.py new file mode 100644 index 000000000..d7038f19a --- /dev/null +++ b/tests/graceful_server.py @@ -0,0 +1,13 @@ +#!/usr/bin/python + +import socket + + +if __name__ == '__main__': + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('127.0.0.1', 8001)) + s.listen(1024) + c = None + while True: + c = s.accept() diff --git a/tests/jenkins.sh b/tests/jenkins.sh index ea5c1630b..32dd30ed8 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -69,6 +69,7 @@ if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then fi run_test tests/test_large_file.sh +run_test tests/test_graceful_restart.sh run_test tests/test_udp_src.sh run_test tests/test_command.sh diff --git a/tests/test_graceful_restart.sh b/tests/test_graceful_restart.sh new file mode 100755 index 000000000..cec984ab9 --- /dev/null +++ b/tests/test_graceful_restart.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +PYTHON="coverage run -p -a" +URL=http://127.0.0.1/file + + +# setup processes +$PYTHON shadowsocks/local.py -c tests/graceful.json & +LOCAL=$! + +$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & +SERVER=$! + +python tests/graceful_server.py & +GSERVER=$! + +sleep 1 + +python tests/graceful_cli.py & +GCLI=$! + +sleep 1 + +# graceful restart server: send SIGQUIT to old process and start a new one +kill -s SIGQUIT $SERVER +$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & +NEWSERVER=$! + +sleep 1 + +# check old server +ps x | grep -v grep | grep $SERVER +OLD_SERVER_RUNNING1=$? +# old server should not quit at this moment +echo old server running: $OLD_SERVER_RUNNING1 + +sleep 1 + +# close connections on old server +kill -s SIGINT $GCLI +kill -s SIGKILL $GSERVER +kill -s SIGINT $LOCAL + +sleep 11 + +# check old server +ps x | grep -v grep | grep $SERVER +OLD_SERVER_RUNNING2=$? +# old server should quit at this moment +echo old server running: $OLD_SERVER_RUNNING2 + +# new server is expected running +kill -s SIGINT $NEWSERVER || exit 1 + +if [ $OLD_SERVER_RUNNING1 -ne 0 ]; then + exit 1 +fi + +if [ $OLD_SERVER_RUNNING2 -ne 1 ]; then + kill -s SIGINT $SERVER + sleep 1 + exit 1 +fi From 02120e3402cfbd940a20893646ec3417564a46f2 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 14:45:15 +0800 Subject: [PATCH 15/47] optimize eventloop --- shadowsocks/asyncdns.py | 6 +++--- shadowsocks/eventloop.py | 20 ++++++++------------ shadowsocks/tcprelay.py | 8 ++++---- shadowsocks/udprelay.py | 4 ++-- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index d807caf03..c5fc99d80 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -357,7 +357,7 @@ def handle_event(self, sock, fd, event): return if event & eventloop.POLL_ERR: logging.error('dns socket err') - self._loop.remove(self._sock, self) + self._loop.remove(self._sock) self._sock.close() # TODO when dns server is IPv6 self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, @@ -380,7 +380,7 @@ def remove_callback(self, callback): del self._cb_to_hostname[callback] arr = self._hostname_to_cb.get(hostname, None) if arr: - arr.remove(callback, self) + arr.remove(callback) if not arr: del self._hostname_to_cb[hostname] if hostname in self._hostname_status: @@ -427,7 +427,7 @@ def close(self): if self._sock: if self._loop: self._loop.remove_periodic(self.handle_periodic) - self._loop.remove(self._sock, self) + self._loop.remove(self._sock) self._sock.close() self._sock = None diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 05927d3ca..bf2f99409 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -150,8 +150,7 @@ def __init__(self): else: raise Exception('can not find any available functions in select ' 'package') - self._fd_to_f = {} - self._fd_to_handler = {} + self._fdmap = {} # (f, handler) self._last_time = time.time() self._periodic_callbacks = [] self._stopping = False @@ -159,20 +158,17 @@ def __init__(self): def poll(self, timeout=None): events = self._impl.poll(timeout) - return [(self._fd_to_f[fd], fd, event) for fd, event in events] + return [(self._fdmap[fd][0], fd, event) for fd, event in events] def add(self, f, mode, handler): fd = f.fileno() - self._fd_to_f[fd] = f + self._fdmap[fd] = (f, handler) self._impl.register(fd, mode) - self._fd_to_handler[fd] = handler - def remove(self, f, handler): + def remove(self, f): fd = f.fileno() - del self._fd_to_f[fd] + del self._fdmap[fd] self._impl.unregister(fd) - if handler is not None: - del self._fd_to_handler[fd] def add_periodic(self, callback): self._periodic_callbacks.append(callback) @@ -182,9 +178,8 @@ def remove_periodic(self, callback): def modify(self, f, mode, handler): fd = f.fileno() + self._fdmap[fd] = (f, handler) self._impl.modify(fd, mode) - if handler is not None: - self._fd_to_handler[fd] = handler def stop(self): self._stopping = True @@ -209,8 +204,9 @@ def run(self): continue for sock, fd, event in events: - handler = self._fd_to_handler.get(fd, None) + handler = self._fdmap.get(fd, None) if handler is not None: + handler = handler[1] try: handler.handle_event(sock, fd, event) except (OSError, IOError) as e: diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 516da328c..d1118de9a 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -534,13 +534,13 @@ def destroy(self): logging.debug('destroy') if self._remote_sock: logging.debug('destroying remote') - self._loop.remove(self._remote_sock, self._server) + self._loop.remove(self._remote_sock) del self._fd_to_handlers[self._remote_sock.fileno()] self._remote_sock.close() self._remote_sock = None if self._local_sock: logging.debug('destroying local') - self._loop.remove(self._local_sock, self._server) + self._loop.remove(self._local_sock) del self._fd_to_handlers[self._local_sock.fileno()] self._local_sock.close() self._local_sock = None @@ -691,7 +691,7 @@ def handle_event(self, sock, fd, event): def handle_periodic(self): if self._closed: if self._server_socket: - self._eventloop.remove(self._server_socket, self) + self._eventloop.remove(self._server_socket) self._server_socket.close() self._server_socket = None logging.info('closed TCP port %d', self._listen_port) @@ -706,5 +706,5 @@ def close(self, next_tick=False): if not next_tick: if self._eventloop: self._eventloop.remove_periodic(self.handle_periodic) - self._eventloop.remove(self._server_socket, self) + self._eventloop.remove(self._server_socket) self._server_socket.close() diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 99e9ed341..856fe08cc 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -135,7 +135,7 @@ def _get_a_server(self): def _close_client(self, client): if hasattr(client, 'close'): self._sockets.remove(client.fileno()) - self._eventloop.remove(client, self) + self._eventloop.remove(client) client.close() else: # just an address @@ -288,5 +288,5 @@ def close(self, next_tick=False): if not next_tick: if self._eventloop: self._eventloop.remove_periodic(self.handle_periodic) - self._eventloop.remove(self._server_socket, self) + self._eventloop.remove(self._server_socket) self._server_socket.close() From b28de8e2f1d92c3df01598c9738e001371034be2 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 15:10:46 +0800 Subject: [PATCH 16/47] fix pyflakes --- tests/graceful_cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/graceful_cli.py b/tests/graceful_cli.py index ef6110e98..e58674bc8 100644 --- a/tests/graceful_cli.py +++ b/tests/graceful_cli.py @@ -1,6 +1,5 @@ #!/usr/bin/python -import socket import socks import time From 177c639b191f87767505eb4322a1f6f3a976d11e Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 15:12:41 +0800 Subject: [PATCH 17/47] fix graceful restart test --- shadowsocks/udprelay.py | 2 +- tests/graceful.json | 2 +- tests/test_graceful_restart.sh | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 856fe08cc..e3fe77c96 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -274,11 +274,11 @@ def handle_event(self, sock, fd, event): def handle_periodic(self): if self._closed: if self._server_socket: - logging.info('closed UDP port %d', self._listen_port) self._server_socket.close() self._server_socket = None for sock in self._sockets: sock.close() + logging.info('closed UDP port %d', self._listen_port) self._cache.sweep() self._client_fd_to_server_addr.sweep() diff --git a/tests/graceful.json b/tests/graceful.json index 4b95434f2..7d94ea5d5 100644 --- a/tests/graceful.json +++ b/tests/graceful.json @@ -1,6 +1,6 @@ { "server":"127.0.0.1", - "server_port":8387, + "server_port":8388, "local_port":1081, "password":"aes_password", "timeout":15, diff --git a/tests/test_graceful_restart.sh b/tests/test_graceful_restart.sh index cec984ab9..d91ba926b 100755 --- a/tests/test_graceful_restart.sh +++ b/tests/test_graceful_restart.sh @@ -23,6 +23,7 @@ sleep 1 # graceful restart server: send SIGQUIT to old process and start a new one kill -s SIGQUIT $SERVER +sleep 0.5 $PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & NEWSERVER=$! @@ -37,7 +38,7 @@ echo old server running: $OLD_SERVER_RUNNING1 sleep 1 # close connections on old server -kill -s SIGINT $GCLI +kill -s SIGKILL $GCLI kill -s SIGKILL $GSERVER kill -s SIGINT $LOCAL @@ -49,6 +50,7 @@ OLD_SERVER_RUNNING2=$? # old server should quit at this moment echo old server running: $OLD_SERVER_RUNNING2 +kill -s SIGINT $SERVER # new server is expected running kill -s SIGINT $NEWSERVER || exit 1 @@ -57,7 +59,6 @@ if [ $OLD_SERVER_RUNNING1 -ne 0 ]; then fi if [ $OLD_SERVER_RUNNING2 -ne 1 ]; then - kill -s SIGINT $SERVER sleep 1 exit 1 fi From d946ac8213c1d523cad09dbfd8e1cbd5527fef50 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 17:33:13 +0800 Subject: [PATCH 18/47] skip graceful restart test on Jenkins --- tests/jenkins.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/jenkins.sh b/tests/jenkins.sh index 32dd30ed8..a85c46170 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -69,7 +69,11 @@ if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then fi run_test tests/test_large_file.sh -run_test tests/test_graceful_restart.sh + +if [ "a$JENKINS" != "a1" ] ; then + # jenkins blocked SIGQUIT with sigprocmask(), we have to skip this test on Jenkins + run_test tests/test_graceful_restart.sh +fi run_test tests/test_udp_src.sh run_test tests/test_command.sh From 999a54168e74fcd65d916532f70436e1f83a2761 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 2 Aug 2015 17:45:48 +0800 Subject: [PATCH 19/47] update CHANGES --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index ada189399..96840f332 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.7 2015-08-02 +- Optimize speed for multiple ports + 2.6.11 2015-07-10 - Fix a compatibility issue in UDP Relay From 42111848863c2b8e86b00786a538824c72d257e8 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 3 Aug 2015 01:40:18 +0800 Subject: [PATCH 20/47] remove unnecessary overwrite --- shadowsocks/eventloop.py | 3 +-- shadowsocks/tcprelay.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index bf2f99409..b27afe308 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -176,9 +176,8 @@ def add_periodic(self, callback): def remove_periodic(self, callback): self._periodic_callbacks.remove(callback) - def modify(self, f, mode, handler): + def modify(self, f, mode): fd = f.fileno() - self._fdmap[fd] = (f, handler) self._impl.modify(fd, mode) def stop(self): diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index d1118de9a..e61b8acf2 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -173,14 +173,14 @@ def _update_stream(self, stream, status): event |= eventloop.POLL_OUT if self._upstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN - self._loop.modify(self._local_sock, event, self._server) + self._loop.modify(self._local_sock, event) if self._remote_sock: event = eventloop.POLL_ERR if self._downstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN if self._upstream_status & WAIT_STATUS_WRITING: event |= eventloop.POLL_OUT - self._loop.modify(self._remote_sock, event, self._server) + self._loop.modify(self._remote_sock, event) def _write_to_sock(self, data, sock): # write data to sock From baad209160268d9fd176d5e98d43b4efb523177d Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 3 Aug 2015 01:41:15 +0800 Subject: [PATCH 21/47] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 195b2bba2..4a6e9f33f 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="shadowsocks", - version="2.7", + version="2.7.1", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 58df1d82d01c3ba33f2d63bfd49fb1cfecde9205 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 3 Aug 2015 23:54:30 +0800 Subject: [PATCH 22/47] close poll object after loop stopped --- shadowsocks/eventloop.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index b27afe308..78b532cc5 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -98,6 +98,9 @@ def modify(self, fd, mode): self.unregister(fd) self.register(fd, mode) + def close(self): + self._kqueue.close() + class SelectLoop(object): @@ -135,6 +138,9 @@ def modify(self, fd, mode): self.unregister(fd) self.register(fd, mode) + def close(self): + pass + class EventLoop(object): def __init__(self): @@ -216,6 +222,9 @@ def run(self): callback() self._last_time = now + def __del__(self): + self._impl.close() + # from tornado def errno_from_exception(e): From 956199efcdbc4a808b7fe0f0d69d12bb4b2835a2 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 18:12:38 +0800 Subject: [PATCH 23/47] add control manager --- shadowsocks/manager.py | 152 ++++++++++++++++++++++++++++++++++++++++ shadowsocks/server.py | 12 +++- shadowsocks/shell.py | 5 +- shadowsocks/tcprelay.py | 2 + shadowsocks/udprelay.py | 2 + 5 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 shadowsocks/manager.py diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py new file mode 100644 index 000000000..199a96dda --- /dev/null +++ b/shadowsocks/manager.py @@ -0,0 +1,152 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2015 clowwindy +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import errno +import traceback +import socket +import logging +import json +import collections + +from shadowsocks import common, eventloop, tcprelay, udprelay, asyncdns, shell + + +BUF_SIZE = 2048 + + +class Manager(object): + + def __init__(self, config): + self._config = config + self._relays = {} # (tcprelay, udprelay) + self._loop = eventloop.EventLoop() + self._dns_resolver = asyncdns.DNSResolver() + self._dns_resolver.add_to_loop(self._loop) + self._control_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.IPPROTO_UDP) + self._statistics = collections.defaultdict(int) + self._control_client_addr = None + try: + self._control_socket.bind(('127.0.0.1', + int(config['manager_port']))) + self._control_socket.setblocking(False) + except (OSError, IOError) as e: + logging.error(e) + logging.error('can not bind to manager port') + exit(1) + self._loop.add(self._control_socket, + eventloop.POLL_IN, self) + + port_password = config['port_password'] + del config['port_password'] + for port, password in port_password.items(): + a_config = config.copy() + a_config['server_port'] = int(port) + a_config['password'] = password + self.add_port(a_config) + + def add_port(self, config): + port = int(config['server_port']) + servers = self._relays.get(port, None) + if servers: + logging.error("server already exists at %s:%d" % (config['server'], + port)) + return + logging.info("adding server at %s:%d" % (config['server'], port)) + t = tcprelay.TCPRelay(config, self._dns_resolver, False) + u = udprelay.UDPRelay(config, self._dns_resolver, False) + t.add_to_loop(self._loop) + u.add_to_loop(self._loop) + self._relays[port] = (t, u) + + def remove_port(self, config): + port = int(config['server_port']) + servers = self._relays.get(port, None) + if servers: + logging.info("removing server at %s:%d" % (config['server'], port)) + t, u = servers + t.close(next_tick=False) + u.close(next_tick=False) + del self._relays[port] + else: + logging.error("server not exist at %s:%d" % (config['server'], + port)) + + def handle_event(self, sock, fd, event): + if sock == self._control_socket and event == eventloop.POLL_IN: + data, self._control_client_addr = sock.recvfrom(BUF_SIZE) + parsed = self._parse_command(data) + if parsed: + command, config = parsed + a_config = self._config.copy() + if config: + a_config.update(config) + if 'server_port' not in a_config: + logging.error('can not find server_port in config') + else: + if command == 'add': + self.add_port(a_config) + elif command == 'remove': + self.remove_port(a_config) + elif command == 'ping': + self._send_control_data(b'pong') + else: + logging.error('unknown command %s', command) + + def _parse_command(self, data): + # commands: + # add: {"server_port": 8000, "password": "foobar"} + # remove: {"server_port": 8000"} + data = common.to_str(data) + parts = data.split(':', 1) + if len(parts) < 2: + return data, None + command, config_json = parts + try: + config = json.loads(config_json) + return command, config + except Exception as e: + logging.error(e) + return None + + def handle_periodic(self): + # TODO send statistics + pass + + def _send_control_data(self, data): + if self._control_client_addr: + try: + self._control_socket.sendto(data, self._control_client_addr) + except (socket.error, OSError, IOError) as e: + error_no = eventloop.errno_from_exception(e) + if error_no in (errno.EAGAIN, errno.EINPROGRESS, + errno.EWOULDBLOCK): + return + else: + shell.print_exception(e) + if self._config['verbose']: + traceback.print_exc() + + def run(self): + self._loop.run() + + +def run(config): + Manager(config).run() \ No newline at end of file diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 429a20a3a..8ff13d671 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -24,7 +24,8 @@ import signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns +from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, \ + asyncdns, manager def main(): @@ -48,10 +49,17 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] + if config['manager_port']: + logging.info('entering manager mode') + manager.run(config) + return + tcp_servers = [] udp_servers = [] dns_resolver = asyncdns.DNSResolver() - for port, password in config['port_password'].items(): + port_password = config['port_password'] + del config['port_password'] + for port, password in port_password.items(): a_config = config.copy() a_config['server_port'] = int(port) a_config['password'] = password diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index f8ae81f56..586c2f658 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -136,7 +136,7 @@ def get_config(is_local): else: shortopts = 'hd:s:p:k:m:c:t:vq' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', - 'forbidden-ip=', 'user=', 'version'] + 'forbidden-ip=', 'user=', 'manager-port=', 'version'] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -181,6 +181,8 @@ def get_config(is_local): config['fast_open'] = True elif key == '--workers': config['workers'] = int(value) + elif key == '--manager-port': + config['manager_port'] = int(value) elif key == '--user': config['user'] = to_str(value) elif key == '--forbidden-ip': @@ -317,6 +319,7 @@ def print_server_help(): --fast-open use TCP_FASTOPEN, requires Linux 3.7+ --workers WORKERS number of workers, available on Unix/Linux --forbidden-ip IPLIST comma seperated IP list forbidden to connect + --manager-port PORT optional server manager UDP port General options: -h, --help show this help message and exit diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index e61b8acf2..e14dcaab9 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -708,3 +708,5 @@ def close(self, next_tick=False): self._eventloop.remove_periodic(self.handle_periodic) self._eventloop.remove(self._server_socket) self._server_socket.close() + for handler in list(self._fd_to_handlers.values()): + handler.destroy() diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index e3fe77c96..a90df9f9d 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -290,3 +290,5 @@ def close(self, next_tick=False): self._eventloop.remove_periodic(self.handle_periodic) self._eventloop.remove(self._server_socket) self._server_socket.close() + for client in list(self._cache.values()): + client.close() From e08845d6f34ae6aecf1e5c2b0ca145e3414ae4bd Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 18:31:55 +0800 Subject: [PATCH 24/47] fix manager --- shadowsocks/manager.py | 2 +- shadowsocks/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 199a96dda..96dcb8b8b 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -149,4 +149,4 @@ def run(self): def run(config): - Manager(config).run() \ No newline at end of file + Manager(config).run() diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 8ff13d671..3489bf545 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -49,7 +49,7 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] - if config['manager_port']: + if config.get('manager_port', 0): logging.info('entering manager mode') manager.run(config) return From 9c3af61433b0a3827ffd8003f426e17d2dbd59fe Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 22:43:21 +0800 Subject: [PATCH 25/47] add statistics --- shadowsocks/manager.py | 33 ++++++++++++++++++++++++++++----- shadowsocks/tcprelay.py | 18 +++++++++++------- shadowsocks/udprelay.py | 8 ++++++-- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 96dcb8b8b..767c00333 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -28,7 +28,8 @@ from shadowsocks import common, eventloop, tcprelay, udprelay, asyncdns, shell -BUF_SIZE = 2048 +BUF_SIZE = 1506 +STAT_SEND_LIMIT = 100 class Manager(object): @@ -44,6 +45,7 @@ def __init__(self, config): self._statistics = collections.defaultdict(int) self._control_client_addr = None try: + # TODO use address instead of port self._control_socket.bind(('127.0.0.1', int(config['manager_port']))) self._control_socket.setblocking(False) @@ -53,6 +55,7 @@ def __init__(self, config): exit(1) self._loop.add(self._control_socket, eventloop.POLL_IN, self) + self._loop.add_periodic(self.handle_periodic) port_password = config['port_password'] del config['port_password'] @@ -70,8 +73,10 @@ def add_port(self, config): port)) return logging.info("adding server at %s:%d" % (config['server'], port)) - t = tcprelay.TCPRelay(config, self._dns_resolver, False) - u = udprelay.UDPRelay(config, self._dns_resolver, False) + t = tcprelay.TCPRelay(config, self._dns_resolver, False, + self.stat_callback) + u = udprelay.UDPRelay(config, self._dns_resolver, False, + self.stat_callback) t.add_to_loop(self._loop) u.add_to_loop(self._loop) self._relays[port] = (t, u) @@ -126,9 +131,27 @@ def _parse_command(self, data): logging.error(e) return None + def stat_callback(self, port, data_len): + self._statistics[port] += data_len + def handle_periodic(self): - # TODO send statistics - pass + r = {} + i = 0 + + def send_data(data_dict): + if data_dict: + data = common.to_bytes(json.dumps(data_dict, + separators=(',', ':'))) + self._send_control_data(b'stat: ' + data) + + for k, v in self._statistics.items(): + r[k] = v + i += 1 + if i >= STAT_SEND_LIMIT: + send_data(r) + r.clear() + send_data(r) + self._statistics.clear() def _send_control_data(self, data): if self._control_client_addr: diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index e14dcaab9..d11af31f5 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -147,10 +147,10 @@ def _get_a_server(self): logging.debug('chosen server: %s:%d', server, server_port) return server, server_port - def _update_activity(self): + def _update_activity(self, data_len=0): # tell the TCP Relay we have activities recently # else it will think we are inactive and timed out - self._server.update_activity(self) + self._server.update_activity(self, data_len) def _update_stream(self, stream, status): # update a stream to a new waiting status @@ -317,7 +317,6 @@ def _handle_stage_addr(self, data): self._log_error(e) if self._config['verbose']: traceback.print_exc() - # TODO use logging when debug completed self.destroy() def _create_remote_socket(self, ip, port): @@ -388,7 +387,6 @@ def _handle_dns_resolved(self, result, error): def _on_local_read(self): # handle all local read events and dispatch them to methods for # each stage - self._update_activity() if not self._local_sock: return is_local = self._is_local @@ -402,6 +400,7 @@ def _on_local_read(self): if not data: self.destroy() return + self._update_activity(len(data)) if not is_local: data = self._encryptor.decrypt(data) if not data: @@ -424,10 +423,10 @@ def _on_local_read(self): def _on_remote_read(self): # handle all remote read events - self._update_activity() data = None try: data = self._remote_sock.recv(BUF_SIZE) + except (OSError, IOError) as e: if eventloop.errno_from_exception(e) in \ (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): @@ -435,6 +434,7 @@ def _on_remote_read(self): if not data: self.destroy() return + self._update_activity(len(data)) if self._is_local: data = self._encryptor.decrypt(data) else: @@ -549,7 +549,7 @@ def destroy(self): class TCPRelay(object): - def __init__(self, config, dns_resolver, is_local): + def __init__(self, config, dns_resolver, is_local, stat_callback=None): self._config = config self._is_local = is_local self._dns_resolver = dns_resolver @@ -589,6 +589,7 @@ def __init__(self, config, dns_resolver, is_local): self._config['fast_open'] = False server_socket.listen(1024) self._server_socket = server_socket + self._stat_callback = stat_callback def add_to_loop(self, loop): if self._eventloop: @@ -607,7 +608,10 @@ def remove_handler(self, handler): self._timeouts[index] = None del self._handler_to_timeouts[hash(handler)] - def update_activity(self, handler): + def update_activity(self, handler, data_len): + if data_len and self._stat_callback: + self._stat_callback(self._listen_port, data_len) + # set handler to active now = int(time.time()) if now - handler.last_activity < eventloop.TIMEOUT_PRECISION: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index a90df9f9d..a36f98199 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -81,7 +81,7 @@ def client_key(source_addr, server_af): class UDPRelay(object): - def __init__(self, config, dns_resolver, is_local): + def __init__(self, config, dns_resolver, is_local, stat_callback=None): self._config = config if is_local: self._listen_addr = config['local_address'] @@ -121,6 +121,7 @@ def __init__(self, config, dns_resolver, is_local): server_socket.bind((self._listen_addr, self._listen_port)) server_socket.setblocking(False) self._server_socket = server_socket + self._stat_callback = stat_callback def _get_a_server(self): server = self._config['server'] @@ -146,6 +147,8 @@ def _handle_server(self): data, r_addr = server.recvfrom(BUF_SIZE) if not data: logging.debug('UDP handle_server: data is empty') + if self._stat_callback: + self._stat_callback(self._listen_port, len(data)) if self._is_local: frag = common.ord(data[2]) if frag != 0: @@ -181,7 +184,6 @@ def _handle_server(self): af, socktype, proto, canonname, sa = addrs[0] key = client_key(r_addr, af) - logging.debug(key) client = self._cache.get(key, None) if not client: # TODO async getaddrinfo @@ -221,6 +223,8 @@ def _handle_client(self, sock): if not data: logging.debug('UDP handle_client: data is empty') return + if self._stat_callback: + self._stat_callback(self._listen_port, len(data)) if not self._is_local: addrlen = len(r_addr[0]) if addrlen > 255: From d20a07192c28aea3cd37b8bc48ab5a14643f38f8 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 23:11:56 +0800 Subject: [PATCH 26/47] allow manager to bind on unix socket --- shadowsocks/manager.py | 18 +++++++++++++----- shadowsocks/server.py | 2 +- shadowsocks/shell.py | 8 ++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 767c00333..2da4f0de4 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -40,18 +40,26 @@ def __init__(self, config): self._loop = eventloop.EventLoop() self._dns_resolver = asyncdns.DNSResolver() self._dns_resolver.add_to_loop(self._loop) - self._control_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.IPPROTO_UDP) + self._statistics = collections.defaultdict(int) self._control_client_addr = None try: + manager_address = config['manager_address'] + if ':' in manager_address: + addr = manager_address.split(':') + addr = addr[0], int(addr[1]) + family = socket.AF_INET + else: + addr = manager_address + family = socket.AF_UNIX # TODO use address instead of port - self._control_socket.bind(('127.0.0.1', - int(config['manager_port']))) + self._control_socket = socket.socket(family, + socket.SOCK_DGRAM) + self._control_socket.bind(addr) self._control_socket.setblocking(False) except (OSError, IOError) as e: logging.error(e) - logging.error('can not bind to manager port') + logging.error('can not bind to manager address') exit(1) self._loop.add(self._control_socket, eventloop.POLL_IN, self) diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 3489bf545..e25db4c87 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -49,7 +49,7 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] - if config.get('manager_port', 0): + if config.get('manager_address', 0): logging.info('entering manager mode') manager.run(config) return diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index 586c2f658..bdba6c64c 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -136,7 +136,7 @@ def get_config(is_local): else: shortopts = 'hd:s:p:k:m:c:t:vq' longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', - 'forbidden-ip=', 'user=', 'manager-port=', 'version'] + 'forbidden-ip=', 'user=', 'manager-address=', 'version'] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -181,8 +181,8 @@ def get_config(is_local): config['fast_open'] = True elif key == '--workers': config['workers'] = int(value) - elif key == '--manager-port': - config['manager_port'] = int(value) + elif key == '--manager-address': + config['manager_address'] = value elif key == '--user': config['user'] = to_str(value) elif key == '--forbidden-ip': @@ -319,7 +319,7 @@ def print_server_help(): --fast-open use TCP_FASTOPEN, requires Linux 3.7+ --workers WORKERS number of workers, available on Unix/Linux --forbidden-ip IPLIST comma seperated IP list forbidden to connect - --manager-port PORT optional server manager UDP port + --manager-address ADDR optional server manager UDP address, see wiki General options: -h, --help show this help message and exit From 4f948b22867b63ab9fccd55f3f351dea488e8001 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 23:59:14 +0800 Subject: [PATCH 27/47] fix password type in udprelay --- shadowsocks/udprelay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index a36f98199..96d1fbd4a 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -94,7 +94,7 @@ def __init__(self, config, dns_resolver, is_local, stat_callback=None): self._remote_addr = None self._remote_port = None self._dns_resolver = dns_resolver - self._password = config['password'] + self._password = common.to_bytes(config['password']) self._method = config['method'] self._timeout = config['timeout'] self._is_local = is_local From a8ae8ab373b215f30f1a54dc28e4e7eef18ee840 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Aug 2015 23:59:33 +0800 Subject: [PATCH 28/47] add unit test for manager --- shadowsocks/manager.py | 88 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 2da4f0de4..7093ad8f0 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -181,3 +181,91 @@ def run(self): def run(config): Manager(config).run() + + +def test(): + import time + import threading + import os + import struct + from shadowsocks import encrypt + + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)-8s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + enc = [] + + def run_server(): + config = { + 'server': '127.0.0.1', + 'local_port': 1081, + 'port_password': { + '8381': 'foobar1', + '8382': 'foobar2' + }, + 'method': 'aes-256-cfb', + 'manager_address': '127.0.0.1:6001', + 'timeout': 60, + 'fast_open': False, + 'verbose': 2 + } + manager = Manager(config) + enc.append(manager) + manager.run() + + t = threading.Thread(target=run_server).start() + time.sleep(2) + manager = enc[0] + cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + cli.connect(('127.0.0.1', 6001)) + + # test add and remove + time.sleep(1) + cli.send(b'add: {"server_port":7001, "password":"1234"}') + time.sleep(1) + assert 7001 in manager._relays + cli.send(b'remove: {"server_port":8381}') + time.sleep(1) + assert 8381 not in manager._relays + logging.info('add and remove test passed') + + # test statistics for TCP + header = common.pack_addr(b'google.com') + struct.pack('>H', 80) + data = encrypt.encrypt_all(b'1234', 'aes-256-cfb', 1, + header + b'GET /\r\n\r\n') + tcp_cli = socket.socket() + tcp_cli.connect(('127.0.0.1', 7001)) + tcp_cli.send(data) + rdata = tcp_cli.recv(4096) + tcp_cli.close() + rdata = encrypt.encrypt_all(b'1234', 'aes-256-cfb', 0, rdata) + + data, addr = cli.recvfrom(1506) + data = common.to_str(data) + assert data.startswith('stat: ') + data = data.split('stat:')[1] + stats = json.loads(data) + assert '7001' in stats + logging.info('TCP statistics test passed') + + # test statistics for UDP + header = common.pack_addr(b'127.0.0.1') + struct.pack('>H', 80) + data = encrypt.encrypt_all(b'foobar2', 'aes-256-cfb', 1, + header + b'test') + udp_cli = socket.socket(type=socket.SOCK_DGRAM) + udp_cli.sendto(data, ('127.0.0.1', 8382)) + tcp_cli.close() + + data, addr = cli.recvfrom(1506) + data = common.to_str(data) + assert data.startswith('stat: ') + data = data.split('stat:')[1] + stats = json.loads(data) + assert '8382' in stats + logging.info('UDP statistics test passed') + + os._exit(0) + + +if __name__ == '__main__': + test() From 30efc303607a44f8978e21c33060a528735714c1 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 00:34:20 +0800 Subject: [PATCH 29/47] fix pyflakes --- shadowsocks/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 7093ad8f0..7883b1035 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -213,7 +213,7 @@ def run_server(): enc.append(manager) manager.run() - t = threading.Thread(target=run_server).start() + threading.Thread(target=run_server).start() time.sleep(2) manager = enc[0] cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) From 249bcc0b2910c6832ad1ae9b4964e362a915e223 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 00:52:25 +0800 Subject: [PATCH 30/47] refine unit test --- shadowsocks/manager.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 7883b1035..75ecc08dd 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -186,14 +186,14 @@ def run(config): def test(): import time import threading - import os import struct from shadowsocks import encrypt - logging.basicConfig(level=logging.DEBUG, + logging.basicConfig(level=5, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') enc = [] + eventloop.TIMEOUT_PRECISION = 1 def run_server(): config = { @@ -213,8 +213,9 @@ def run_server(): enc.append(manager) manager.run() - threading.Thread(target=run_server).start() - time.sleep(2) + t = threading.Thread(target=run_server) + t.start() + time.sleep(1) manager = enc[0] cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) cli.connect(('127.0.0.1', 6001)) @@ -236,9 +237,8 @@ def run_server(): tcp_cli = socket.socket() tcp_cli.connect(('127.0.0.1', 7001)) tcp_cli.send(data) - rdata = tcp_cli.recv(4096) + tcp_cli.recv(4096) tcp_cli.close() - rdata = encrypt.encrypt_all(b'1234', 'aes-256-cfb', 0, rdata) data, addr = cli.recvfrom(1506) data = common.to_str(data) @@ -264,7 +264,8 @@ def run_server(): assert '8382' in stats logging.info('UDP statistics test passed') - os._exit(0) + manager._loop.stop() + t.join() if __name__ == '__main__': From 5ed73c15f0624ec67feb4ee1c0aeceae3e4d20b0 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 01:04:03 +0800 Subject: [PATCH 31/47] bump 2.8 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4a6e9f33f..0a438d4f4 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="shadowsocks", - version="2.7.1", + version="2.8", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From 583b54f4260cca431dc1a7b32231699125aac9c6 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 14:58:40 +0800 Subject: [PATCH 32/47] update CHANGES --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 96840f332..ff8516bc7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.8 2015-08-06 +- Add Shadowsocks manager + 2.7 2015-08-02 - Optimize speed for multiple ports From 3576b3006c2737fa24b459992f6bb156e06791fc Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 15:13:29 +0800 Subject: [PATCH 33/47] support IPv6 on control socket --- shadowsocks/manager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 75ecc08dd..8639fdaf9 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -46,13 +46,17 @@ def __init__(self, config): try: manager_address = config['manager_address'] if ':' in manager_address: - addr = manager_address.split(':') + addr = manager_address.rsplit(':', 1) addr = addr[0], int(addr[1]) - family = socket.AF_INET + addrs = socket.getaddrinfo(addr[0], addr[1]) + if addrs: + family = addrs[0][0] + else: + logging.error('invalid address: %s', manager_address) + exit(1) else: addr = manager_address family = socket.AF_UNIX - # TODO use address instead of port self._control_socket = socket.socket(family, socket.SOCK_DGRAM) self._control_socket.bind(addr) From 77f9979b2e400784cd7d2978acc0fc9ded5424c1 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 17:24:51 +0800 Subject: [PATCH 34/47] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbdb9c11b..5d94e0b0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,8 @@ How to Contribute ================= +Notice this is the repository for Shadowsocks Python version. If you have problems with Android / iOS / Windows etc clients, please post your questions in their issue trackers. + Pull Requests ------------- From c8b3f71e1b106024dde1f73281519c5ef1e5edcc Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 19:44:59 +0800 Subject: [PATCH 35/47] respond ok to add and remove commands --- shadowsocks/manager.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index 8639fdaf9..d93eacc95 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -114,14 +114,17 @@ def handle_event(self, sock, fd, event): command, config = parsed a_config = self._config.copy() if config: + # let the command override the configuration file a_config.update(config) if 'server_port' not in a_config: logging.error('can not find server_port in config') else: if command == 'add': self.add_port(a_config) + self._send_control_data(b'ok') elif command == 'remove': self.remove_port(a_config) + self._send_control_data(b'ok') elif command == 'ping': self._send_control_data(b'pong') else: @@ -152,6 +155,7 @@ def handle_periodic(self): def send_data(data_dict): if data_dict: + # use compact JSON format (without space) data = common.to_bytes(json.dumps(data_dict, separators=(',', ':'))) self._send_control_data(b'stat: ' + data) @@ -159,6 +163,7 @@ def send_data(data_dict): for k, v in self._statistics.items(): r[k] = v i += 1 + # split the data into segments that fit in UDP packets if i >= STAT_SEND_LIMIT: send_data(r) r.clear() @@ -229,9 +234,14 @@ def run_server(): cli.send(b'add: {"server_port":7001, "password":"1234"}') time.sleep(1) assert 7001 in manager._relays + data, addr = cli.recvfrom(1506) + assert b'ok' in data + cli.send(b'remove: {"server_port":8381}') time.sleep(1) assert 8381 not in manager._relays + data, addr = cli.recvfrom(1506) + assert b'ok' in data logging.info('add and remove test passed') # test statistics for TCP From f04e26888596f0d9bbe909c2cd20d761f57e7b39 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 6 Aug 2015 19:47:33 +0800 Subject: [PATCH 36/47] bump 2.8.1 --- CHANGES | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ff8516bc7..679524465 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.8.1 2015-08-06 +- Respond ok to add and remove commands + 2.8 2015-08-06 - Add Shadowsocks manager diff --git a/setup.py b/setup.py index 0a438d4f4..e5e719328 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="shadowsocks", - version="2.8", + version="2.8.1", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From c5dd081216ee83ef6a106157694cdc125c235718 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 9 Aug 2015 01:33:27 +0800 Subject: [PATCH 37/47] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 76d759a26..7cda7e7b2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ shadowsocks A fast tunnel proxy that helps you bypass firewalls. +Features: +- TCP & UDP support +- User management API +- TCP Fast Open +- Workers and graceful restart +- Destination IP blacklist + Server ------ From 3c1154923fe1bf1e0c1521cd38d1681a6a39e98d Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 10 Aug 2015 00:05:22 +0800 Subject: [PATCH 38/47] fix json unicode issue in manager --- shadowsocks/manager.py | 2 +- shadowsocks/shell.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index d93eacc95..e125ef495 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -258,7 +258,7 @@ def run_server(): data = common.to_str(data) assert data.startswith('stat: ') data = data.split('stat:')[1] - stats = json.loads(data) + stats = shell.parse_json_in_str(data) assert '7001' in stats logging.info('TCP statistics test passed') diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index bdba6c64c..c91fc222c 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -148,8 +148,7 @@ def get_config(is_local): logging.info('loading config from %s' % config_path) with open(config_path, 'rb') as f: try: - config = json.loads(f.read().decode('utf8'), - object_hook=_decode_dict) + config = parse_json_in_str(f.read().decode('utf8')) except ValueError as e: logging.error('found an error in config.json: %s', e.message) @@ -359,3 +358,8 @@ def _decode_dict(data): value = _decode_dict(value) rv[key] = value return rv + + +def parse_json_in_str(data): + # parse json and convert everything from unicode to str + return json.loads(data, object_hook=_decode_dict) From a434eef096fe6cd339a205b85205b6ec8e443ad0 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 10 Aug 2015 11:53:54 +0800 Subject: [PATCH 39/47] fix json decode issue --- shadowsocks/manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index e125ef495..e8009b481 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -140,7 +140,7 @@ def _parse_command(self, data): return data, None command, config_json = parts try: - config = json.loads(config_json) + config = shell.parse_json_in_str(config_json) return command, config except Exception as e: logging.error(e) @@ -231,7 +231,7 @@ def run_server(): # test add and remove time.sleep(1) - cli.send(b'add: {"server_port":7001, "password":"1234"}') + cli.send(b'add: {"server_port":7001, "password":"asdfadsfasdf"}') time.sleep(1) assert 7001 in manager._relays data, addr = cli.recvfrom(1506) @@ -246,7 +246,7 @@ def run_server(): # test statistics for TCP header = common.pack_addr(b'google.com') + struct.pack('>H', 80) - data = encrypt.encrypt_all(b'1234', 'aes-256-cfb', 1, + data = encrypt.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', 1, header + b'GET /\r\n\r\n') tcp_cli = socket.socket() tcp_cli.connect(('127.0.0.1', 7001)) From 7c08101ce8a673fafb22477e8ad720aa57114a1f Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 10 Aug 2015 12:37:42 +0800 Subject: [PATCH 40/47] update CHANGES --- CHANGES | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 679524465..d6fe932dc 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +2.8.2 2015-08-10 +- Fix a encoding problem in manager + 2.8.1 2015-08-06 - Respond ok to add and remove commands diff --git a/setup.py b/setup.py index e5e719328..e6a7ff7c8 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="shadowsocks", - version="2.8.1", + version="2.8.2", license='http://www.apache.org/licenses/LICENSE-2.0', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', From a2bc6e19457f51a421b7d2866be5903e0d71fd2f Mon Sep 17 00:00:00 2001 From: clowwindy Date: Tue, 11 Aug 2015 17:55:16 +0800 Subject: [PATCH 41/47] fix #414 --- shadowsocks/server.py | 13 +++++++------ shadowsocks/shell.py | 7 ++++--- tests/server-multi-passwd-empty.json | 8 ++++++++ 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 tests/server-multi-passwd-empty.json diff --git a/shadowsocks/server.py b/shadowsocks/server.py index e25db4c87..d4cf961ed 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -42,12 +42,13 @@ def main(): 'will be ignored') else: config['port_password'] = {} - server_port = config['server_port'] - if type(server_port) == list: - for a_server_port in server_port: - config['port_password'][a_server_port] = config['password'] - else: - config['port_password'][str(server_port)] = config['password'] + server_port = config.get('server_port', None) + if server_port: + if type(server_port) == list: + for a_server_port in server_port: + config['port_password'][a_server_port] = config['password'] + else: + config['port_password'][str(server_port)] = config['password'] if config.get('manager_address', 0): logging.info('entering manager mode') diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index c91fc222c..a78e18f52 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -84,7 +84,8 @@ def check_config(config, is_local): sys.exit(2) if not is_local and not config.get('password', None) \ - and not config.get('port_password', None): + and not config.get('port_password', None) \ + and not config.get('manager_address'): logging.error('password or port_password not specified') print_help(is_local) sys.exit(2) @@ -92,7 +93,7 @@ def check_config(config, is_local): if 'local_port' in config: config['local_port'] = int(config['local_port']) - if 'server_port' in config and type(config['server_port']) != list: + if config.get('server_port', None) and type(config['server_port']) != list: config['server_port'] = int(config['server_port']) if config.get('local_address', '') in [b'0.0.0.0']: @@ -240,7 +241,7 @@ def get_config(is_local): except Exception as e: logging.error(e) sys.exit(2) - config['server_port'] = config.get('server_port', 8388) + config['server_port'] = config.get('server_port', None) logging.getLogger('').handlers = [] logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') diff --git a/tests/server-multi-passwd-empty.json b/tests/server-multi-passwd-empty.json new file mode 100644 index 000000000..de354e5df --- /dev/null +++ b/tests/server-multi-passwd-empty.json @@ -0,0 +1,8 @@ +{ + "server": "127.0.0.1", + "local_port": 1081, + "port_password": { + }, + "timeout": 60, + "method": "aes-256-cfb" +} From f19d0ea6fdc4dba41fb3a0cc2454e1888efa984b Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sat, 15 Aug 2015 22:36:29 +0800 Subject: [PATCH 42/47] fix #425 --- shadowsocks/daemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/daemon.py b/shadowsocks/daemon.py index 8dc5608d1..77ed32317 100644 --- a/shadowsocks/daemon.py +++ b/shadowsocks/daemon.py @@ -117,7 +117,7 @@ def handle_exit(signum, _): sys.exit(1) os.setsid() - signal.signal(signal.SIG_IGN, signal.SIGHUP) + signal.signal(signal.SIGHUP, signal.SIG_IGN) print('started') os.kill(ppid, signal.SIGTERM) From eb033a8f6af6d442c8b63a87d4fb0013dc4888b5 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Sun, 16 Aug 2015 19:53:31 +0800 Subject: [PATCH 43/47] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cda7e7b2..232eba682 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ shadowsocks [![Build Status]][Travis CI] [![Coverage Status]][Coverage] -A fast tunnel proxy that helps you bypass firewalls. +A fast tunnel proxy that helps you [bypass firewalls]. Features: - TCP & UDP support @@ -95,6 +95,7 @@ Bugs and Issues [Android]: https://github.com/shadowsocks/shadowsocks-android [Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat +[bypass firewalls]: https://github.com/shadowsocks/shadowsocks/wiki/Objective [Configuration]: https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File [Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks [Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html From 0c4f79284ea9518baa029eb00c65bc37ec7ab4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=9E=97=E5=93=B2?= Date: Mon, 11 May 2015 09:32:40 +0800 Subject: [PATCH 44/47] allow user to override system dnsserver with config. config item: dns_server list eg: dns_server:[8.8.8.8,8.8.4.4] --- shadowsocks/asyncdns.py | 9 ++++++--- shadowsocks/server.py | 7 ++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index c5fc99d80..25a0e6b8a 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -248,7 +248,7 @@ def __str__(self): class DNSResolver(object): - def __init__(self): + def __init__(self, server_list=None): self._loop = None self._hosts = {} self._hostname_status = {} @@ -256,8 +256,11 @@ def __init__(self): self._cb_to_hostname = {} self._cache = lru_cache.LRUCache(timeout=300) self._sock = None - self._servers = None - self._parse_resolv() + if server_list is None: + self._servers = None + self._parse_resolv() + else: + self._servers = server_list self._parse_hosts() # TODO monitor hosts change and reload hosts # TODO parse /etc/gai.conf and follow its rules diff --git a/shadowsocks/server.py b/shadowsocks/server.py index d4cf961ed..5896c7d7b 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -57,7 +57,12 @@ def main(): tcp_servers = [] udp_servers = [] - dns_resolver = asyncdns.DNSResolver() + + if 'dns_server' in config: # allow override settings in resolv.conf + dns_resolver = asyncdns.DNSResolver(config['dns_server']) + else: + dns_resolver = asyncdns.DNSResolver() + port_password = config['port_password'] del config['port_password'] for port, password in port_password.items(): From ffed9b2cae3265842b4be5aa8058e5f638ffc4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=9E=97=E5=93=B2?= Date: Mon, 17 Aug 2015 10:14:23 +0800 Subject: [PATCH 45/47] dns_server config test --- tests/jenkins.sh | 1 + tests/server-dnsserver.json | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 tests/server-dnsserver.json diff --git a/tests/jenkins.sh b/tests/jenkins.sh index a85c46170..5b53e9319 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -42,6 +42,7 @@ run_test python tests/test.py --with-coverage -c tests/chacha20.json run_test python tests/test.py --with-coverage -c tests/table.json run_test python tests/test.py --with-coverage -c tests/server-multi-ports.json run_test python tests/test.py --with-coverage -s tests/aes.json -c tests/client-multi-server-ip.json +run_test python tests/test.py --with-coverage -s tests/server-dnsserver.json -c tests/client-multi-server-ip.json run_test python tests/test.py --with-coverage -s tests/server-multi-passwd.json -c tests/server-multi-passwd-client-side.json run_test python tests/test.py --with-coverage -c tests/workers.json run_test python tests/test.py --with-coverage -s tests/ipv6.json -c tests/ipv6-client-side.json diff --git a/tests/server-dnsserver.json b/tests/server-dnsserver.json new file mode 100644 index 000000000..5d55cdc56 --- /dev/null +++ b/tests/server-dnsserver.json @@ -0,0 +1,11 @@ +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false, + "dns_server": ["8.8.8.8","8.8.4.4"] +} From 61a0b2d1ac81ab93734f6cef5494186b8abb4bd9 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Mon, 17 Aug 2015 20:36:34 +0800 Subject: [PATCH 46/47] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 232eba682..372896a72 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ shadowsocks [![Build Status]][Travis CI] [![Coverage Status]][Coverage] +[中文说明] + A fast tunnel proxy that helps you [bypass firewalls]. Features: @@ -112,3 +114,4 @@ Bugs and Issues [Troubleshooting]: https://github.com/shadowsocks/shadowsocks/wiki/Troubleshooting [Wiki]: https://github.com/shadowsocks/shadowsocks/wiki [Windows]: https://github.com/shadowsocks/shadowsocks-csharp +[中文说明]: https://github.com/shadowsocks/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E From 5b450acfaa15cd6c2d3e8ab99f9297542df74025 Mon Sep 17 00:00:00 2001 From: clowwindy Date: Thu, 20 Aug 2015 18:52:42 +0800 Subject: [PATCH 47/47] Update README.md --- README.md | 62 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 372896a72..b61c0dbd6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,7 @@ shadowsocks =========== -[![PyPI version]][PyPI] -[![Build Status]][Travis CI] -[![Coverage Status]][Coverage] - -[中文说明] - -A fast tunnel proxy that helps you [bypass firewalls]. +A fast tunnel proxy that helps you bypass firewalls. Features: - TCP & UDP support @@ -54,16 +48,6 @@ To check the log: Check all the options via `-h`. You can also use a [Configuration] file instead. -Client ------- - -* [Windows] / [OS X] -* [Android] / [iOS] -* [OpenWRT] - -Use GUI clients on your local PC/phones. Check the README of your client -for more information. - Documentation ------------- @@ -72,46 +56,4 @@ You can find all the documentation in the [Wiki]. License ------- -Copyright 2015 clowwindy - -Licensed under the Apache License, Version 2.0 (the "License"); you may -not use this file except in compliance with the License. You may obtain -a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -License for the specific language governing permissions and limitations -under the License. - -Bugs and Issues ----------------- - -* [Troubleshooting] -* [Issue Tracker] -* [Mailing list] - - - -[Android]: https://github.com/shadowsocks/shadowsocks-android -[Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat -[bypass firewalls]: https://github.com/shadowsocks/shadowsocks/wiki/Objective -[Configuration]: https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File -[Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks -[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html -[Debian sid]: https://packages.debian.org/unstable/python/shadowsocks -[iOS]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help -[Issue Tracker]: https://github.com/shadowsocks/shadowsocks/issues?state=open -[Install Server on Windows]: https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows -[Mailing list]: https://groups.google.com/group/shadowsocks -[OpenWRT]: https://github.com/shadowsocks/openwrt-shadowsocks -[OS X]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Shadowsocks-for-OSX-Help -[PyPI]: https://pypi.python.org/pypi/shadowsocks -[PyPI version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat -[Travis CI]: https://travis-ci.org/shadowsocks/shadowsocks -[Troubleshooting]: https://github.com/shadowsocks/shadowsocks/wiki/Troubleshooting -[Wiki]: https://github.com/shadowsocks/shadowsocks/wiki -[Windows]: https://github.com/shadowsocks/shadowsocks-csharp -[中文说明]: https://github.com/shadowsocks/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E +Apache License