diff --git a/.travis.yml b/.travis.yml index f29cb96..014fa07 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 cymysql - sudo tests/socksify/install.sh - sudo tests/libsodium/install.sh - sudo tests/setup_tc.sh diff --git a/README.md b/README.md index 76d759a..9c38e3e 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Bugs and Issues [Android]: https://github.com/shadowsocks/shadowsocks-android -[Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat +[Build Status]: https://travis-ci.org/falseen/shadowsocks.svg?branch=manyuser-travis [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 @@ -100,7 +100,7 @@ Bugs and Issues [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 +[Travis CI]: https://travis-ci.org/falseen/shadowsocks [Troubleshooting]: https://github.com/shadowsocks/shadowsocks/wiki/Troubleshooting [Wiki]: https://github.com/shadowsocks/shadowsocks/wiki [Windows]: https://github.com/shadowsocks/shadowsocks-csharp diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 013e4b7..0000000 --- a/__init__.py +++ /dev/null @@ -1 +0,0 @@ -#!/usr/bin/python diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..689dd73 --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +import codecs +from setuptools import setup + + +with codecs.open('README.rst', encoding='utf-8') as f: + long_description = f.read() + +setup( + name="shadowsocks", + 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', + author_email='clowwindy42@gmail.com', + url='https://github.com/shadowsocks/shadowsocks', + packages=['shadowsocks', 'shadowsocks.crypto'], + package_data={ + 'shadowsocks': ['README.rst', 'LICENSE'] + }, + install_requires=[], + entry_points=""" + [console_scripts] + sslocal = shadowsocks.local:main + ssserver = shadowsocks.server:main + """, + classifiers=[ + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Internet :: Proxy Servers', + ], + long_description=long_description, +) diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index 4a34b6b..50c62ba 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -28,7 +28,6 @@ if __name__ == '__main__': import sys import inspect file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) - os.chdir(file_path) sys.path.insert(0, os.path.join(file_path, '../')) from shadowsocks import common, lru_cache, eventloop, shell diff --git a/shadowsocks/common.py b/shadowsocks/common.py index ddbbb60..39903fd 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -294,12 +294,12 @@ def test_inet_conv(): def test_parse_header(): assert parse_header(b'\x03\x0ewww.google.com\x00\x50') == \ - (3, b'www.google.com', 80, 18) + (0, b'www.google.com', 80, 18) assert parse_header(b'\x01\x08\x08\x08\x08\x00\x35') == \ - (1, b'8.8.8.8', 53, 7) + (0, b'8.8.8.8', 53, 7) assert parse_header((b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00' b'\x00\x10\x11\x00\x50')) == \ - (4, b'2404:6800:4005:805::1011', 80, 19) + (0, b'2404:6800:4005:805::1011', 80, 19) def test_pack_header(): diff --git a/shadowsocks/crypto/m2.py b/shadowsocks/crypto/m2.py deleted file mode 100644 index 4c7e148..0000000 --- a/shadowsocks/crypto/m2.py +++ /dev/null @@ -1,117 +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 - - -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/encrypt_test.py b/shadowsocks/encrypt_test.py index 68228e1..0121d63 100644 --- a/shadowsocks/encrypt_test.py +++ b/shadowsocks/encrypt_test.py @@ -7,10 +7,10 @@ import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from crypto import rc4_md5 -from crypto import openssl -from crypto import sodium -from crypto import table +from shadowsocks.crypto import rc4_md5 +from shadowsocks.crypto import openssl +from shadowsocks.crypto import sodium +from shadowsocks.crypto import table def main(): print("\n""rc4_md5") diff --git a/shadowsocks/local.py b/shadowsocks/local.py index 3b6523f..9f54d93 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -26,7 +26,6 @@ import signal if __name__ == '__main__': import inspect file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) - os.chdir(file_path) sys.path.insert(0, os.path.join(file_path, '../')) from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index bfabd7f..57b95b7 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -207,7 +207,9 @@ def test(): eventloop.TIMEOUT_PRECISION = 1 def run_server(): - config = { + config = shell.get_config(True) + config = config.copy() + a_config = { 'server': '127.0.0.1', 'local_port': 1081, 'port_password': { @@ -220,6 +222,7 @@ def test(): 'fast_open': False, 'verbose': 2 } + config.update(a_config) manager = Manager(config) enc.append(manager) manager.run() diff --git a/shadowsocks/server.py b/shadowsocks/server.py index a3a9327..c0b6ed8 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -26,7 +26,6 @@ import signal if __name__ == '__main__': import inspect file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) - os.chdir(file_path) sys.path.insert(0, os.path.join(file_path, '../')) from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, \ diff --git a/tests/assert.sh b/tests/assert.sh old mode 100644 new mode 100755 diff --git a/tests/test.py b/tests/test.py index 29b57d4..4083401 100755 --- a/tests/test.py +++ b/tests/test.py @@ -44,7 +44,7 @@ parser.add_argument('--dns', type=str, default='8.8.8.8') config = parser.parse_args() if config.with_coverage: - python = ['coverage', 'run', '-p', '-a'] + python = ['coverage', 'run', '-p'] client_args = python + ['shadowsocks/local.py', '-v'] server_args = python + ['shadowsocks/server.py', '-v'] diff --git a/tests/test_command.sh b/tests/test_command.sh index be05704..a1a777b 100755 --- a/tests/test_command.sh +++ b/tests/test_command.sh @@ -2,18 +2,13 @@ . tests/assert.sh -PYTHON="coverage run -a -p" +PYTHON="coverage run -p" LOCAL="$PYTHON shadowsocks/local.py" SERVER="$PYTHON shadowsocks/server.py" assert "$LOCAL --version 2>&1 | grep Shadowsocks | awk -F\" \" '{print \$1}'" "Shadowsocks" assert "$SERVER --version 2>&1 | grep Shadowsocks | awk -F\" \" '{print \$1}'" "Shadowsocks" -assert "$LOCAL 2>&1 | grep ERROR" "ERROR: config not specified" -assert "$LOCAL 2>&1 | grep usage | cut -d: -f1" "usage" - -assert "$SERVER 2>&1 | grep ERROR" "ERROR: config not specified" -assert "$SERVER 2>&1 | grep usage | cut -d: -f1" "usage" assert "$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: server set to listen on 127.0.0.1:8388, are you sure?" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop @@ -30,14 +25,6 @@ $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d sto assert "$LOCAL 2>&1 -m rc4-md5 -k mypassword -s 0.0.0.0 -p 8388 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " DON'T USE DEFAULT PASSWORD! Please change it in your config.json!" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop -assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": server addr not specified" -$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop - -assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password not specified" -$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop - -assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password or port_password not specified" -$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop assert "$SERVER 2>&1 --forbidden-ip 127.0.0.1/4a -m rc4-md5 -k 12345 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": Not a valid CIDR notation: 127.0.0.1/4a" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop diff --git a/tests/test_daemon.sh b/tests/test_daemon.sh index 40f35ef..7a192bd 100755 --- a/tests/test_daemon.sh +++ b/tests/test_daemon.sh @@ -18,7 +18,7 @@ function run_test { for module in local server do -command="coverage run -p -a shadowsocks/$module.py" +command="coverage run -p shadowsocks/$module.py" mkdir -p tmp diff --git a/tests/test_large_file.sh b/tests/test_large_file.sh index 33bcb59..7a61caf 100755 --- a/tests/test_large_file.sh +++ b/tests/test_large_file.sh @@ -1,6 +1,6 @@ #!/bin/bash -PYTHON="coverage run -p -a" +PYTHON="coverage run -p" URL=http://127.0.0.1/file mkdir -p tmp diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py new file mode 100644 index 0000000..e8fa505 --- /dev/null +++ b/tests/test_udp_src.py @@ -0,0 +1,83 @@ +#!/usr/bin/python + +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, SERVER_IP, SERVER_PORT) + 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(b'data', ('127.0.0.1', 9001)) + result1 = sock_in1.recvfrom(8) + + sock_out.sendto(b'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 + + # 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, SERVER_IP, SERVER_PORT) + 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, SERVER_IP, SERVER_PORT) + 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 new file mode 100755 index 0000000..6a778ab --- /dev/null +++ b/tests/test_udp_src.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +PYTHON="coverage run -p" + +mkdir -p tmp + +$PYTHON shadowsocks/local.py -c tests/aes.json -v & +LOCAL=$! + +$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" -v & +SERVER=$! + +sleep 3 + +python tests/test_udp_src.py +r=$? + +kill -s SIGINT $LOCAL +kill -s SIGINT $SERVER + +sleep 2 + +exit $r