diff --git a/.jenkins.sh b/.jenkins.sh index 4c85f1c..bb58e04 100755 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -38,7 +38,6 @@ run_test python tests/test.py --with-coverage -c tests/aes-cfb8.json run_test python tests/test.py --with-coverage -c tests/rc4-md5.json run_test python tests/test.py --with-coverage -c tests/salsa20.json run_test python tests/test.py --with-coverage -c tests/chacha20.json -run_test python tests/test.py --with-coverage -c tests/salsa20-ctr.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/server-multi-passwd.json -c tests/server-multi-passwd-client-side.json @@ -46,6 +45,7 @@ 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 run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -q" -a "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -vv" run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" +run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then diff --git a/.travis.yml b/.travis.yml index 4fbe78c..7a222ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,10 @@ cache: - dante-1.4.0 before_install: - sudo apt-get update -qq - - sudo apt-get install -qq build-essential libssl-dev swig python-m2crypto python-numpy dnsutils iproute nginx bc + - sudo apt-get install -qq build-essential dnsutils iproute nginx bc - sudo dd if=/dev/urandom of=/usr/share/nginx/www/file bs=1M count=10 - sudo service nginx restart - - pip install m2crypto salsa20 pep8 pyflakes nose coverage + - pip install pep8 pyflakes nose coverage - sudo tests/socksify/install.sh - sudo tests/libsodium/install.sh - sudo tests/setup_tc.sh diff --git a/CHANGES b/CHANGES index 62aec8a..b5c9062 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +2.6.3 2015-01-03 +- Support --forbidden-ip to ban some IP, i.e. localhost +- Search OpenSSL and libsodium harder +- Now works on OpenWRT + 2.6.2 2015-01-03 - Log client IP diff --git a/setup.py b/setup.py index 2bfc2c5..725ec71 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="2.6.2", + version="2.6.3", license='MIT', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', diff --git a/shadowsocks/crypto/m2.py b/shadowsocks/crypto/m2.py deleted file mode 100644 index 5ad48a8..0000000 --- a/shadowsocks/crypto/m2.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2014 clowwindy -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import sys -import logging - -__all__ = ['ciphers'] - -has_m2 = True -try: - __import__('M2Crypto') -except ImportError: - has_m2 = False -if bytes != str: - has_m2 = False - - -def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, - padding=1): - - import M2Crypto.EVP - return M2Crypto.EVP.Cipher(alg.replace('-', '_'), key, iv, op, - key_as_bytes=0, d='md5', salt=None, i=1, - padding=1) - - -def err(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, padding=1): - logging.error(('M2Crypto is required to use %s, please run' - ' `apt-get install python-m2crypto`') % alg) - sys.exit(1) - - -if has_m2: - ciphers = { - b'aes-128-cfb': (16, 16, create_cipher), - b'aes-192-cfb': (24, 16, create_cipher), - b'aes-256-cfb': (32, 16, create_cipher), - b'bf-cfb': (16, 8, create_cipher), - b'camellia-128-cfb': (16, 16, create_cipher), - b'camellia-192-cfb': (24, 16, create_cipher), - b'camellia-256-cfb': (32, 16, create_cipher), - b'cast5-cfb': (16, 8, create_cipher), - b'des-cfb': (8, 8, create_cipher), - b'idea-cfb': (16, 8, create_cipher), - b'rc2-cfb': (16, 8, create_cipher), - b'rc4': (16, 0, create_cipher), - b'seed-cfb': (16, 16, create_cipher), - } -else: - ciphers = {} - - -def run_method(method): - from shadowsocks.crypto import util - - cipher = create_cipher(method, b'k' * 32, b'i' * 16, 1) - decipher = create_cipher(method, b'k' * 32, b'i' * 16, 0) - - util.run_cipher(cipher, decipher) - - -def check_env(): - # skip this test on pypy and Python 3 - try: - import __pypy__ - del __pypy__ - from nose.plugins.skip import SkipTest - raise SkipTest - except ImportError: - pass - if bytes != str: - from nose.plugins.skip import SkipTest - raise SkipTest - - -def test_aes_128_cfb(): - check_env() - run_method(b'aes-128-cfb') - - -def test_aes_256_cfb(): - check_env() - run_method(b'aes-256-cfb') - - -def test_bf_cfb(): - check_env() - run_method(b'bf-cfb') - - -def test_rc4(): - check_env() - run_method(b'rc4') - - -if __name__ == '__main__': - test_aes_128_cfb() diff --git a/shadowsocks/crypto/ctypes_openssl.py b/shadowsocks/crypto/openssl.py similarity index 70% rename from shadowsocks/crypto/ctypes_openssl.py rename to shadowsocks/crypto/openssl.py index 9e0dfca..bb11627 100644 --- a/shadowsocks/crypto/ctypes_openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -23,10 +23,11 @@ from __future__ import absolute_import, division, print_function, \ with_statement -import logging -from ctypes import CDLL, c_char_p, c_int, c_long, byref,\ +from ctypes import c_char_p, c_int, c_long, byref,\ create_string_buffer, c_void_p +from shadowsocks.crypto import util + __all__ = ['ciphers'] libcrypto = None @@ -38,20 +39,12 @@ buf_size = 2048 def load_openssl(): global loaded, libcrypto, buf - from ctypes.util import find_library - libcrypto_path = None - for p in ('crypto', 'eay32', 'libeay32'): - libcrypto_path = find_library(p) - if libcrypto_path: - break - else: - import glob - for libcrypto_path in glob.glob('/usr/lib/libcrypto.*'): - pass - if libcrypto_path is None: + libcrypto = util.find_library(('crypto', 'eay32'), + 'EVP_get_cipherbyname', + 'libcrypto') + if libcrypto is None: raise Exception('libcrypto(OpenSSL) not found') - logging.info('loading libcrypto from %s', libcrypto_path) - libcrypto = CDLL(libcrypto_path) + libcrypto.EVP_get_cipherbyname.restype = c_void_p libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p @@ -81,11 +74,11 @@ def load_cipher(cipher_name): return None -class CtypesCrypto(object): +class OpenSSLCrypto(object): def __init__(self, cipher_name, key, iv, op): + self._ctx = None if not loaded: load_openssl() - self._ctx = None cipher = libcrypto.EVP_get_cipherbyname(cipher_name) if not cipher: cipher = load_cipher(cipher_name) @@ -124,39 +117,38 @@ class CtypesCrypto(object): ciphers = { - b'aes-128-cfb': (16, 16, CtypesCrypto), - b'aes-192-cfb': (24, 16, CtypesCrypto), - b'aes-256-cfb': (32, 16, CtypesCrypto), - b'aes-128-ofb': (16, 16, CtypesCrypto), - b'aes-192-ofb': (24, 16, CtypesCrypto), - b'aes-256-ofb': (32, 16, CtypesCrypto), - b'aes-128-ctr': (16, 16, CtypesCrypto), - b'aes-192-ctr': (24, 16, CtypesCrypto), - b'aes-256-ctr': (32, 16, CtypesCrypto), - b'aes-128-cfb8': (16, 16, CtypesCrypto), - b'aes-192-cfb8': (24, 16, CtypesCrypto), - b'aes-256-cfb8': (32, 16, CtypesCrypto), - b'aes-128-cfb1': (16, 16, CtypesCrypto), - b'aes-192-cfb1': (24, 16, CtypesCrypto), - b'aes-256-cfb1': (32, 16, CtypesCrypto), - b'bf-cfb': (16, 8, CtypesCrypto), - b'camellia-128-cfb': (16, 16, CtypesCrypto), - b'camellia-192-cfb': (24, 16, CtypesCrypto), - b'camellia-256-cfb': (32, 16, CtypesCrypto), - b'cast5-cfb': (16, 8, CtypesCrypto), - b'des-cfb': (8, 8, CtypesCrypto), - b'idea-cfb': (16, 8, CtypesCrypto), - b'rc2-cfb': (16, 8, CtypesCrypto), - b'rc4': (16, 0, CtypesCrypto), - b'seed-cfb': (16, 16, CtypesCrypto), + b'aes-128-cfb': (16, 16, OpenSSLCrypto), + b'aes-192-cfb': (24, 16, OpenSSLCrypto), + b'aes-256-cfb': (32, 16, OpenSSLCrypto), + b'aes-128-ofb': (16, 16, OpenSSLCrypto), + b'aes-192-ofb': (24, 16, OpenSSLCrypto), + b'aes-256-ofb': (32, 16, OpenSSLCrypto), + b'aes-128-ctr': (16, 16, OpenSSLCrypto), + b'aes-192-ctr': (24, 16, OpenSSLCrypto), + b'aes-256-ctr': (32, 16, OpenSSLCrypto), + b'aes-128-cfb8': (16, 16, OpenSSLCrypto), + b'aes-192-cfb8': (24, 16, OpenSSLCrypto), + b'aes-256-cfb8': (32, 16, OpenSSLCrypto), + b'aes-128-cfb1': (16, 16, OpenSSLCrypto), + b'aes-192-cfb1': (24, 16, OpenSSLCrypto), + b'aes-256-cfb1': (32, 16, OpenSSLCrypto), + b'bf-cfb': (16, 8, OpenSSLCrypto), + b'camellia-128-cfb': (16, 16, OpenSSLCrypto), + b'camellia-192-cfb': (24, 16, OpenSSLCrypto), + b'camellia-256-cfb': (32, 16, OpenSSLCrypto), + b'cast5-cfb': (16, 8, OpenSSLCrypto), + b'des-cfb': (8, 8, OpenSSLCrypto), + b'idea-cfb': (16, 8, OpenSSLCrypto), + b'rc2-cfb': (16, 8, OpenSSLCrypto), + b'rc4': (16, 0, OpenSSLCrypto), + b'seed-cfb': (16, 16, OpenSSLCrypto), } def run_method(method): - from shadowsocks.crypto import util - cipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 1) - decipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 0) + cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1) + decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) diff --git a/shadowsocks/crypto/rc4_md5.py b/shadowsocks/crypto/rc4_md5.py index 3062dcc..33d481d 100644 --- a/shadowsocks/crypto/rc4_md5.py +++ b/shadowsocks/crypto/rc4_md5.py @@ -25,6 +25,7 @@ from __future__ import absolute_import, division, print_function, \ import hashlib +from shadowsocks.crypto import openssl __all__ = ['ciphers'] @@ -35,15 +36,7 @@ def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, md5.update(key) md5.update(iv) rc4_key = md5.digest() - - try: - from shadowsocks.crypto import ctypes_openssl - return ctypes_openssl.CtypesCrypto(b'rc4', rc4_key, b'', op) - except: - import M2Crypto.EVP - return M2Crypto.EVP.Cipher(b'rc4', rc4_key, b'', op, - key_as_bytes=0, d='md5', salt=None, i=1, - padding=1) + return openssl.OpenSSLCrypto(b'rc4', rc4_key, b'', op) ciphers = { diff --git a/shadowsocks/crypto/salsa20_ctr.py b/shadowsocks/crypto/salsa20_ctr.py deleted file mode 100644 index 0ea13b8..0000000 --- a/shadowsocks/crypto/salsa20_ctr.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2014 clowwindy -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import struct -import logging -import sys - -slow_xor = False -imported = False - -salsa20 = None -numpy = None - -BLOCK_SIZE = 16384 - - -def run_imports(): - global imported, slow_xor, salsa20, numpy - if not imported: - imported = True - try: - numpy = __import__('numpy') - except ImportError: - logging.error('can not import numpy, using SLOW XOR') - logging.error('please install numpy if you use salsa20') - slow_xor = True - try: - salsa20 = __import__('salsa20') - except ImportError: - logging.error('you have to install salsa20 before you use salsa20') - sys.exit(1) - - -def numpy_xor(a, b): - if slow_xor: - return py_xor_str(a, b) - dtype = numpy.byte - if len(a) % 4 == 0: - dtype = numpy.uint32 - elif len(a) % 2 == 0: - dtype = numpy.uint16 - - ab = numpy.frombuffer(a, dtype=dtype) - bb = numpy.frombuffer(b, dtype=dtype) - c = numpy.bitwise_xor(ab, bb) - r = c.tostring() - return r - - -def py_xor_str(a, b): - c = [] - if bytes == str: - for i in range(0, len(a)): - c.append(chr(ord(a[i]) ^ ord(b[i]))) - return ''.join(c) - else: - for i in range(0, len(a)): - c.append(a[i] ^ b[i]) - return bytes(c) - - -class Salsa20Cipher(object): - """a salsa20 CTR implemetation, provides m2crypto like cipher API""" - - def __init__(self, alg, key, iv, op, key_as_bytes=0, d=None, salt=None, - i=1, padding=1): - run_imports() - if alg != b'salsa20-ctr': - raise Exception('unknown algorithm') - self._key = key - self._nonce = struct.unpack('= BLOCK_SIZE: - self._next_stream() - self._pos = 0 - if not data: - break - return b''.join(results) - - -ciphers = { - b'salsa20-ctr': (32, 8, Salsa20Cipher), -} - - -def test(): - from shadowsocks.crypto import util - - cipher = Salsa20Cipher(b'salsa20-ctr', b'k' * 32, b'i' * 8, 1) - decipher = Salsa20Cipher(b'salsa20-ctr', b'k' * 32, b'i' * 8, 1) - - util.run_cipher(cipher, decipher) - - -if __name__ == '__main__': - test() diff --git a/shadowsocks/crypto/ctypes_libsodium.py b/shadowsocks/crypto/sodium.py similarity index 78% rename from shadowsocks/crypto/ctypes_libsodium.py rename to shadowsocks/crypto/sodium.py index e74d577..74fbb33 100644 --- a/shadowsocks/crypto/ctypes_libsodium.py +++ b/shadowsocks/crypto/sodium.py @@ -23,10 +23,11 @@ from __future__ import absolute_import, division, print_function, \ with_statement -import logging -from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \ +from ctypes import c_char_p, c_int, c_ulonglong, byref, \ create_string_buffer, c_void_p +from shadowsocks.crypto import util + __all__ = ['ciphers'] libsodium = None @@ -41,21 +42,11 @@ BLOCK_SIZE = 64 def load_libsodium(): global loaded, libsodium, buf - from ctypes.util import find_library - libsodium_path = None - for p in ('sodium', 'libsodium'): - libsodium_path = find_library(p) - if libsodium_path: - break - else: - import glob - for libsodium_path in glob.glob('/usr/lib/libsodium.*'): - pass - if libsodium_path is None: + libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', + 'libsodium') + if libsodium is None: raise Exception('libsodium not found') - logging.info('loading libsodium from %s', libsodium_path) - libsodium = CDLL(libsodium_path) - libsodium.sodium_init.restype = c_int + libsodium.crypto_stream_salsa20_xor_ic.restype = c_int libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, c_ulonglong, @@ -67,13 +58,11 @@ def load_libsodium(): c_char_p, c_ulonglong, c_char_p) - libsodium.sodium_init() - buf = create_string_buffer(buf_size) loaded = True -class Salsa20Crypto(object): +class SodiumCrypto(object): def __init__(self, cipher_name, key, iv, op): if not loaded: load_libsodium() @@ -112,25 +101,22 @@ class Salsa20Crypto(object): ciphers = { - b'salsa20': (32, 8, Salsa20Crypto), - b'chacha20': (32, 8, Salsa20Crypto), + b'salsa20': (32, 8, SodiumCrypto), + b'chacha20': (32, 8, SodiumCrypto), } def test_salsa20(): - from shadowsocks.crypto import util - - cipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 1) - decipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 0) + cipher = SodiumCrypto(b'salsa20', b'k' * 32, b'i' * 16, 1) + decipher = SodiumCrypto(b'salsa20', b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) def test_chacha20(): - from shadowsocks.crypto import util - cipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 1) - decipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 0) + cipher = SodiumCrypto(b'chacha20', b'k' * 32, b'i' * 16, 1) + decipher = SodiumCrypto(b'chacha20', b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index 3bac1db..6d7d222 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -20,6 +20,62 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from __future__ import absolute_import, division, print_function, \ + with_statement + +import logging + + +def find_library(possible_lib_names, search_symbol, library_name): + import ctypes.util + from ctypes import CDLL + + paths = [] + + if type(possible_lib_names) not in (list, tuple): + possible_lib_names = [possible_lib_names] + + lib_names = [] + for lib_name in possible_lib_names: + lib_names.append(lib_name) + lib_names.append('lib' + lib_name) + + for name in lib_names: + path = ctypes.util.find_library(name) + if path: + paths.append(path) + + if not paths: + # We may get here when find_library fails because, for example, + # the user does not have sufficient privileges to access those + # tools underlying find_library on linux. + import glob + + for name in lib_names: + patterns = [ + '/usr/local/lib*/lib%s.*' % name, + '/usr/lib*/lib%s.*' % name, + 'lib%s.*' % name, + '%s.dll' % name, + 'lib%s.dll' % name] + + for pat in patterns: + files = glob.glob(pat) + if files: + paths.extend(files) + for path in paths: + try: + lib = CDLL(path) + if hasattr(lib, search_symbol): + logging.info('loading %s from %s', library_name, path) + return lib + else: + logging.warn('can\'t find symbol %s in %s', search_symbol, + path) + except Exception: + pass + return None + def run_cipher(cipher, decipher): from os import urandom @@ -49,3 +105,19 @@ def run_cipher(cipher, decipher): end = time.time() print('speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start))) assert b''.join(results) == plain + + +def test_find_library(): + assert find_library('c', 'strcpy', 'libc') is not None + assert find_library(['c'], 'strcpy', 'libc') is not None + assert find_library(('c',), 'strcpy', 'libc') is not None + assert find_library(('crypto', 'eay32'), 'EVP_CipherUpdate', + 'libcrypto') is not None + assert find_library('notexist', 'strcpy', 'libnotexist') is None + assert find_library('c', 'symbol_not_exist', 'c') is None + assert find_library(('notexist', 'c', 'crypto', 'eay32'), + 'EVP_CipherUpdate', 'libc') is not None + + +if __name__ == '__main__': + test_find_library() diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py index ba02101..3dd9264 100644 --- a/shadowsocks/encrypt.py +++ b/shadowsocks/encrypt.py @@ -28,26 +28,18 @@ import sys import hashlib import logging -from shadowsocks.crypto import m2, rc4_md5, salsa20_ctr,\ - ctypes_openssl, ctypes_libsodium, table +from shadowsocks.crypto import rc4_md5, openssl, sodium, table method_supported = {} method_supported.update(rc4_md5.ciphers) -method_supported.update(salsa20_ctr.ciphers) -method_supported.update(ctypes_openssl.ciphers) -method_supported.update(ctypes_libsodium.ciphers) -# let M2Crypto override ctypes_openssl -method_supported.update(m2.ciphers) +method_supported.update(openssl.ciphers) +method_supported.update(sodium.ciphers) method_supported.update(table.ciphers) def random_string(length): - try: - import M2Crypto.Rand - return M2Crypto.Rand.rand_bytes(length) - except ImportError: - return os.urandom(length) + return os.urandom(length) cached_keys = {} diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 55c30bb..304b229 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -232,8 +232,9 @@ class EventLoop(object): logging.error(e) import traceback traceback.print_exc() - for handler in self._handlers_to_remove: - self._handlers.remove(handler) + if self._handlers_to_remove: + for handler in self._handlers_to_remove: + self._handlers.remove(handler) self._handlers_to_remove = [] self._iterating = False diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 79bd1a5..c148208 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -123,6 +123,10 @@ class TCPRelayHandler(object): self._downstream_status = WAIT_STATUS_INIT self._client_address = local_sock.getpeername()[:2] self._remote_address = None + if 'forbidden_ip' in config: + self._forbidden_iplist = config['forbidden_ip'] + else: + self._forbidden_iplist = None if is_local: self._chosen_server = self._get_a_server() fd_to_handlers[local_sock.fileno()] = self @@ -331,6 +335,10 @@ class TCPRelayHandler(object): if len(addrs) == 0: raise Exception("getaddrinfo failed for %s:%d" % (ip, port)) af, socktype, proto, canonname, sa = addrs[0] + if self._forbidden_iplist: + if common.to_str(sa[0]) in self._forbidden_iplist: + raise Exception('IP %s is in forbidden list, reject' % + common.to_str(sa[0])) remote_sock = socket.socket(af, socktype, proto) self._remote_sock = remote_sock self._fd_to_handlers[remote_sock.fileno()] = self @@ -346,6 +354,7 @@ class TCPRelayHandler(object): if result: ip = result[1] if ip: + try: self._stage = STAGE_CONNECTING remote_addr = ip diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 2b8b12f..ff6391c 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -112,6 +112,10 @@ class UDPRelay(object): self._closed = False self._last_time = time.time() self._sockets = set() + if 'forbidden_ip' in config: + self._forbidden_iplist = config['forbidden_ip'] + else: + self._forbidden_iplist = None addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) @@ -178,6 +182,12 @@ class UDPRelay(object): 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 diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 0247d0f..a51c965 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -100,7 +100,8 @@ def get_config(is_local): longopts = ['help', 'fast-open', 'pid-file=', 'log-file='] else: shortopts = 'hd:s:p:k:m:c:t:vq' - longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers='] + longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', + 'forbidden-ip='] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) @@ -146,6 +147,8 @@ def get_config(is_local): config['fast_open'] = True elif key == '--workers': config['workers'] = int(value) + elif key == '--forbidden-ip': + config['forbidden_ip'] = to_str(value).split(',') elif key in ('-h', '--help'): if is_local: print_local_help() @@ -286,6 +289,7 @@ Proxy options: -t TIMEOUT timeout in seconds, default: 300 --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 General options: -d start/stop/restart daemon mode diff --git a/tests/test.py b/tests/test.py index 0b63a18..933027b 100755 --- a/tests/test.py +++ b/tests/test.py @@ -40,6 +40,9 @@ parser.add_argument('-s', '--server-conf', type=str, default=None) parser.add_argument('-a', '--client-args', type=str, default=None) parser.add_argument('-b', '--server-args', type=str, default=None) parser.add_argument('--with-coverage', action='store_true', default=None) +parser.add_argument('--should-fail', action='store_true', default=None) +parser.add_argument('--url', type=str, default='http://www.example.com/') +parser.add_argument('--dns', type=str, default='8.8.8.8') config = parser.parse_args() @@ -94,7 +97,7 @@ try: stage = 5 if bytes != str: line = str(line, 'utf8') - sys.stdout.write(line) + sys.stderr.write(line) if line.find('starting local') >= 0: local_ready = True if line.find('starting server') >= 0: @@ -103,7 +106,7 @@ try: if stage == 1: time.sleep(2) - p3 = Popen(['curl', 'http://www.example.com/', '-v', '-L', + p3 = Popen(['curl', config.url, '-v', '-L', '--socks5-hostname', '127.0.0.1:1081', '-m', '15', '--connect-timeout', '10'], stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) @@ -118,9 +121,14 @@ try: fdset.remove(p3.stdout) fdset.remove(p3.stderr) r = p3.wait() - if r != 0: - sys.exit(1) - p4 = Popen(['socksify', 'dig', '@8.8.8.8', 'www.google.com'], + if config.should_fail: + if r == 0: + sys.exit(1) + else: + if r != 0: + sys.exit(1) + p4 = Popen(['socksify', 'dig', '@%s' % config.dns, + 'www.google.com'], stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) if p4 is not None: fdset.append(p4.stdout) @@ -131,9 +139,14 @@ try: if stage == 5: r = p4.wait() - if r != 0: - sys.exit(1) - print('test passed') + if config.should_fail: + if r == 0: + sys.exit(1) + print('test passed (expecting failure)') + else: + if r != 0: + sys.exit(1) + print('test passed') break finally: for p in [p1, p2]: