shadowsocks/0000755000175000017500000000000012376320350012407 5ustar shellshellshadowsocks/setup.py0000644000175000017500000000163312375672543014141 0ustar shellshellfrom setuptools import setup with open('README.rst') as f: long_description = f.read() setup( name="shadowsocks", version="2.1.0", license='MIT', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', author_email='clowwindy42@gmail.com', url='https://github.com/clowwindy/shadowsocks', packages=['shadowsocks'], package_data={ 'shadowsocks': ['README.rst', 'LICENSE'] }, install_requires=[], entry_points=""" [console_scripts] sslocal = shadowsocks.local:main ssserver = shadowsocks.server:main """, classifiers=[ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Internet :: Proxy Servers', ], long_description=long_description, ) shadowsocks/CHANGES0000644000175000017500000000564612375672543013432 0ustar shellshell2.1.0 2014-08-10 - Use only IPv4 DNS server 2.1.0 2014-08-01 - Does not ship config.json - Better error message 2.0.12 2014-07-26 - Support -q quiet mode - Exit 0 when showing help with -h 2.0.11 2014-07-12 - Prefers IP addresses over hostnames, more friendly with socksify and openvpn 2.0.10 2014-07-11 - Fix UDP on local 2.0.9 2014-07-06 - Fix EWOULDBLOCK on Windows - Fix Unicode config problem on some platforms 2.0.8 2014-06-23 - Use multiple DNS to query hostnames 2.0.7 2014-06-21 - Fix fastopen on local - Fallback when fastopen is not available - Add verbose logging mode -vv - Verify if hostname is valid 2.0.6 2014-06-19 - Fix CPU 100% on POLL_HUP - More friendly logging 2.0.5 2014-06-18 - Support a simple config format for multiple ports 2.0.4 2014-06-12 - Fix worker master 2.0.3 2014-06-11 - Fix table encryption with UDP 2.0.2 2014-06-11 - Add asynchronous DNS in TCP relay 2.0.1 2014-06-05 - Better logging - Maybe fix bad file descriptor 2.0 2014-06-05 - Use a new event model - Remove gevent - Refuse to use default password - Fix a problem when using multiple passwords with table encryption 1.4.5 2014-05-24 - Add timeout in TCP server - Close sockets in master process 1.4.4 2014-05-17 - Support multiple workers 1.4.3 2014-05-13 - Fix Windows 1.4.2 2014-05-10 - Add salsa20-ctr cipher 1.4.1 2014-05-03 - Fix error log - Fix EINPROGESS with some version of gevent 1.4.0 2014-05-02 - Adds UDP relay - TCP fast open support on Linux 3.7+ 1.3.7 2014-04-10 - Fix a typo in help 1.3.6 2014-04-10 - Fix a typo in help 1.3.5 2014-04-07 - Add help - Change default local binding address into 127.0.0.1 1.3.4 2014-02-17 - Fix a bug when no config file exists - Client now support multiple server ports and multiple server/port pairs - Better error message with bad config.json format and wrong password 1.3.3 2013-07-09 - Fix default key length of rc2 1.3.2 2013-07-04 - Server will listen at server IP specified in config - Check config file and show some warning messages 1.3.1 2013-06-29 - Fix -c arg 1.3.0 2013-06-22 - Move to pypi 1.2.3 2013-06-14 - add bind address 1.2.2 2013-05-31 - local can listen at ::0 with -6 arg; bump 1.2.2 1.2.1 2013-05-23 - Fix an OpenSSL crash 1.2 2013-05-22 - Use random iv, we finally have strong encryption 1.1.1 2013-05-21 - Add encryption, AES, blowfish, etc. 1.1 2013-05-16 - Support IPv6 addresses (type 4) - Drop Python 2.5 support 1.0 2013-04-03 - Fix -6 IPv6 0.9.4 2013-03-04 - Support Python 2.5 0.9.3 2013-01-14 - Fix conn termination null data 0.9.2 2013-01-05 - Change default timeout 0.9.1 2013-01-05 - Add Travis-CI test 0.9 2012-12-30 - Replace send with sendall, fix FreeBSD 0.6 2012-12-06 - Support args 0.5 2012-11-08 - Fix encryption with negative md5sum 0.4 2012-11-02 - Move config into a JSON file - Auto-detect config path 0.3 2012-06-06 - Move socks5 negotiation to local 0.2 2012-05-11 - Add -6 arg for IPv6 - Fix socket.error 0.1 2012-04-20 - Initial version shadowsocks/MANIFEST.in0000644000175000017500000000007212375672543014161 0ustar shellshellrecursive-include *.py include README.rst include LICENSE shadowsocks/CONTRIBUTING.md0000644000175000017500000000303412375672543014655 0ustar shellshellHow to contribute ================= 在你提交问题前,请先[自行诊断]一下。提交时附上诊断过程中的问题和下列结果, 否则如果我们无法重现你的问题,也就不能帮助你。 Before you submit issues, please read [Troubleshooting] and take a few minutes to read this guide. 问题反馈 ------- 请提交下面的信息: 1. 你是如何搭建环境的?(操作系统,Shadowsocks 版本) 2. 有无错误提示?错误是发生在哪里,客户端还是服务器? 3. 浏览器里的现象是什么?一直转菊花,还是有提示错误? 4. 发生错误时,客户端最后一页完整的日志。 5. 发生错误时,服务器端最后一页完整的日志。 6. 其它你认为可能和问题有关的信息。 如果你不清楚其中某条的含义, 可以直接跳过那一条。 Issues ------ Please include the following information in your submission: 1. How did you set up your environment? (OS, version of Shadowsocks) 2. Did you see any error? Where did you see this error, was it on local or on server? 3. What happened in your browser? Just no response, or any error message? 4. 10 lines of log on the local side of shadowsocks when the error happened. 5. 10 lines of log on the server side of shadowsocks when the error happened. 6. Any other useful information. Skip any of them if you don't know its meaning. [Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting [自行诊断]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting shadowsocks/packaging/0000755000175000017500000000000012375672543014350 5ustar shellshellshadowsocks/packaging/py2exe/0000755000175000017500000000000012375672543015564 5ustar shellshellshadowsocks/packaging/py2exe/setup.py0000644000175000017500000000116412375672543017300 0ustar shellshellfrom distutils.core import setup # NOTICE!! # This setup.py is written for py2exe # Don't make a python package using this file! try: import py2exe except ImportError: pass setup(name='shadowsocks', version='1.2.3', description='a lightweight tunnel proxy which can help you get through firewalls', author='clowwindy', author_email='clowwindy42@gmail.com', url='https://github.com/clowwindy/shadowsocks', options = {'py2exe': {'bundle_files': 1, 'compressed': True}}, windows = [{"script":"local.py", "dest_base": "shadowsocks_local",}], zipfile = None) shadowsocks/LICENSE0000644000175000017500000000205112375672543013427 0ustar shellshellShadowsocks 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.shadowsocks/debian/0000755000175000017500000000000012376320351013632 5ustar shellshellshadowsocks/debian/init.d0000644000175000017500000001014012376126223014740 0ustar shellshell#!/bin/sh ### BEGIN INIT INFO # Provides: shadowsocks # Required-Start: $network $local_fs $remote_fs # Required-Stop: $network $local_fs $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Fast tunnel proxy that helps you bypass firewalls # Description: A secure socks5 proxy, designed to protect your Internet traffic. # This package contain local and server part of shadowsocks, a fast, # powerful tunnel proxy to bypass firewalls. ### END INIT INFO # Author: Shell.Xu # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC=shadowsocks # Introduce a short description here NAME=shadowsocks # Introduce the short server's name here DAEMON=/usr/bin/ssserver # Introduce the server's location here DAEMON_ARGS="" # Arguments to run the daemon with PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME LOGFILE=/var/log/$NAME.log # Exit if the package is not installed [ -x $DAEMON ] || exit 0 # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \ --background --make-pidfile --chdir / --chuid $USERID --no-close --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \ --background --make-pidfile --chdir / --chuid $USERID --no-close -- \ $DAEMON_ARGS $DAEMON_OPTS >> $LOGFILE 2>&1 \ || return 2 # Add code here, if necessary, that waits for the process to be ready # to handle requests from services started subsequently which depend # on this one. As a last resort, sleep for some time. } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return "$RETVAL" } # # Function that sends a SIGHUP to the daemon/service # do_reload() { # # If the daemon can reload its configuration without # restarting (for example, when it is sent a SIGHUP), # then implement that here. # start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; #reload|force-reload) # # If do_reload() is not implemented then leave this commented out # and leave 'force-reload' as an alias for 'restart'. # #log_daemon_msg "Reloading $DESC" "$NAME" #do_reload #log_end_msg $? #;; restart|force-reload) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 exit 3 ;; esac : shadowsocks/debian/rules0000755000175000017500000000023712376275733014731 0ustar shellshell#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ --with python2 --buildsystem=python_distutils shadowsocks/debian/ssserver.10000644000175000017500000000225112376034623015574 0ustar shellshell.\" Hey, EMACS: -*- nroff -*- .\" (C) Copyright 2014 Shell.Xu , .\" .TH SHADOWSOCKS 1 "August 23, 2014" .SH NAME shadowsocks \- Fast tunnel proxy that helps you bypass firewalls .SH SYNOPSIS .B ssserver .RI [ options ] .br .B sslocal .RI [ options ] .SH DESCRIPTION shadowsocks is a tunnel proxy helps you bypass firewall. .B ssserver is the server part, and .B sslocal is the local part. .SH OPTIONS .TP .B \-h, \-\-help Show this help message and exit. .TP .B \-s SERVER_ADDR Server address, default: 0.0.0.0. .TP .B \-p SERVER_PORT Server port, default: 8388. .TP .B \-k PASSWORD Password. .TP .B \-m METHOD Encryption method, default: aes-256-cfb. .TP .B \-t TIMEOUT Timeout in seconds, default: 300. .TP .B \-c CONFIG Path to config file. .TP .B \-\-fast-open Use TCP_FASTOPEN, requires Linux 3.7+. .TP .B \-\-workers WORKERS Number of workers, available on Unix/Linux. .TP .B \-v, \-vv Verbose mode. .TP .B \-q, \-qq Quiet mode, only show warnings/errors. .SH SEE ALSO .br The programs are documented fully by .IR "Shell Xu " and .IR "Clowwindy ", available via the Info system. shadowsocks/debian/changelog0000644000175000017500000000023212376275716015517 0ustar shellshellshadowsocks (2.1.0-1) unstable; urgency=low * Initial release (Closes: #758900) -- Shell.Xu Sat, 23 Aug 2014 00:56:04 +0800 shadowsocks/debian/docs0000644000175000017500000000002512375673047014516 0ustar shellshellREADME.md README.rst shadowsocks/debian/source/0000755000175000017500000000000012375700640015134 5ustar shellshellshadowsocks/debian/source/format0000644000175000017500000000001412375700640016342 0ustar shellshell3.0 (quilt) shadowsocks/debian/install0000644000175000017500000000004312375677666015247 0ustar shellshelldebian/config.json etc/shadowsocks/shadowsocks/debian/config.json0000644000175000017500000000034612375677613016014 0ustar shellshell{ "server":"my_server_ip", "server_port":8388, "local_address": "127.0.0.1", "local_port":1080, "password":"mypassword", "timeout":300, "method":"aes-256-cfb", "fast_open": false, "workers": 1 }shadowsocks/debian/sslocal.10000644000175000017500000000225112376034614015360 0ustar shellshell.\" Hey, EMACS: -*- nroff -*- .\" (C) Copyright 2014 Shell.Xu , .\" .TH SHADOWSOCKS 1 "August 23, 2014" .SH NAME shadowsocks \- Fast tunnel proxy that helps you bypass firewalls .SH SYNOPSIS .B ssserver .RI [ options ] .br .B sslocal .RI [ options ] .SH DESCRIPTION shadowsocks is a tunnel proxy helps you bypass firewall. .B ssserver is the server part, and .B sslocal is the local part. .SH OPTIONS .TP .B \-h, \-\-help Show this help message and exit. .TP .B \-s SERVER_ADDR Server address, default: 0.0.0.0. .TP .B \-p SERVER_PORT Server port, default: 8388. .TP .B \-k PASSWORD Password. .TP .B \-m METHOD Encryption method, default: aes-256-cfb. .TP .B \-t TIMEOUT Timeout in seconds, default: 300. .TP .B \-c CONFIG Path to config file. .TP .B \-\-fast-open Use TCP_FASTOPEN, requires Linux 3.7+. .TP .B \-\-workers WORKERS Number of workers, available on Unix/Linux. .TP .B \-v, \-vv Verbose mode. .TP .B \-q, \-qq Quiet mode, only show warnings/errors. .SH SEE ALSO .br The programs are documented fully by .IR "Shell Xu " and .IR "Clowwindy ", available via the Info system. shadowsocks/debian/control0000644000175000017500000000126612376320324015242 0ustar shellshellSource: shadowsocks Section: python Priority: extra Maintainer: Shell.Xu Build-Depends: debhelper (>= 8), python, python-setuptools Standards-Version: 3.9.4 Homepage: https://github.com/clowwindy/shadowsocks Vcs-Git: git://github.com/shell909090/shadowsocks.git Vcs-Browser: http://github.com/shell909090/shadowsocks Package: shadowsocks Architecture: all Depends: ${misc:Depends}, python, python-setuptools, python-m2crypto Description: Fast tunnel proxy that helps you bypass firewalls A secure socks5 proxy, designed to protect your Internet traffic. . This package contain local and server part of shadowsocks, a fast, powerful tunnel proxy to bypass firewalls.shadowsocks/debian/shadowsocks.manpages0000644000175000017500000000004212376034230017671 0ustar shellshelldebian/sslocal.1 debian/ssserver.1shadowsocks/debian/copyright0000644000175000017500000000252512376275677015615 0ustar shellshellFormat: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: shadowsocks Source: https://github.com/clowwindy/shadowsocks Files: debian/* Copyright: 2014 Shell.Xu License: Expat Files: * Copyright: 2014 clowwindy License: Expat License: Expat 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. shadowsocks/debian/shadowsocks.default0000644000175000017500000000045212376161357017542 0ustar shellshell# Defaults for shadowsocks initscript # sourced by /etc/init.d/shadowsocks # installed at /etc/default/shadowsocks by the maintainer scripts USERID="nobody" # # This is a POSIX shell fragment # # Additional options that are passed to the Daemon. DAEMON_OPTS="-q -c /etc/shadowsocks/config.json" shadowsocks/debian/compat0000644000175000017500000000000212375673047015044 0ustar shellshell8 shadowsocks/README.rst0000644000175000017500000001363612375672543014124 0ustar shellshellshadowsocks =========== |PyPI version| |Build Status| A fast tunnel proxy that help you get through firewalls. `中文说明 `__ Install ------- You'll have a client on your local machine, and install a server on a remote server. Client ~~~~~~ - `Windows `__ / `OS X `__ - `Android `__ / `iOS `__ - `OpenWRT `__ Server ~~~~~~ Debian / Ubuntu: ^^^^^^^^^^^^^^^^ :: apt-get install python-pip python-m2crypto pip install shadowsocks CentOS: ^^^^^^^ :: yum install m2crypto python-setuptools easy_install pip pip install shadowsocks Configuration ------------- On your server create a config file ``/etc/shadowsocks.json``. Example: :: { "server":"my_server_ip", "server_port":8388, "local_address": "127.0.0.1", "local_port":1080, "password":"mypassword", "timeout":300, "method":"aes-256-cfb", "fast_open": false, "workers": 1 } Explanation of the fields: +------------------+-----------------------------------------------------------------------------------------------------+ | Name | Explanation | +==================+=====================================================================================================+ | server | the address your server listens | +------------------+-----------------------------------------------------------------------------------------------------+ | server\_port | server port | +------------------+-----------------------------------------------------------------------------------------------------+ | local\_address | the address your local listens | +------------------+-----------------------------------------------------------------------------------------------------+ | local\_port | local port | +------------------+-----------------------------------------------------------------------------------------------------+ | password | password used for encryption | +------------------+-----------------------------------------------------------------------------------------------------+ | timeout | in seconds | +------------------+-----------------------------------------------------------------------------------------------------+ | method | encryption method, "aes-256-cfb" is recommended | +------------------+-----------------------------------------------------------------------------------------------------+ | fast\_open | use `TCP\_FASTOPEN `__, true / false | +------------------+-----------------------------------------------------------------------------------------------------+ | workers | number of workers, available on Unix/Linux | +------------------+-----------------------------------------------------------------------------------------------------+ Run ``ssserver -c /etc/shadowsocks.json`` on your server. To run it in the background, use `Supervisor `__. On your client machine, use the same configuration as your server, and start your client. If you use Chrome, it's recommended to use `SwitchySharp `__. Change the proxy settings to :: protocol: socks5 hostname: 127.0.0.1 port: your local_port If you can't install `SwitchySharp `__, you can launch Chrome with the following arguments to force Chrome to use the proxy: :: Chrome.exe --proxy-server="socks5://127.0.0.1:1080" --host-resolver-rules="MAP * 0.0.0.0 , EXCLUDE localhost" If you can't even download Chrome, find a friend to download a `Chrome Standalone `__ installer for you. Command line args ----------------- You can use args to override settings from ``config.json``. :: sslocal -s server_name -p server_port -l local_port -k password -m bf-cfb ssserver -p server_port -k password -m bf-cfb --workers 2 ssserver -c /etc/shadowsocks/config.json List all available args with ``-h``. Wiki ---- https://github.com/clowwindy/shadowsocks/wiki License ------- MIT Bugs and Issues --------------- - `Troubleshooting `__ - `Issue Tracker `__ - `Mailing list `__ .. |PyPI version| image:: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat :target: https://pypi.python.org/pypi/shadowsocks .. |Build Status| image:: https://img.shields.io/travis/clowwindy/shadowsocks/master.svg?style=flat :target: https://travis-ci.org/clowwindy/shadowsocks shadowsocks/README.md0000644000175000017500000001042012375672543013700 0ustar shellshellshadowsocks =========== [![PyPI version]][PyPI] [![Build Status]][Travis CI] A fast tunnel proxy that helps you bypass firewalls. [中文说明][Chinese Readme] Install ------- You'll have a client on your local machine, and install a server on a remote server. ### Client * [Windows] / [OS X] * [Android] / [iOS] * [OpenWRT] ### Server #### Debian / Ubuntu: apt-get install python-pip python-m2crypto pip install shadowsocks #### CentOS: yum install m2crypto python-setuptools easy_install pip pip install shadowsocks Configuration ------------- On your server create a config file `/etc/shadowsocks.json`. Example: { "server":"my_server_ip", "server_port":8388, "local_address": "127.0.0.1", "local_port":1080, "password":"mypassword", "timeout":300, "method":"aes-256-cfb", "fast_open": false, "workers": 1 } Explanation of the fields: | Name | Explanation | | ------------- | ----------------------------------------------- | | server | the address your server listens | | server_port | server port | | local_address | the address your local listens | | local_port | local port | | password | password used for encryption | | timeout | in seconds | | method | encryption method, "aes-256-cfb" is recommended | | fast_open | use [TCP_FASTOPEN], true / false | | workers | number of workers, available on Unix/Linux | Run `ssserver -c /etc/shadowsocks.json` on your server. To run it in the background, use [Supervisor]. On your client machine, use the same configuration as your server, and start your client. If you use Chrome, it's recommended to use [SwitchySharp]. Change the proxy settings to protocol: socks5 hostname: 127.0.0.1 port: your local_port If you can't install [SwitchySharp], you can launch Chrome with the following arguments to force Chrome to use the proxy: Chrome.exe --proxy-server="socks5://127.0.0.1:1080" --host-resolver-rules="MAP * 0.0.0.0 , EXCLUDE localhost" If you can't even download Chrome, find a friend to download a [Chrome Standalone] installer for you. Command line args ------------------ You can use args to override settings from `config.json`. sslocal -s server_name -p server_port -l local_port -k password -m bf-cfb ssserver -p server_port -k password -m bf-cfb --workers 2 ssserver -c /etc/shadowsocks/config.json List all available args with `-h`. Wiki ---- https://github.com/clowwindy/shadowsocks/wiki License ------- MIT Bugs and Issues ---------------- * [Troubleshooting] * [Issue Tracker] * [Mailing list] [Android]: https://github.com/clowwindy/shadowsocks/wiki/Ports-and-Clients#android [Build Status]: https://img.shields.io/travis/clowwindy/shadowsocks/master.svg?style=flat [Chinese Readme]: https://github.com/clowwindy/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E [Chrome Standalone]: https://support.google.com/installer/answer/126299 [iOS]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help [Issue Tracker]: https://github.com/clowwindy/shadowsocks/issues?state=open [Mailing list]: http://groups.google.com/group/shadowsocks [OpenWRT]: https://github.com/clowwindy/shadowsocks/wiki/Ports-and-Clients#openwrt [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 [Supervisor]: https://github.com/clowwindy/shadowsocks/wiki/Configure-Shadowsocks-with-Supervisor [TCP_FASTOPEN]: https://github.com/clowwindy/shadowsocks/wiki/TCP-Fast-Open [Travis CI]: https://travis-ci.org/clowwindy/shadowsocks [Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting [SwitchySharp]: https://chrome.google.com/webstore/detail/proxy-switchysharp/dpplabbmogkhghncfbfdeeokoefdjegm [Windows]: https://github.com/clowwindy/shadowsocks/wiki/Ports-and-Clients#windows shadowsocks/shadowsocks/0000755000175000017500000000000012375672543014754 5ustar shellshellshadowsocks/shadowsocks/__init__.py0000644000175000017500000000002212375672543017057 0ustar shellshell#!/usr/bin/python shadowsocks/shadowsocks/lru_cache.py0000644000175000017500000000360112375672543017253 0ustar shellshell#!/usr/bin/python # -*- coding: utf-8 -*- import collections import logging import heapq import time class LRUCache(collections.MutableMapping): """This class is not thread safe""" def __init__(self, timeout=60, close_callback=None, *args, **kwargs): self.timeout = timeout self.close_callback = close_callback self._store = {} self._time_to_keys = collections.defaultdict(list) self._last_visits = [] self.update(dict(*args, **kwargs)) # use the free update to set keys def __getitem__(self, key): # O(logm) t = time.time() self._time_to_keys[t].append(key) heapq.heappush(self._last_visits, t) return self._store[key] def __setitem__(self, key, value): # O(logm) t = time.time() self._store[key] = value self._time_to_keys[t].append(key) heapq.heappush(self._last_visits, t) def __delitem__(self, key): # O(1) del self._store[key] def __iter__(self): return iter(self._store) def __len__(self): return len(self._store) def sweep(self): # O(m) now = time.time() c = 0 while len(self._last_visits) > 0: least = self._last_visits[0] if now - least <= self.timeout: break if self.close_callback is not None: for key in self._time_to_keys[least]: if self._store.__contains__(key): value = self._store[key] self.close_callback(value) for key in self._time_to_keys[least]: heapq.heappop(self._last_visits) if self._store.__contains__(key): del self._store[key] c += 1 del self._time_to_keys[least] if c: logging.debug('%d keys swept' % c) shadowsocks/shadowsocks/udprelay.py0000644000175000017500000002365212375672543017163 0ustar shellshell#!/usr/bin/python # -*- coding: utf-8 -*- # 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. # SOCKS5 UDP Request # +----+------+------+----------+----------+----------+ # |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | # +----+------+------+----------+----------+----------+ # | 2 | 1 | 1 | Variable | 2 | Variable | # +----+------+------+----------+----------+----------+ # SOCKS5 UDP Response # +----+------+------+----------+----------+----------+ # |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | # +----+------+------+----------+----------+----------+ # | 2 | 1 | 1 | Variable | 2 | Variable | # +----+------+------+----------+----------+----------+ # shadowsocks UDP Request (before encrypted) # +------+----------+----------+----------+ # | ATYP | DST.ADDR | DST.PORT | DATA | # +------+----------+----------+----------+ # | 1 | Variable | 2 | Variable | # +------+----------+----------+----------+ # shadowsocks UDP Response (before encrypted) # +------+----------+----------+----------+ # | ATYP | DST.ADDR | DST.PORT | DATA | # +------+----------+----------+----------+ # | 1 | Variable | 2 | Variable | # +------+----------+----------+----------+ # shadowsocks UDP Request and Response (after encrypted) # +-------+--------------+ # | IV | PAYLOAD | # +-------+--------------+ # | Fixed | Variable | # +-------+--------------+ # HOW TO NAME THINGS # ------------------ # `dest` means destination server, which is from DST fields in the SOCKS5 # request # `local` means local server of shadowsocks # `remote` means remote server of shadowsocks # `client` means UDP clients that connects to other servers # `server` means the UDP server that handles user requests import time import socket import logging import struct import errno import random import encrypt import eventloop import lru_cache from common import parse_header, pack_addr BUF_SIZE = 65536 def client_key(a, b, c, d): return '%s:%s:%s:%s' % (a, b, c, d) class UDPRelay(object): def __init__(self, config, dns_resolver, is_local): self._config = config if is_local: self._listen_addr = config['local_address'] self._listen_port = config['local_port'] self._remote_addr = config['server'] self._remote_port = config['server_port'] else: self._listen_addr = config['server'] self._listen_port = config['server_port'] self._remote_addr = None self._remote_port = None self._dns_resolver = dns_resolver self._password = config['password'] self._method = config['method'] self._timeout = config['timeout'] self._is_local = is_local self._cache = lru_cache.LRUCache(timeout=config['timeout'], close_callback=self._close_client) self._client_fd_to_server_addr = \ lru_cache.LRUCache(timeout=config['timeout']) self._eventloop = None self._closed = False self._last_time = time.time() self._sockets = set() addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) if len(addrs) == 0: raise Exception("can't get addrinfo for %s:%d" % (self._listen_addr, self._listen_port)) af, socktype, proto, canonname, sa = addrs[0] server_socket = socket.socket(af, socktype, proto) server_socket.bind((self._listen_addr, self._listen_port)) server_socket.setblocking(False) self._server_socket = server_socket def _get_a_server(self): server = self._config['server'] server_port = self._config['server_port'] if type(server_port) == list: server_port = random.choice(server_port) logging.debug('chosen server: %s:%d', server, server_port) # TODO support multiple server IP return server, server_port def _close_client(self, client): if hasattr(client, 'close'): self._sockets.remove(client.fileno()) self._eventloop.remove(client) client.close() else: # just an address pass def _handle_server(self): server = self._server_socket data, r_addr = server.recvfrom(BUF_SIZE) if not data: logging.debug('UDP handle_server: data is empty') if self._is_local: frag = ord(data[2]) if frag != 0: logging.warn('drop a message since frag is not 0') return else: data = data[3:] else: data = encrypt.encrypt_all(self._password, self._method, 0, data) # decrypt data if not data: logging.debug('UDP handle_server: data is empty after decrypt') return header_result = parse_header(data) if header_result is None: return addrtype, dest_addr, dest_port, header_length = header_result if self._is_local: server_addr, server_port = self._get_a_server() else: server_addr, server_port = dest_addr, dest_port key = client_key(r_addr[0], r_addr[1], dest_addr, dest_port) 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] 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 self._sockets.add(client.fileno()) self._eventloop.add(client, eventloop.POLL_IN) if self._is_local: data = encrypt.encrypt_all(self._password, self._method, 1, data) if not data: return else: data = data[header_length:] if not data: return try: client.sendto(data, (server_addr, server_port)) except IOError as e: err = eventloop.errno_from_exception(e) if err in (errno.EINPROGRESS, errno.EAGAIN): pass else: logging.error(e) def _handle_client(self, sock): data, r_addr = sock.recvfrom(BUF_SIZE) if not data: logging.debug('UDP handle_client: data is empty') return if not self._is_local: addrlen = len(r_addr[0]) if addrlen > 255: # drop return data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data response = encrypt.encrypt_all(self._password, self._method, 1, data) if not response: return else: data = encrypt.encrypt_all(self._password, self._method, 0, data) if not data: return header_result = parse_header(data) if header_result is None: return # addrtype, dest_addr, dest_port, header_length = header_result response = '\x00\x00\x00' + data client_addr = self._client_fd_to_server_addr.get(sock.fileno()) if client_addr: self._server_socket.sendto(response, client_addr) else: # this packet is from somewhere else we know # simply drop that packet pass def add_to_loop(self, loop): if self._eventloop: raise Exception('already add to 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) 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.5: self._cache.sweep() if now - self._last_time > 7: self._client_fd_to_server_addr.sweep() self._last_time = now def close(self): self._closed = True self._server_socket.close() shadowsocks/shadowsocks/asyncdns.py0000644000175000017500000004103712375672543017155 0ustar shellshell#!/usr/bin/env python # -*- coding: utf-8 -*- # 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. import time import os import socket import struct import re import logging import common import lru_cache import eventloop CACHE_SWEEP_INTERVAL = 30 VALID_HOSTNAME = re.compile("(?!-)[A-Z\d-]{1,63}(? 63: return None results.append(chr(l)) results.append(label) results.append('\0') return ''.join(results) def build_request(address, qtype, request_id): header = struct.pack('!HBBHHHH', request_id, 1, 0, 1, 0, 0, 0) addr = build_address(address) qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN) return header + addr + qtype_qclass def parse_ip(addrtype, data, length, offset): if addrtype == QTYPE_A: return socket.inet_ntop(socket.AF_INET, data[offset:offset + length]) elif addrtype == QTYPE_AAAA: return socket.inet_ntop(socket.AF_INET6, data[offset:offset + length]) elif addrtype in [QTYPE_CNAME, QTYPE_NS]: return parse_name(data, offset)[1] else: return data[offset:offset + length] def parse_name(data, offset): p = offset labels = [] l = ord(data[p]) while l > 0: if (l & (128 + 64)) == (128 + 64): # pointer pointer = struct.unpack('!H', data[p:p + 2])[0] pointer &= 0x3FFF r = parse_name(data, pointer) labels.append(r[1]) p += 2 # pointer is the end return p - offset, '.'.join(labels) else: labels.append(data[p + 1:p + 1 + l]) p += 1 + l l = ord(data[p]) return p - offset + 1, '.'.join(labels) # rfc1035 # record # 1 1 1 1 1 1 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | | # / / # / NAME / # | | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | TYPE | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | CLASS | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | TTL | # | | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | RDLENGTH | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| # / RDATA / # / / # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ def parse_record(data, offset, question=False): nlen, name = parse_name(data, offset) if not question: record_type, record_class, record_ttl, record_rdlength = struct.unpack( '!HHiH', data[offset + nlen:offset + nlen + 10] ) ip = parse_ip(record_type, data, record_rdlength, offset + nlen + 10) return nlen + 10 + record_rdlength, \ (name, ip, record_type, record_class, record_ttl) else: record_type, record_class = struct.unpack( '!HH', data[offset + nlen:offset + nlen + 4] ) return nlen + 4, (name, None, record_type, record_class, None, None) def parse_header(data): if len(data) >= 12: header = struct.unpack('!HBBHHHH', data[:12]) res_id = header[0] res_qr = header[1] & 128 res_tc = header[1] & 2 res_ra = header[2] & 128 res_rcode = header[2] & 15 # assert res_tc == 0 # assert res_rcode in [0, 3] res_qdcount = header[3] res_ancount = header[4] res_nscount = header[5] res_arcount = header[6] return (res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, res_ancount, res_nscount, res_arcount) return None def parse_response(data): try: if len(data) >= 12: header = parse_header(data) if not header: return None res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, \ res_ancount, res_nscount, res_arcount = header qds = [] ans = [] offset = 12 for i in xrange(0, res_qdcount): l, r = parse_record(data, offset, True) offset += l if r: qds.append(r) for i in xrange(0, res_ancount): l, r = parse_record(data, offset) offset += l if r: ans.append(r) for i in xrange(0, res_nscount): l, r = parse_record(data, offset) offset += l for i in xrange(0, res_arcount): l, r = parse_record(data, offset) offset += l response = DNSResponse() if qds: response.hostname = qds[0][0] for an in ans: response.answers.append((an[1], an[2], an[3])) return response except Exception as e: import traceback traceback.print_exc() logging.error(e) return None def is_ip(address): for family in (socket.AF_INET, socket.AF_INET6): try: socket.inet_pton(family, address) return family except (TypeError, ValueError, OSError, IOError): pass return False def is_valid_hostname(hostname): if len(hostname) > 255: return False if hostname[-1] == ".": hostname = hostname[:-1] return all(VALID_HOSTNAME.match(x) for x in hostname.split(".")) class DNSResponse(object): def __init__(self): self.hostname = None self.answers = [] # each: (addr, type, class) def __str__(self): return '%s: %s' % (self.hostname, str(self.answers)) STATUS_IPV4 = 0 STATUS_IPV6 = 1 class DNSResolver(object): def __init__(self): self._loop = None self._request_id = 1 self._hosts = {} self._hostname_status = {} 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() self._parse_hosts() # TODO monitor hosts change and reload hosts # TODO parse /etc/gai.conf and follow its rules def _parse_resolv(self): self._servers = [] try: with open('/etc/resolv.conf', 'rb') as f: content = f.readlines() for line in content: line = line.strip() if line: if line.startswith('nameserver'): parts = line.split() if len(parts) >= 2: server = parts[1] if is_ip(server) == socket.AF_INET: self._servers.append(server) except IOError: pass if not self._servers: self._servers = ['8.8.4.4', '8.8.8.8'] def _parse_hosts(self): etc_path = '/etc/hosts' if os.environ.__contains__('WINDIR'): etc_path = os.environ['WINDIR'] + '/system32/drivers/etc/hosts' try: with open(etc_path, 'rb') as f: for line in f.readlines(): line = line.strip() parts = line.split() if len(parts) >= 2: ip = parts[0] if is_ip(ip): for i in xrange(1, len(parts)): hostname = parts[i] if hostname: self._hosts[hostname] = ip except IOError: self._hosts['localhost'] = '127.0.0.1' def add_to_loop(self, loop): if self._loop: raise Exception('already add to loop') self._loop = loop # TODO when dns server is IPv6 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) def _call_callback(self, hostname, ip, error=None): callbacks = self._hostname_to_cb.get(hostname, []) for callback in callbacks: if self._cb_to_hostname.__contains__(callback): del self._cb_to_hostname[callback] if ip or error: callback((hostname, ip), error) else: callback((hostname, None), Exception('unknown hostname %s' % hostname)) if self._hostname_to_cb.__contains__(hostname): del self._hostname_to_cb[hostname] if self._hostname_status.__contains__(hostname): del self._hostname_status[hostname] def _handle_data(self, data): response = parse_response(data) if response and response.hostname: hostname = response.hostname ip = None for answer in response.answers: if answer[1] in (QTYPE_A, QTYPE_AAAA) and \ answer[2] == QCLASS_IN: ip = answer[0] break if not ip and self._hostname_status.get(hostname, STATUS_IPV6) \ == STATUS_IPV4: self._hostname_status[hostname] = STATUS_IPV6 self._send_req(hostname, QTYPE_AAAA) else: if ip: self._cache[hostname] = ip self._call_callback(hostname, ip) 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 remove_callback(self, callback): hostname = self._cb_to_hostname.get(callback) if hostname: del self._cb_to_hostname[callback] arr = self._hostname_to_cb.get(hostname, None) if arr: arr.remove(callback) if not arr: del self._hostname_to_cb[hostname] if self._hostname_status.__contains__(hostname): del self._hostname_status[hostname] def _send_req(self, hostname, qtype): self._request_id += 1 if self._request_id > 32768: self._request_id = 1 req = build_request(hostname, qtype, self._request_id) for server in self._servers: logging.debug('resolving %s with type %d using server %s', hostname, qtype, server) self._sock.sendto(req, (server, 53)) def resolve(self, hostname, callback): if not hostname: callback(None, Exception('empty hostname')) elif is_ip(hostname): callback((hostname, hostname), None) elif self._hosts.__contains__(hostname): logging.debug('hit hosts: %s', hostname) ip = self._hosts[hostname] callback((hostname, ip), None) elif self._cache.__contains__(hostname): logging.debug('hit cache: %s', hostname) ip = self._cache[hostname] callback((hostname, ip), None) else: if not is_valid_hostname(hostname): callback(None, Exception('invalid hostname: %s' % hostname)) return arr = self._hostname_to_cb.get(hostname, None) if not arr: self._hostname_status[hostname] = STATUS_IPV4 self._send_req(hostname, QTYPE_A) self._hostname_to_cb[hostname] = [callback] self._cb_to_hostname[callback] = hostname else: arr.append(callback) # TODO send again only if waited too long self._send_req(hostname, QTYPE_A) def close(self): if self._sock: self._sock.close() self._sock = None def test(): logging.getLogger('').handlers = [] logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') def _callback(address, error): print error, address loop = eventloop.EventLoop() resolver = DNSResolver() resolver.add_to_loop(loop) for hostname in ['www.google.com', '8.8.8.8', 'localhost', 'activate.adobe.com', 'www.twitter.com', 'ipv6.google.com', 'ipv6.l.google.com', 'www.gmail.com', 'r4---sn-3qqp-ioql.googlevideo.com', 'www.baidu.com', 'www.a.shifen.com', 'm.baidu.jp', 'www.youku.com', 'www.twitter.com', 'ipv6.google.com']: resolver.resolve(hostname, _callback) loop.run() if __name__ == '__main__': test() shadowsocks/shadowsocks/local.py0000755000175000017500000000437412375672543016433 0ustar shellshell#!/usr/bin/env python # -*- coding: utf-8 -*- # 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. import sys import os import logging import utils import encrypt import eventloop import tcprelay import udprelay import asyncdns def main(): utils.check_python() # fix py2exe if hasattr(sys, "frozen") and sys.frozen in \ ("windows_exe", "console_exe"): p = os.path.dirname(os.path.abspath(sys.executable)) os.chdir(p) config = utils.get_config(True) utils.print_shadowsocks() encrypt.init_table(config['password'], config['method']) try: logging.info("starting local at %s:%d" % (config['local_address'], config['local_port'])) dns_resolver = asyncdns.DNSResolver() tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) udp_server = udprelay.UDPRelay(config, dns_resolver, True) loop = eventloop.EventLoop() dns_resolver.add_to_loop(loop) tcp_server.add_to_loop(loop) udp_server.add_to_loop(loop) loop.run() except (KeyboardInterrupt, IOError, OSError) as e: logging.error(e) import traceback traceback.print_exc() os._exit(0) if __name__ == '__main__': main() shadowsocks/shadowsocks/server.py0000755000175000017500000001035312375672543016641 0ustar shellshell#!/usr/bin/env python # -*- coding: utf-8 -*- # 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. import sys import os import logging import utils import encrypt import eventloop import tcprelay import udprelay import asyncdns def main(): utils.check_python() config = utils.get_config(False) utils.print_shadowsocks() if config['port_password']: if config['server_port'] or config['password']: logging.warn('warning: port_password should not be used with ' 'server_port and password. server_port and password ' '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'] encrypt.init_table(config['password'], config['method']) tcp_servers = [] udp_servers = [] dns_resolver = asyncdns.DNSResolver() for port, password in config['port_password'].items(): a_config = config.copy() a_config['server_port'] = int(port) a_config['password'] = password logging.info("starting server at %s:%d" % (a_config['server'], int(port))) tcp_servers.append(tcprelay.TCPRelay(a_config, dns_resolver, False)) udp_servers.append(udprelay.UDPRelay(a_config, dns_resolver, False)) def run_server(): try: loop = eventloop.EventLoop() dns_resolver.add_to_loop(loop) for tcp_server in tcp_servers: tcp_server.add_to_loop(loop) for udp_server in udp_servers: udp_server.add_to_loop(loop) loop.run() except (KeyboardInterrupt, IOError, OSError) as e: logging.error(e) import traceback traceback.print_exc() os._exit(0) if int(config['workers']) > 1: if os.name == 'posix': children = [] is_child = False for i in xrange(0, int(config['workers'])): r = os.fork() if r == 0: logging.info('worker started') is_child = True run_server() break else: children.append(r) if not is_child: def handler(signum, _): for pid in children: os.kill(pid, signum) os.waitpid(pid, 0) sys.exit() import signal signal.signal(signal.SIGTERM, handler) # master for a_tcp_server in tcp_servers: a_tcp_server.close() for a_udp_server in udp_servers: a_udp_server.close() dns_resolver.close() for child in children: os.waitpid(child, 0) else: logging.warn('worker is only available on Unix/Linux') run_server() else: run_server() if __name__ == '__main__': main() shadowsocks/shadowsocks/eventloop.py0000644000175000017500000001571312375672543017350 0ustar shellshell#!/usr/bin/python # -*- coding: utf-8 -*- # 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 ssloop # https://github.com/clowwindy/ssloop import os import socket import select import errno import logging from collections import defaultdict __all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR', 'POLL_HUP', 'POLL_NVAL', 'EVENT_NAMES'] POLL_NULL = 0x00 POLL_IN = 0x01 POLL_OUT = 0x04 POLL_ERR = 0x08 POLL_HUP = 0x10 POLL_NVAL = 0x20 EVENT_NAMES = { POLL_NULL: 'POLL_NULL', POLL_IN: 'POLL_IN', POLL_OUT: 'POLL_OUT', POLL_ERR: 'POLL_ERR', POLL_HUP: 'POLL_HUP', 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) class KqueueLoop(object): MAX_EVENTS = 1024 def __init__(self): self._kqueue = select.kqueue() self._fds = {} def _control(self, fd, mode, flags): events = [] if mode & POLL_IN: events.append(select.kevent(fd, select.KQ_FILTER_READ, flags)) if mode & POLL_OUT: events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags)) for e in events: self._kqueue.control([e], 0) def poll(self, timeout): if timeout < 0: timeout = None # kqueue behaviour events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout) results = defaultdict(lambda: POLL_NULL) for e in events: fd = e.ident if e.filter == select.KQ_FILTER_READ: results[fd] |= POLL_IN elif e.filter == select.KQ_FILTER_WRITE: results[fd] |= POLL_OUT return results.iteritems() def add_fd(self, fd, mode): self._fds[fd] = mode self._control(fd, mode, select.KQ_EV_ADD) def remove_fd(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) class SelectLoop(object): def __init__(self): self._r_list = set() self._w_list = set() self._x_list = set() def poll(self, timeout): r, w, x = select.select(self._r_list, self._w_list, self._x_list, timeout) results = defaultdict(lambda: POLL_NULL) for p in [(r, POLL_IN), (w, POLL_OUT), (x, POLL_ERR)]: for fd in p[0]: results[fd] |= p[1] return results.items() def add_fd(self, fd, mode): if mode & POLL_IN: self._r_list.add(fd) if mode & POLL_OUT: self._w_list.add(fd) if mode & POLL_ERR: self._x_list.add(fd) def remove_fd(self, fd): if fd in self._r_list: self._r_list.remove(fd) if fd in self._w_list: self._w_list.remove(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) class EventLoop(object): def __init__(self): if hasattr(select, 'epoll'): self._impl = EpollLoop() model = 'epoll' elif hasattr(select, 'kqueue'): self._impl = KqueueLoop() model = 'kqueue' elif hasattr(select, 'select'): self._impl = SelectLoop() model = 'select' else: raise Exception('can not find any available functions in select ' 'package') self._fd_to_f = {} self._handlers = [] 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): fd = f.fileno() self._fd_to_f[fd] = f self._impl.add_fd(fd, mode) def remove(self, f): fd = f.fileno() self._fd_to_f[fd] = None self._impl.remove_fd(fd) def modify(self, f, mode): fd = f.fileno() self._impl.modify_fd(fd, mode) def add_handler(self, handler): self._handlers.append(handler) def run(self): while not self.stopping: try: events = self.poll(1) except (OSError, IOError) as e: if errno_from_exception(e) == errno.EPIPE: # Happens when the client closes the connection logging.error('poll:%s', e) continue else: logging.error('poll:%s', e) import traceback traceback.print_exc() continue for handler in self._handlers: # TODO when there are a lot of handlers try: handler(events) except (OSError, IOError) as e: logging.error(e) import traceback traceback.print_exc() # from tornado def errno_from_exception(e): """Provides the errno from an Exception object. There are cases that the errno attribute was not set so we pull the errno out of the args but if someone instatiates an Exception without any args you will get a tuple error. So this function abstracts all that behavior to give you a safe way to get the errno. """ if hasattr(e, 'errno'): return e.errno elif e.args: return e.args[0] else: return None # from tornado def get_sock_error(sock): error_number = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) return socket.error(error_number, os.strerror(error_number)) shadowsocks/shadowsocks/tcprelay.py0000644000175000017500000006117012375672543017156 0ustar shellshell#!/usr/bin/python # -*- coding: utf-8 -*- # 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. import time import socket import errno import struct import logging import traceback import random import encrypt import eventloop import utils from common import parse_header TIMEOUTS_CLEAN_SIZE = 512 TIMEOUT_PRECISION = 4 MSG_FASTOPEN = 0x20000000 CMD_CONNECT = 1 CMD_BIND = 2 CMD_UDP_ASSOCIATE = 3 # local: # stage 0 init # stage 1 hello received, hello sent # stage 2 UDP assoc # stage 3 DNS # stage 4 addr received, reply sent # stage 5 remote connected # remote: # stage 0 init # stage 3 DNS # stage 4 addr received, reply sent # stage 5 remote connected STAGE_INIT = 0 STAGE_HELLO = 1 STAGE_UDP_ASSOC = 2 STAGE_DNS = 3 STAGE_REPLY = 4 STAGE_STREAM = 5 STAGE_DESTROYED = -1 # stream direction STREAM_UP = 0 STREAM_DOWN = 1 # stream wait status WAIT_STATUS_INIT = 0 WAIT_STATUS_READING = 1 WAIT_STATUS_WRITING = 2 WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING BUF_SIZE = 32 * 1024 class TCPRelayHandler(object): def __init__(self, server, fd_to_handlers, loop, local_sock, config, dns_resolver, is_local): self._server = server self._fd_to_handlers = fd_to_handlers self._loop = loop self._local_sock = local_sock self._remote_sock = None self._config = config self._dns_resolver = dns_resolver self._is_local = is_local self._stage = STAGE_INIT self._encryptor = encrypt.Encryptor(config['password'], config['method']) self._fastopen_connected = False self._data_to_write_to_local = [] self._data_to_write_to_remote = [] self._upstream_status = WAIT_STATUS_READING self._downstream_status = WAIT_STATUS_INIT self._remote_address = None if is_local: self._chosen_server = self._get_a_server() 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) self.last_activity = 0 self._update_activity() def __hash__(self): # default __hash__ is id / 16 # we want to eliminate collisions return id(self) @property def remote_address(self): return self._remote_address def _get_a_server(self): server = self._config['server'] server_port = self._config['server_port'] if type(server_port) == list: server_port = random.choice(server_port) logging.debug('chosen server: %s:%d', server, server_port) # TODO support multiple server IP return server, server_port def _update_activity(self): self._server.update_activity(self) def _update_stream(self, stream, status): dirty = False if stream == STREAM_DOWN: if self._downstream_status != status: self._downstream_status = status dirty = True elif stream == STREAM_UP: if self._upstream_status != status: self._upstream_status = status dirty = True if dirty: if self._local_sock: event = eventloop.POLL_ERR if self._downstream_status & WAIT_STATUS_WRITING: event |= eventloop.POLL_OUT if self._upstream_status & WAIT_STATUS_READING: event |= eventloop.POLL_IN 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) def _write_to_sock(self, data, sock): if not data or not sock: return False uncomplete = False try: l = len(data) s = sock.send(data) if s < l: data = data[s:] uncomplete = True except (OSError, IOError) as e: error_no = eventloop.errno_from_exception(e) if error_no in (errno.EAGAIN, errno.EINPROGRESS, errno.EWOULDBLOCK): uncomplete = True else: logging.error(e) if self._config['verbose']: traceback.print_exc() self.destroy() return False if uncomplete: if sock == self._local_sock: self._data_to_write_to_local.append(data) self._update_stream(STREAM_DOWN, WAIT_STATUS_WRITING) elif sock == self._remote_sock: self._data_to_write_to_remote.append(data) self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) else: logging.error('write_all_to_sock:unknown socket') else: if sock == self._local_sock: self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) elif sock == self._remote_sock: self._update_stream(STREAM_UP, WAIT_STATUS_READING) else: logging.error('write_all_to_sock:unknown socket') return True def _handle_stage_reply(self, data): if self._is_local: data = self._encryptor.encrypt(data) self._data_to_write_to_remote.append(data) if self._is_local and not self._fastopen_connected and \ self._config['fast_open']: try: self._fastopen_connected = True remote_sock = self._create_remote_socket(self._chosen_server[0], self._chosen_server[1]) self._loop.add(remote_sock, eventloop.POLL_ERR) data = ''.join(self._data_to_write_to_local) l = len(data) s = remote_sock.sendto(data, MSG_FASTOPEN, self._chosen_server) if s < l: data = data[s:] self._data_to_write_to_local = [data] self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) else: self._data_to_write_to_local = [] self._update_stream(STREAM_UP, WAIT_STATUS_READING) self._stage = STAGE_STREAM except (OSError, IOError) as e: if eventloop.errno_from_exception(e) == errno.EINPROGRESS: self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) elif eventloop.errno_from_exception(e) == errno.ENOTCONN: logging.error('fast open not supported on this OS') self._config['fast_open'] = False self.destroy() else: logging.error(e) if self._config['verbose']: traceback.print_exc() self.destroy() def _handle_stage_hello(self, data): try: if self._is_local: cmd = ord(data[1]) if cmd == CMD_UDP_ASSOCIATE: logging.debug('UDP associate') if self._local_sock.family == socket.AF_INET6: header = '\x05\x00\x00\x04' else: header = '\x05\x00\x00\x01' addr, port = self._local_sock.getsockname() addr_to_send = socket.inet_pton(self._local_sock.family, addr) port_to_send = struct.pack('>H', port) self._write_to_sock(header + addr_to_send + port_to_send, self._local_sock) self._stage = STAGE_UDP_ASSOC # just wait for the client to disconnect return elif cmd == CMD_CONNECT: # just trim VER CMD RSV data = data[3:] else: logging.error('unknown command %d', cmd) self.destroy() return header_result = parse_header(data) if header_result is None: raise Exception('can not parse header') addrtype, remote_addr, remote_port, header_length = header_result logging.info('connecting %s:%d' % (remote_addr, remote_port)) self._remote_address = (remote_addr, remote_port) # pause reading self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) self._stage = STAGE_DNS if self._is_local: # forward address to remote self._write_to_sock('\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10', self._local_sock) data_to_send = self._encryptor.encrypt(data) self._data_to_write_to_remote.append(data_to_send) # notice here may go into _handle_dns_resolved directly self._dns_resolver.resolve(self._chosen_server[0], self._handle_dns_resolved) else: if len(data) > header_length: self._data_to_write_to_remote.append(data[header_length:]) # notice here may go into _handle_dns_resolved directly self._dns_resolver.resolve(remote_addr, self._handle_dns_resolved) except Exception as e: logging.error(e) if self._config['verbose']: traceback.print_exc() # TODO use logging when debug completed self.destroy() def _create_remote_socket(self, ip, port): addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, socket.SOL_TCP) if len(addrs) == 0: raise Exception("getaddrinfo failed for %s:%d" % (ip, port)) af, socktype, proto, canonname, sa = addrs[0] remote_sock = socket.socket(af, socktype, proto) self._remote_sock = remote_sock self._fd_to_handlers[remote_sock.fileno()] = self remote_sock.setblocking(False) remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) return remote_sock def _handle_dns_resolved(self, result, error): if error: logging.error(error) self.destroy() return if result: ip = result[1] if ip: try: self._stage = STAGE_REPLY remote_addr = ip if self._is_local: remote_port = self._chosen_server[1] else: remote_port = self._remote_address[1] if self._is_local and self._config['fast_open']: # wait for more data to arrive and send them in one SYN self._stage = STAGE_REPLY self._update_stream(STREAM_UP, WAIT_STATUS_READING) # TODO when there is already data in this packet else: remote_sock = self._create_remote_socket(remote_addr, remote_port) try: remote_sock.connect((remote_addr, remote_port)) except (OSError, IOError) as e: if eventloop.errno_from_exception(e) == \ errno.EINPROGRESS: pass self._loop.add(remote_sock, eventloop.POLL_ERR | eventloop.POLL_OUT) self._stage = STAGE_REPLY self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) return except (OSError, IOError) as e: logging.error(e) if self._config['verbose']: traceback.print_exc() self.destroy() def _on_local_read(self): self._update_activity() if not self._local_sock: return is_local = self._is_local data = None try: data = self._local_sock.recv(BUF_SIZE) except (OSError, IOError) as e: if eventloop.errno_from_exception(e) in \ (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): return if not data: self.destroy() return if not is_local: data = self._encryptor.decrypt(data) if not data: return if self._stage == STAGE_STREAM: if self._is_local: data = self._encryptor.encrypt(data) self._write_to_sock(data, self._remote_sock) return elif is_local and self._stage == STAGE_INIT: # TODO check auth method self._write_to_sock('\x05\00', self._local_sock) self._stage = STAGE_HELLO return elif self._stage == STAGE_REPLY: self._handle_stage_reply(data) elif (is_local and self._stage == STAGE_HELLO) or \ (not is_local and self._stage == STAGE_INIT): self._handle_stage_hello(data) def _on_remote_read(self): 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): return if not data: self.destroy() return if self._is_local: data = self._encryptor.decrypt(data) else: data = self._encryptor.encrypt(data) try: self._write_to_sock(data, self._local_sock) except Exception as e: logging.error(e) if self._config['verbose']: traceback.print_exc() # TODO use logging when debug completed self.destroy() def _on_local_write(self): if self._data_to_write_to_local: data = ''.join(self._data_to_write_to_local) self._data_to_write_to_local = [] self._write_to_sock(data, self._local_sock) else: self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) def _on_remote_write(self): self._stage = STAGE_STREAM if self._data_to_write_to_remote: data = ''.join(self._data_to_write_to_remote) self._data_to_write_to_remote = [] self._write_to_sock(data, self._remote_sock) else: self._update_stream(STREAM_UP, WAIT_STATUS_READING) def _on_local_error(self): logging.debug('got local error') if self._local_sock: logging.error(eventloop.get_sock_error(self._local_sock)) self.destroy() def _on_remote_error(self): logging.debug('got remote error') if self._remote_sock: logging.error(eventloop.get_sock_error(self._remote_sock)) self.destroy() def handle_event(self, sock, event): if self._stage == STAGE_DESTROYED: logging.debug('ignore handle_event: destroyed') return # order is important if sock == self._remote_sock: if event & eventloop.POLL_ERR: self._on_remote_error() if self._stage == STAGE_DESTROYED: return if event & (eventloop.POLL_IN | eventloop.POLL_HUP): self._on_remote_read() if self._stage == STAGE_DESTROYED: return if event & eventloop.POLL_OUT: self._on_remote_write() elif sock == self._local_sock: if event & eventloop.POLL_ERR: self._on_local_error() if self._stage == STAGE_DESTROYED: return if event & (eventloop.POLL_IN | eventloop.POLL_HUP): self._on_local_read() if self._stage == STAGE_DESTROYED: return if event & eventloop.POLL_OUT: self._on_local_write() else: logging.warn('unknown socket') def destroy(self): if self._stage == STAGE_DESTROYED: logging.debug('already destroyed') return self._stage = STAGE_DESTROYED if self._remote_address: logging.debug('destroy: %s:%d' % self._remote_address) else: logging.debug('destroy') if self._remote_sock: logging.debug('destroying remote') 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) del self._fd_to_handlers[self._local_sock.fileno()] self._local_sock.close() self._local_sock = None self._dns_resolver.remove_callback(self._handle_dns_resolved) self._server.remove_handler(self) class TCPRelay(object): def __init__(self, config, dns_resolver, is_local): self._config = config self._is_local = is_local self._dns_resolver = dns_resolver 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 self._timeout_offset = 0 # last checked position for timeout # we trim the timeouts once a while self._handler_to_timeouts = {} # key: handler value: index in timeouts if is_local: listen_addr = config['local_address'] listen_port = config['local_port'] else: listen_addr = config['server'] listen_port = config['server_port'] addrs = socket.getaddrinfo(listen_addr, listen_port, 0, socket.SOCK_STREAM, socket.SOL_TCP) if len(addrs) == 0: raise Exception("can't get addrinfo for %s:%d" % (listen_addr, listen_port)) af, socktype, proto, canonname, sa = addrs[0] server_socket = socket.socket(af, socktype, proto) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(sa) server_socket.setblocking(False) if config['fast_open']: try: server_socket.setsockopt(socket.SOL_TCP, 23, 5) except socket.error: logging.error('warning: fast open is not available') self._config['fast_open'] = False server_socket.listen(1024) self._server_socket = server_socket def add_to_loop(self, loop): if self._eventloop: raise Exception('already add to 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) def remove_handler(self, handler): index = self._handler_to_timeouts.get(hash(handler), -1) if index >= 0: # delete is O(n), so we just set it to None self._timeouts[index] = None del self._handler_to_timeouts[hash(handler)] def update_activity(self, handler): """ set handler to active """ now = int(time.time()) if now - handler.last_activity < TIMEOUT_PRECISION: # thus we can lower timeout modification frequency return handler.last_activity = now index = self._handler_to_timeouts.get(hash(handler), -1) if index >= 0: # delete is O(n), so we just set it to None self._timeouts[index] = None length = len(self._timeouts) self._timeouts.append(handler) self._handler_to_timeouts[hash(handler)] = length def _sweep_timeout(self): # tornado's timeout memory management is more flexible than we need # we just need a sorted last_activity queue and it's faster than heapq # in fact we can do O(1) insertion/remove so we invent our own if self._timeouts: logging.log(utils.VERBOSE_LEVEL, 'sweeping timeouts') now = time.time() length = len(self._timeouts) pos = self._timeout_offset while pos < length: handler = self._timeouts[pos] if handler: if now - handler.last_activity < self._timeout: break else: if handler.remote_address: logging.warn('timed out: %s:%d' % handler.remote_address) else: logging.warn('timed out') handler.destroy() self._timeouts[pos] = None # free memory pos += 1 else: pos += 1 if pos > TIMEOUTS_CLEAN_SIZE and pos > length >> 1: # clean up the timeout queue when it gets larger than half # of the queue self._timeouts = self._timeouts[pos:] for key in self._handler_to_timeouts: self._handler_to_timeouts[key] -= pos pos = 0 self._timeout_offset = pos def _handle_events(self, events): for sock, fd, event in events: if sock: logging.log(utils.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: logging.error(e) if self._config['verbose']: traceback.print_exc() else: if sock: handler = self._fd_to_handlers.get(fd, None) if handler: handler.handle_event(sock, event) else: logging.warn('poll removed fd') now = time.time() if now - self._last_time > TIMEOUT_PRECISION: self._sweep_timeout() self._last_time = now def close(self): self._closed = True self._server_socket.close() shadowsocks/shadowsocks/encrypt_salsa20.py0000644000175000017500000000714012375672543020341 0ustar shellshell#!/usr/bin/python import time import struct import logging import sys slow_xor = False imported = False BLOCK_SIZE = 16384 def run_imports(): global imported, slow_xor, salsa20, numpy if not imported: imported = True try: 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: 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 = [] for i in xrange(0, len(a)): c.append(chr(ord(a[i]) ^ ord(b[i]))) return ''.join(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 != '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 ''.join(results) def test(): from os import urandom import random rounds = 1 * 1024 plain = urandom(BLOCK_SIZE * rounds) import M2Crypto.EVP # cipher = M2Crypto.EVP.Cipher('aes_128_cfb', 'k' * 32, 'i' * 16, 1, # key_as_bytes=0, d='md5', salt=None, i=1, # padding=1) # decipher = M2Crypto.EVP.Cipher('aes_128_cfb', 'k' * 32, 'i' * 16, 0, # key_as_bytes=0, d='md5', salt=None, i=1, # padding=1) cipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1) decipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1) results = [] pos = 0 print 'salsa20 test start' start = time.time() while pos < len(plain): l = random.randint(100, 32768) c = cipher.update(plain[pos:pos + l]) results.append(c) pos += l pos = 0 c = ''.join(results) results = [] while pos < len(plain): l = random.randint(100, 32768) results.append(decipher.update(c[pos:pos + l])) pos += l end = time.time() print 'speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start)) assert ''.join(results) == plain if __name__ == '__main__': test()shadowsocks/shadowsocks/common.py0000644000175000017500000001063512375672543016623 0ustar shellshell#!/usr/bin/python # -*- coding: utf-8 -*- # 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. import socket import struct import logging def inet_ntop(family, ipstr): if family == socket.AF_INET: return socket.inet_ntoa(ipstr) elif family == socket.AF_INET6: v6addr = ':'.join(('%02X%02X' % (ord(i), ord(j))) for i, j in zip(ipstr[::2], ipstr[1::2])) return v6addr def inet_pton(family, addr): if family == socket.AF_INET: return socket.inet_aton(addr) elif family == socket.AF_INET6: if '.' in addr: # a v4 addr v4addr = addr[addr.rindex(':') + 1:] v4addr = socket.inet_aton(v4addr) v4addr = map(lambda x: ('%02X' % ord(x)), v4addr) v4addr.insert(2, ':') newaddr = addr[:addr.rindex(':') + 1] + ''.join(v4addr) return inet_pton(family, newaddr) dbyts = [0] * 8 # 8 groups grps = addr.split(':') for i, v in enumerate(grps): if v: dbyts[i] = int(v, 16) else: for j, w in enumerate(grps[::-1]): if w: dbyts[7 - j] = int(w, 16) else: break break return ''.join((chr(i // 256) + chr(i % 256)) for i in dbyts) else: raise RuntimeError("What family?") def patch_socket(): if not hasattr(socket, 'inet_pton'): socket.inet_pton = inet_pton if not hasattr(socket, 'inet_ntop'): socket.inet_ntop = inet_ntop patch_socket() ADDRTYPE_IPV4 = 1 ADDRTYPE_IPV6 = 4 ADDRTYPE_HOST = 3 def pack_addr(address): for family in (socket.AF_INET, socket.AF_INET6): try: r = socket.inet_pton(family, address) if family == socket.AF_INET6: return '\x04' + r else: return '\x01' + r except (TypeError, ValueError, OSError, IOError): pass if len(address) > 255: address = address[:255] # TODO return '\x03' + chr(len(address)) + address def parse_header(data): addrtype = ord(data[0]) dest_addr = None dest_port = None header_length = 0 if addrtype == ADDRTYPE_IPV4: if len(data) >= 7: dest_addr = socket.inet_ntoa(data[1:5]) dest_port = struct.unpack('>H', data[5:7])[0] header_length = 7 else: logging.warn('header is too short') elif addrtype == ADDRTYPE_HOST: if len(data) > 2: addrlen = ord(data[1]) if len(data) >= 2 + addrlen: dest_addr = data[2:2 + addrlen] dest_port = struct.unpack('>H', data[2 + addrlen:4 + addrlen])[0] header_length = 4 + addrlen else: logging.warn('header is too short') else: logging.warn('header is too short') elif addrtype == ADDRTYPE_IPV6: if len(data) >= 19: dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17]) dest_port = struct.unpack('>H', data[17:19])[0] header_length = 19 else: logging.warn('header is too short') else: logging.warn('unsupported addrtype %d, maybe wrong password' % addrtype) if dest_addr is None: return None return addrtype, dest_addr, dest_port, header_length shadowsocks/shadowsocks/utils.py0000644000175000017500000002320312375672543016466 0ustar shellshell#!/usr/bin/python # -*- coding: utf-8 -*- # 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. import os import json import sys import getopt import logging VERBOSE_LEVEL = 5 def check_python(): info = sys.version_info if not (info[0] == 2 and info[1] >= 6): print 'Python 2.6 or 2.7 required' sys.exit(1) def print_shadowsocks(): version = '' try: import pkg_resources version = pkg_resources.get_distribution('shadowsocks').version except Exception: pass print 'shadowsocks %s' % version def find_config(): config_path = 'config.json' if os.path.exists(config_path): return config_path config_path = os.path.join(os.path.dirname(__file__), '../', 'config.json') if os.path.exists(config_path): return config_path return None def check_config(config): if config.get('local_address', '') in ['0.0.0.0']: logging.warn('warning: local set to listen 0.0.0.0, which is not safe') if config.get('server', '') in ['127.0.0.1', 'localhost']: logging.warn('warning: server set to listen %s:%s, are you sure?' % (config['server'], config['server_port'])) if (config.get('method', '') or '').lower() == '': logging.warn('warning: table is not safe; please use a safer cipher, ' 'like AES-256-CFB') if (config.get('method', '') or '').lower() == 'rc4': logging.warn('warning: RC4 is not safe; please use a safer cipher, ' 'like AES-256-CFB') if config.get('timeout', 300) < 100: logging.warn('warning: your timeout %d seems too short' % int(config.get('timeout'))) if config.get('timeout', 300) > 600: logging.warn('warning: your timeout %d seems too long' % int(config.get('timeout'))) if config.get('password') in ['mypassword', 'barfoo!']: logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' 'config.json!') exit(1) def get_config(is_local): logging.basicConfig(level=logging.INFO, format='%(levelname)-s: %(message)s', filemode='a+') if is_local: shortopts = 'hs:b:p:k:l:m:c:t:vq' longopts = ['fast-open'] else: shortopts = 'hs:p:k:m:c:t:vq' longopts = ['fast-open', 'workers:'] try: config_path = find_config() optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) for key, value in optlist: if key == '-c': config_path = value if config_path: logging.info('loading config from %s' % config_path) with open(config_path, 'rb') as f: try: config = json.load(f, object_hook=_decode_dict) except ValueError as e: logging.error('found an error in config.json: %s', e.message) sys.exit(1) else: config = {} optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) v_count = 0 for key, value in optlist: if key == '-p': config['server_port'] = int(value) elif key == '-k': config['password'] = value elif key == '-l': config['local_port'] = int(value) elif key == '-s': config['server'] = value elif key == '-m': config['method'] = value elif key == '-b': config['local_address'] = value elif key == '-v': v_count += 1 # '-vv' turns on more verbose mode config['verbose'] = v_count elif key == '-t': config['timeout'] = int(value) elif key == '--fast-open': config['fast_open'] = True elif key == '--workers': config['workers'] = value elif key == '-h': if is_local: print_local_help() else: print_server_help() sys.exit(0) elif key == '-q': v_count -= 1 config['verbose'] = v_count except getopt.GetoptError as e: print >>sys.stderr, e print_help(is_local) sys.exit(2) if not config: logging.error('config not specified') print_help(is_local) sys.exit(2) config['password'] = config.get('password', None) config['method'] = config.get('method', 'aes-256-cfb') config['port_password'] = config.get('port_password', None) config['timeout'] = int(config.get('timeout', 300)) config['fast_open'] = config.get('fast_open', False) config['workers'] = config.get('workers', 1) config['verbose'] = config.get('verbose', False) config['local_address'] = config.get('local_address', '127.0.0.1') config['local_port'] = config.get('local_port', 1080) if is_local: if config.get('server', None) is None: logging.error('server addr not specified') print_local_help() sys.exit(2) else: config['server'] = config.get('server', '0.0.0.0') config['server_port'] = config.get('server_port', 8388) if not ('password' in config and config['password']): logging.error('password not specified') print_help(is_local) sys.exit(2) logging.getLogger('').handlers = [] logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') if config['verbose'] >= 2: level = VERBOSE_LEVEL elif config['verbose'] == 1: level = logging.DEBUG elif config['verbose'] == -1: level = logging.WARN elif config['verbose'] <= -2: level = logging.ERROR else: level = logging.INFO logging.basicConfig(level=level, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') check_config(config) return config def print_help(is_local): if is_local: print_local_help() else: print_server_help() def print_local_help(): print '''usage: sslocal [-h] -s SERVER_ADDR [-p SERVER_PORT] [-b LOCAL_ADDR] [-l LOCAL_PORT] -k PASSWORD [-m METHOD] [-t TIMEOUT] [-c CONFIG] [--fast-open] [-v] [-q] optional arguments: -h, --help show this help message and exit -s SERVER_ADDR server address -p SERVER_PORT server port, default: 8388 -b LOCAL_ADDR local binding address, default: 127.0.0.1 -l LOCAL_PORT local port, default: 1080 -k PASSWORD password -m METHOD encryption method, default: aes-256-cfb -t TIMEOUT timeout in seconds, default: 300 -c CONFIG path to config file --fast-open use TCP_FASTOPEN, requires Linux 3.7+ -v, -vv verbose mode -q, -qq quiet mode, only show warnings/errors Online help: ''' def print_server_help(): print '''usage: ssserver [-h] [-s SERVER_ADDR] [-p SERVER_PORT] -k PASSWORD -m METHOD [-t TIMEOUT] [-c CONFIG] [--fast-open] [--workers WORKERS] [-v] [-q] optional arguments: -h, --help show this help message and exit -s SERVER_ADDR server address, default: 0.0.0.0 -p SERVER_PORT server port, default: 8388 -k PASSWORD password -m METHOD encryption method, default: aes-256-cfb -t TIMEOUT timeout in seconds, default: 300 -c CONFIG path to config file --fast-open use TCP_FASTOPEN, requires Linux 3.7+ --workers WORKERS number of workers, available on Unix/Linux -v, -vv verbose mode -q, -qq quiet mode, only show warnings/errors Online help: ''' def _decode_list(data): rv = [] for item in data: if isinstance(item, unicode): item = item.encode('utf-8') elif isinstance(item, list): item = _decode_list(item) elif isinstance(item, dict): item = _decode_dict(item) rv.append(item) return rv def _decode_dict(data): rv = {} for key, value in data.iteritems(): if isinstance(key, unicode): key = key.encode('utf-8') if isinstance(value, unicode): value = value.encode('utf-8') elif isinstance(value, list): value = _decode_list(value) elif isinstance(value, dict): value = _decode_dict(value) rv[key] = value return rvshadowsocks/shadowsocks/encrypt.py0000644000175000017500000001636212375672543017022 0ustar shellshell#!/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. import os import sys import hashlib import string import struct import logging import encrypt_salsa20 def random_string(length): try: import M2Crypto.Rand return M2Crypto.Rand.rand_bytes(length) except ImportError: # TODO really strong enough on Linux? return os.urandom(length) cached_tables = {} cached_keys = {} def get_table(key): m = hashlib.md5() m.update(key) s = m.digest() (a, b) = struct.unpack(' 0: data = m[i - 1] + password md5.update(data) m.append(md5.digest()) i += 1 ms = ''.join(m) key = ms[:key_len] iv = ms[key_len:key_len + iv_len] cached_keys[password] = (key, iv) return (key, iv) method_supported = { 'aes-128-cfb': (16, 16), 'aes-192-cfb': (24, 16), 'aes-256-cfb': (32, 16), 'bf-cfb': (16, 8), 'camellia-128-cfb': (16, 16), 'camellia-192-cfb': (24, 16), 'camellia-256-cfb': (32, 16), 'cast5-cfb': (16, 8), 'des-cfb': (8, 8), 'idea-cfb': (16, 8), 'rc2-cfb': (16, 8), 'rc4': (16, 0), 'seed-cfb': (16, 16), 'salsa20-ctr': (32, 8), } class Encryptor(object): def __init__(self, key, method=None): if method == 'table': method = None self.key = key self.method = method self.iv = None self.iv_sent = False self.cipher_iv = '' self.decipher = None if method: self.cipher = self.get_cipher(key, method, 1, iv=random_string(32)) else: self.encrypt_table, self.decrypt_table = init_table(key) self.cipher = None def get_cipher_len(self, method): method = method.lower() m = method_supported.get(method, None) return m def iv_len(self): return len(self.cipher_iv) def get_cipher(self, password, method, op, iv=None): password = password.encode('utf-8') method = method.lower() m = self.get_cipher_len(method) if m: key, iv_ = EVP_BytesToKey(password, m[0], m[1]) if iv is None: iv = iv_ iv = iv[:m[1]] if op == 1: self.cipher_iv = iv[:m[1]] # this iv is for cipher not decipher if method != 'salsa20-ctr': import M2Crypto.EVP return M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv, op, key_as_bytes=0, d='md5', salt=None, i=1, padding=1) else: return encrypt_salsa20.Salsa20Cipher(method, key, iv, op) logging.error('method %s not supported' % method) sys.exit(1) def encrypt(self, buf): if len(buf) == 0: return buf if not self.method: return string.translate(buf, self.encrypt_table) else: if self.iv_sent: return self.cipher.update(buf) else: self.iv_sent = True return self.cipher_iv + self.cipher.update(buf) def decrypt(self, buf): if len(buf) == 0: return buf if not self.method: return string.translate(buf, self.decrypt_table) else: if self.decipher is None: decipher_iv_len = self.get_cipher_len(self.method)[1] decipher_iv = buf[:decipher_iv_len] self.decipher = self.get_cipher(self.key, self.method, 0, iv=decipher_iv) buf = buf[decipher_iv_len:] if len(buf) == 0: return buf return self.decipher.update(buf) def encrypt_all(password, method, op, data): if method is not None and method.lower() == 'table': method = None if not method: [encrypt_table, decrypt_table] = init_table(password) if op: return string.translate(data, encrypt_table) else: return string.translate(data, decrypt_table) else: import M2Crypto.EVP result = [] method = method.lower() (key_len, iv_len) = method_supported[method] (key, _) = EVP_BytesToKey(password, key_len, iv_len) if op: iv = random_string(iv_len) result.append(iv) else: iv = data[:iv_len] data = data[iv_len:] if method != 'salsa20-ctr': cipher = M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv, op, key_as_bytes=0, d='md5', salt=None, i=1, padding=1) else: cipher = encrypt_salsa20.Salsa20Cipher(method, key, iv, op) result.append(cipher.update(data)) return ''.join(result) shadowsocks/shadowsocks.egg-info/0000755000175000017500000000000012376026570016440 5ustar shellshellshadowsocks/shadowsocks.egg-info/entry_points.txt0000644000175000017500000000014712376320335021734 0ustar shellshell [console_scripts] sslocal = shadowsocks.local:main ssserver = shadowsocks.server:main shadowsocks/shadowsocks.egg-info/top_level.txt0000644000175000017500000000001412376320335021161 0ustar shellshellshadowsocks shadowsocks/shadowsocks.egg-info/SOURCES.txt0000644000175000017500000000076712376320335020332 0ustar shellshellLICENSE MANIFEST.in README.rst setup.py shadowsocks/__init__.py shadowsocks/asyncdns.py shadowsocks/common.py shadowsocks/encrypt.py shadowsocks/encrypt_salsa20.py shadowsocks/eventloop.py shadowsocks/local.py shadowsocks/lru_cache.py shadowsocks/server.py shadowsocks/tcprelay.py shadowsocks/udprelay.py shadowsocks/utils.py shadowsocks.egg-info/PKG-INFO shadowsocks.egg-info/SOURCES.txt shadowsocks.egg-info/dependency_links.txt shadowsocks.egg-info/entry_points.txt shadowsocks.egg-info/top_level.txtshadowsocks/shadowsocks.egg-info/dependency_links.txt0000644000175000017500000000000112376320335022502 0ustar shellshell shadowsocks/shadowsocks.egg-info/PKG-INFO0000644000175000017500000001716312376320335017541 0ustar shellshellMetadata-Version: 1.1 Name: shadowsocks Version: 2.1.0 Summary: A fast tunnel proxy that help you get through firewalls Home-page: https://github.com/clowwindy/shadowsocks Author: clowwindy Author-email: clowwindy42@gmail.com License: MIT Description: shadowsocks =========== |PyPI version| |Build Status| A fast tunnel proxy that help you get through firewalls. `中文说明 `__ Install ------- You'll have a client on your local machine, and install a server on a remote server. Client ~~~~~~ - `Windows `__ / `OS X `__ - `Android `__ / `iOS `__ - `OpenWRT `__ Server ~~~~~~ Debian / Ubuntu: ^^^^^^^^^^^^^^^^ :: apt-get install python-pip python-m2crypto pip install shadowsocks CentOS: ^^^^^^^ :: yum install m2crypto python-setuptools easy_install pip pip install shadowsocks Configuration ------------- On your server create a config file ``/etc/shadowsocks.json``. Example: :: { "server":"my_server_ip", "server_port":8388, "local_address": "127.0.0.1", "local_port":1080, "password":"mypassword", "timeout":300, "method":"aes-256-cfb", "fast_open": false, "workers": 1 } Explanation of the fields: +------------------+-----------------------------------------------------------------------------------------------------+ | Name | Explanation | +==================+=====================================================================================================+ | server | the address your server listens | +------------------+-----------------------------------------------------------------------------------------------------+ | server\_port | server port | +------------------+-----------------------------------------------------------------------------------------------------+ | local\_address | the address your local listens | +------------------+-----------------------------------------------------------------------------------------------------+ | local\_port | local port | +------------------+-----------------------------------------------------------------------------------------------------+ | password | password used for encryption | +------------------+-----------------------------------------------------------------------------------------------------+ | timeout | in seconds | +------------------+-----------------------------------------------------------------------------------------------------+ | method | encryption method, "aes-256-cfb" is recommended | +------------------+-----------------------------------------------------------------------------------------------------+ | fast\_open | use `TCP\_FASTOPEN `__, true / false | +------------------+-----------------------------------------------------------------------------------------------------+ | workers | number of workers, available on Unix/Linux | +------------------+-----------------------------------------------------------------------------------------------------+ Run ``ssserver -c /etc/shadowsocks.json`` on your server. To run it in the background, use `Supervisor `__. On your client machine, use the same configuration as your server, and start your client. If you use Chrome, it's recommended to use `SwitchySharp `__. Change the proxy settings to :: protocol: socks5 hostname: 127.0.0.1 port: your local_port If you can't install `SwitchySharp `__, you can launch Chrome with the following arguments to force Chrome to use the proxy: :: Chrome.exe --proxy-server="socks5://127.0.0.1:1080" --host-resolver-rules="MAP * 0.0.0.0 , EXCLUDE localhost" If you can't even download Chrome, find a friend to download a `Chrome Standalone `__ installer for you. Command line args ----------------- You can use args to override settings from ``config.json``. :: sslocal -s server_name -p server_port -l local_port -k password -m bf-cfb ssserver -p server_port -k password -m bf-cfb --workers 2 ssserver -c /etc/shadowsocks/config.json List all available args with ``-h``. Wiki ---- https://github.com/clowwindy/shadowsocks/wiki License ------- MIT Bugs and Issues --------------- - `Troubleshooting `__ - `Issue Tracker `__ - `Mailing list `__ .. |PyPI version| image:: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat :target: https://pypi.python.org/pypi/shadowsocks .. |Build Status| image:: https://img.shields.io/travis/clowwindy/shadowsocks/master.svg?style=flat :target: https://travis-ci.org/clowwindy/shadowsocks Platform: UNKNOWN Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Topic :: Internet :: Proxy Servers shadowsocks/.travis.yml0000644000175000017500000000124112375672543014533 0ustar shellshelllanguage: python python: - 2.6 - 2.7 - pypy cache: directories: - 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 - pip install m2crypto salsa20 - sudo tests/socksify/install.sh script: - python tests/test.py -c tests/table.json - python tests/test.py -c tests/aes.json - python tests/test.py -c tests/salsa20.json - python tests/test.py -c tests/server-multi-ports.json - python tests/test.py -c tests/server-multi-passwd.json - python tests/test.py -c tests/server-multi-passwd-table.json - python tests/test.py -c tests/workers.json shadowsocks/tests/0000755000175000017500000000000012375672543013566 5ustar shellshellshadowsocks/tests/table.json0000644000175000017500000000032712375672543015552 0ustar shellshell{ "server":"127.0.0.1", "server_port":8388, "local_port":1081, "password":"table_password", "timeout":60, "method":"table", "local_address":"127.0.0.1", "fast_open":false } shadowsocks/tests/server-multi-ports.json0000644000175000017500000000025612375672543020267 0ustar shellshell{ "server": "127.0.0.1", "server_port": [8384, 8345, 8346, 8347], "local_port": 1081, "password": "foobar4", "timeout": 60, "method": "aes-256-cfb" } shadowsocks/tests/test_latency.py0000644000175000017500000000031012375672543016630 0ustar shellshell#!/usr/bin/python import sys import time before = time.time() for line in sys.stdin: if 'HTTP/1.1 ' in line: diff = time.time() - before print 'headline %dms' % (diff * 1000) shadowsocks/tests/server-multi-passwd.json0000644000175000017500000000065212375672543020421 0ustar shellshell{ "server": "127.0.0.1", "server_port": 8384, "local_port": 1081, "password": "foobar4", "port_password": { "8381": "foobar1", "8382": "foobar2", "8383": "foobar3", "8384": "foobar4", "8385": "foobar5", "8386": "foobar6", "8387": "foobar7", "8388": "foobar8", "8389": "foobar9" }, "timeout": 60, "method": "aes-256-cfb" } shadowsocks/tests/salsa20.json0000644000175000017500000000033712375672543015731 0ustar shellshell{ "server":"127.0.0.1", "server_port":8388, "local_port":1081, "password":"salsa20_password", "timeout":60, "method":"salsa20-ctr", "local_address":"127.0.0.1", "fast_open":false } shadowsocks/tests/workers.json0000644000175000017500000000033212375672543016153 0ustar shellshell{ "server":"127.0.0.1", "server_port":8388, "local_port":1081, "password":"workers_password", "timeout":60, "method":"aes-256-cfb", "local_address":"127.0.0.1", "workers": 4 } shadowsocks/tests/aes.json0000644000175000017500000000033312375672543015230 0ustar shellshell{ "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 } shadowsocks/tests/socksify/0000755000175000017500000000000012375672543015420 5ustar shellshellshadowsocks/tests/socksify/socks.conf0000644000175000017500000000017712375672543017416 0ustar shellshellroute { from: 0.0.0.0/0 to: 0.0.0.0/0 via: 127.0.0.1 port = 1081 proxyprotocol: socks_v5 method: none }shadowsocks/tests/socksify/install.sh0000755000175000017500000000041512375672543017425 0ustar shellshell#!/bin/bash if [ ! -d dante-1.4.0 ]; then wget http://www.inet.no/dante/files/dante-1.4.0.tar.gz || exit 1 tar xf dante-1.4.0.tar.gz || exit 1 fi pushd dante-1.4.0 ./configure && make && make install || exit 1 popd cp tests/socksify/socks.conf /etc/ || exit 1 shadowsocks/tests/server-multi-passwd-table.json0000644000175000017500000000064412375672543021507 0ustar shellshell{ "server": "127.0.0.1", "server_port": 8384, "local_port": 1081, "password": "foobar4", "port_password": { "8381": "foobar1", "8382": "foobar2", "8383": "foobar3", "8384": "foobar4", "8385": "foobar5", "8386": "foobar6", "8387": "foobar7", "8388": "foobar8", "8389": "foobar9" }, "timeout": 60, "method": "table" } shadowsocks/tests/fastopen.json0000644000175000017500000000033712375672543016303 0ustar shellshell{ "server":"127.0.0.1", "server_port":8388, "local_port":1081, "password":"fastopen_password", "timeout":60, "method":"aes-256-cfb", "local_address":"127.0.0.1", "fast_open":true } shadowsocks/tests/test.py0000755000175000017500000000337112375672543015126 0ustar shellshell#!/usr/bin/python # -*- coding: utf-8 -*- import sys import os import signal import select import time from subprocess import Popen, PIPE sys.path.insert(0, './') if 'salsa20' in sys.argv[-1]: from shadowsocks import encrypt_salsa20 encrypt_salsa20.test() print 'encryption test passed' p1 = Popen(['python', 'shadowsocks/server.py', '-c', sys.argv[-1]], stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) p2 = Popen(['python', 'shadowsocks/local.py', '-c', sys.argv[-1]], stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) p3 = None try: local_ready = False server_ready = False fdset = [p1.stdout, p2.stdout, p1.stderr, p2.stderr] while True: r, w, e = select.select(fdset, [], fdset) if e: break for fd in r: line = fd.readline() sys.stdout.write(line) if line.find('starting local') >= 0: local_ready = True if line.find('starting server') >= 0: server_ready = True if local_ready and server_ready and p3 is None: time.sleep(1) break p3 = Popen(['curl', 'http://www.example.com/', '-v', '-L', '--socks5-hostname', '127.0.0.1:1081'], close_fds=True) if p3 is not None: r = p3.wait() if r != 0: sys.exit(r) else: sys.exit(1) p4 = Popen(['socksify', 'dig', '@8.8.8.8', 'www.google.com'], close_fds=True) if p4 is not None: r = p4.wait() if r != 0: sys.exit(r) else: sys.exit(1) print 'test passed' finally: for p in [p1, p2]: try: os.kill(p.pid, signal.SIGTERM) except OSError: pass