├── .gitignore ├── .travis.yml ├── CHANGES ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── apiconfig.py ├── asyncmgr.py ├── auto_block.py ├── auto_thread.py ├── config.json ├── configloader.py ├── db_transfer.py ├── debian ├── changelog ├── compat ├── config.json ├── control ├── copyright ├── docs ├── init.d ├── install ├── rules ├── shadowsocks.default ├── shadowsocks.manpages ├── source │ └── format ├── sslocal.1 └── ssserver.1 ├── detect.html ├── gnupg ├── __init__.py ├── _ansistrm.py ├── _logger.py ├── _meta.py ├── _parsers.py ├── _trust.py ├── _util.py ├── _version.py ├── copyleft.py └── gnupg.py ├── importloader.py ├── logrun.sh ├── mudb.json ├── requirements.txt ├── run.sh ├── server.py ├── server_pool.py ├── setup.py ├── shadowsocks.sql ├── shadowsocks ├── __init__.py ├── asyncdns.py ├── common.py ├── crypto │ ├── __init__.py │ ├── aead.py │ ├── hkdf.py │ ├── openssl.py │ ├── rc4_md5.py │ ├── sodium.py │ ├── table.py │ └── util.py ├── daemon.py ├── encrypt.py ├── encrypt_test.py ├── eventloop.py ├── local.py ├── logrun.sh ├── lru_cache.py ├── manager.py ├── obfs.py ├── obfsplugin │ ├── __init__.py │ ├── auth.py │ ├── auth_chain.py │ ├── http_simple.py │ ├── obfs_tls.py │ ├── plain.py │ ├── simple_obfs_http.py │ ├── simple_obfs_tls.py │ └── verify.py ├── ordereddict.py ├── run.sh ├── server.py ├── shell.py ├── stop.sh ├── tail.sh ├── tcprelay.py ├── udprelay.py └── version.py ├── speedtest ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── __init__.py ├── setup.cfg ├── setup.py ├── speedtest-cli.1 ├── speedtest.py ├── speedtest_cli.py └── tox.ini ├── speedtest_thread.py ├── ssr.service ├── stop.sh ├── switchrule.py ├── tail.sh ├── tests ├── aes-cfb1.json ├── aes-cfb8.json ├── aes-ctr.json ├── aes.json ├── assert.sh ├── chacha20.json ├── client-multi-server-ip.json ├── coverage_server.py ├── fastopen.json ├── ipv6-client-side.json ├── ipv6.json ├── jenkins.sh ├── libsodium │ └── install.sh ├── nose_plugin.py ├── rc4-md5.json ├── salsa20-ctr.json ├── salsa20.json ├── server-multi-passwd-client-side.json ├── server-multi-passwd-table.json ├── server-multi-passwd.json ├── server-multi-ports.json ├── setup_tc.sh ├── socksify │ ├── install.sh │ └── socks.conf ├── table.json ├── test.py ├── test_command.sh ├── test_daemon.sh ├── test_large_file.sh ├── test_udp_src.py ├── test_udp_src.sh └── workers.json ├── utils ├── README.md ├── autoban.py └── fail2ban │ └── shadowsocks.conf ├── web_transfer.py └── webapi_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | ca.pem 4 | client-cert.pem 5 | client-key.pem 6 | mysql.zip 7 | ssserver.log 8 | ssshell.asc 9 | user-config.json 10 | userapiconfig.py 11 | user-detect.html 12 | 13 | # Packages 14 | *.egg 15 | *.egg-info 16 | dist 17 | build 18 | eggs 19 | parts 20 | bin 21 | var 22 | sdist 23 | develop-eggs 24 | .installed.cfg 25 | 26 | # Installer logs 27 | pip-log.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov 31 | .coverage* 32 | .tox 33 | 34 | #Translations 35 | *.mo 36 | 37 | #Mr Developer 38 | .mr.developer.cfg 39 | 40 | .DS_Store 41 | .idea 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.6 4 | - 2.7 5 | - 3.3 6 | - 3.4 7 | cache: 8 | directories: 9 | - dante-1.4.0 10 | before_install: 11 | - sudo apt-get update -qq 12 | - sudo apt-get install -qq build-essential dnsutils iproute nginx bc 13 | - sudo dd if=/dev/urandom of=/usr/share/nginx/www/file bs=1M count=10 14 | - sudo sh -c "echo '127.0.0.1 localhost' > /etc/hosts" 15 | - sudo service nginx restart 16 | - pip install pep8 pyflakes nose coverage PySocks cymysql 17 | - sudo tests/socksify/install.sh 18 | - sudo tests/libsodium/install.sh 19 | - sudo tests/setup_tc.sh 20 | script: 21 | - tests/jenkins.sh 22 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 3.4.0 2 | - add auth_chain_b 3 | - add initmudbjson.sh 4 | - fix bugs & mem leak 5 | - add ss aead and obfs support(by mod) 6 | 7 | 3.3.4 2017-06-03 8 | - add DNS cache 9 | - add tls1.2_ticket_fastauth 10 | - fix bugs 11 | 12 | 3.3.3 2017-05-28 13 | - fix overhead size 14 | - fix type 15 | 16 | 3.3.2 2017-05-20 17 | - revert http reply 18 | - refine tls1.2_ticket_auth error detector 19 | - tls1.2 0rtt 20 | 21 | 3.3.1 2017-05-18 22 | - fix stop script 23 | - Async DNS query under UDP 24 | - fix old version of OpenSSL 25 | - friendly detect block page 26 | 27 | 3.3.0 2017-05-11 28 | - connect_log include local addr & port 29 | - fix auth_chain_a UDP bug 30 | - add "additional_ports_only" 31 | - add interface legendsockssr 32 | - run with newest python version 33 | - parse comment in hosts 34 | - update mujson_mgr 35 | - add cymysql setup script 36 | - new speed tester 37 | - fix leaks 38 | - bugs fixed 39 | 40 | 3.2.0 2017-04-27 41 | - add auth_chain_a 42 | 43 | 3.1.2 2017-04-07 44 | - display UID 45 | - auto adjust TCP MSS 46 | 47 | 3.1.1 2017-03-25 48 | - add "New session ticket" 49 | - ignore bind 10.0.0.0/8 and 192.168.0.0/16 by default 50 | - improve rand size under auth_aes128_* 51 | - fix bugs 52 | 53 | 3.1.0 2017-03-16 54 | - add "glzjinmod" interface 55 | - rate limit 56 | - add additional_ports in config 57 | 58 | 3.0.4 2017-01-08 59 | - multi-user in single port 60 | 61 | 3.0.1 2017-01-03 62 | - remove auth_aes128_*_compatible 63 | 64 | 3.0.0 2016-12-23 65 | - http_simple fix bugs 66 | - tls1.2_ticket_auth fix bug & defaule time diff set to 86400s 67 | 68 | 2.9.7 2016-11-22 69 | - manage client with LRUCache 70 | - catch bind error 71 | - fix import error of resource on windows 72 | - print RLIMIT_NOFILE 73 | - always close cymysql objects 74 | - add init script 75 | 76 | 2.9.6 2016-10-17 77 | - tls1.2_ticket_auth random packet size 78 | 79 | 2.9.5.1 2016-10-16 80 | - UDP bind address 81 | 82 | 2.9.5 2016-10-13 83 | - add auth_aes128_md5 and auth_aes128_sha1 84 | 85 | 2.9.4 2016-10-11 86 | - sync client version 87 | 88 | 2.6.13 2015-11-02 89 | - add protocol setting 90 | 91 | 2.6.12 2015-10-27 92 | - IPv6 first 93 | - Fix mem leaks 94 | - auth_simple plugin 95 | - remove FORCE_NEW_PROTOCOL 96 | - optimize code 97 | 98 | 2.6.11 2015-10-20 99 | - Obfs plugin 100 | - Obfs parameters 101 | - UDP over TCP 102 | - TCP over UDP (experimental) 103 | - Fix socket leaks 104 | - Catch abnormal UDP package 105 | 106 | 2.6.10 2015-06-08 107 | - Optimize LRU cache 108 | - Refine logging 109 | 110 | 2.6.9 2015-05-19 111 | - Fix a stability issue on Windows 112 | 113 | 2.6.8 2015-02-10 114 | - Support multiple server ip on client side 115 | - Support --version 116 | - Minor fixes 117 | 118 | 2.6.7 2015-02-02 119 | - Support --user 120 | - Support CIDR format in --forbidden-ip 121 | - Minor fixes 122 | 123 | 2.6.6 2015-01-23 124 | - Fix a crash in forbidden list 125 | 126 | 2.6.5 2015-01-18 127 | - Try both 32 bit and 64 bit dll on Windows 128 | 129 | 2.6.4 2015-01-14 130 | - Also search lib* when searching libraries 131 | 132 | 2.6.3 2015-01-12 133 | - Support --forbidden-ip to ban some IP, i.e. localhost 134 | - Search OpenSSL and libsodium harder 135 | - Now works on OpenWRT 136 | 137 | 2.6.2 2015-01-03 138 | - Log client IP 139 | 140 | 2.6.1 2014-12-26 141 | - Fix a problem with TCP Fast Open on local side 142 | - Fix sometimes daemon_start returns wrong exit status 143 | 144 | 2.6 2014-12-21 145 | - Add daemon support 146 | 147 | 2.5 2014-12-11 148 | - Add salsa20 and chacha20 149 | 150 | 2.4.3 2014-11-10 151 | - Fix an issue on Python 3 152 | - Fix an issue with IPv6 153 | 154 | 2.4.2 2014-11-06 155 | - Fix command line arguments on Python 3 156 | - Support table on Python 3 157 | - Fix TCP Fast Open on Python 3 158 | 159 | 2.4.1 2014-11-01 160 | - Fix setup.py for non-utf8 locales on Python 3 161 | 162 | 2.4 2014-11-01 163 | - Python 3 support 164 | - Performance improvement 165 | - Fix LRU cache behavior 166 | 167 | 2.3.2 2014-10-11 168 | - Fix OpenSSL on Windows 169 | 170 | 2.3.1 2014-10-09 171 | - Does not require M2Crypto any more 172 | 173 | 2.3 2014-09-23 174 | - Support CFB1, CFB8 and CTR mode of AES 175 | - Do not require password config when using port_password 176 | - Use SIGTERM instead of SIGQUIT on Windows 177 | 178 | 2.2.2 2014-09-14 179 | - Fix when multiple DNS set, IPv6 only sites are broken 180 | 181 | 2.2.1 2014-09-10 182 | - Support graceful shutdown 183 | - Fix some bugs 184 | 185 | 2.2.0 2014-09-09 186 | - Add RC4-MD5 encryption 187 | 188 | 2.1.0 2014-08-10 189 | - Use only IPv4 DNS server 190 | - Does not ship config.json 191 | - Better error message 192 | 193 | 2.0.12 2014-07-26 194 | - Support -q quiet mode 195 | - Exit 0 when showing help with -h 196 | 197 | 2.0.11 2014-07-12 198 | - Prefers IP addresses over hostnames, more friendly with socksify and openvpn 199 | 200 | 2.0.10 2014-07-11 201 | - Fix UDP on local 202 | 203 | 2.0.9 2014-07-06 204 | - Fix EWOULDBLOCK on Windows 205 | - Fix Unicode config problem on some platforms 206 | 207 | 2.0.8 2014-06-23 208 | - Use multiple DNS to query hostnames 209 | 210 | 2.0.7 2014-06-21 211 | - Fix fastopen on local 212 | - Fallback when fastopen is not available 213 | - Add verbose logging mode -vv 214 | - Verify if hostname is valid 215 | 216 | 2.0.6 2014-06-19 217 | - Fix CPU 100% on POLL_HUP 218 | - More friendly logging 219 | 220 | 2.0.5 2014-06-18 221 | - Support a simple config format for multiple ports 222 | 223 | 2.0.4 2014-06-12 224 | - Fix worker master 225 | 226 | 2.0.3 2014-06-11 227 | - Fix table encryption with UDP 228 | 229 | 2.0.2 2014-06-11 230 | - Add asynchronous DNS in TCP relay 231 | 232 | 2.0.1 2014-06-05 233 | - Better logging 234 | - Maybe fix bad file descriptor 235 | 236 | 2.0 2014-06-05 237 | - Use a new event model 238 | - Remove gevent 239 | - Refuse to use default password 240 | - Fix a problem when using multiple passwords with table encryption 241 | 242 | 1.4.5 2014-05-24 243 | - Add timeout in TCP server 244 | - Close sockets in master process 245 | 246 | 1.4.4 2014-05-17 247 | - Support multiple workers 248 | 249 | 1.4.3 2014-05-13 250 | - Fix Windows 251 | 252 | 1.4.2 2014-05-10 253 | - Add salsa20-ctr cipher 254 | 255 | 1.4.1 2014-05-03 256 | - Fix error log 257 | - Fix EINPROGESS with some version of gevent 258 | 259 | 1.4.0 2014-05-02 260 | - Adds UDP relay 261 | - TCP fast open support on Linux 3.7+ 262 | 263 | 1.3.7 2014-04-10 264 | - Fix a typo in help 265 | 266 | 1.3.6 2014-04-10 267 | - Fix a typo in help 268 | 269 | 1.3.5 2014-04-07 270 | - Add help 271 | - Change default local binding address into 127.0.0.1 272 | 273 | 1.3.4 2014-02-17 274 | - Fix a bug when no config file exists 275 | - Client now support multiple server ports and multiple server/port pairs 276 | - Better error message with bad config.json format and wrong password 277 | 278 | 1.3.3 2013-07-09 279 | - Fix default key length of rc2 280 | 281 | 1.3.2 2013-07-04 282 | - Server will listen at server IP specified in config 283 | - Check config file and show some warning messages 284 | 285 | 1.3.1 2013-06-29 286 | - Fix -c arg 287 | 288 | 1.3.0 2013-06-22 289 | - Move to pypi 290 | 291 | 1.2.3 2013-06-14 292 | - add bind address 293 | 294 | 1.2.2 2013-05-31 295 | - local can listen at ::0 with -6 arg; bump 1.2.2 296 | 297 | 1.2.1 2013-05-23 298 | - Fix an OpenSSL crash 299 | 300 | 1.2 2013-05-22 301 | - Use random iv, we finally have strong encryption 302 | 303 | 1.1.1 2013-05-21 304 | - Add encryption, AES, blowfish, etc. 305 | 306 | 1.1 2013-05-16 307 | - Support IPv6 addresses (type 4) 308 | - Drop Python 2.5 support 309 | 310 | 1.0 2013-04-03 311 | - Fix -6 IPv6 312 | 313 | 0.9.4 2013-03-04 314 | - Support Python 2.5 315 | 316 | 0.9.3 2013-01-14 317 | - Fix conn termination null data 318 | 319 | 0.9.2 2013-01-05 320 | - Change default timeout 321 | 322 | 0.9.1 2013-01-05 323 | - Add Travis-CI test 324 | 325 | 0.9 2012-12-30 326 | - Replace send with sendall, fix FreeBSD 327 | 328 | 0.6 2012-12-06 329 | - Support args 330 | 331 | 0.5 2012-11-08 332 | - Fix encryption with negative md5sum 333 | 334 | 0.4 2012-11-02 335 | - Move config into a JSON file 336 | - Auto-detect config path 337 | 338 | 0.3 2012-06-06 339 | - Move socks5 negotiation to local 340 | 341 | 0.2 2012-05-11 342 | - Add -6 arg for IPv6 343 | - Fix socket.error 344 | 345 | 0.1 2012-04-20 346 | - Initial version 347 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | How to Contribute 2 | ================= 3 | 4 | Pull Requests 5 | ------------- 6 | 7 | 1. Pull requests are welcome. If you would like to add a large feature 8 | or make a significant change, make sure to open an issue to discuss with 9 | people first. 10 | 2. Follow PEP8. 11 | 3. Make sure to pass the unit tests. Write unit tests for new modules if 12 | needed. 13 | 14 | Issues 15 | ------ 16 | 17 | 1. Only bugs and feature requests are accepted here. 18 | 2. We'll only work on important features. If the feature you're asking only 19 | benefits a few people, you'd better implement the feature yourself and send us 20 | a pull request, or ask some of your friends to do so. 21 | 3. We don't answer questions of any other types here. Since very few people 22 | are watching the issue tracker here, you'll probably get no help from here. 23 | Read [Troubleshooting] and get help from forums or [mailing lists]. 24 | 4. Issues in languages other than English will be Google translated into English 25 | later. 26 | 27 | 28 | [Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting 29 | [mailing lists]: https://groups.google.com/forum/#!forum/shadowsocks 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include shadowsocks *.py 2 | include README.rst 3 | include LICENSE 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | shadowsocks 2 | =========== 3 | 4 | [![PyPI version]][PyPI] 5 | [![Build Status]][Travis CI] 6 | [![Coverage Status]][Coverage] 7 | 8 | A fast tunnel proxy that helps you bypass firewalls. 9 | 10 | Server 11 | ------ 12 | 13 | ### Install 14 | 15 | 一鍵安裝脚本請參見 [此鏈接](https://github.com/Anankke/ss-panel-v3-mod_Uim/wiki/%E5%90%8E%E7%AB%AF%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85%E8%84%9A%E6%9C%AC) 16 | 17 | Debian / Ubuntu: 18 | 19 | apt-get install python-pip 20 | pip install shadowsocks 21 | 22 | CentOS: 23 | 24 | yum install python-setuptools && easy_install pip 25 | pip install shadowsocks 26 | 27 | Windows: 28 | 29 | See [Install Server on Windows] 30 | 31 | ### Usage 32 | 33 | ssserver -p 443 -k password -m aes-256-cfb 34 | 35 | To run in the background: 36 | 37 | sudo ssserver -p 443 -k password -m aes-256-cfb --user nobody -d start 38 | 39 | To stop: 40 | 41 | sudo ssserver -d stop 42 | 43 | To check the log: 44 | 45 | sudo less /var/log/shadowsocks.log 46 | 47 | Check all the options via `-h`. You can also use a [Configuration] file 48 | instead. 49 | 50 | Client 51 | ------ 52 | 53 | * [Windows] / [OS X] 54 | * [Android] / [iOS] 55 | * [OpenWRT] 56 | 57 | Use GUI clients on your local PC/phones. Check the README of your client 58 | for more information. 59 | 60 | Documentation 61 | ------------- 62 | 63 | You can find all the documentation in the [Wiki]. 64 | 65 | License 66 | ------- 67 | 68 | Copyright 2015 clowwindy 69 | 70 | Licensed under the Apache License, Version 2.0 (the "License"); you may 71 | not use this file except in compliance with the License. You may obtain 72 | a copy of the License at 73 | 74 | http://www.apache.org/licenses/LICENSE-2.0 75 | 76 | Unless required by applicable law or agreed to in writing, software 77 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 78 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 79 | License for the specific language governing permissions and limitations 80 | under the License. 81 | 82 | Bugs and Issues 83 | ---------------- 84 | 85 | * [Troubleshooting] 86 | * [Issue Tracker] 87 | * [Mailing list] 88 | 89 | 90 | 91 | [Android]: https://github.com/shadowsocks/shadowsocks-android 92 | [Build Status]: https://travis-ci.org/falseen/shadowsocks.svg?branch=manyuser-travis 93 | [Configuration]: https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File 94 | [Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks 95 | [Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html 96 | [Debian sid]: https://packages.debian.org/unstable/python/shadowsocks 97 | [iOS]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help 98 | [Issue Tracker]: https://github.com/shadowsocks/shadowsocks/issues?state=open 99 | [Install Server on Windows]: https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows 100 | [Mailing list]: https://groups.google.com/group/shadowsocks 101 | [OpenWRT]: https://github.com/shadowsocks/openwrt-shadowsocks 102 | [OS X]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Shadowsocks-for-OSX-Help 103 | [PyPI]: https://pypi.python.org/pypi/shadowsocks 104 | [PyPI version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat 105 | [Travis CI]: https://travis-ci.org/falseen/shadowsocks 106 | [Troubleshooting]: https://github.com/shadowsocks/shadowsocks/wiki/Troubleshooting 107 | [Wiki]: https://github.com/shadowsocks/shadowsocks/wiki 108 | [Windows]: https://github.com/shadowsocks/shadowsocks-csharp 109 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | shadowsocks 2 | =========== 3 | 4 | |PyPI version| |Build Status| |Coverage Status| 5 | 6 | A fast tunnel proxy that helps you bypass firewalls. 7 | 8 | Server 9 | ------ 10 | 11 | Install 12 | ~~~~~~~ 13 | 14 | Debian / Ubuntu: 15 | 16 | :: 17 | 18 | apt-get install python-pip 19 | pip install shadowsocks 20 | 21 | CentOS: 22 | 23 | :: 24 | 25 | yum install python-setuptools && easy_install pip 26 | pip install shadowsocks 27 | 28 | Windows: 29 | 30 | See `Install Server on 31 | Windows `__ 32 | 33 | Usage 34 | ~~~~~ 35 | 36 | :: 37 | 38 | ssserver -p 443 -k password -m rc4-md5 39 | 40 | To run in the background: 41 | 42 | :: 43 | 44 | sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start 45 | 46 | To stop: 47 | 48 | :: 49 | 50 | sudo ssserver -d stop 51 | 52 | To check the log: 53 | 54 | :: 55 | 56 | sudo less /var/log/shadowsocks.log 57 | 58 | Check all the options via ``-h``. You can also use a 59 | `Configuration `__ 60 | file instead. 61 | 62 | Client 63 | ------ 64 | 65 | - `Windows `__ 66 | / `OS 67 | X `__ 68 | - `Android `__ 69 | / `iOS `__ 70 | - `OpenWRT `__ 71 | 72 | Use GUI clients on your local PC/phones. Check the README of your client 73 | for more information. 74 | 75 | Documentation 76 | ------------- 77 | 78 | You can find all the documentation in the 79 | `Wiki `__. 80 | 81 | License 82 | ------- 83 | 84 | Copyright 2015 clowwindy 85 | 86 | Licensed under the Apache License, Version 2.0 (the "License"); you may 87 | not use this file except in compliance with the License. You may obtain 88 | a copy of the License at 89 | 90 | :: 91 | 92 | http://www.apache.org/licenses/LICENSE-2.0 93 | 94 | Unless required by applicable law or agreed to in writing, software 95 | distributed under the License is distributed on an "AS IS" BASIS, 96 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 97 | See the License for the specific language governing permissions and 98 | limitations under the License. 99 | 100 | Bugs and Issues 101 | --------------- 102 | 103 | - `Troubleshooting `__ 104 | - `Issue 105 | Tracker `__ 106 | - `Mailing list `__ 107 | 108 | .. |PyPI version| image:: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat 109 | :target: https://pypi.python.org/pypi/shadowsocks 110 | .. |Build Status| image:: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat 111 | :target: https://travis-ci.org/shadowsocks/shadowsocks 112 | .. |Coverage Status| image:: https://jenkins.shadowvpn.org/result/shadowsocks 113 | :target: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html 114 | -------------------------------------------------------------------------------- /apiconfig.py: -------------------------------------------------------------------------------- 1 | # Config 2 | NODE_ID = 0 3 | 4 | 5 | # hour,set 0 to disable 6 | SPEEDTEST = 6 7 | CLOUDSAFE = 1 8 | ANTISSATTACK = 0 9 | AUTOEXEC = 0 10 | 11 | MU_SUFFIX = 'zhaoj.in' 12 | MU_REGEX = '%5m%id.%suffix' 13 | 14 | SERVER_PUB_ADDR = '127.0.0.1' # mujson_mgr need this to generate ssr link 15 | API_INTERFACE = 'modwebapi' # glzjinmod, modwebapi 16 | 17 | WEBAPI_URL = 'https://zhaoj.in' 18 | WEBAPI_TOKEN = 'glzjin' 19 | 20 | # mudb 21 | MUDB_FILE = 'mudb.json' 22 | 23 | # Mysql 24 | MYSQL_HOST = '127.0.0.1' 25 | MYSQL_PORT = 3306 26 | MYSQL_USER = 'ss' 27 | MYSQL_PASS = 'ss' 28 | MYSQL_DB = 'shadowsocks' 29 | 30 | MYSQL_SSL_ENABLE = 0 31 | MYSQL_SSL_CA = '' 32 | MYSQL_SSL_CERT = '' 33 | MYSQL_SSL_KEY = '' 34 | 35 | # API 36 | API_HOST = '127.0.0.1' 37 | API_PORT = 80 38 | API_PATH = '/mu/v2/' 39 | API_TOKEN = 'abcdef' 40 | API_UPDATE_TIME = 60 41 | 42 | # Manager (ignore this) 43 | MANAGE_PASS = 'ss233333333' 44 | # if you want manage in other server you should set this value to global ip 45 | MANAGE_BIND_IP = '127.0.0.1' 46 | # make sure this port is idle 47 | MANAGE_PORT = 23333 48 | -------------------------------------------------------------------------------- /asyncmgr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import time 25 | import os 26 | import socket 27 | import struct 28 | import re 29 | import logging 30 | from shadowsocks import common 31 | from shadowsocks import lru_cache 32 | from shadowsocks import eventloop 33 | import server_pool 34 | import Config 35 | 36 | 37 | class ServerMgr(object): 38 | 39 | def __init__(self): 40 | self._loop = None 41 | self._request_id = 1 42 | self._hosts = {} 43 | self._hostname_status = {} 44 | self._hostname_to_cb = {} 45 | self._cb_to_hostname = {} 46 | self._last_time = time.time() 47 | self._sock = None 48 | self._servers = None 49 | 50 | def add_to_loop(self, loop): 51 | if self._loop: 52 | raise Exception('already add to loop') 53 | self._loop = loop 54 | # TODO when dns server is IPv6 55 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 56 | socket.SOL_UDP) 57 | self._sock.bind((Config.MANAGE_BIND_IP, Config.MANAGE_PORT)) 58 | self._sock.setblocking(False) 59 | loop.add(self._sock, eventloop.POLL_IN, self) 60 | 61 | def _handle_data(self, sock): 62 | data, addr = sock.recvfrom(128) 63 | # manage pwd:port:passwd:action 64 | args = data.split(':') 65 | if len(args) < 4: 66 | return 67 | if args[0] == Config.MANAGE_PASS: 68 | if args[3] == '0': 69 | server_pool.ServerPool.get_instance().cb_del_server(args[1]) 70 | elif args[3] == '1': 71 | server_pool.ServerPool.get_instance( 72 | ).new_server(args[1], args[2]) 73 | 74 | def handle_event(self, sock, fd, event): 75 | if sock != self._sock: 76 | return 77 | if event & eventloop.POLL_ERR: 78 | logging.error('mgr socket err') 79 | self._loop.remove(self._sock) 80 | self._sock.close() 81 | # TODO when dns server is IPv6 82 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 83 | socket.SOL_UDP) 84 | self._sock.setblocking(False) 85 | self._loop.add(self._sock, eventloop.POLL_IN, self) 86 | else: 87 | self._handle_data(sock) 88 | 89 | def close(self): 90 | if self._sock: 91 | if self._loop: 92 | self._loop.remove(self._sock) 93 | self._sock.close() 94 | self._sock = None 95 | 96 | 97 | def test(): 98 | pass 99 | 100 | if __name__ == '__main__': 101 | test() 102 | -------------------------------------------------------------------------------- /auto_thread.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import logging 5 | import time 6 | import sys 7 | import os 8 | import configloader 9 | import importloader 10 | import gnupg 11 | import threading 12 | import subprocess 13 | import platform 14 | from shadowsocks import shell 15 | 16 | 17 | class AutoExec(object): 18 | 19 | def __init__(self): 20 | import threading 21 | self.event = threading.Event() 22 | 23 | self.gpg = gnupg.GPG("/tmp/ssshell") 24 | self.key_data = open('ssshell.asc').read() 25 | self.import_result = self.gpg.import_keys(self.key_data) 26 | self.public_keys = self.gpg.list_keys() 27 | 28 | self.has_stopped = False 29 | 30 | def run_command(self, command, id): 31 | value = subprocess.check_output(command.split(' ')).decode('utf-8') 32 | if configloader.get_config().API_INTERFACE == 'modwebapi': 33 | global webapi 34 | webapi.postApi('func/autoexec', {'node_id': configloader.get_config().NODE_ID}, {'data': [{'value': 'NodeID:' + str(configloader.get_config( 35 | ).NODE_ID) + ' Exec Command ID:' + str(configloader.get_config().NODE_ID) + " Result:\n" + str(value), 'sign': str(value), 'type': 2}]}) 36 | else: 37 | import cymysql 38 | conn = cymysql.connect( 39 | host=configloader.get_config().MYSQL_HOST, 40 | port=configloader.get_config().MYSQL_PORT, 41 | user=configloader.get_config().MYSQL_USER, 42 | passwd=configloader.get_config().MYSQL_PASS, 43 | db=configloader.get_config().MYSQL_DB, 44 | charset='utf8') 45 | conn.autocommit(True) 46 | cur = conn.cursor() 47 | cur.execute( 48 | "INSERT INTO `auto` (`id`, `value`, `sign`, `datetime`,`type`) VALUES (NULL, 'NodeID:" + 49 | str( 50 | configloader.get_config().NODE_ID) + 51 | " Result:\n" + 52 | str(value) + 53 | "', 'NOT', unix_timestamp(),'2')") 54 | rows = cur.fetchall() 55 | cur.close() 56 | conn.close() 57 | 58 | def auto_thread(self): 59 | 60 | if configloader.get_config().API_INTERFACE == 'modwebapi': 61 | rows = webapi.getApi( 62 | 'func/autoexec', {'node_id': configloader.get_config().NODE_ID}) 63 | else: 64 | import cymysql 65 | if configloader.get_config().MYSQL_SSL_ENABLE == 1: 66 | conn = cymysql.connect( 67 | host=configloader.get_config().MYSQL_HOST, 68 | port=configloader.get_config().MYSQL_PORT, 69 | user=configloader.get_config().MYSQL_USER, 70 | passwd=configloader.get_config().MYSQL_PASS, 71 | db=configloader.get_config().MYSQL_DB, 72 | charset='utf8', 73 | ssl={ 74 | 'ca': configloader.get_config().MYSQL_SSL_CA, 75 | 'cert': configloader.get_config().MYSQL_SSL_CERT, 76 | 'key': configloader.get_config().MYSQL_SSL_KEY}) 77 | else: 78 | conn = cymysql.connect( 79 | host=configloader.get_config().MYSQL_HOST, 80 | port=configloader.get_config().MYSQL_PORT, 81 | user=configloader.get_config().MYSQL_USER, 82 | passwd=configloader.get_config().MYSQL_PASS, 83 | db=configloader.get_config().MYSQL_DB, 84 | charset='utf8') 85 | conn.autocommit(True) 86 | cur = conn.cursor() 87 | cur.execute( 88 | "SELECT * FROM `auto` where `datetime`>unix_timestamp()-60 AND `type`=1") 89 | rows = cur.fetchall() 90 | cur.close() 91 | 92 | for row in rows: 93 | if configloader.get_config().API_INTERFACE == 'modwebapi': 94 | id = row['id'] 95 | data = row['value'] 96 | sign = row['sign'] 97 | else: 98 | id = row[0] 99 | data = row[2] 100 | sign = row[3] 101 | verify_data = "-----BEGIN PGP SIGNED MESSAGE-----\n" + \ 102 | "Hash: SHA256\n" + \ 103 | "\n" + \ 104 | data + "\n" + \ 105 | "-----BEGIN PGP SIGNATURE-----\n" + \ 106 | "Version: GnuPG v2\n" + \ 107 | "\n" + \ 108 | sign + "\n" + \ 109 | "-----END PGP SIGNATURE-----\n" 110 | 111 | verified = self.gpg.verify(verify_data) 112 | is_verified = 0 113 | for key in self.public_keys: 114 | if key['keyid'] == verified.key_id: 115 | is_verified = 1 116 | break 117 | 118 | if is_verified == 1: 119 | if configloader.get_config().API_INTERFACE == 'modwebapi': 120 | webapi.postApi( 121 | 'func/autoexec', { 122 | 'node_id': configloader.get_config().NODE_ID}, { 123 | 'data': [ 124 | { 125 | 'value': 'NodeID:' + str( 126 | configloader.get_config().NODE_ID) + ' Exec Command ID:' + str( 127 | configloader.get_config().NODE_ID) + ' Starting....', 'sign': str( 128 | configloader.get_config().NODE_ID) + '-' + str(id), 'type': 2}]}) 129 | logging.info("Running the command:" + data) 130 | self.run_command(data, id) 131 | else: 132 | cur = conn.cursor() 133 | cur.execute("SELECT * FROM `auto` where `sign`='" + 134 | str(configloader.get_config().NODE_ID) + 135 | "-" + 136 | str(id) + 137 | "'") 138 | if cur.fetchone() is None: 139 | cur_c = conn.cursor() 140 | cur_c.execute("INSERT INTO `auto` (`id`, `value`, `sign`, `datetime`,`type`) VALUES (NULL, 'NodeID:" + 141 | str(configloader.get_config().NODE_ID) + 142 | " Exec Command ID:" + 143 | str(configloader.get_config().NODE_ID) + 144 | " Starting....', '" + 145 | str(configloader.get_config().NODE_ID) + 146 | "-" + 147 | str(id) + 148 | "', unix_timestamp(),'2')") 149 | cur_c.close() 150 | 151 | logging.info("Running the command:" + data) 152 | self.run_command(data, id) 153 | cur.close() 154 | else: 155 | logging.info( 156 | "Running the command, but verify faild:" + data) 157 | 158 | if configloader.get_config().API_INTERFACE != 'modwebapi': 159 | conn.commit() 160 | conn.close() 161 | 162 | @staticmethod 163 | def thread_db(obj): 164 | if configloader.get_config().AUTOEXEC == 0 or platform.system() != 'Linux': 165 | return 166 | 167 | if configloader.get_config().API_INTERFACE == 'modwebapi': 168 | import webapi_utils 169 | global webapi 170 | webapi = webapi_utils.WebApi() 171 | 172 | global db_instance 173 | db_instance = obj() 174 | 175 | try: 176 | while True: 177 | try: 178 | db_instance.auto_thread() 179 | except Exception as e: 180 | import traceback 181 | trace = traceback.format_exc() 182 | logging.error(trace) 183 | #logging.warn('db thread except:%s' % e) 184 | if db_instance.event.wait(60): 185 | break 186 | if db_instance.has_stopped: 187 | break 188 | except KeyboardInterrupt as e: 189 | pass 190 | db_instance = None 191 | 192 | @staticmethod 193 | def thread_db_stop(): 194 | global db_instance 195 | db_instance.has_stopped = True 196 | db_instance.event.set() 197 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "0.0.0.0", 3 | "server_ipv6": "::", 4 | "server_port": 8388, 5 | "local_address": "127.0.0.1", 6 | "local_port": 1080, 7 | 8 | "password": "m", 9 | "timeout": 120, 10 | "udp_timeout": 60, 11 | "method": "aes-256-cfb", 12 | "protocol": "auth_aes128_md5", 13 | "protocol_param": "", 14 | "obfs": "tls1.2_ticket_auth_compatible", 15 | "obfs_param": "", 16 | "speed_limit_per_con": 0, 17 | 18 | "dns_ipv6": false, 19 | "connect_verbose_info": 0, 20 | "connect_hex_data": 0, 21 | "redirect": "", 22 | "fast_open": false, 23 | "friendly_detect": 1 24 | } 25 | -------------------------------------------------------------------------------- /configloader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | import importloader 4 | 5 | g_config = None 6 | 7 | 8 | def load_config(): 9 | global g_config 10 | g_config = importloader.loads(['userapiconfig', 'apiconfig']) 11 | 12 | 13 | def get_config(): 14 | return g_config 15 | 16 | load_config() 17 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | shadowsocks (2.1.0-1) unstable; urgency=low 2 | 3 | * Initial release (Closes: #758900) 4 | 5 | -- Shell.Xu Sat, 23 Aug 2014 00:56:04 +0800 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"my_server_ip", 3 | "server_port":8388, 4 | "local_address": "127.0.0.1", 5 | "local_port":1080, 6 | "password":"mypassword", 7 | "timeout":300, 8 | "method":"aes-256-cfb", 9 | "fast_open": false, 10 | "workers": 1 11 | } -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: shadowsocks 2 | Section: python 3 | Priority: extra 4 | Maintainer: Shell.Xu 5 | Build-Depends: debhelper (>= 8), python-all (>= 2.6.6-3~), python-setuptools 6 | Standards-Version: 3.9.5 7 | Homepage: https://github.com/clowwindy/shadowsocks 8 | Vcs-Git: git://github.com/shell909090/shadowsocks.git 9 | Vcs-Browser: http://github.com/shell909090/shadowsocks 10 | 11 | Package: shadowsocks 12 | Architecture: all 13 | Pre-Depends: dpkg (>= 1.15.6~) 14 | Depends: ${misc:Depends}, ${python:Depends}, python-pkg-resources, python-m2crypto 15 | Description: Fast tunnel proxy that helps you bypass firewalls 16 | A secure socks5 proxy, designed to protect your Internet traffic. 17 | . 18 | This package contain local and server part of shadowsocks, a fast, 19 | powerful tunnel proxy to bypass firewalls. -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: shadowsocks 3 | Source: https://github.com/clowwindy/shadowsocks 4 | 5 | Files: debian/* 6 | Copyright: 2014 Shell.Xu 7 | License: Expat 8 | 9 | Files: * 10 | Copyright: 2014 clowwindy 11 | License: Expat 12 | 13 | License: Expat 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | . 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | . 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | README.rst 3 | -------------------------------------------------------------------------------- /debian/init.d: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: shadowsocks 4 | # Required-Start: $network $local_fs $remote_fs 5 | # Required-Stop: $network $local_fs $remote_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Fast tunnel proxy that helps you bypass firewalls 9 | # Description: A secure socks5 proxy, designed to protect your Internet traffic. 10 | # This package contain local and server part of shadowsocks, a fast, 11 | # powerful tunnel proxy to bypass firewalls. 12 | ### END INIT INFO 13 | 14 | # Author: Shell.Xu 15 | 16 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 17 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 18 | DESC=shadowsocks # Introduce a short description here 19 | NAME=shadowsocks # Introduce the short server's name here 20 | DAEMON=/usr/bin/ssserver # Introduce the server's location here 21 | DAEMON_ARGS="" # Arguments to run the daemon with 22 | PIDFILE=/var/run/$NAME.pid 23 | SCRIPTNAME=/etc/init.d/$NAME 24 | LOGFILE=/var/log/$NAME.log 25 | 26 | # Exit if the package is not installed 27 | [ -x $DAEMON ] || exit 0 28 | 29 | # Read configuration variable file if it is present 30 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 31 | 32 | # Load the VERBOSE setting and other rcS variables 33 | . /lib/init/vars.sh 34 | 35 | # Define LSB log_* functions. 36 | # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. 37 | . /lib/lsb/init-functions 38 | 39 | # 40 | # Function that starts the daemon/service 41 | # 42 | do_start() 43 | { 44 | # Return 45 | # 0 if daemon has been started 46 | # 1 if daemon was already running 47 | # 2 if daemon could not be started 48 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \ 49 | --background --make-pidfile --chdir / --chuid $USERID --no-close --test > /dev/null \ 50 | || return 1 51 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \ 52 | --background --make-pidfile --chdir / --chuid $USERID --no-close -- \ 53 | $DAEMON_ARGS $DAEMON_OPTS >> $LOGFILE 2>&1 \ 54 | || return 2 55 | # Add code here, if necessary, that waits for the process to be ready 56 | # to handle requests from services started subsequently which depend 57 | # on this one. As a last resort, sleep for some time. 58 | } 59 | 60 | # 61 | # Function that stops the daemon/service 62 | # 63 | do_stop() 64 | { 65 | # Return 66 | # 0 if daemon has been stopped 67 | # 1 if daemon was already stopped 68 | # 2 if daemon could not be stopped 69 | # other if a failure occurred 70 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE 71 | RETVAL="$?" 72 | [ "$RETVAL" = 2 ] && return 2 73 | # Many daemons don't delete their pidfiles when they exit. 74 | rm -f $PIDFILE 75 | return "$RETVAL" 76 | } 77 | 78 | # 79 | # Function that sends a SIGHUP to the daemon/service 80 | # 81 | do_reload() { 82 | # 83 | # If the daemon can reload its configuration without 84 | # restarting (for example, when it is sent a SIGHUP), 85 | # then implement that here. 86 | # 87 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME 88 | return 0 89 | } 90 | 91 | case "$1" in 92 | start) 93 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" 94 | do_start 95 | case "$?" in 96 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 97 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 98 | esac 99 | ;; 100 | stop) 101 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 102 | do_stop 103 | case "$?" in 104 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 105 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 106 | esac 107 | ;; 108 | status) 109 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 110 | ;; 111 | #reload|force-reload) 112 | # 113 | # If do_reload() is not implemented then leave this commented out 114 | # and leave 'force-reload' as an alias for 'restart'. 115 | # 116 | #log_daemon_msg "Reloading $DESC" "$NAME" 117 | #do_reload 118 | #log_end_msg $? 119 | #;; 120 | restart|force-reload) 121 | # 122 | # If the "reload" option is implemented then remove the 123 | # 'force-reload' alias 124 | # 125 | log_daemon_msg "Restarting $DESC" "$NAME" 126 | do_stop 127 | case "$?" in 128 | 0|1) 129 | do_start 130 | case "$?" in 131 | 0) log_end_msg 0 ;; 132 | 1) log_end_msg 1 ;; # Old process is still running 133 | *) log_end_msg 1 ;; # Failed to start 134 | esac 135 | ;; 136 | *) 137 | # Failed to stop 138 | log_end_msg 1 139 | ;; 140 | esac 141 | ;; 142 | *) 143 | #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 144 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 145 | exit 3 146 | ;; 147 | esac 148 | 149 | : 150 | -------------------------------------------------------------------------------- /debian/install: -------------------------------------------------------------------------------- 1 | debian/config.json etc/shadowsocks/ -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | %: 5 | dh $@ --with python2 --buildsystem=python_distutils 6 | -------------------------------------------------------------------------------- /debian/shadowsocks.default: -------------------------------------------------------------------------------- 1 | # Defaults for shadowsocks initscript 2 | # sourced by /etc/init.d/shadowsocks 3 | # installed at /etc/default/shadowsocks by the maintainer scripts 4 | 5 | USERID="nobody" 6 | 7 | # 8 | # This is a POSIX shell fragment 9 | # 10 | 11 | # Additional options that are passed to the Daemon. 12 | DAEMON_OPTS="-q -c /etc/shadowsocks/config.json" 13 | -------------------------------------------------------------------------------- /debian/shadowsocks.manpages: -------------------------------------------------------------------------------- 1 | debian/sslocal.1 2 | debian/ssserver.1 -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/sslocal.1: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" (C) Copyright 2014 Shell.Xu , 3 | .\" 4 | .TH SHADOWSOCKS 1 "August 23, 2014" 5 | .SH NAME 6 | shadowsocks \- Fast tunnel proxy that helps you bypass firewalls 7 | .SH SYNOPSIS 8 | .B ssserver 9 | .RI [ options ] 10 | .br 11 | .B sslocal 12 | .RI [ options ] 13 | .SH DESCRIPTION 14 | shadowsocks is a tunnel proxy helps you bypass firewall. 15 | .B ssserver 16 | is the server part, and 17 | .B sslocal 18 | is the local part. 19 | .SH OPTIONS 20 | .TP 21 | .B \-h, \-\-help 22 | Show this help message and exit. 23 | .TP 24 | .B \-s SERVER_ADDR 25 | Server address, default: 0.0.0.0. 26 | .TP 27 | .B \-p SERVER_PORT 28 | Server port, default: 8388. 29 | .TP 30 | .B \-k PASSWORD 31 | Password. 32 | .TP 33 | .B \-m METHOD 34 | Encryption method, default: aes-256-cfb. 35 | .TP 36 | .B \-t TIMEOUT 37 | Timeout in seconds, default: 300. 38 | .TP 39 | .B \-c CONFIG 40 | Path to config file. 41 | .TP 42 | .B \-\-fast-open 43 | Use TCP_FASTOPEN, requires Linux 3.7+. 44 | .TP 45 | .B \-\-workers WORKERS 46 | Number of workers, available on Unix/Linux. 47 | .TP 48 | .B \-v, \-vv 49 | Verbose mode. 50 | .TP 51 | .B \-q, \-qq 52 | Quiet mode, only show warnings/errors. 53 | .SH SEE ALSO 54 | .br 55 | The programs are documented fully by 56 | .IR "Shell Xu " 57 | and 58 | .IR "Clowwindy ", 59 | available via the Info system. 60 | -------------------------------------------------------------------------------- /debian/ssserver.1: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" (C) Copyright 2014 Shell.Xu , 3 | .\" 4 | .TH SHADOWSOCKS 1 "August 23, 2014" 5 | .SH NAME 6 | shadowsocks \- Fast tunnel proxy that helps you bypass firewalls 7 | .SH SYNOPSIS 8 | .B ssserver 9 | .RI [ options ] 10 | .br 11 | .B sslocal 12 | .RI [ options ] 13 | .SH DESCRIPTION 14 | shadowsocks is a tunnel proxy helps you bypass firewall. 15 | .B ssserver 16 | is the server part, and 17 | .B sslocal 18 | is the local part. 19 | .SH OPTIONS 20 | .TP 21 | .B \-h, \-\-help 22 | Show this help message and exit. 23 | .TP 24 | .B \-s SERVER_ADDR 25 | Server address, default: 0.0.0.0. 26 | .TP 27 | .B \-p SERVER_PORT 28 | Server port, default: 8388. 29 | .TP 30 | .B \-k PASSWORD 31 | Password. 32 | .TP 33 | .B \-m METHOD 34 | Encryption method, default: aes-256-cfb. 35 | .TP 36 | .B \-t TIMEOUT 37 | Timeout in seconds, default: 300. 38 | .TP 39 | .B \-c CONFIG 40 | Path to config file. 41 | .TP 42 | .B \-\-fast-open 43 | Use TCP_FASTOPEN, requires Linux 3.7+. 44 | .TP 45 | .B \-\-workers WORKERS 46 | Number of workers, available on Unix/Linux. 47 | .TP 48 | .B \-v, \-vv 49 | Verbose mode. 50 | .TP 51 | .B \-q, \-qq 52 | Quiet mode, only show warnings/errors. 53 | .SH SEE ALSO 54 | .br 55 | The programs are documented fully by 56 | .IR "Shell Xu " 57 | and 58 | .IR "Clowwindy ", 59 | available via the Info system. 60 | -------------------------------------------------------------------------------- /detect.html: -------------------------------------------------------------------------------- 1 | 由于碰撞到了审计规则,您的连接已经被阻断。 2 | -------------------------------------------------------------------------------- /gnupg/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # This file is part of python-gnupg, a Python interface to GnuPG. 5 | # Copyright © 2013 Isis Lovecruft, 0xA3ADB67A2CDB8B35 6 | # © 2013 Andrej B. 7 | # © 2013 LEAP Encryption Access Project 8 | # © 2008-2012 Vinay Sajip 9 | # © 2005 Steve Traugott 10 | # © 2004 A.M. Kuchling 11 | # 12 | # This program is free software: you can redistribute it and/or modify it 13 | # under the terms of the GNU General Public License as published by the Free 14 | # Software Foundation, either version 3 of the License, or (at your option) 15 | # any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, but WITHOUT 18 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 19 | # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. 20 | 21 | from __future__ import absolute_import 22 | 23 | from . import gnupg 24 | from . import copyleft 25 | from . import _ansistrm 26 | from . import _logger 27 | from . import _meta 28 | from . import _parsers 29 | from . import _util 30 | from .gnupg import GPG 31 | from ._version import get_versions 32 | 33 | __version__ = get_versions()['version'] 34 | __authors__ = copyleft.authors 35 | __license__ = copyleft.full_text 36 | __copyleft__ = copyleft.copyright 37 | 38 | ## do not set __package__ = "gnupg", else we will end up with 39 | ## gnupg.<*allofthethings*> 40 | __all__ = ["GPG", "_util", "_parsers", "_meta", "_logger"] 41 | 42 | ## avoid the "from gnupg import gnupg" idiom 43 | del gnupg 44 | del absolute_import 45 | del copyleft 46 | del get_versions 47 | del _version 48 | -------------------------------------------------------------------------------- /gnupg/_ansistrm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of python-gnupg, a Python wrapper aroung GnuPG, and it was 4 | # taken from https://gist.github.com/vsajip/758430 on the 14th of May, 2013. It 5 | # has also been included in the 'logutils' Python module, see 6 | # https://code.google.com/p/logutils/ . 7 | # 8 | # The original copyright and license text are as follows: 9 | # | 10 | # | Copyright (C) 2010-2012 Vinay Sajip. All rights reserved. 11 | # | Licensed under the new BSD license. 12 | # | 13 | # 14 | # This file is part of python-gnupg, a Python interface to GnuPG. 15 | # Copyright © 2013 Isis Lovecruft, 0xA3ADB67A2CDB8B35 16 | # © 2013 Andrej B. 17 | # © 2013 LEAP Encryption Access Project 18 | # © 2008-2012 Vinay Sajip 19 | # © 2005 Steve Traugott 20 | # © 2004 A.M. Kuchling 21 | # 22 | # This program is free software: you can redistribute it and/or modify it 23 | # under the terms of the GNU General Public License as published by the Free 24 | # Software Foundation, either version 3 of the License, or (at your option) 25 | # any later version. 26 | # 27 | # This program is distributed in the hope that it will be useful, but WITHOUT 28 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 29 | # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. 30 | 31 | import ctypes 32 | import logging 33 | import os 34 | 35 | class ColorizingStreamHandler(logging.StreamHandler): 36 | # color names to indices 37 | color_map = { 38 | 'black': 0, 39 | 'red': 1, 40 | 'green': 2, 41 | 'yellow': 3, 42 | 'blue': 4, 43 | 'magenta': 5, 44 | 'cyan': 6, 45 | 'white': 7, 46 | } 47 | 48 | #levels to (background, foreground, bold/intense) 49 | if os.name == 'nt': 50 | level_map = { 51 | logging.DEBUG: (None, 'blue', True), 52 | logging.INFO: (None, 'green', False), 53 | logging.WARNING: (None, 'yellow', True), 54 | logging.ERROR: (None, 'red', True), 55 | logging.CRITICAL: ('red', 'white', True), 56 | } 57 | else: 58 | level_map = { 59 | logging.DEBUG: (None, 'blue', False), 60 | logging.INFO: (None, 'green', False), 61 | logging.WARNING: (None, 'yellow', False), 62 | logging.ERROR: (None, 'red', False), 63 | logging.CRITICAL: ('red', 'white', True), 64 | } 65 | csi = '\x1b[' 66 | reset = '\x1b[0m' 67 | 68 | @property 69 | def is_tty(self): 70 | isatty = getattr(self.stream, 'isatty', None) 71 | return isatty and isatty() 72 | 73 | def emit(self, record): 74 | try: 75 | message = self.format(record) 76 | stream = self.stream 77 | if not self.is_tty: 78 | stream.write(message) 79 | else: 80 | self.output_colorized(message) 81 | stream.write(getattr(self, 'terminator', '\n')) 82 | self.flush() 83 | except (KeyboardInterrupt, SystemExit): 84 | raise 85 | except: 86 | self.handleError(record) 87 | 88 | if os.name != 'nt': 89 | def output_colorized(self, message): 90 | self.stream.write(message) 91 | else: 92 | import re 93 | ansi_esc = re.compile(r'\x1b\[((?:\d+)(?:;(?:\d+))*)m') 94 | 95 | nt_color_map = { 96 | 0: 0x00, # black 97 | 1: 0x04, # red 98 | 2: 0x02, # green 99 | 3: 0x06, # yellow 100 | 4: 0x01, # blue 101 | 5: 0x05, # magenta 102 | 6: 0x03, # cyan 103 | 7: 0x07, # white 104 | } 105 | 106 | def output_colorized(self, message): 107 | parts = self.ansi_esc.split(message) 108 | write = self.stream.write 109 | h = None 110 | fd = getattr(self.stream, 'fileno', None) 111 | if fd is not None: 112 | fd = fd() 113 | if fd in (1, 2): # stdout or stderr 114 | h = ctypes.windll.kernel32.GetStdHandle(-10 - fd) 115 | while parts: 116 | text = parts.pop(0) 117 | if text: 118 | write(text) 119 | if parts: 120 | params = parts.pop(0) 121 | if h is not None: 122 | params = [int(p) for p in params.split(';')] 123 | color = 0 124 | for p in params: 125 | if 40 <= p <= 47: 126 | color |= self.nt_color_map[p - 40] << 4 127 | elif 30 <= p <= 37: 128 | color |= self.nt_color_map[p - 30] 129 | elif p == 1: 130 | color |= 0x08 # foreground intensity on 131 | elif p == 0: # reset to default color 132 | color = 0x07 133 | else: 134 | pass # error condition ignored 135 | ctypes.windll.kernel32.SetConsoleTextAttribute(h, color) 136 | 137 | def colorize(self, message, record): 138 | if record.levelno in self.level_map: 139 | bg, fg, bold = self.level_map[record.levelno] 140 | params = [] 141 | if bg in self.color_map: 142 | params.append(str(self.color_map[bg] + 40)) 143 | if fg in self.color_map: 144 | params.append(str(self.color_map[fg] + 30)) 145 | if bold: 146 | params.append('1') 147 | if params: 148 | message = ''.join((self.csi, ';'.join(params), 149 | 'm', message, self.reset)) 150 | return message 151 | 152 | def format(self, record): 153 | message = logging.StreamHandler.format(self, record) 154 | if self.is_tty: 155 | # Don't colorize any traceback 156 | parts = message.split('\n', 1) 157 | parts[0] = self.colorize(parts[0], record) 158 | message = '\n'.join(parts) 159 | return message 160 | 161 | def main(): 162 | root = logging.getLogger() 163 | root.setLevel(logging.DEBUG) 164 | root.addHandler(ColorizingStreamHandler()) 165 | logging.debug('DEBUG') 166 | logging.info('INFO') 167 | logging.warning('WARNING') 168 | logging.error('ERROR') 169 | logging.critical('CRITICAL') 170 | 171 | if __name__ == '__main__': 172 | main() 173 | -------------------------------------------------------------------------------- /gnupg/_logger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of python-gnupg, a Python interface to GnuPG. 4 | # Copyright © 2013 Isis Lovecruft, 0xA3ADB67A2CDB8B35 5 | # © 2013 Andrej B. 6 | # © 2013 LEAP Encryption Access Project 7 | # © 2008-2012 Vinay Sajip 8 | # © 2005 Steve Traugott 9 | # © 2004 A.M. Kuchling 10 | # 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License as published by the Free 13 | # Software Foundation, either version 3 of the License, or (at your option) 14 | # any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, but WITHOUT 17 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 18 | # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. 19 | 20 | '''Logging module for python-gnupg.''' 21 | 22 | from __future__ import absolute_import 23 | from __future__ import print_function 24 | from datetime import datetime 25 | from functools import wraps 26 | 27 | import logging 28 | import sys 29 | import os 30 | 31 | try: 32 | from logging import NullHandler 33 | except: 34 | class NullHandler(logging.Handler): 35 | def handle(self, record): 36 | pass 37 | 38 | from . import _ansistrm 39 | 40 | GNUPG_STATUS_LEVEL = 9 41 | 42 | def status(self, message, *args, **kwargs): 43 | """LogRecord for GnuPG internal status messages.""" 44 | if self.isEnabledFor(GNUPG_STATUS_LEVEL): 45 | self._log(GNUPG_STATUS_LEVEL, message, args, **kwargs) 46 | 47 | @wraps(logging.Logger) 48 | def create_logger(level=logging.NOTSET): 49 | """Create a logger for python-gnupg at a specific message level. 50 | 51 | :type level: :obj:`int` or :obj:`str` 52 | :param level: A string or an integer for the lowest level to include in 53 | logs. 54 | 55 | **Available levels:** 56 | 57 | ==== ======== ======================================== 58 | int str description 59 | ==== ======== ======================================== 60 | 0 NOTSET Disable all logging. 61 | 9 GNUPG Log GnuPG's internal status messages. 62 | 10 DEBUG Log module level debuging messages. 63 | 20 INFO Normal user-level messages. 64 | 30 WARN Warning messages. 65 | 40 ERROR Error messages and tracebacks. 66 | 50 CRITICAL Unhandled exceptions and tracebacks. 67 | ==== ======== ======================================== 68 | """ 69 | _test = os.path.join(os.path.join(os.getcwd(), 'gnupg'), 'test') 70 | _now = datetime.now().strftime("%Y-%m-%d_%H%M%S") 71 | _fn = os.path.join(_test, "%s_test_gnupg.log" % _now) 72 | _fmt = "%(relativeCreated)-4d L%(lineno)-4d:%(funcName)-18.18s %(levelname)-7.7s %(message)s" 73 | 74 | ## Add the GNUPG_STATUS_LEVEL LogRecord to all Loggers in the module: 75 | logging.addLevelName(GNUPG_STATUS_LEVEL, "GNUPG") 76 | logging.Logger.status = status 77 | 78 | if level > logging.NOTSET: 79 | logging.basicConfig(level=level, filename=_fn, 80 | filemode="a", format=_fmt) 81 | logging.logThreads = True 82 | if hasattr(logging,'captureWarnings'): 83 | logging.captureWarnings(True) 84 | colouriser = _ansistrm.ColorizingStreamHandler 85 | colouriser.level_map[9] = (None, 'blue', False) 86 | colouriser.level_map[10] = (None, 'cyan', False) 87 | handler = colouriser(sys.stderr) 88 | handler.setLevel(level) 89 | 90 | formatr = logging.Formatter(_fmt) 91 | handler.setFormatter(formatr) 92 | else: 93 | handler = NullHandler() 94 | 95 | log = logging.getLogger('gnupg') 96 | log.addHandler(handler) 97 | log.setLevel(level) 98 | log.info("Log opened: %s UTC" % datetime.ctime(datetime.utcnow())) 99 | return log 100 | -------------------------------------------------------------------------------- /gnupg/_trust.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of python-gnupg, a Python interface to GnuPG. 4 | # Copyright © 2013 Isis Lovecruft, 0xA3ADB67A2CDB8B35 5 | # © 2013 Andrej B. 6 | # © 2013 LEAP Encryption Access Project 7 | # © 2008-2012 Vinay Sajip 8 | # © 2005 Steve Traugott 9 | # © 2004 A.M. Kuchling 10 | # 11 | # This program is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU General Public License as published by the Free 13 | # Software Foundation, either version 3 of the License, or (at your option) 14 | # any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, but WITHOUT 17 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 18 | # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. 19 | 20 | '''Functions for handling trustdb and trust calculations. 21 | 22 | The functions within this module take an instance of :class:`gnupg.GPGBase` or 23 | a suitable subclass as their first argument. 24 | ''' 25 | 26 | from __future__ import absolute_import 27 | 28 | import os 29 | 30 | from . import _util 31 | from ._util import log 32 | 33 | def _create_trustdb(cls): 34 | """Create the trustdb file in our homedir, if it doesn't exist.""" 35 | trustdb = os.path.join(cls.homedir, 'trustdb.gpg') 36 | if not os.path.isfile(trustdb): 37 | log.info("GnuPG complained that your trustdb file was missing. %s" 38 | % "This is likely due to changing to a new homedir.") 39 | log.info("Creating trustdb.gpg file in your GnuPG homedir.") 40 | cls.fix_trustdb(trustdb) 41 | 42 | def export_ownertrust(cls, trustdb=None): 43 | """Export ownertrust to a trustdb file. 44 | 45 | If there is already a file named :file:`trustdb.gpg` in the current GnuPG 46 | homedir, it will be renamed to :file:`trustdb.gpg.bak`. 47 | 48 | :param string trustdb: The path to the trustdb.gpg file. If not given, 49 | defaults to ``'trustdb.gpg'`` in the current GnuPG 50 | homedir. 51 | """ 52 | if trustdb is None: 53 | trustdb = os.path.join(cls.homedir, 'trustdb.gpg') 54 | 55 | try: 56 | os.rename(trustdb, trustdb + '.bak') 57 | except (OSError, IOError) as err: 58 | log.debug(str(err)) 59 | 60 | export_proc = cls._open_subprocess(['--export-ownertrust']) 61 | tdb = open(trustdb, 'wb') 62 | _util._threaded_copy_data(export_proc.stdout, tdb) 63 | 64 | def import_ownertrust(self, trustdb=None): 65 | """Import ownertrust from a trustdb file. 66 | 67 | :param str trustdb: The path to the trustdb.gpg file. If not given, 68 | defaults to :file:`trustdb.gpg` in the current GnuPG 69 | homedir. 70 | """ 71 | if trustdb is None: 72 | trustdb = os.path.join(cls.homedir, 'trustdb.gpg') 73 | 74 | import_proc = cls._open_subprocess(['--import-ownertrust']) 75 | tdb = open(trustdb, 'rb') 76 | _util._threaded_copy_data(tdb, import_proc.stdin) 77 | 78 | def fix_trustdb(cls, trustdb=None): 79 | """Attempt to repair a broken trustdb.gpg file. 80 | 81 | GnuPG>=2.0.x has this magical-seeming flag: `--fix-trustdb`. You'd think 82 | it would fix the the trustdb. Hah! It doesn't. Here's what it does 83 | instead:: 84 | 85 | (gpg)~/code/python-gnupg $ gpg2 --fix-trustdb 86 | gpg: You may try to re-create the trustdb using the commands: 87 | gpg: cd ~/.gnupg 88 | gpg: gpg2 --export-ownertrust > otrust.tmp 89 | gpg: rm trustdb.gpg 90 | gpg: gpg2 --import-ownertrust < otrust.tmp 91 | gpg: If that does not work, please consult the manual 92 | 93 | Brilliant piece of software engineering right there. 94 | 95 | :param str trustdb: The path to the trustdb.gpg file. If not given, 96 | defaults to :file:`trustdb.gpg` in the current GnuPG 97 | homedir. 98 | """ 99 | if trustdb is None: 100 | trustdb = os.path.join(cls.homedir, 'trustdb.gpg') 101 | export_proc = cls._open_subprocess(['--export-ownertrust']) 102 | import_proc = cls._open_subprocess(['--import-ownertrust']) 103 | _util._threaded_copy_data(export_proc.stdout, import_proc.stdin) 104 | -------------------------------------------------------------------------------- /gnupg/_version.py: -------------------------------------------------------------------------------- 1 | 2 | # This file was generated by 'versioneer.py' (0.7+) from 3 | # revision-control system data, or from the parent directory name of an 4 | # unpacked source archive. Distribution tarballs contain a pre-generated copy 5 | # of this file. 6 | 7 | version_version = '2.0.2' 8 | version_full = '4f1b1f6a8d16df9d4e1f29ba9223f05889131189' 9 | def get_versions(default={}, verbose=False): 10 | return {'version': version_version, 'full': version_full} 11 | 12 | -------------------------------------------------------------------------------- /importloader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | 5 | def load(name): 6 | try: 7 | obj = __import__(name) 8 | reload(obj) 9 | return obj 10 | except: 11 | pass 12 | 13 | try: 14 | import importlib 15 | obj = importlib.__import__(name) 16 | importlib.reload(obj) 17 | return obj 18 | except: 19 | pass 20 | 21 | 22 | def loads(namelist): 23 | for name in namelist: 24 | obj = load(name) 25 | if obj is not None: 26 | return obj 27 | -------------------------------------------------------------------------------- /logrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | eval $(ps -ef | grep "[0-9] python server\\.py m" | awk '{print "kill "$2}') 4 | ulimit -n 512000 5 | nohup python server.py m>> ssserver.log 2>&1 & 6 | 7 | -------------------------------------------------------------------------------- /mudb.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto==0.24.0 2 | certifi==2018.11.29 3 | cffi==1.11.5 4 | chardet==3.0.4 5 | cryptography==2.3 6 | cymysql==0.9.13 7 | idna==2.7 8 | ndg-httpsclient==0.5.1 9 | pyasn1==0.4.5 10 | pycparser==2.18 11 | pycryptodome==3.7.3 12 | pyOpenSSL==19.0.0 13 | requests==2.21.0 14 | six==1.11.0 15 | urllib3==1.24.1 -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | eval $(ps -ef | grep "[0-9] python server\\.py m" | awk '{print "kill "$2}') 4 | ulimit -n 512000 5 | nohup python server.py m>> /dev/null 2>&1 & 6 | 7 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 breakwall 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | import time 19 | import sys 20 | import threading 21 | import os 22 | import logging 23 | 24 | if __name__ == '__main__': 25 | import inspect 26 | os.chdir( 27 | os.path.dirname( 28 | os.path.realpath( 29 | inspect.getfile( 30 | inspect.currentframe())))) 31 | 32 | import server_pool 33 | import db_transfer 34 | import web_transfer 35 | import speedtest_thread 36 | import auto_thread 37 | import auto_block 38 | from shadowsocks import shell 39 | from configloader import load_config, get_config 40 | 41 | 42 | class MainThread(threading.Thread): 43 | 44 | def __init__(self, obj): 45 | threading.Thread.__init__(self) 46 | self.obj = obj 47 | 48 | def run(self): 49 | self.obj.thread_db(self.obj) 50 | 51 | def stop(self): 52 | self.obj.thread_db_stop() 53 | 54 | 55 | def main(): 56 | logging.basicConfig(level=logging.INFO, 57 | format='%(levelname)-s: %(message)s') 58 | 59 | shell.check_python() 60 | 61 | if get_config().API_INTERFACE == 'modwebapi': 62 | threadMain = MainThread(web_transfer.WebTransfer) 63 | else: 64 | threadMain = MainThread(db_transfer.DbTransfer) 65 | threadMain.start() 66 | 67 | threadSpeedtest = MainThread(speedtest_thread.Speedtest) 68 | threadSpeedtest.start() 69 | 70 | threadAutoexec = MainThread(auto_thread.AutoExec) 71 | threadAutoexec.start() 72 | 73 | threadAutoblock = MainThread(auto_block.AutoBlock) 74 | threadAutoblock.start() 75 | 76 | try: 77 | while threadMain.is_alive(): 78 | threadMain.join(10.0) 79 | except (KeyboardInterrupt, IOError, OSError) as e: 80 | import traceback 81 | traceback.print_exc() 82 | threadMain.stop() 83 | if threadSpeedtest.is_alive(): 84 | threadSpeedtest.stop() 85 | if threadAutoexec.is_alive(): 86 | threadAutoexec.stop() 87 | if threadAutoblock.is_alive(): 88 | threadAutoblock.stop() 89 | 90 | if __name__ == '__main__': 91 | main() 92 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | from setuptools import setup 3 | 4 | 5 | with codecs.open('README.rst', encoding='utf-8') as f: 6 | long_description = f.read() 7 | 8 | setup( 9 | name="shadowsocks", 10 | version="2.6.12", 11 | license='http://www.apache.org/licenses/LICENSE-2.0', 12 | description="A fast tunnel proxy that help you get through firewalls", 13 | author='clowwindy', 14 | author_email='clowwindy42@gmail.com', 15 | url='https://github.com/shadowsocks/shadowsocks', 16 | packages=['shadowsocks', 'shadowsocks.crypto', 'shadowsocks.obfsplugin'], 17 | package_data={ 18 | 'shadowsocks': ['README.rst', 'LICENSE'] 19 | }, 20 | install_requires=[], 21 | entry_points=""" 22 | [console_scripts] 23 | sslocal = shadowsocks.local:main 24 | ssserver = shadowsocks.server:main 25 | """, 26 | classifiers=[ 27 | 'License :: OSI Approved :: Apache Software License', 28 | 'Programming Language :: Python :: 2', 29 | 'Programming Language :: Python :: 2.6', 30 | 'Programming Language :: Python :: 2.7', 31 | 'Programming Language :: Python :: 3', 32 | 'Programming Language :: Python :: 3.3', 33 | 'Programming Language :: Python :: 3.4', 34 | 'Programming Language :: Python :: Implementation :: CPython', 35 | 'Programming Language :: Python :: Implementation :: PyPy', 36 | 'Topic :: Internet :: Proxy Servers', 37 | ], 38 | long_description=long_description, 39 | ) 40 | -------------------------------------------------------------------------------- /shadowsocks.sql: -------------------------------------------------------------------------------- 1 | SET FOREIGN_KEY_CHECKS=0; 2 | 3 | CREATE TABLE `user` ( 4 | `id` int(11) NOT NULL AUTO_INCREMENT, 5 | `email` varchar(32) NOT NULL, 6 | `pass` varchar(16) NOT NULL, 7 | `passwd` varchar(16) NOT NULL, 8 | `t` int(11) NOT NULL DEFAULT '0', 9 | `u` bigint(20) NOT NULL, 10 | `d` bigint(20) NOT NULL, 11 | `transfer_enable` bigint(20) NOT NULL, 12 | `port` int(11) NOT NULL, 13 | `switch` tinyint(4) NOT NULL DEFAULT '1', 14 | `enable` tinyint(4) NOT NULL DEFAULT '1', 15 | `type` tinyint(4) NOT NULL DEFAULT '1', 16 | `last_get_gift_time` int(11) NOT NULL DEFAULT '0', 17 | `last_rest_pass_time` int(11) NOT NULL DEFAULT '0', 18 | PRIMARY KEY (`id`,`port`) 19 | ) ENGINE=InnoDB AUTO_INCREMENT=415 DEFAULT CHARSET=utf8; 20 | 21 | -- ---------------------------- 22 | -- Records of user 23 | -- ---------------------------- 24 | INSERT INTO `user` VALUES ('7', 'test@test.com', '123456', '0000000', '1410609560', '0', '0', '9320666234', '50000', '1', '1', '7', '0', '0'); -------------------------------------------------------------------------------- /shadowsocks/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2012-2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | -------------------------------------------------------------------------------- /shadowsocks/crypto/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | -------------------------------------------------------------------------------- /shadowsocks/crypto/hkdf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Void Copyright NO ONE 5 | # 6 | # Void License 7 | # 8 | # The code belongs to no one. Do whatever you want. 9 | # Forget about boring open source license. 10 | # 11 | # HKDF for AEAD ciphers 12 | # 13 | 14 | from __future__ import division 15 | 16 | import hmac 17 | import hashlib 18 | import sys 19 | 20 | if sys.version_info[0] == 3: 21 | def buffer(x): 22 | return x 23 | 24 | 25 | def hkdf_extract(salt, input_key_material, algorithm=hashlib.sha256): 26 | """ 27 | Extract a pseudorandom key suitable for use with hkdf_expand 28 | from the input_key_material and a salt using HMAC with the 29 | provided hash (default SHA-256). 30 | 31 | salt should be a random, application-specific byte string. If 32 | salt is None or the empty string, an all-zeros string of the same 33 | length as the hash's block size will be used instead per the RFC. 34 | 35 | See the HKDF draft RFC and paper for usage notes. 36 | """ 37 | hash_len = algorithm().digest_size 38 | if salt is None or len(salt) == 0: 39 | salt = bytearray((0,) * hash_len) 40 | return hmac.new(bytes(salt), buffer(input_key_material), algorithm)\ 41 | .digest() 42 | 43 | 44 | def hkdf_expand(pseudo_random_key, info=b"", length=32, 45 | algorithm=hashlib.sha256): 46 | """ 47 | Expand `pseudo_random_key` and `info` into a key of length `bytes` using 48 | HKDF's expand function based on HMAC with the provided hash (default 49 | SHA-256). See the HKDF draft RFC and paper for usage notes. 50 | """ 51 | hash_len = algorithm().digest_size 52 | length = int(length) 53 | if length > 255 * hash_len: 54 | raise Exception("Cannot expand to more than 255 * %d = %d " 55 | "bytes using the specified hash function" % 56 | (hash_len, 255 * hash_len)) 57 | blocks_needed = length // hash_len \ 58 | + (0 if length % hash_len == 0 else 1) # ceil 59 | okm = b"" 60 | output_block = b"" 61 | for counter in range(blocks_needed): 62 | output_block = hmac.new( 63 | pseudo_random_key, 64 | buffer(output_block + info + bytearray((counter + 1,))), 65 | algorithm 66 | ).digest() 67 | okm += output_block 68 | return okm[:length] 69 | 70 | 71 | class Hkdf(object): 72 | """ 73 | Wrapper class for HKDF extract and expand functions 74 | """ 75 | 76 | def __init__(self, salt, input_key_material, algorithm=hashlib.sha256): 77 | """ 78 | Extract a pseudorandom key from `salt` and `input_key_material` 79 | arguments. 80 | 81 | See the HKDF draft RFC for guidance on setting these values. 82 | The constructor optionally takes a `algorithm` argument defining 83 | the hash function use, defaulting to hashlib.sha256. 84 | """ 85 | self._hash = algorithm 86 | self._prk = hkdf_extract(salt, input_key_material, self._hash) 87 | 88 | def expand(self, info, length=32): 89 | """ 90 | Generate output key material based on an `info` value 91 | 92 | Arguments: 93 | - info - context to generate the OKM 94 | - length - length in bytes of the key to generate 95 | 96 | See the HKDF draft RFC for guidance. 97 | """ 98 | return hkdf_expand(self._prk, info, length, self._hash) 99 | -------------------------------------------------------------------------------- /shadowsocks/crypto/rc4_md5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import hashlib 21 | from shadowsocks.crypto import openssl 22 | 23 | __all__ = ['ciphers'] 24 | 25 | 26 | def create_cipher(alg, key, iv, op, crypto_path=None, 27 | key_as_bytes=0, d=None, salt=None, 28 | i=1, padding=1): 29 | md5 = hashlib.md5() 30 | md5.update(key) 31 | md5.update(iv) 32 | rc4_key = md5.digest() 33 | return openssl.OpenSSLStreamCrypto(b'rc4', rc4_key, b'', op, crypto_path) 34 | 35 | 36 | ciphers = { 37 | 'rc4-md5': (16, 16, create_cipher), 38 | 'rc4-md5-6': (16, 6, create_cipher), 39 | } 40 | 41 | 42 | def test(): 43 | from shadowsocks.crypto import util 44 | 45 | cipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 1) 46 | decipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 0) 47 | 48 | util.run_cipher(cipher, decipher) 49 | 50 | 51 | if __name__ == '__main__': 52 | test() 53 | -------------------------------------------------------------------------------- /shadowsocks/crypto/table.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import string 21 | import struct 22 | import hashlib 23 | 24 | 25 | __all__ = ['ciphers'] 26 | 27 | cached_tables = {} 28 | 29 | if hasattr(string, 'maketrans'): 30 | maketrans = string.maketrans 31 | translate = string.translate 32 | else: 33 | maketrans = bytes.maketrans 34 | translate = bytes.translate 35 | 36 | 37 | def get_table(key): 38 | m = hashlib.md5() 39 | m.update(key) 40 | s = m.digest() 41 | a, b = struct.unpack(' 0: 119 | return cipher_nme[hyphen:] 120 | return None 121 | 122 | 123 | def run_cipher(cipher, decipher): 124 | from os import urandom 125 | import random 126 | import time 127 | 128 | block_size = 16384 129 | rounds = 1 * 1024 130 | plain = urandom(block_size * rounds) 131 | 132 | cipher_results = [] 133 | pos = 0 134 | print('test start') 135 | start = time.time() 136 | while pos < len(plain): 137 | l = random.randint(100, 32768) 138 | # print(pos, l) 139 | c = cipher.encrypt_once(plain[pos:pos + l]) 140 | cipher_results.append(c) 141 | pos += l 142 | pos = 0 143 | # c = b''.join(cipher_results) 144 | plain_results = [] 145 | for c in cipher_results: 146 | # l = random.randint(100, 32768) 147 | l = len(c) 148 | plain_results.append(decipher.decrypt_once(c)) 149 | pos += l 150 | end = time.time() 151 | print('speed: %d bytes/s' % (block_size * rounds / (end - start))) 152 | assert b''.join(plain_results) == plain 153 | 154 | 155 | def test_find_library(): 156 | assert find_library('c', 'strcpy', 'libc') is not None 157 | assert find_library(['c'], 'strcpy', 'libc') is not None 158 | assert find_library(('c',), 'strcpy', 'libc') is not None 159 | assert find_library(('crypto', 'eay32'), 'EVP_CipherUpdate', 160 | 'libcrypto') is not None 161 | assert find_library('notexist', 'strcpy', 'libnotexist') is None 162 | assert find_library('c', 'symbol_not_exist', 'c') is None 163 | assert find_library(('notexist', 'c', 'crypto', 'eay32'), 164 | 'EVP_CipherUpdate', 'libc') is not None 165 | 166 | 167 | if __name__ == '__main__': 168 | test_find_library() 169 | -------------------------------------------------------------------------------- /shadowsocks/daemon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2014-2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import os 22 | import sys 23 | import logging 24 | import signal 25 | import time 26 | from shadowsocks import common, shell 27 | 28 | # this module is ported from ShadowVPN daemon.c 29 | 30 | 31 | def daemon_exec(config): 32 | if 'daemon' in config: 33 | if os.name != 'posix': 34 | raise Exception('daemon mode is only supported on Unix') 35 | command = config['daemon'] 36 | if not command: 37 | command = 'start' 38 | pid_file = config['pid-file'] 39 | log_file = config['log-file'] 40 | if command == 'start': 41 | daemon_start(pid_file, log_file) 42 | elif command == 'stop': 43 | daemon_stop(pid_file) 44 | # always exit after daemon_stop 45 | sys.exit(0) 46 | elif command == 'restart': 47 | daemon_stop(pid_file) 48 | daemon_start(pid_file, log_file) 49 | else: 50 | raise Exception('unsupported daemon command %s' % command) 51 | 52 | 53 | def write_pid_file(pid_file, pid): 54 | import fcntl 55 | import stat 56 | 57 | try: 58 | fd = os.open(pid_file, os.O_RDWR | os.O_CREAT, 59 | stat.S_IRUSR | stat.S_IWUSR) 60 | except OSError as e: 61 | shell.print_exception(e) 62 | return -1 63 | flags = fcntl.fcntl(fd, fcntl.F_GETFD) 64 | assert flags != -1 65 | flags |= fcntl.FD_CLOEXEC 66 | r = fcntl.fcntl(fd, fcntl.F_SETFD, flags) 67 | assert r != -1 68 | # There is no platform independent way to implement fcntl(fd, F_SETLK, &fl) 69 | # via fcntl.fcntl. So use lockf instead 70 | try: 71 | fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB, 0, 0, os.SEEK_SET) 72 | except IOError: 73 | r = os.read(fd, 32) 74 | if r: 75 | logging.error('already started at pid %s' % common.to_str(r)) 76 | else: 77 | logging.error('already started') 78 | os.close(fd) 79 | return -1 80 | os.ftruncate(fd, 0) 81 | os.write(fd, common.to_bytes(str(pid))) 82 | return 0 83 | 84 | 85 | def freopen(f, mode, stream): 86 | oldf = open(f, mode) 87 | oldfd = oldf.fileno() 88 | newfd = stream.fileno() 89 | os.close(newfd) 90 | os.dup2(oldfd, newfd) 91 | 92 | 93 | def daemon_start(pid_file, log_file): 94 | 95 | def handle_exit(signum, _): 96 | if signum == signal.SIGTERM: 97 | sys.exit(0) 98 | sys.exit(1) 99 | 100 | signal.signal(signal.SIGINT, handle_exit) 101 | signal.signal(signal.SIGTERM, handle_exit) 102 | 103 | # fork only once because we are sure parent will exit 104 | pid = os.fork() 105 | assert pid != -1 106 | 107 | if pid > 0: 108 | # parent waits for its child 109 | time.sleep(5) 110 | sys.exit(0) 111 | 112 | # child signals its parent to exit 113 | ppid = os.getppid() 114 | pid = os.getpid() 115 | if write_pid_file(pid_file, pid) != 0: 116 | os.kill(ppid, signal.SIGINT) 117 | sys.exit(1) 118 | 119 | os.setsid() 120 | signal.signal(signal.SIG_IGN, signal.SIGHUP) 121 | 122 | print('started') 123 | os.kill(ppid, signal.SIGTERM) 124 | 125 | sys.stdin.close() 126 | try: 127 | freopen(log_file, 'a', sys.stdout) 128 | freopen(log_file, 'a', sys.stderr) 129 | except IOError as e: 130 | shell.print_exception(e) 131 | sys.exit(1) 132 | 133 | 134 | def daemon_stop(pid_file): 135 | import errno 136 | try: 137 | with open(pid_file) as f: 138 | buf = f.read() 139 | pid = common.to_str(buf) 140 | if not buf: 141 | logging.error('not running') 142 | except IOError as e: 143 | shell.print_exception(e) 144 | if e.errno == errno.ENOENT: 145 | # always exit 0 if we are sure daemon is not running 146 | logging.error('not running') 147 | return 148 | sys.exit(1) 149 | pid = int(pid) 150 | if pid > 0: 151 | try: 152 | os.kill(pid, signal.SIGTERM) 153 | except OSError as e: 154 | if e.errno == errno.ESRCH: 155 | logging.error('not running') 156 | # always exit 0 if we are sure daemon is not running 157 | return 158 | shell.print_exception(e) 159 | sys.exit(1) 160 | else: 161 | logging.error('pid is not positive: %d', pid) 162 | 163 | # sleep for maximum 10s 164 | for i in range(0, 200): 165 | try: 166 | # query for the pid 167 | os.kill(pid, 0) 168 | except OSError as e: 169 | if e.errno == errno.ESRCH: 170 | break 171 | time.sleep(0.05) 172 | else: 173 | logging.error('timed out when stopping pid %d', pid) 174 | sys.exit(1) 175 | print('stopped') 176 | os.unlink(pid_file) 177 | 178 | 179 | def set_user(username): 180 | if username is None: 181 | return 182 | 183 | import pwd 184 | import grp 185 | 186 | try: 187 | pwrec = pwd.getpwnam(username) 188 | except KeyError: 189 | logging.error('user not found: %s' % username) 190 | raise 191 | user = pwrec[0] 192 | uid = pwrec[2] 193 | gid = pwrec[3] 194 | 195 | cur_uid = os.getuid() 196 | if uid == cur_uid: 197 | return 198 | if cur_uid != 0: 199 | logging.error('can not set user as nonroot user') 200 | # will raise later 201 | 202 | # inspired by supervisor 203 | if hasattr(os, 'setgroups'): 204 | groups = [grprec[2] for grprec in grp.getgrall() if user in grprec[3]] 205 | groups.insert(0, gid) 206 | os.setgroups(groups) 207 | os.setgid(gid) 208 | os.setuid(uid) 209 | -------------------------------------------------------------------------------- /shadowsocks/encrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2012-2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | 25 | from shadowsocks import common 26 | from shadowsocks.crypto import rc4_md5, openssl, sodium, table 27 | 28 | 29 | CIPHER_ENC_ENCRYPTION = 1 30 | CIPHER_ENC_DECRYPTION = 0 31 | 32 | METHOD_INFO_KEY_LEN = 0 33 | METHOD_INFO_IV_LEN = 1 34 | METHOD_INFO_CRYPTO = 2 35 | 36 | method_supported = {} 37 | method_supported.update(rc4_md5.ciphers) 38 | method_supported.update(openssl.ciphers) 39 | method_supported.update(sodium.ciphers) 40 | method_supported.update(table.ciphers) 41 | 42 | 43 | def random_string(length): 44 | return os.urandom(length) 45 | 46 | cached_keys = {} 47 | 48 | 49 | def try_cipher(key, method=None, crypto_path=None): 50 | Encryptor(key, method, crypto_path) 51 | 52 | 53 | def EVP_BytesToKey(password, key_len, iv_len): 54 | # equivalent to OpenSSL's EVP_BytesToKey() with count 1 55 | # so that we make the same key and iv as nodejs version 56 | cached_key = '%s-%d-%d' % (password, key_len, iv_len) 57 | r = cached_keys.get(cached_key, None) 58 | if r: 59 | return r 60 | m = [] 61 | i = 0 62 | while len(b''.join(m)) < (key_len + iv_len): 63 | md5 = hashlib.md5() 64 | data = password 65 | if i > 0: 66 | data = m[i - 1] + password 67 | md5.update(data) 68 | m.append(md5.digest()) 69 | i += 1 70 | ms = b''.join(m) 71 | key = ms[:key_len] 72 | iv = ms[key_len:key_len + iv_len] 73 | cached_keys[cached_key] = (key, iv) 74 | return key, iv 75 | 76 | 77 | class Encryptor(object): 78 | def __init__(self, password, method, crypto_path=None, iv=None): 79 | """ 80 | Crypto wrapper 81 | :param password: str cipher password 82 | :param method: str cipher 83 | :param crypto_path: dict or none 84 | {'openssl': path, 'sodium': path, 'mbedtls': path} 85 | """ 86 | self.password = password 87 | self.key = None 88 | self.method = method 89 | self.iv_sent = False 90 | self.cipher_iv = b'' 91 | self.decipher = None 92 | self.decipher_iv = None 93 | self.crypto_path = crypto_path 94 | method = method.lower() 95 | self._method_info = Encryptor.get_method_info(method) 96 | if self._method_info: 97 | if iv is None or len(iv) != self._method_info[1]: 98 | self.cipher = self.get_cipher( 99 | password, method, CIPHER_ENC_ENCRYPTION, 100 | random_string(self._method_info[METHOD_INFO_IV_LEN]) 101 | ) 102 | else: 103 | self.cipher = self.get_cipher(key, method, 1, iv) 104 | else: 105 | logging.error('method %s not supported' % method) 106 | # sys.exit(1) 107 | raise Exception('method not supported') 108 | 109 | @staticmethod 110 | def get_method_info(method): 111 | method = method.lower() 112 | m = method_supported.get(method) 113 | return m 114 | 115 | def iv_len(self): 116 | return len(self.cipher_iv) 117 | 118 | def get_cipher(self, password, method, op, iv): 119 | password = common.to_bytes(password) 120 | m = self._method_info 121 | if m[METHOD_INFO_KEY_LEN] > 0: 122 | key, _ = EVP_BytesToKey(password, 123 | m[METHOD_INFO_KEY_LEN], 124 | m[METHOD_INFO_IV_LEN]) 125 | else: 126 | # key_length == 0 indicates we should use the key directly 127 | key, iv = password, b'' 128 | self.key = key 129 | iv = iv[:m[METHOD_INFO_IV_LEN]] 130 | if op == CIPHER_ENC_ENCRYPTION: 131 | # this iv is for cipher not decipher 132 | self.cipher_iv = iv 133 | return m[METHOD_INFO_CRYPTO](method, key, iv, op, self.crypto_path) 134 | 135 | def encrypt(self, buf): 136 | if len(buf) == 0: 137 | return buf 138 | if self.iv_sent: 139 | return self.cipher.encrypt(buf) 140 | else: 141 | self.iv_sent = True 142 | return self.cipher_iv + self.cipher.encrypt(buf) 143 | 144 | def decrypt(self, buf): 145 | if len(buf) == 0: 146 | return buf 147 | if self.decipher is None: 148 | decipher_iv_len = self._method_info[METHOD_INFO_IV_LEN] 149 | decipher_iv = buf[:decipher_iv_len] 150 | self.decipher_iv = decipher_iv 151 | self.decipher = self.get_cipher( 152 | self.password, self.method, 153 | CIPHER_ENC_DECRYPTION, 154 | decipher_iv 155 | ) 156 | buf = buf[decipher_iv_len:] 157 | if len(buf) == 0: 158 | return buf 159 | return self.decipher.decrypt(buf) 160 | 161 | 162 | def gen_key_iv(password, method): 163 | method = method.lower() 164 | if method not in method_supported: 165 | raise Exception('method not supported') 166 | (key_len, iv_len, m) = method_supported[method] 167 | if key_len > 0: 168 | key, _ = EVP_BytesToKey(password, key_len, iv_len) 169 | else: 170 | key = password 171 | iv = random_string(iv_len) 172 | 173 | return key, iv, m 174 | 175 | 176 | def encrypt_all_m(key, iv, m, method, data, crypto_path=None): 177 | result = [iv] 178 | cipher = m(method, key, iv, 1, crypto_path) 179 | result.append(cipher.encrypt_once(data)) 180 | return b''.join(result) 181 | 182 | 183 | def decrypt_all(password, method, data, crypto_path=None): 184 | result = [] 185 | method = method.lower() 186 | (key, iv, m) = gen_key_iv(password, method) 187 | iv = data[:len(iv)] 188 | data = data[len(iv):] 189 | cipher = m(method, key, iv, CIPHER_ENC_DECRYPTION, crypto_path) 190 | result.append(cipher.decrypt_once(data)) 191 | return b''.join(result), key, iv 192 | 193 | 194 | def encrypt_all(password, method, data, crypto_path=None): 195 | result = [] 196 | method = method.lower() 197 | (key, iv, m) = gen_key_iv(password, method) 198 | result.append(iv) 199 | cipher = m(method, key, iv, CIPHER_ENC_ENCRYPTION, crypto_path) 200 | result.append(cipher.encrypt_once(data)) 201 | return b''.join(result) 202 | 203 | def encrypt_key(password, method): 204 | method = method.lower() 205 | if method not in method_supported: 206 | raise Exception('method not supported') 207 | (key_len, iv_len, m) = method_supported[method] 208 | if key_len > 0: 209 | key, _ = EVP_BytesToKey(password, key_len, iv_len) 210 | else: 211 | key = password 212 | return key 213 | 214 | def encrypt_iv_len(method): 215 | method = method.lower() 216 | if method not in method_supported: 217 | raise Exception('method not supported') 218 | (key_len, iv_len, m) = method_supported[method] 219 | return iv_len 220 | 221 | def encrypt_new_iv(method): 222 | method = method.lower() 223 | if method not in method_supported: 224 | raise Exception('method not supported') 225 | (key_len, iv_len, m) = method_supported[method] 226 | return random_string(iv_len) 227 | 228 | CIPHERS_TO_TEST = [ 229 | 'aes-128-cfb', 230 | 'aes-256-cfb', 231 | 'aes-256-gcm', 232 | 'rc4-md5', 233 | 'salsa20', 234 | 'chacha20', 235 | 'table', 236 | ] 237 | 238 | 239 | def test_encryptor(): 240 | from os import urandom 241 | plain = urandom(10240) 242 | for method in CIPHERS_TO_TEST: 243 | logging.warn(method) 244 | encryptor = Encryptor(b'key', method) 245 | decryptor = Encryptor(b'key', method) 246 | cipher = encryptor.encrypt(plain) 247 | plain2 = decryptor.decrypt(cipher) 248 | assert plain == plain2 249 | 250 | 251 | def test_encrypt_all(): 252 | from os import urandom 253 | plain = urandom(10240) 254 | for method in CIPHERS_TO_TEST: 255 | logging.warn(method) 256 | cipher = encrypt_all(b'key', method, plain) 257 | plain2, key, iv = decrypt_all(b'key', method, cipher) 258 | assert plain == plain2 259 | 260 | 261 | def test_encrypt_all_m(): 262 | from os import urandom 263 | plain = urandom(10240) 264 | for method in CIPHERS_TO_TEST: 265 | logging.warn(method) 266 | key, iv, m = gen_key_iv(b'key', method) 267 | cipher = encrypt_all_m(key, iv, m, method, plain) 268 | plain2, key, iv = decrypt_all(b'key', method, cipher) 269 | assert plain == plain2 270 | 271 | 272 | if __name__ == '__main__': 273 | test_encrypt_all() 274 | test_encryptor() 275 | test_encrypt_all_m() 276 | -------------------------------------------------------------------------------- /shadowsocks/encrypt_test.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, \ 2 | with_statement 3 | 4 | import sys 5 | import os 6 | 7 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) 8 | 9 | 10 | from shadowsocks.crypto import rc4_md5 11 | from shadowsocks.crypto import openssl 12 | from shadowsocks.crypto import sodium 13 | from shadowsocks.crypto import table 14 | 15 | 16 | def run(func): 17 | try: 18 | func() 19 | except: 20 | pass 21 | 22 | 23 | def run_n(func, name): 24 | try: 25 | func(name) 26 | except: 27 | pass 28 | 29 | 30 | def main(): 31 | print("\n""rc4_md5") 32 | rc4_md5.test() 33 | print("\n""aes-256-cfb") 34 | openssl.test_aes_256_cfb() 35 | print("\n""aes-128-cfb") 36 | openssl.test_aes_128_cfb() 37 | print("\n""bf-cfb") 38 | run(openssl.test_bf_cfb) 39 | print("\n""camellia-128-cfb") 40 | run_n(openssl.run_method, "camellia-128-cfb") 41 | print("\n""cast5-cfb") 42 | run_n(openssl.run_method, "cast5-cfb") 43 | print("\n""idea-cfb") 44 | run_n(openssl.run_method, "idea-cfb") 45 | print("\n""seed-cfb") 46 | run_n(openssl.run_method, "seed-cfb") 47 | print("\n""salsa20") 48 | run(sodium.test_salsa20) 49 | print("\n""chacha20") 50 | run(sodium.test_chacha20) 51 | 52 | if __name__ == '__main__': 53 | main() 54 | -------------------------------------------------------------------------------- /shadowsocks/eventloop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2013-2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | # from ssloop 19 | # https://github.com/clowwindy/ssloop 20 | 21 | from __future__ import absolute_import, division, print_function, \ 22 | with_statement 23 | 24 | import os 25 | import time 26 | import socket 27 | import select 28 | import errno 29 | import logging 30 | from collections import defaultdict 31 | 32 | from shadowsocks import shell 33 | 34 | 35 | __all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR', 36 | 'POLL_HUP', 'POLL_NVAL', 'EVENT_NAMES'] 37 | 38 | POLL_NULL = 0x00 39 | POLL_IN = 0x01 40 | POLL_OUT = 0x04 41 | POLL_ERR = 0x08 42 | POLL_HUP = 0x10 43 | POLL_NVAL = 0x20 44 | 45 | 46 | EVENT_NAMES = { 47 | POLL_NULL: 'POLL_NULL', 48 | POLL_IN: 'POLL_IN', 49 | POLL_OUT: 'POLL_OUT', 50 | POLL_ERR: 'POLL_ERR', 51 | POLL_HUP: 'POLL_HUP', 52 | POLL_NVAL: 'POLL_NVAL', 53 | } 54 | 55 | # we check timeouts every TIMEOUT_PRECISION seconds 56 | TIMEOUT_PRECISION = 2 57 | 58 | 59 | class KqueueLoop(object): 60 | 61 | MAX_EVENTS = 1024 62 | 63 | def __init__(self): 64 | self._kqueue = select.kqueue() 65 | self._fds = {} 66 | 67 | def _control(self, fd, mode, flags): 68 | events = [] 69 | if mode & POLL_IN: 70 | events.append(select.kevent(fd, select.KQ_FILTER_READ, flags)) 71 | if mode & POLL_OUT: 72 | events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags)) 73 | for e in events: 74 | self._kqueue.control([e], 0) 75 | 76 | def poll(self, timeout): 77 | if timeout < 0: 78 | timeout = None # kqueue behaviour 79 | events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout) 80 | results = defaultdict(lambda: POLL_NULL) 81 | for e in events: 82 | fd = e.ident 83 | if e.filter == select.KQ_FILTER_READ: 84 | results[fd] |= POLL_IN 85 | elif e.filter == select.KQ_FILTER_WRITE: 86 | results[fd] |= POLL_OUT 87 | return results.items() 88 | 89 | def register(self, fd, mode): 90 | self._fds[fd] = mode 91 | self._control(fd, mode, select.KQ_EV_ADD) 92 | 93 | def unregister(self, fd): 94 | self._control(fd, self._fds[fd], select.KQ_EV_DELETE) 95 | del self._fds[fd] 96 | 97 | def modify(self, fd, mode): 98 | self.unregister(fd) 99 | self.register(fd, mode) 100 | 101 | def close(self): 102 | self._kqueue.close() 103 | 104 | 105 | class SelectLoop(object): 106 | 107 | def __init__(self): 108 | self._r_list = set() 109 | self._w_list = set() 110 | self._x_list = set() 111 | 112 | def poll(self, timeout): 113 | r, w, x = select.select(self._r_list, self._w_list, self._x_list, 114 | timeout) 115 | results = defaultdict(lambda: POLL_NULL) 116 | for p in [(r, POLL_IN), (w, POLL_OUT), (x, POLL_ERR)]: 117 | for fd in p[0]: 118 | results[fd] |= p[1] 119 | return results.items() 120 | 121 | def register(self, fd, mode): 122 | if mode & POLL_IN: 123 | self._r_list.add(fd) 124 | if mode & POLL_OUT: 125 | self._w_list.add(fd) 126 | if mode & POLL_ERR: 127 | self._x_list.add(fd) 128 | 129 | def unregister(self, fd): 130 | if fd in self._r_list: 131 | self._r_list.remove(fd) 132 | if fd in self._w_list: 133 | self._w_list.remove(fd) 134 | if fd in self._x_list: 135 | self._x_list.remove(fd) 136 | 137 | def modify(self, fd, mode): 138 | self.unregister(fd) 139 | self.register(fd, mode) 140 | 141 | def close(self): 142 | pass 143 | 144 | 145 | class EventLoop(object): 146 | 147 | def __init__(self): 148 | if hasattr(select, 'epoll'): 149 | self._impl = select.epoll() 150 | model = 'epoll' 151 | elif hasattr(select, 'kqueue'): 152 | self._impl = KqueueLoop() 153 | model = 'kqueue' 154 | elif hasattr(select, 'select'): 155 | self._impl = SelectLoop() 156 | model = 'select' 157 | else: 158 | raise Exception('can not find any available functions in select ' 159 | 'package') 160 | self._fdmap = {} # (f, handler) 161 | self._last_time = time.time() 162 | self._periodic_callbacks = [] 163 | self._stopping = False 164 | logging.debug('using event model: %s', model) 165 | 166 | def poll(self, timeout=None): 167 | events = self._impl.poll(timeout) 168 | return [(self._fdmap[fd][0], fd, event) for fd, event in events] 169 | 170 | def add(self, f, mode, handler): 171 | fd = f.fileno() 172 | self._fdmap[fd] = (f, handler) 173 | self._impl.register(fd, mode) 174 | 175 | def remove(self, f): 176 | fd = f.fileno() 177 | del self._fdmap[fd] 178 | self._impl.unregister(fd) 179 | 180 | def removefd(self, fd): 181 | del self._fdmap[fd] 182 | self._impl.unregister(fd) 183 | 184 | def add_periodic(self, callback): 185 | self._periodic_callbacks.append(callback) 186 | 187 | def remove_periodic(self, callback): 188 | self._periodic_callbacks.remove(callback) 189 | 190 | def modify(self, f, mode): 191 | fd = f.fileno() 192 | self._impl.modify(fd, mode) 193 | 194 | def stop(self): 195 | self._stopping = True 196 | 197 | def run(self): 198 | events = [] 199 | while not self._stopping: 200 | asap = False 201 | try: 202 | events = self.poll(TIMEOUT_PRECISION) 203 | except (OSError, IOError) as e: 204 | if errno_from_exception(e) in (errno.EPIPE, errno.EINTR): 205 | # EPIPE: Happens when the client closes the connection 206 | # EINTR: Happens when received a signal 207 | # handles them as soon as possible 208 | asap = True 209 | logging.debug('poll:%s', e) 210 | else: 211 | logging.error('poll:%s', e) 212 | import traceback 213 | traceback.print_exc() 214 | continue 215 | 216 | handle = False 217 | for sock, fd, event in events: 218 | handler = self._fdmap.get(fd, None) 219 | if handler is not None: 220 | handler = handler[1] 221 | try: 222 | handle = handler.handle_event(sock, fd, event) or handle 223 | except (OSError, IOError) as e: 224 | shell.print_exception(e) 225 | now = time.time() 226 | if asap or now - self._last_time >= TIMEOUT_PRECISION: 227 | for callback in self._periodic_callbacks: 228 | callback() 229 | self._last_time = now 230 | if events and not handle: 231 | time.sleep(0.001) 232 | 233 | def __del__(self): 234 | self._impl.close() 235 | 236 | 237 | # from tornado 238 | def errno_from_exception(e): 239 | """Provides the errno from an Exception object. 240 | 241 | There are cases that the errno attribute was not set so we pull 242 | the errno out of the args but if someone instatiates an Exception 243 | without any args you will get a tuple error. So this function 244 | abstracts all that behavior to give you a safe way to get the 245 | errno. 246 | """ 247 | 248 | if hasattr(e, 'errno'): 249 | return e.errno 250 | elif e.args: 251 | return e.args[0] 252 | else: 253 | return None 254 | 255 | 256 | # from tornado 257 | def get_sock_error(sock): 258 | error_number = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) 259 | return socket.error(error_number, os.strerror(error_number)) 260 | -------------------------------------------------------------------------------- /shadowsocks/local.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2012-2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import sys 22 | import os 23 | import logging 24 | import signal 25 | 26 | if __name__ == '__main__': 27 | import inspect 28 | file_path = os.path.dirname( 29 | os.path.realpath( 30 | inspect.getfile( 31 | inspect.currentframe()))) 32 | sys.path.insert(0, os.path.join(file_path, '../')) 33 | 34 | from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns 35 | 36 | 37 | def main(): 38 | shell.check_python() 39 | 40 | # fix py2exe 41 | if hasattr(sys, "frozen") and sys.frozen in \ 42 | ("windows_exe", "console_exe"): 43 | p = os.path.dirname(os.path.abspath(sys.executable)) 44 | os.chdir(p) 45 | 46 | config = shell.get_config(True) 47 | 48 | if not config.get('dns_ipv6', False): 49 | asyncdns.IPV6_CONNECTION_SUPPORT = False 50 | 51 | daemon.daemon_exec(config) 52 | logging.info( 53 | "local start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" % 54 | (config['protocol'], 55 | config['password'], 56 | config['method'], 57 | config['obfs'], 58 | config['obfs_param'])) 59 | 60 | try: 61 | logging.info("starting local at %s:%d" % 62 | (config['local_address'], config['local_port'])) 63 | 64 | dns_resolver = asyncdns.DNSResolver() 65 | tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) 66 | udp_server = udprelay.UDPRelay(config, dns_resolver, True) 67 | loop = eventloop.EventLoop() 68 | dns_resolver.add_to_loop(loop) 69 | tcp_server.add_to_loop(loop) 70 | udp_server.add_to_loop(loop) 71 | 72 | def handler(signum, _): 73 | logging.warn('received SIGQUIT, doing graceful shutting down..') 74 | tcp_server.close(next_tick=True) 75 | udp_server.close(next_tick=True) 76 | signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) 77 | 78 | def int_handler(signum, _): 79 | sys.exit(1) 80 | signal.signal(signal.SIGINT, int_handler) 81 | 82 | daemon.set_user(config.get('user', None)) 83 | loop.run() 84 | except Exception as e: 85 | shell.print_exception(e) 86 | sys.exit(1) 87 | 88 | if __name__ == '__main__': 89 | main() 90 | -------------------------------------------------------------------------------- /shadowsocks/logrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | eval $(ps -ef | grep "[0-9] python server\\.py a" | awk '{print "kill "$2}') 4 | ulimit -n 4096 5 | nohup python server.py a >> ssserver.log 2>&1 & 6 | 7 | -------------------------------------------------------------------------------- /shadowsocks/lru_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import collections 22 | import logging 23 | import time 24 | 25 | if __name__ == '__main__': 26 | import os 27 | import sys 28 | import inspect 29 | file_path = os.path.dirname( 30 | os.path.realpath( 31 | inspect.getfile( 32 | inspect.currentframe()))) 33 | sys.path.insert(0, os.path.join(file_path, '../')) 34 | 35 | try: 36 | from collections import OrderedDict 37 | print("loaded collections.OrderedDict") 38 | except: 39 | from shadowsocks.ordereddict import OrderedDict 40 | 41 | # this LRUCache is optimized for concurrency, not QPS 42 | # n: concurrency, keys stored in the cache 43 | # m: visits not timed out, proportional to QPS * timeout 44 | # get & set is O(1), not O(n). thus we can support very large n 45 | # sweep is O((n - m)) or O(1024) at most, 46 | # no metter how large the cache or timeout value is 47 | 48 | SWEEP_MAX_ITEMS = 1024 49 | 50 | 51 | class LRUCache(collections.MutableMapping): 52 | """This class is not thread safe""" 53 | 54 | def __init__(self, timeout=60, close_callback=None, *args, **kwargs): 55 | self.timeout = timeout 56 | self.close_callback = close_callback 57 | self._store = {} 58 | self._keys_to_last_time = OrderedDict() 59 | self.update(dict(*args, **kwargs)) # use the free update to set keys 60 | 61 | def __getitem__(self, key): 62 | # O(1) 63 | t = time.time() 64 | last_t = self._keys_to_last_time[key] 65 | del self._keys_to_last_time[key] 66 | self._keys_to_last_time[key] = t 67 | return self._store[key] 68 | 69 | def __setitem__(self, key, value): 70 | # O(1) 71 | t = time.time() 72 | if key in self._keys_to_last_time: 73 | del self._keys_to_last_time[key] 74 | self._keys_to_last_time[key] = t 75 | self._store[key] = value 76 | 77 | def __delitem__(self, key): 78 | # O(1) 79 | last_t = self._keys_to_last_time[key] 80 | del self._store[key] 81 | del self._keys_to_last_time[key] 82 | 83 | def __iter__(self): 84 | return iter(self._store) 85 | 86 | def __len__(self): 87 | return len(self._store) 88 | 89 | def first(self): 90 | if len(self._keys_to_last_time) > 0: 91 | for key in self._keys_to_last_time: 92 | return key 93 | 94 | def sweep(self, sweep_item_cnt=SWEEP_MAX_ITEMS): 95 | # O(n - m) 96 | now = time.time() 97 | c = 0 98 | while c < sweep_item_cnt: 99 | if len(self._keys_to_last_time) == 0: 100 | break 101 | for key in self._keys_to_last_time: 102 | break 103 | last_t = self._keys_to_last_time[key] 104 | if now - last_t <= self.timeout: 105 | break 106 | value = self._store[key] 107 | if self.close_callback is not None: 108 | self.close_callback(value) 109 | del self._store[key] 110 | del self._keys_to_last_time[key] 111 | c += 1 112 | if c: 113 | logging.debug('%d keys swept' % c) 114 | return c < SWEEP_MAX_ITEMS 115 | 116 | def clear(self, keep): 117 | now = time.time() 118 | c = 0 119 | while len(self._keys_to_last_time) > keep: 120 | if len(self._keys_to_last_time) == 0: 121 | break 122 | for key in self._keys_to_last_time: 123 | break 124 | last_t = self._keys_to_last_time[key] 125 | value = self._store[key] 126 | if self.close_callback is not None: 127 | self.close_callback(value) 128 | del self._store[key] 129 | del self._keys_to_last_time[key] 130 | c += 1 131 | if c: 132 | logging.debug('%d keys swept' % c) 133 | return c < SWEEP_MAX_ITEMS 134 | 135 | 136 | def test(): 137 | c = LRUCache(timeout=0.3) 138 | 139 | c['a'] = 1 140 | assert c['a'] == 1 141 | c['a'] = 1 142 | 143 | time.sleep(0.5) 144 | c.sweep() 145 | assert 'a' not in c 146 | 147 | c['a'] = 2 148 | c['b'] = 3 149 | time.sleep(0.2) 150 | c.sweep() 151 | assert c['a'] == 2 152 | assert c['b'] == 3 153 | 154 | time.sleep(0.2) 155 | c.sweep() 156 | c['b'] 157 | time.sleep(0.2) 158 | c.sweep() 159 | assert 'a' not in c 160 | assert c['b'] == 3 161 | 162 | time.sleep(0.5) 163 | c.sweep() 164 | assert 'a' not in c 165 | assert 'b' not in c 166 | 167 | global close_cb_called 168 | close_cb_called = False 169 | 170 | def close_cb(t): 171 | global close_cb_called 172 | assert not close_cb_called 173 | close_cb_called = True 174 | 175 | c = LRUCache(timeout=0.1, close_callback=close_cb) 176 | c['s'] = 1 177 | c['s'] 178 | time.sleep(0.1) 179 | c['s'] 180 | time.sleep(0.3) 181 | c.sweep() 182 | 183 | if __name__ == '__main__': 184 | test() 185 | -------------------------------------------------------------------------------- /shadowsocks/obfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2015 breakwa11 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | 25 | from shadowsocks import common 26 | from shadowsocks.obfsplugin import plain, http_simple, obfs_tls, verify, auth, auth_chain, simple_obfs_http, simple_obfs_tls 27 | 28 | 29 | method_supported = {} 30 | method_supported.update(plain.obfs_map) 31 | method_supported.update(http_simple.obfs_map) 32 | method_supported.update(obfs_tls.obfs_map) 33 | method_supported.update(verify.obfs_map) 34 | method_supported.update(auth.obfs_map) 35 | method_supported.update(auth_chain.obfs_map) 36 | method_supported.update(simple_obfs_http.obfs_map) 37 | method_supported.update(simple_obfs_tls.obfs_map) 38 | 39 | 40 | class server_info(object): 41 | 42 | def __init__(self, data): 43 | self.data = data 44 | 45 | 46 | class obfs(object): 47 | 48 | def __init__(self, method): 49 | method = common.to_str(method) 50 | self.method = method 51 | self._method_info = self.get_method_info(method) 52 | if self._method_info: 53 | self.obfs = self.get_obfs(method) 54 | else: 55 | raise Exception('obfs plugin [%s] not supported' % method) 56 | 57 | def init_data(self): 58 | return self.obfs.init_data() 59 | 60 | def set_server_info(self, server_info): 61 | return self.obfs.set_server_info(server_info) 62 | 63 | def get_server_info(self): 64 | return self.obfs.get_server_info() 65 | 66 | def get_method_info(self, method): 67 | method = method.lower() 68 | m = method_supported.get(method) 69 | return m 70 | 71 | def get_obfs(self, method): 72 | m = self._method_info 73 | return m[0](method) 74 | 75 | def get_overhead(self, direction): 76 | return self.obfs.get_overhead(direction) 77 | 78 | def client_pre_encrypt(self, buf): 79 | return self.obfs.client_pre_encrypt(buf) 80 | 81 | def client_encode(self, buf): 82 | return self.obfs.client_encode(buf) 83 | 84 | def client_decode(self, buf): 85 | return self.obfs.client_decode(buf) 86 | 87 | def client_post_decrypt(self, buf): 88 | return self.obfs.client_post_decrypt(buf) 89 | 90 | def server_pre_encrypt(self, buf): 91 | return self.obfs.server_pre_encrypt(buf) 92 | 93 | def server_encode(self, buf): 94 | return self.obfs.server_encode(buf) 95 | 96 | def server_decode(self, buf): 97 | return self.obfs.server_decode(buf) 98 | 99 | def server_post_decrypt(self, buf): 100 | return self.obfs.server_post_decrypt(buf) 101 | 102 | def client_udp_pre_encrypt(self, buf): 103 | return self.obfs.client_udp_pre_encrypt(buf) 104 | 105 | def client_udp_post_decrypt(self, buf): 106 | return self.obfs.client_udp_post_decrypt(buf) 107 | 108 | def server_udp_pre_encrypt(self, buf, uid): 109 | return self.obfs.server_udp_pre_encrypt(buf, uid) 110 | 111 | def server_udp_post_decrypt(self, buf): 112 | return self.obfs.server_udp_post_decrypt(buf) 113 | 114 | def dispose(self): 115 | self.obfs.dispose() 116 | del self.obfs 117 | 118 | def get_hostname(self): 119 | return self.obfs.host_name 120 | -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/plain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2015 breakwa11 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | 25 | from shadowsocks.common import ord 26 | 27 | def create_obfs(method): 28 | return plain(method) 29 | 30 | obfs_map = { 31 | 'plain': (create_obfs,), 32 | 'origin': (create_obfs,), 33 | } 34 | 35 | class plain(object): 36 | def __init__(self, method): 37 | self.method = method 38 | self.server_info = None 39 | 40 | def init_data(self): 41 | return b'' 42 | 43 | def get_overhead(self, direction): # direction: true for c->s false for s->c 44 | return 0 45 | 46 | def get_server_info(self): 47 | return self.server_info 48 | 49 | def set_server_info(self, server_info): 50 | self.server_info = server_info 51 | 52 | def client_pre_encrypt(self, buf): 53 | return buf 54 | 55 | def client_encode(self, buf): 56 | return buf 57 | 58 | def client_decode(self, buf): 59 | # (buffer_to_recv, is_need_to_encode_and_send_back) 60 | return (buf, False) 61 | 62 | def client_post_decrypt(self, buf): 63 | return buf 64 | 65 | def server_pre_encrypt(self, buf): 66 | return buf 67 | 68 | def server_encode(self, buf): 69 | return buf 70 | 71 | def server_decode(self, buf): 72 | # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) 73 | return (buf, True, False) 74 | 75 | def server_post_decrypt(self, buf): 76 | return (buf, False) 77 | 78 | def client_udp_pre_encrypt(self, buf): 79 | return buf 80 | 81 | def client_udp_post_decrypt(self, buf): 82 | return buf 83 | 84 | def server_udp_pre_encrypt(self, buf, uid): 85 | return buf 86 | 87 | def server_udp_post_decrypt(self, buf): 88 | return (buf, None) 89 | 90 | def dispose(self): 91 | pass 92 | 93 | def get_head_size(self, buf, def_value): 94 | if len(buf) < 2: 95 | return def_value 96 | head_type = ord(buf[0]) & 0x7 97 | if head_type == 1: 98 | return 7 99 | if head_type == 4: 100 | return 19 101 | if head_type == 3: 102 | return 4 + ord(buf[1]) 103 | return def_value 104 | -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/simple_obfs_http.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2015 breakwa11 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | import binascii 25 | import struct 26 | import base64 27 | import datetime 28 | import random 29 | 30 | from shadowsocks import common 31 | from shadowsocks.obfsplugin import plain 32 | from shadowsocks.common import to_bytes, to_str, ord, chr 33 | 34 | def create_simple_obfs_http_obfs(method): 35 | return simple_obfs_http(method) 36 | 37 | obfs_map = { 38 | 'simple_obfs_http': (create_simple_obfs_http_obfs,), 39 | 'simple_obfs_http_compatible': (create_simple_obfs_http_obfs,), 40 | } 41 | 42 | def match_begin(str1, str2): 43 | if len(str1) >= len(str2): 44 | if str1[:len(str2)] == str2: 45 | return True 46 | return False 47 | 48 | class simple_obfs_http(plain.plain): 49 | def __init__(self, method): 50 | self.method = method 51 | self.has_sent_header = False 52 | self.has_recv_header = False 53 | self.host = None 54 | self.port = 0 55 | self.recv_buffer = b'' 56 | 57 | self.curl_version = b"7." + common.to_bytes(str(random.randint(0, 51))) + b"." + common.to_bytes(str(random.randint(0, 2))) 58 | self.nginx_version = b"1." + common.to_bytes(str(random.randint(0, 11))) + b"." + common.to_bytes(str(random.randint(0, 12))) 59 | 60 | def encode_head(self, buf): 61 | hexstr = binascii.hexlify(buf) 62 | chs = [] 63 | for i in range(0, len(hexstr), 2): 64 | chs.append(b"%" + hexstr[i:i+2]) 65 | return b''.join(chs) 66 | 67 | def client_encode(self, buf): 68 | raise Exception('Need to finish') 69 | if self.has_sent_header: 70 | return buf 71 | port = b'' 72 | if self.server_info.port != 80: 73 | port = b':' + to_bytes(str(self.server_info.port)) 74 | hosts = (self.server_info.obfs_param or self.server_info.host) 75 | pos = hosts.find("#") 76 | if pos >= 0: 77 | body = hosts[pos + 1:].replace("\\n", "\r\n") 78 | hosts = hosts[:pos] 79 | hosts = hosts.split(',') 80 | host = random.choice(hosts) 81 | http_head = b"GET /" + b" HTTP/1.1\r\n" 82 | http_head += b"Host: " + to_bytes(host) + port + b"\r\n" 83 | http_head += b"User-Agent: curl/" + self.curl_version + b"\r\n" 84 | http_head += b"Upgrade: websocket\r\n" 85 | http_head += b"Connection: Upgrade\r\n" 86 | http_head += b"Sec-WebSocket-Key: " + common.to_bytes(common.random_base64_str(64)) + b"\r\n" 87 | http_head += b"Content-Length: " + len(buf) + b"\r\n" 88 | http_head += b"\r\n" 89 | self.has_sent_header = True 90 | return http_head + buf 91 | 92 | def client_decode(self, buf): 93 | raise Exception('Need to finish') 94 | if self.has_recv_header: 95 | return (buf, False) 96 | pos = buf.find(b'\r\n\r\n') 97 | if pos >= 0: 98 | self.has_recv_header = True 99 | return (buf[pos + 4:], False) 100 | else: 101 | return (b'', False) 102 | 103 | def server_encode(self, buf): 104 | if self.has_sent_header: 105 | return buf 106 | 107 | header = b'HTTP/1.1 101 Switching Protocols\r\n' 108 | header += b'Server: nginx/' + self.nginx_version + b'\r\n' 109 | header += b'Date: ' + to_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')) 110 | header += b'\r\n' 111 | header += b'Upgrade: websocket\r\n' 112 | header += b'Connection: Upgrade\r\n' 113 | header += b'Sec-WebSocket-Accept: ' + common.to_bytes(common.random_base64_str(64)) + b'\r\n' 114 | header += b'\r\n' 115 | self.has_sent_header = True 116 | return header + buf 117 | 118 | def get_host_from_http_header(self, buf): 119 | ret_buf = b'' 120 | lines = buf.split(b'\r\n') 121 | if lines and len(lines) > 1: 122 | for line in lines: 123 | if match_begin(line, b"Host: "): 124 | return common.to_str(line[6:]) 125 | 126 | def not_match_return(self, buf): 127 | self.has_sent_header = True 128 | self.has_recv_header = True 129 | if self.method == 'simple_obfs_http': 130 | return (b'E'*2048, False, False) 131 | return (buf, True, False) 132 | 133 | def server_decode(self, buf): 134 | if self.has_recv_header: 135 | return (buf, True, False) 136 | 137 | self.recv_buffer += buf 138 | buf = self.recv_buffer 139 | if len(buf) > 4: 140 | if match_begin(buf, b'GET /') or match_begin(buf, b'POST /'): 141 | if len(buf) > 65536: 142 | self.recv_buffer = None 143 | logging.warn('simple_obfs_http: over size') 144 | return self.not_match_return(buf) 145 | else: #not http header, run on original protocol 146 | self.recv_buffer = None 147 | logging.debug('simple_obfs_http: not match begin') 148 | return self.not_match_return(buf) 149 | else: 150 | return (b'', True, False) 151 | 152 | if b'\r\n\r\n' in buf: 153 | if b'Upgrade: websocket' not in buf: 154 | self.recv_buffer = None 155 | logging.debug('simple_obfs_http: protocol error') 156 | return self.not_match_return(buf) 157 | datas = buf.split(b'\r\n\r\n', 1) 158 | host = self.get_host_from_http_header(buf) 159 | if host and self.server_info.obfs_param: 160 | pos = host.find(":") 161 | if pos >= 0: 162 | host = host[:pos] 163 | hosts = self.server_info.obfs_param.split(b',') 164 | if common.to_bytes(host) not in hosts: 165 | return self.not_match_return(buf) 166 | if len(datas) > 1: 167 | self.has_recv_header = True 168 | return (datas[1], True, False, host) 169 | return self.not_match_return(buf) 170 | else: 171 | return (b'', True, False) 172 | -------------------------------------------------------------------------------- /shadowsocks/ordereddict.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | ########################################################################## 4 | # OrderedDict 5 | ########################################################################## 6 | 7 | 8 | class OrderedDict(dict): 9 | 'Dictionary that remembers insertion order' 10 | # An inherited dict maps keys to values. 11 | # The inherited dict provides __getitem__, __len__, __contains__, and get. 12 | # The remaining methods are order-aware. 13 | # Big-O running times for all methods are the same as regular dictionaries. 14 | 15 | # The internal self.__map dict maps keys to links in a doubly linked list. 16 | # The circular doubly linked list starts and ends with a sentinel element. 17 | # The sentinel element never gets deleted (this simplifies the algorithm). 18 | # Each link is stored as a list of length three: [PREV, NEXT, KEY]. 19 | 20 | def __init__(*args, **kwds): 21 | '''Initialize an ordered dictionary. The signature is the same as 22 | regular dictionaries, but keyword arguments are not recommended because 23 | their insertion order is arbitrary. 24 | 25 | ''' 26 | if not args: 27 | raise TypeError("descriptor '__init__' of 'OrderedDict' object " 28 | "needs an argument") 29 | self = args[0] 30 | args = args[1:] 31 | if len(args) > 1: 32 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 33 | try: 34 | self.__root 35 | except AttributeError: 36 | self.__root = root = [] # sentinel node 37 | root[:] = [root, root, None] 38 | self.__map = {} 39 | self.__update(*args, **kwds) 40 | 41 | def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 42 | 'od.__setitem__(i, y) <==> od[i]=y' 43 | # Setting a new item creates a new link at the end of the linked list, 44 | # and the inherited dictionary is updated with the new key/value pair. 45 | if key not in self: 46 | root = self.__root 47 | last = root[0] 48 | last[1] = root[0] = self.__map[key] = [last, root, key] 49 | return dict_setitem(self, key, value) 50 | 51 | def __delitem__(self, key, dict_delitem=dict.__delitem__): 52 | 'od.__delitem__(y) <==> del od[y]' 53 | # Deleting an existing item uses self.__map to find the link which gets 54 | # removed by updating the links in the predecessor and successor nodes. 55 | dict_delitem(self, key) 56 | link_prev, link_next, _ = self.__map.pop(key) 57 | # update link_prev[NEXT] 58 | link_prev[1] = link_next 59 | # update link_next[PREV] 60 | link_next[0] = link_prev 61 | 62 | def __iter__(self): 63 | 'od.__iter__() <==> iter(od)' 64 | # Traverse the linked list in order. 65 | root = self.__root 66 | # start at the first node 67 | curr = root[1] 68 | while curr is not root: 69 | yield curr[2] # yield the curr[KEY] 70 | curr = curr[1] # move to next node 71 | 72 | def __reversed__(self): 73 | 'od.__reversed__() <==> reversed(od)' 74 | # Traverse the linked list in reverse order. 75 | root = self.__root 76 | # start at the last node 77 | curr = root[0] 78 | while curr is not root: 79 | yield curr[2] # yield the curr[KEY] 80 | curr = curr[0] # move to previous node 81 | 82 | def clear(self): 83 | 'od.clear() -> None. Remove all items from od.' 84 | root = self.__root 85 | root[:] = [root, root, None] 86 | self.__map.clear() 87 | dict.clear(self) 88 | 89 | # -- the following methods do not depend on the internal structure -- 90 | 91 | def keys(self): 92 | 'od.keys() -> list of keys in od' 93 | return list(self) 94 | 95 | def values(self): 96 | 'od.values() -> list of values in od' 97 | return [self[key] for key in self] 98 | 99 | def items(self): 100 | 'od.items() -> list of (key, value) pairs in od' 101 | return [(key, self[key]) for key in self] 102 | 103 | def iterkeys(self): 104 | 'od.iterkeys() -> an iterator over the keys in od' 105 | return iter(self) 106 | 107 | def itervalues(self): 108 | 'od.itervalues -> an iterator over the values in od' 109 | for k in self: 110 | yield self[k] 111 | 112 | def iteritems(self): 113 | 'od.iteritems -> an iterator over the (key, value) pairs in od' 114 | for k in self: 115 | yield (k, self[k]) 116 | 117 | update = collections.MutableMapping.update 118 | 119 | __update = update # let subclasses override update without breaking __init__ 120 | 121 | __marker = object() 122 | 123 | def pop(self, key, default=__marker): 124 | '''od.pop(k[,d]) -> v, remove specified key and return the corresponding 125 | value. If key is not found, d is returned if given, otherwise KeyError 126 | is raised. 127 | 128 | ''' 129 | if key in self: 130 | result = self[key] 131 | del self[key] 132 | return result 133 | if default is self.__marker: 134 | raise KeyError(key) 135 | return default 136 | 137 | def setdefault(self, key, default=None): 138 | 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' 139 | if key in self: 140 | return self[key] 141 | self[key] = default 142 | return default 143 | 144 | def popitem(self, last=True): 145 | '''od.popitem() -> (k, v), return and remove a (key, value) pair. 146 | Pairs are returned in LIFO order if last is true or FIFO order if false. 147 | 148 | ''' 149 | if not self: 150 | raise KeyError('dictionary is empty') 151 | key = next(reversed(self) if last else iter(self)) 152 | value = self.pop(key) 153 | return key, value 154 | 155 | def __repr__(self, _repr_running={}): 156 | 'od.__repr__() <==> repr(od)' 157 | call_key = id(self), _get_ident() 158 | if call_key in _repr_running: 159 | return '...' 160 | _repr_running[call_key] = 1 161 | try: 162 | if not self: 163 | return '%s()' % (self.__class__.__name__,) 164 | return '%s(%r)' % (self.__class__.__name__, self.items()) 165 | finally: 166 | del _repr_running[call_key] 167 | 168 | def __reduce__(self): 169 | 'Return state information for pickling' 170 | items = [[k, self[k]] for k in self] 171 | inst_dict = vars(self).copy() 172 | for k in vars(OrderedDict()): 173 | inst_dict.pop(k, None) 174 | if inst_dict: 175 | return (self.__class__, (items,), inst_dict) 176 | return self.__class__, (items,) 177 | 178 | def copy(self): 179 | 'od.copy() -> a shallow copy of od' 180 | return self.__class__(self) 181 | 182 | @classmethod 183 | def fromkeys(cls, iterable, value=None): 184 | '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S. 185 | If not specified, the value defaults to None. 186 | 187 | ''' 188 | self = cls() 189 | for key in iterable: 190 | self[key] = value 191 | return self 192 | 193 | def __eq__(self, other): 194 | '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive 195 | while comparison to a regular mapping is order-insensitive. 196 | 197 | ''' 198 | if isinstance(other, OrderedDict): 199 | return dict.__eq__(self, other) and all(_imap(_eq, self, other)) 200 | return dict.__eq__(self, other) 201 | 202 | def __ne__(self, other): 203 | 'od.__ne__(y) <==> od!=y' 204 | return not self == other 205 | 206 | # -- the following methods support python 3.x style dictionary views -- 207 | 208 | def viewkeys(self): 209 | "od.viewkeys() -> a set-like object providing a view on od's keys" 210 | return KeysView(self) 211 | 212 | def viewvalues(self): 213 | "od.viewvalues() -> an object providing a view on od's values" 214 | return ValuesView(self) 215 | 216 | def viewitems(self): 217 | "od.viewitems() -> a set-like object providing a view on od's items" 218 | return ItemsView(self) 219 | -------------------------------------------------------------------------------- /shadowsocks/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | eval $(ps -ef | grep "[0-9] python server\\.py a" | awk '{print "kill "$2}') 4 | ulimit -n 4096 5 | nohup python server.py a >> /dev/null 2>&1 & 6 | 7 | -------------------------------------------------------------------------------- /shadowsocks/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | eval $(ps -ef | grep "[0-9] python server\\.py a" | awk '{print "kill "$2}') 4 | -------------------------------------------------------------------------------- /shadowsocks/tail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tail -f ssserver.log 4 | -------------------------------------------------------------------------------- /shadowsocks/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2016 breakwa11 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | def version(): 19 | return '3.4.0 mod by esdeathlove' 20 | -------------------------------------------------------------------------------- /speedtest/.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | -------------------------------------------------------------------------------- /speedtest/.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 2.7 5 | 6 | addons: 7 | apt: 8 | sources: 9 | - deadsnakes 10 | packages: 11 | - python2.4 12 | - python2.5 13 | - python2.6 14 | - pypy 15 | 16 | env: 17 | - TOXENV=py24 18 | - TOXENV=py25 19 | - TOXENV=py26 20 | - TOXENV=py27 21 | - TOXENV=py32 22 | - TOXENV=py33 23 | - TOXENV=py34 24 | - TOXENV=py35 25 | - TOXENV=pypy 26 | - TOXENV=flake8 27 | 28 | install: 29 | - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py3[12])") != 0 ]]; then pip install virtualenv==1.7.2 tox==1.3; fi; 30 | - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py3[12])") == 0 ]]; then pip install tox; fi; 31 | 32 | script: 33 | - tox 34 | 35 | notifications: 36 | email: 37 | - matt@sivel.net 38 | -------------------------------------------------------------------------------- /speedtest/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Pull Requests 2 | 3 | ## Pull requests should be 4 | 5 | 1. Made against the `devel` branch. 6 | 1. Made from a git feature branch. 7 | 8 | ## Pull requests will not be accepted that 9 | 10 | 1. Are not made against the `devel` branch 11 | 1. Are submitted from a branch named `devel` 12 | 1. Do not pass pep8/pyflakes/flake8 13 | 1. Do not work with Python 2.4-3.4 or pypy 14 | 1. Add python modules not included with the Python standard library 15 | 1. Are made by editing files via the GitHub website 16 | 17 | # Coding Guidelines 18 | 19 | In general, I follow strict pep8 and pyflakes. All code must pass these tests. Since we support python 2.4-3.4 and pypy, pyflakes reports unknown names in python 3. pyflakes is run in python 2.7 only in my tests. 20 | 21 | ## Some other points 22 | 23 | 1. Do not use `\` for line continuations, long strings should be wrapped in `()`. Imports should start a brand new line in the form of `from foo import...` 24 | 1. String quoting should be done with single quotes `'`, except for situations where you would otherwise have to escape an internal single quote 25 | 1. Docstrings should use three double quotes `"""` 26 | 1. All functions, classes and modules should have docstrings following both the PEP257 and PEP8 standards 27 | 1. Inline comments should only be used on code where it is not immediately obvious what the code achieves 28 | 29 | # Supported Python Versions 30 | 31 | All code needs to support Python 2.4-3.4 and pypy. 32 | 33 | # Permitted Python Modules 34 | 35 | Only modules included in the standard library are permitted for use in this application. This application should not be dependent on any 3rd party modules that would need to be installed external to just Python itself. 36 | 37 | # Testing 38 | 39 | Currently there are no unit tests, but they are planned. 40 | -------------------------------------------------------------------------------- /speedtest/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | -------------------------------------------------------------------------------- /speedtest/README.rst: -------------------------------------------------------------------------------- 1 | speedtest-cli 2 | ============= 3 | 4 | Command line interface for testing internet bandwidth using 5 | speedtest.net 6 | 7 | .. image:: https://img.shields.io/pypi/v/speedtest-cli.svg 8 | :target: https://pypi.python.org/pypi/speedtest-cli/ 9 | :alt: Latest Version 10 | .. image:: https://img.shields.io/pypi/dm/speedtest-cli.svg 11 | :target: https://pypi.python.org/pypi/speedtest-cli/ 12 | :alt: Downloads 13 | .. image:: https://img.shields.io/pypi/l/speedtest-cli.svg 14 | :target: https://pypi.python.org/pypi/speedtest-cli/ 15 | :alt: License 16 | 17 | Versions 18 | -------- 19 | 20 | speedtest-cli works with Python 2.4-3.5 21 | 22 | .. image:: https://img.shields.io/pypi/pyversions/speedtest-cli.svg 23 | :target: https://pypi.python.org/pypi/speedtest-cli/ 24 | :alt: Versions 25 | 26 | Installation 27 | ------------ 28 | 29 | pip / easy\_install 30 | ~~~~~~~~~~~~~~~~~~~ 31 | 32 | :: 33 | 34 | pip install speedtest-cli 35 | 36 | or 37 | 38 | :: 39 | 40 | easy_install speedtest-cli 41 | 42 | Github 43 | ~~~~~~ 44 | 45 | :: 46 | 47 | pip install git+https://github.com/sivel/speedtest-cli.git 48 | 49 | or 50 | 51 | :: 52 | 53 | git clone https://github.com/sivel/speedtest-cli.git 54 | python speedtest-cli/setup.py install 55 | 56 | Just download (Like the way it used to be) 57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 | 59 | :: 60 | 61 | wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py 62 | chmod +x speedtest-cli 63 | 64 | or 65 | 66 | :: 67 | 68 | curl -Lo speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py 69 | chmod +x speedtest-cli 70 | 71 | Usage 72 | ----- 73 | 74 | :: 75 | 76 | $ speedtest-cli -h 77 | usage: speedtest-cli [-h] [--bytes] [--share] [--simple] [--csv] 78 | [--csv-delimiter CSV_DELIMITER] [--csv-header] [--json] 79 | [--list] [--server SERVER] [--mini MINI] [--source SOURCE] 80 | [--timeout TIMEOUT] [--secure] [--version] 81 | 82 | Command line interface for testing internet bandwidth using speedtest.net. 83 | -------------------------------------------------------------------------- 84 | https://github.com/sivel/speedtest-cli 85 | 86 | optional arguments: 87 | -h, --help show this help message and exit 88 | --bytes Display values in bytes instead of bits. Does not 89 | affect the image generated by --share, nor output from 90 | --json or --csv 91 | --share Generate and provide a URL to the speedtest.net share 92 | results image 93 | --simple Suppress verbose output, only show basic information 94 | --csv Suppress verbose output, only show basic information 95 | in CSV format. Speeds listed in bit/s and not affected 96 | by --bytes 97 | --csv-delimiter CSV_DELIMITER 98 | Single character delimiter to use in CSV output. 99 | Default "," 100 | --csv-header Print CSV headers 101 | --json Suppress verbose output, only show basic information 102 | in JSON format. Speeds listed in bit/s and not 103 | affected by --bytes 104 | --list Display a list of speedtest.net servers sorted by 105 | distance 106 | --server SERVER Specify a server ID to test against 107 | --mini MINI URL of the Speedtest Mini server 108 | --source SOURCE Source IP address to bind to 109 | --timeout TIMEOUT HTTP timeout in seconds. Default 10 110 | --secure Use HTTPS instead of HTTP when communicating with 111 | speedtest.net operated servers 112 | --version Show the version number and exit 113 | 114 | Inconsistency 115 | ------------- 116 | 117 | It is not a goal of this application to be a reliable latency reporting tool. 118 | 119 | Latency reported by this tool should not be relied on as a value indicative of ICMP 120 | style latency. It is a relative value used for determining the lowest latency server 121 | for performing the actual speed test against. 122 | 123 | There is the potential for this tool to report results inconsistent with Speedtest.net. 124 | There are several concepts to be aware of that factor into the potential inconsistency: 125 | 126 | 1. Speedtest.net has migrated to using pure socket tests instead of HTTP based tests 127 | 2. This application is written in Python 128 | 3. Different versions of Python will execute certain parts of the code faster than others 129 | 4. CPU and Memory capacity and speed will play a large part in inconsistency between 130 | Speedtest.net and even other machines on the same network 131 | 132 | Issues relating to inconsistencies will be closed as wontfix and without 133 | additional reason or context. 134 | -------------------------------------------------------------------------------- /speedtest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NimaQu/shadowsocks/97c4c65cbc0b0e7794719ddb68ee25d33f78d80a/speedtest/__init__.py -------------------------------------------------------------------------------- /speedtest/setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /speedtest/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Copyright 2012-2016 Matt Martz 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | import os 19 | import re 20 | import codecs 21 | 22 | from setuptools import setup 23 | 24 | here = os.path.abspath(os.path.dirname(__file__)) 25 | 26 | 27 | # Read the version number from a source file. 28 | # Why read it, and not import? 29 | # see https://groups.google.com/d/topic/pypa-dev/0PkjVpcxTzQ/discussion 30 | def find_version(*file_paths): 31 | # Open in Latin-1 so that we avoid encoding errors. 32 | # Use codecs.open for Python 2 compatibility 33 | try: 34 | f = codecs.open(os.path.join(here, *file_paths), 'r', 'latin1') 35 | version_file = f.read() 36 | f.close() 37 | except: 38 | raise RuntimeError("Unable to find version string.") 39 | 40 | # The version line must have the form 41 | # __version__ = 'ver' 42 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 43 | version_file, re.M) 44 | if version_match: 45 | return version_match.group(1) 46 | raise RuntimeError("Unable to find version string.") 47 | 48 | 49 | # Get the long description from the relevant file 50 | try: 51 | f = codecs.open('README.rst', encoding='utf-8') 52 | long_description = f.read() 53 | f.close() 54 | except: 55 | long_description = '' 56 | 57 | 58 | setup( 59 | name='speedtest-cli', 60 | version=find_version('speedtest.py'), 61 | description=('Command line interface for testing internet bandwidth using ' 62 | 'speedtest.net'), 63 | long_description=long_description, 64 | keywords='speedtest speedtest.net', 65 | author='Matt Martz', 66 | author_email='matt@sivel.net', 67 | url='https://github.com/sivel/speedtest-cli', 68 | license='Apache License, Version 2.0', 69 | py_modules=['speedtest', 'speedtest_cli'], 70 | entry_points={ 71 | 'console_scripts': [ 72 | 'speedtest=speedtest:main', 73 | 'speedtest-cli=speedtest:main' 74 | ] 75 | }, 76 | classifiers=[ 77 | 'Development Status :: 5 - Production/Stable', 78 | 'Programming Language :: Python', 79 | 'Environment :: Console', 80 | 'License :: OSI Approved :: Apache Software License', 81 | 'Operating System :: OS Independent', 82 | 'Programming Language :: Python :: 2', 83 | 'Programming Language :: Python :: 2.4', 84 | 'Programming Language :: Python :: 2.5', 85 | 'Programming Language :: Python :: 2.6', 86 | 'Programming Language :: Python :: 2.7', 87 | 'Programming Language :: Python :: 3', 88 | 'Programming Language :: Python :: 3.1', 89 | 'Programming Language :: Python :: 3.2', 90 | 'Programming Language :: Python :: 3.3', 91 | 'Programming Language :: Python :: 3.4', 92 | 'Programming Language :: Python :: 3.5', 93 | ] 94 | ) 95 | -------------------------------------------------------------------------------- /speedtest/speedtest-cli.1: -------------------------------------------------------------------------------- 1 | .TH "speedtest-cli" 1 "2014-04-23" "speedtest-cli" 2 | .SH NAME 3 | speedtest\-cli \- Command line interface for testing internet bandwidth using speedtest.net 4 | .SH SYNOPSIS 5 | .B speedtest\-cli 6 | [OPTION...] 7 | .SH DESCRIPTION 8 | Speedtest.net is a web service for testing your broadband connection by downloading a file 9 | from a nearby speedtest.net server on the web. This tool allows you to access the service 10 | from the command line. 11 | 12 | Speedtest mini is a version of the Speedtest.net server that you can host locally. 13 | 14 | .SH OPTIONS 15 | Usage: speedtest\-cli [OPTION...] 16 | 17 | .B Help Options 18 | 19 | \fB\-h, \-\-help\fR 20 | .RS 21 | Displays usage for the tool. 22 | .RE 23 | 24 | .B Options 25 | 26 | \fB\-\-bytes\fR 27 | .RS 28 | Display values in bytes instead of bits. Does not affect the image generated by \-\-share 29 | .RE 30 | 31 | \fB\-\-share\fR 32 | .RS 33 | Generate and provide a URL to the speedtest.net share results image 34 | .RE 35 | 36 | \fB\-\-simple\fR 37 | .RS 38 | Suppress verbose output, only show basic information 39 | .RE 40 | 41 | \fB\-\-csv\fR 42 | .RS 43 | Suppress verbose output, only show basic information in CSV format. Speeds listed in bit/s and not affected by \-\-bytes 44 | .RE 45 | 46 | \fB\-\-csv-delimiter CSV_DELIMITER\fR 47 | .RS 48 | Single character delimiter to use in CSV output. Default "," 49 | .RE 50 | 51 | \fB\-\-csv-header\fR 52 | .RS 53 | Print CSV headers 54 | .RE 55 | 56 | \fB\-\-json\fR 57 | .RS 58 | Suppress verbose output, only show basic information in JSON format. Speeds listed in bit/s and not affected by \-\-bytes 59 | .RE 60 | 61 | \fB\-\-list\fR 62 | .RS 63 | Display a list of speedtest.net servers sorted by distance 64 | .RE 65 | 66 | \fB\-\-server SERVER\fR 67 | .RS 68 | Specify a server ID to test against 69 | .RE 70 | 71 | \fB\-\-mini MINI\fR 72 | .RS 73 | URL of the Speedtest Mini server 74 | .RE 75 | 76 | \fB\-\-source SOURCE\fR 77 | .RS 78 | Source IP address to bind to 79 | .RE 80 | 81 | \fB\-\-timeout TIMEOUT\fR 82 | .RS 83 | HTTP timeout in seconds. Default 10 84 | .RE 85 | 86 | \fB\-\-secure\fR 87 | .RS 88 | Use HTTPS instead of HTTP when communicating with speedtest.net operated servers 89 | .RE 90 | 91 | \fB\-\-version\fR 92 | .RS 93 | Show the version number and exit 94 | .RE 95 | 96 | .SH EXAMPLES 97 | 98 | \fBAutomatically find closest server and start testing\fR 99 | .RS 100 | speedtest\-cli 101 | .RE 102 | 103 | \fBSpecify testing against server 1491\fR 104 | .RS 105 | speedtest-cli \-\-server 1491 106 | .RE 107 | 108 | \fBTesting against Speedtest Mini\fR 109 | .RS 110 | speedtest-cli \-\-mini 172.18.66.1 111 | .RE 112 | 113 | .SH REPORTING BUGS 114 | Please file issues on the Github bug tracker: https://github.com/sivel/speedtest\-cli 115 | 116 | .SH AUTHORS 117 | This manual page was written by Jonathan Carter 118 | Speedtest\-cli was written by Matt Martz 119 | -------------------------------------------------------------------------------- /speedtest/speedtest_cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Copyright 2012-2016 Matt Martz 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | import warnings 19 | 20 | DEPRECATED_MSG = ('The file speedtest_cli.py has been deprecated in favor of ' 21 | 'speedtest.py\nand is available for download at:\n\n' 22 | 'https://raw.githubusercontent.com/sivel/speedtest-cli/' 23 | 'master/speedtest.py') 24 | 25 | 26 | if __name__ == '__main__': 27 | raise SystemExit(DEPRECATED_MSG) 28 | else: 29 | try: 30 | from speedtest import * 31 | except ImportError: 32 | raise SystemExit(DEPRECATED_MSG) 33 | else: 34 | warnings.warn(DEPRECATED_MSG, UserWarning) 35 | -------------------------------------------------------------------------------- /speedtest/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist=true 3 | 4 | [testenv] 5 | commands = 6 | {envpython} -V 7 | {envpython} -m compileall speedtest.py 8 | {envpython} speedtest.py 9 | 10 | [testenv:flake8] 11 | basepython=python 12 | deps=flake8 13 | commands = 14 | {envpython} -V 15 | flake8 speedtest.py 16 | 17 | [testenv:pypy] 18 | commands = 19 | pypy -V 20 | pypy -m compileall speedtest.py 21 | pypy speedtest.py 22 | -------------------------------------------------------------------------------- /speedtest_thread.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import logging 5 | import time 6 | import sys 7 | import os 8 | import configloader 9 | import importloader 10 | from speedtest import speedtest 11 | from shadowsocks import common, shell 12 | 13 | class Speedtest(object): 14 | 15 | def __init__(self): 16 | import threading 17 | self.event = threading.Event() 18 | self.has_stopped = False 19 | 20 | def speedtest_thread(self): 21 | if self.event.wait(600): 22 | return 23 | 24 | logging.info("Speedtest starting...You can't stop right now!") 25 | CTid = 0 26 | speedtest_ct = speedtest.Speedtest() 27 | speedtest_ct.get_servers() 28 | servers_list = [] 29 | for _, servers in sorted(speedtest_ct.servers.items()): 30 | for server in servers: 31 | if server['country'].find( 32 | 'China') != -1 and server['sponsor'].find('Telecom') != -1: 33 | servers_list.append(server) 34 | speedtest_ct.get_best_server(servers_list) 35 | results_ct = speedtest_ct.results 36 | CTPing = str(results_ct.server['latency']) + ' ms' 37 | speedtest_ct.download() 38 | CTDLSpeed = str( 39 | round( 40 | (results_ct.download / 1000 / 1000), 41 | 2)) + " Mbit/s" 42 | speedtest_ct.upload() 43 | CTUpSpeed = str( 44 | round( 45 | (results_ct.upload / 1000 / 1000), 46 | 2)) + " Mbit/s" 47 | 48 | CUid = 0 49 | speedtest_cu = speedtest.Speedtest() 50 | speedtest_cu.get_servers() 51 | servers_list = [] 52 | for _, servers in sorted(speedtest_cu.servers.items()): 53 | for server in servers: 54 | if server['country'].find( 55 | 'China') != -1 and server['sponsor'].find('Unicom') != -1: 56 | servers_list.append(server) 57 | speedtest_cu.get_best_server(servers_list) 58 | results_cu = speedtest_cu.results 59 | CUPing = str(results_cu.server['latency']) + ' ms' 60 | speedtest_cu.download() 61 | CUDLSpeed = str( 62 | round( 63 | (results_cu.download / 1000 / 1000), 64 | 2)) + " Mbit/s" 65 | speedtest_cu.upload() 66 | CUUpSpeed = str( 67 | round( 68 | (results_cu.upload / 1000 / 1000), 69 | 2)) + " Mbit/s" 70 | 71 | CMid = 0 72 | speedtest_cm = speedtest.Speedtest() 73 | speedtest_cm.get_servers() 74 | servers_list = [] 75 | for _, servers in sorted(speedtest_cm.servers.items()): 76 | for server in servers: 77 | if server['country'].find( 78 | 'China') != -1 and server['sponsor'].find('Mobile') != -1: 79 | servers_list.append(server) 80 | speedtest_cm.get_best_server(servers_list) 81 | results_cm = speedtest_cm.results 82 | CMPing = str(results_cm.server['latency']) + ' ms' 83 | speedtest_cm.download() 84 | CMDLSpeed = str( 85 | round( 86 | (results_cm.download / 1000 / 1000), 87 | 2)) + " Mbit/s" 88 | speedtest_cm.upload() 89 | CMUpSpeed = str( 90 | round( 91 | (results_cm.upload / 1000 / 1000), 92 | 2)) + " Mbit/s" 93 | 94 | if configloader.get_config().API_INTERFACE == 'modwebapi': 95 | webapi.postApi('func/speedtest', 96 | {'node_id': configloader.get_config().NODE_ID}, 97 | {'data': [{'telecomping': CTPing, 98 | 'telecomeupload': CTUpSpeed, 99 | 'telecomedownload': CTDLSpeed, 100 | 'unicomping': CUPing, 101 | 'unicomupload': CUUpSpeed, 102 | 'unicomdownload': CUDLSpeed, 103 | 'cmccping': CMPing, 104 | 'cmccupload': CMUpSpeed, 105 | 'cmccdownload': CMDLSpeed}]}) 106 | else: 107 | import cymysql 108 | if configloader.get_config().MYSQL_SSL_ENABLE == 1: 109 | conn = cymysql.connect( 110 | host=configloader.get_config().MYSQL_HOST, 111 | port=configloader.get_config().MYSQL_PORT, 112 | user=configloader.get_config().MYSQL_USER, 113 | passwd=configloader.get_config().MYSQL_PASS, 114 | db=configloader.get_config().MYSQL_DB, 115 | charset='utf8', 116 | ssl={ 117 | 'ca': configloader.get_config().MYSQL_SSL_CA, 118 | 'cert': configloader.get_config().MYSQL_SSL_CERT, 119 | 'key': configloader.get_config().MYSQL_SSL_KEY}) 120 | else: 121 | conn = cymysql.connect( 122 | host=configloader.get_config().MYSQL_HOST, 123 | port=configloader.get_config().MYSQL_PORT, 124 | user=configloader.get_config().MYSQL_USER, 125 | passwd=configloader.get_config().MYSQL_PASS, 126 | db=configloader.get_config().MYSQL_DB, 127 | charset='utf8') 128 | conn.autocommit(True) 129 | cur = conn.cursor() 130 | cur.execute( 131 | "INSERT INTO `speedtest` (`id`, `nodeid`, `datetime`, `telecomping`, `telecomeupload`, `telecomedownload`, `unicomping`, `unicomupload`, `unicomdownload`, `cmccping`, `cmccupload`, `cmccdownload`) VALUES (NULL, '" + 132 | str( 133 | configloader.get_config().NODE_ID) + 134 | "', unix_timestamp(), '" + 135 | CTPing + 136 | "', '" + 137 | CTUpSpeed + 138 | "', '" + 139 | CTDLSpeed + 140 | "', '" + 141 | CUPing + 142 | "', '" + 143 | CUUpSpeed + 144 | "', '" + 145 | CUDLSpeed + 146 | "', '" + 147 | CMPing + 148 | "', '" + 149 | CMUpSpeed + 150 | "', '" + 151 | CMDLSpeed + 152 | "')") 153 | cur.close() 154 | conn.close() 155 | 156 | logging.info("Speedtest finished") 157 | 158 | @staticmethod 159 | def thread_db(obj): 160 | 161 | if configloader.get_config().SPEEDTEST == 0: 162 | return 163 | 164 | if configloader.get_config().API_INTERFACE == 'modwebapi': 165 | import webapi_utils 166 | 167 | global webapi 168 | webapi = webapi_utils.WebApi() 169 | 170 | global db_instance 171 | db_instance = obj() 172 | 173 | try: 174 | while True: 175 | try: 176 | db_instance.speedtest_thread() 177 | except Exception as e: 178 | import traceback 179 | trace = traceback.format_exc() 180 | logging.error(trace) 181 | #logging.warn('db thread except:%s' % e) 182 | if db_instance.event.wait(configloader.get_config().SPEEDTEST * 3600): 183 | break 184 | if db_instance.has_stopped: 185 | break 186 | except KeyboardInterrupt as e: 187 | pass 188 | db_instance = None 189 | 190 | @staticmethod 191 | def thread_db_stop(): 192 | global db_instance 193 | db_instance.has_stopped = True 194 | db_instance.event.set() 195 | -------------------------------------------------------------------------------- /ssr.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ssr deamon 3 | After=rc-local.service 4 | 5 | [Service] 6 | Type=simple 7 | User=root 8 | Group=root 9 | WorkingDirectory=/root/shadowsocks 10 | ExecStart=/usr/bin/python server.py 11 | Restart=always 12 | LimitNOFILE=512000 13 | [Install] 14 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | eval $(ps -ef | grep "[0-9] python server\\.py m" | awk '{print "kill -9 "$2}') 4 | -------------------------------------------------------------------------------- /switchrule.py: -------------------------------------------------------------------------------- 1 | from configloader import load_config, get_config 2 | 3 | 4 | def getKeys(): 5 | key_list = ['id', 'port', 'u', 'd', 'transfer_enable', 'passwd', 'enable'] 6 | if get_config().API_INTERFACE == 'sspanelv3': 7 | key_list += ['method'] 8 | elif get_config().API_INTERFACE == 'sspanelv3ssr': 9 | key_list += ['method', 'obfs', 'protocol'] 10 | elif get_config().API_INTERFACE == 'glzjinmod': 11 | key_list += ['method', 12 | 'obfs', 13 | 'obfs_param', 14 | 'protocol', 15 | 'protocol_param', 16 | 'id', 17 | 'node_speedlimit', 18 | 'forbidden_ip', 19 | 'forbidden_port', 20 | 'disconnect_ip', 21 | 'is_multi_user'] 22 | return key_list 23 | # return key_list + ['plan'] # append the column name 'plan' 24 | 25 | 26 | def isTurnOn(row): 27 | return True 28 | # return row['plan'] == 'B' # then judge here 29 | -------------------------------------------------------------------------------- /tail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | tail -f ssserver.log 4 | -------------------------------------------------------------------------------- /tests/aes-cfb1.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb1", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/aes-cfb8.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb8", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/aes-ctr.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"aes-256-ctr", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/aes.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/assert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # assert.sh 1.0 - bash unit testing framework 3 | # Copyright (C) 2009, 2010, 2011, 2012 Robert Lehmann 4 | # 5 | # http://github.com/lehmannro/assert.sh 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Lesser General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with this program. If not, see . 19 | 20 | export DISCOVERONLY=${DISCOVERONLY:-} 21 | export DEBUG=${DEBUG:-} 22 | export STOP=${STOP:-} 23 | export INVARIANT=${INVARIANT:-} 24 | export CONTINUE=${CONTINUE:-} 25 | 26 | args="$(getopt -n "$0" -l \ 27 | verbose,help,stop,discover,invariant,continue vhxdic $*)" \ 28 | || exit -1 29 | for arg in $args; do 30 | case "$arg" in 31 | -h) 32 | echo "$0 [-vxidc]" \ 33 | "[--verbose] [--stop] [--invariant] [--discover] [--continue]" 34 | echo "`sed 's/./ /g' <<< "$0"` [-h] [--help]" 35 | exit 0;; 36 | --help) 37 | cat < [stdin] 98 | (( tests_ran++ )) || : 99 | [[ -n "$DISCOVERONLY" ]] && return || true 100 | # printf required for formatting 101 | printf -v expected "x${2:-}" # x required to overwrite older results 102 | result="$(eval 2>/dev/null $1 <<< ${3:-})" || true 103 | # Note: $expected is already decorated 104 | if [[ "x$result" == "$expected" ]]; then 105 | [[ -n "$DEBUG" ]] && echo -n . || true 106 | return 107 | fi 108 | result="$(sed -e :a -e '$!N;s/\n/\\n/;ta' <<< "$result")" 109 | [[ -z "$result" ]] && result="nothing" || result="\"$result\"" 110 | [[ -z "$2" ]] && expected="nothing" || expected="\"$2\"" 111 | _assert_fail "expected $expected${_indent}got $result" "$1" "$3" 112 | } 113 | 114 | assert_raises() { 115 | # assert_raises [stdin] 116 | (( tests_ran++ )) || : 117 | [[ -n "$DISCOVERONLY" ]] && return || true 118 | status=0 119 | (eval $1 <<< ${3:-}) > /dev/null 2>&1 || status=$? 120 | expected=${2:-0} 121 | if [[ "$status" -eq "$expected" ]]; then 122 | [[ -n "$DEBUG" ]] && echo -n . || true 123 | return 124 | fi 125 | _assert_fail "program terminated with code $status instead of $expected" "$1" "$3" 126 | } 127 | 128 | _assert_fail() { 129 | # _assert_fail 130 | [[ -n "$DEBUG" ]] && echo -n X 131 | report="test #$tests_ran \"$2${3:+ <<< $3}\" failed:${_indent}$1" 132 | if [[ -n "$STOP" ]]; then 133 | [[ -n "$DEBUG" ]] && echo 134 | echo "$report" 135 | exit 1 136 | fi 137 | tests_errors[$tests_failed]="$report" 138 | (( tests_failed++ )) || : 139 | } 140 | 141 | _assert_reset 142 | : ${tests_suite_status:=0} # remember if any of the tests failed so far 143 | _assert_cleanup() { 144 | local status=$? 145 | # modify exit code if it's not already non-zero 146 | [[ $status -eq 0 && -z $CONTINUE ]] && exit $tests_suite_status 147 | } 148 | trap _assert_cleanup EXIT 149 | -------------------------------------------------------------------------------- /tests/chacha20.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"salsa20_password", 6 | "timeout":60, 7 | "method":"chacha20", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/client-multi-server-ip.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":["127.0.0.1", "127.0.0.1"], 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/coverage_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | if __name__ == '__main__': 18 | import tornado.ioloop 19 | import tornado.web 20 | import urllib 21 | 22 | class MainHandler(tornado.web.RequestHandler): 23 | def get(self, project): 24 | try: 25 | with open('/tmp/%s-coverage' % project, 'rb') as f: 26 | coverage = f.read().strip() 27 | n = int(coverage.strip('%')) 28 | if n >= 80: 29 | color = 'brightgreen' 30 | else: 31 | color = 'yellow' 32 | self.redirect(('https://img.shields.io/badge/' 33 | 'coverage-%s-%s.svg' 34 | '?style=flat') % 35 | (urllib.quote(coverage), color)) 36 | except IOError: 37 | raise tornado.web.HTTPError(404) 38 | 39 | application = tornado.web.Application([ 40 | (r"/([a-zA-Z0-9\-_]+)", MainHandler), 41 | ]) 42 | 43 | if __name__ == "__main__": 44 | application.listen(8888, address='127.0.0.1') 45 | tornado.ioloop.IOLoop.instance().start() 46 | -------------------------------------------------------------------------------- /tests/fastopen.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"fastopen_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb", 8 | "local_address":"127.0.0.1", 9 | "fast_open":true 10 | } 11 | -------------------------------------------------------------------------------- /tests/ipv6-client-side.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"::1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/ipv6.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"::", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | result=0 4 | 5 | function run_test { 6 | printf '\e[0;36m' 7 | echo "running test: $command $@" 8 | printf '\e[0m' 9 | 10 | $command "$@" 11 | status=$? 12 | if [ $status -ne 0 ]; then 13 | printf '\e[0;31m' 14 | echo "test failed: $command $@" 15 | printf '\e[0m' 16 | echo 17 | result=1 18 | else 19 | printf '\e[0;32m' 20 | echo OK 21 | printf '\e[0m' 22 | echo 23 | fi 24 | return 0 25 | } 26 | 27 | python --version 28 | coverage erase 29 | mkdir tmp 30 | run_test pep8 --ignore=E402 . 31 | run_test pyflakes . 32 | run_test coverage run tests/nose_plugin.py -v 33 | run_test python setup.py sdist 34 | run_test tests/test_daemon.sh 35 | run_test python tests/test.py --with-coverage -c tests/aes.json 36 | run_test python tests/test.py --with-coverage -c tests/aes-ctr.json 37 | run_test python tests/test.py --with-coverage -c tests/aes-cfb1.json 38 | run_test python tests/test.py --with-coverage -c tests/aes-cfb8.json 39 | run_test python tests/test.py --with-coverage -c tests/rc4-md5.json 40 | run_test python tests/test.py --with-coverage -c tests/salsa20.json 41 | run_test python tests/test.py --with-coverage -c tests/chacha20.json 42 | run_test python tests/test.py --with-coverage -c tests/table.json 43 | run_test python tests/test.py --with-coverage -c tests/server-multi-ports.json 44 | run_test python tests/test.py --with-coverage -s tests/aes.json -c tests/client-multi-server-ip.json 45 | run_test python tests/test.py --with-coverage -s tests/server-multi-passwd.json -c tests/server-multi-passwd-client-side.json 46 | run_test python tests/test.py --with-coverage -c tests/workers.json 47 | run_test python tests/test.py --with-coverage -s tests/ipv6.json -c tests/ipv6-client-side.json 48 | run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -q" -a "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -vv" 49 | run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" 50 | run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" 51 | 52 | # test if DNS works 53 | run_test python tests/test.py --with-coverage -c tests/aes.json --url="https://clients1.google.com/generate_204" 54 | 55 | # test localhost is in the forbidden list by default 56 | run_test python tests/test.py --with-coverage --should-fail --tcp-only --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" 57 | 58 | # test localhost is available when forbidden list is empty 59 | run_test python tests/test.py --with-coverage --tcp-only --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" 60 | 61 | if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then 62 | if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then 63 | # we have to run it twice: 64 | # the first time there's no syn cookie 65 | # the second time there is syn cookie 66 | run_test python tests/test.py --with-coverage -c tests/fastopen.json 67 | run_test python tests/test.py --with-coverage -c tests/fastopen.json 68 | fi 69 | fi 70 | 71 | run_test tests/test_large_file.sh 72 | run_test tests/test_udp_src.sh 73 | run_test tests/test_command.sh 74 | 75 | coverage combine && coverage report --include=shadowsocks/* 76 | rm -rf htmlcov 77 | rm -rf tmp 78 | coverage html --include=shadowsocks/* 79 | 80 | coverage report --include=shadowsocks/* | tail -n1 | rev | cut -d' ' -f 1 | rev > /tmp/shadowsocks-coverage 81 | 82 | exit $result 83 | -------------------------------------------------------------------------------- /tests/libsodium/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -d libsodium-1.0.1 ]; then 4 | wget https://github.com/jedisct1/libsodium/releases/download/1.0.1/libsodium-1.0.1.tar.gz || exit 1 5 | tar xf libsodium-1.0.1.tar.gz || exit 1 6 | fi 7 | pushd libsodium-1.0.1 8 | ./configure && make -j2 && make install || exit 1 9 | sudo ldconfig 10 | popd 11 | -------------------------------------------------------------------------------- /tests/nose_plugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import nose 18 | from nose.plugins.base import Plugin 19 | 20 | 21 | class ExtensionPlugin(Plugin): 22 | 23 | name = "ExtensionPlugin" 24 | 25 | def options(self, parser, env): 26 | Plugin.options(self, parser, env) 27 | 28 | def configure(self, options, config): 29 | Plugin.configure(self, options, config) 30 | self.enabled = True 31 | 32 | def wantFile(self, file): 33 | return file.endswith('.py') 34 | 35 | def wantDirectory(self, directory): 36 | return True 37 | 38 | def wantModule(self, file): 39 | return True 40 | 41 | 42 | if __name__ == '__main__': 43 | nose.main(addplugins=[ExtensionPlugin()]) 44 | -------------------------------------------------------------------------------- /tests/rc4-md5.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"aes_password", 6 | "timeout":60, 7 | "method":"rc4-md5", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/salsa20-ctr.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"salsa20_password", 6 | "timeout":60, 7 | "method":"salsa20-ctr", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/salsa20.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"salsa20_password", 6 | "timeout":60, 7 | "method":"salsa20", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/server-multi-passwd-client-side.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "127.0.0.1", 3 | "server_port": "8385", 4 | "local_port": 1081, 5 | "password": "foobar5", 6 | "timeout": 60, 7 | "method": "aes-256-cfb" 8 | } 9 | -------------------------------------------------------------------------------- /tests/server-multi-passwd-table.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "127.0.0.1", 3 | "server_port": 8384, 4 | "local_port": 1081, 5 | "password": "foobar4", 6 | "port_password": { 7 | "8381": "foobar1", 8 | "8382": "foobar2", 9 | "8383": "foobar3", 10 | "8384": "foobar4", 11 | "8385": "foobar5", 12 | "8386": "foobar6", 13 | "8387": "foobar7", 14 | "8388": "foobar8", 15 | "8389": "foobar9" 16 | }, 17 | "timeout": 60, 18 | "method": "table" 19 | } 20 | -------------------------------------------------------------------------------- /tests/server-multi-passwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "127.0.0.1", 3 | "local_port": 1081, 4 | "port_password": { 5 | "8381": "foobar1", 6 | "8382": "foobar2", 7 | "8383": "foobar3", 8 | "8384": "foobar4", 9 | "8385": "foobar5", 10 | "8386": "foobar6", 11 | "8387": "foobar7", 12 | "8388": "foobar8", 13 | "8389": "foobar9" 14 | }, 15 | "timeout": 60, 16 | "method": "aes-256-cfb" 17 | } 18 | -------------------------------------------------------------------------------- /tests/server-multi-ports.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "127.0.0.1", 3 | "server_port": [8384, 8345, 8346, 8347], 4 | "local_port": 1081, 5 | "password": "foobar4", 6 | "timeout": 60, 7 | "method": "aes-256-cfb" 8 | } 9 | -------------------------------------------------------------------------------- /tests/setup_tc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DEV=lo 4 | PORT=8388 5 | DELAY=100ms 6 | 7 | type tc 2> /dev/null && ( 8 | tc qdisc add dev $DEV root handle 1: htb 9 | tc class add dev $DEV parent 1: classid 1:1 htb rate 2mbps 10 | tc class add dev $DEV parent 1:1 classid 1:6 htb rate 2mbps ceil 1mbps prio 0 11 | tc filter add dev $DEV parent 1:0 prio 0 protocol ip handle 6 fw flowid 1:6 12 | 13 | tc filter add dev $DEV parent 1:0 protocol ip u32 match ip dport $PORT 0xffff flowid 1:6 14 | tc filter add dev $DEV parent 1:0 protocol ip u32 match ip sport $PORT 0xffff flowid 1:6 15 | 16 | tc qdisc show dev lo 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /tests/socksify/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -d dante-1.4.0 ]; then 4 | wget http://www.inet.no/dante/files/dante-1.4.0.tar.gz || exit 1 5 | tar xf dante-1.4.0.tar.gz || exit 1 6 | fi 7 | pushd dante-1.4.0 8 | ./configure && make -j4 && make install || exit 1 9 | popd 10 | cp tests/socksify/socks.conf /etc/ || exit 1 11 | -------------------------------------------------------------------------------- /tests/socksify/socks.conf: -------------------------------------------------------------------------------- 1 | route { 2 | from: 0.0.0.0/0 to: 0.0.0.0/0 via: 127.0.0.1 port = 1081 3 | proxyprotocol: socks_v5 4 | method: none 5 | } -------------------------------------------------------------------------------- /tests/table.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"table_password", 6 | "timeout":60, 7 | "method":"table", 8 | "local_address":"127.0.0.1", 9 | "fast_open":false 10 | } 11 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import sys 22 | import os 23 | import signal 24 | import select 25 | import time 26 | import argparse 27 | from subprocess import Popen, PIPE 28 | 29 | python = ['python'] 30 | 31 | default_url = 'http://localhost/' 32 | 33 | parser = argparse.ArgumentParser(description='test Shadowsocks') 34 | parser.add_argument('-c', '--client-conf', type=str, default=None) 35 | parser.add_argument('-s', '--server-conf', type=str, default=None) 36 | parser.add_argument('-a', '--client-args', type=str, default=None) 37 | parser.add_argument('-b', '--server-args', type=str, default=None) 38 | parser.add_argument('--with-coverage', action='store_true', default=None) 39 | parser.add_argument('--should-fail', action='store_true', default=None) 40 | parser.add_argument('--tcp-only', action='store_true', default=None) 41 | parser.add_argument('--url', type=str, default=default_url) 42 | parser.add_argument('--dns', type=str, default='8.8.8.8') 43 | 44 | config = parser.parse_args() 45 | 46 | if config.with_coverage: 47 | python = ['coverage', 'run', '-p'] 48 | 49 | client_args = python + ['shadowsocks/local.py', '-v'] 50 | server_args = python + ['shadowsocks/server.py', '-v'] 51 | 52 | if config.client_conf: 53 | client_args.extend(['-c', config.client_conf]) 54 | if config.server_conf: 55 | server_args.extend(['-c', config.server_conf]) 56 | else: 57 | server_args.extend(['-c', config.client_conf]) 58 | if config.client_args: 59 | client_args.extend(config.client_args.split()) 60 | if config.server_args: 61 | server_args.extend(config.server_args.split()) 62 | else: 63 | server_args.extend(config.client_args.split()) 64 | if config.url == default_url: 65 | server_args.extend(['--forbidden-ip', '']) 66 | 67 | p1 = Popen(server_args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) 68 | p2 = Popen(client_args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) 69 | p3 = None 70 | p4 = None 71 | p3_fin = False 72 | p4_fin = False 73 | 74 | # 1 shadowsocks started 75 | # 2 curl started 76 | # 3 curl finished 77 | # 4 dig started 78 | # 5 dig finished 79 | stage = 1 80 | 81 | try: 82 | local_ready = False 83 | server_ready = False 84 | fdset = [p1.stdout, p2.stdout, p1.stderr, p2.stderr] 85 | while True: 86 | r, w, e = select.select(fdset, [], fdset) 87 | if e: 88 | break 89 | 90 | for fd in r: 91 | line = fd.readline() 92 | if not line: 93 | if stage == 2 and fd == p3.stdout: 94 | stage = 3 95 | if stage == 4 and fd == p4.stdout: 96 | stage = 5 97 | if bytes != str: 98 | line = str(line, 'utf8') 99 | sys.stderr.write(line) 100 | if line.find('starting local') >= 0: 101 | local_ready = True 102 | if line.find('starting server') >= 0: 103 | server_ready = True 104 | 105 | if stage == 1: 106 | time.sleep(2) 107 | 108 | p3 = Popen(['curl', config.url, '-v', '-L', 109 | '--socks5-hostname', '127.0.0.1:1081', 110 | '-m', '15', '--connect-timeout', '10'], 111 | stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) 112 | if p3 is not None: 113 | fdset.append(p3.stdout) 114 | fdset.append(p3.stderr) 115 | stage = 2 116 | else: 117 | sys.exit(1) 118 | 119 | if stage == 3 and p3 is not None: 120 | fdset.remove(p3.stdout) 121 | fdset.remove(p3.stderr) 122 | r = p3.wait() 123 | if config.should_fail: 124 | if r == 0: 125 | sys.exit(1) 126 | else: 127 | if r != 0: 128 | sys.exit(1) 129 | if config.tcp_only: 130 | break 131 | p4 = Popen(['socksify', 'dig', '@%s' % config.dns, 132 | 'www.google.com'], 133 | stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) 134 | if p4 is not None: 135 | fdset.append(p4.stdout) 136 | fdset.append(p4.stderr) 137 | stage = 4 138 | else: 139 | sys.exit(1) 140 | 141 | if stage == 5: 142 | r = p4.wait() 143 | if config.should_fail: 144 | if r == 0: 145 | sys.exit(1) 146 | print('test passed (expecting failure)') 147 | else: 148 | if r != 0: 149 | sys.exit(1) 150 | print('test passed') 151 | break 152 | finally: 153 | for p in [p1, p2]: 154 | try: 155 | os.kill(p.pid, signal.SIGINT) 156 | os.waitpid(p.pid, 0) 157 | except OSError: 158 | pass 159 | -------------------------------------------------------------------------------- /tests/test_command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . tests/assert.sh 4 | 5 | PYTHON="coverage run -p" 6 | LOCAL="$PYTHON shadowsocks/local.py" 7 | SERVER="$PYTHON shadowsocks/server.py" 8 | 9 | assert "$LOCAL --version 2>&1 | grep Shadowsocks | awk -F\" \" '{print \$1}'" "Shadowsocks" 10 | assert "$SERVER --version 2>&1 | grep Shadowsocks | awk -F\" \" '{print \$1}'" "Shadowsocks" 11 | 12 | 13 | assert "$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: server set to listen on 127.0.0.1:8388, are you sure?" 14 | $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop 15 | 16 | assert "$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 0.0.0.0 -p 8388 -t10 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: your timeout 10 seems too short" 17 | $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop 18 | 19 | assert "$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 0.0.0.0 -p 8388 -t1000 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: your timeout 1000 seems too long" 20 | $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop 21 | 22 | assert "$LOCAL 2>&1 -m rc4 -k testrc4 -s 0.0.0.0 -p 8388 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: RC4 is not safe; please use a safer cipher, like AES-256-CFB" 23 | $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop 24 | 25 | assert "$LOCAL 2>&1 -m rc4-md5 -k mypassword -s 0.0.0.0 -p 8388 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " DON'T USE DEFAULT PASSWORD! Please change it in your config.json!" 26 | $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop 27 | 28 | 29 | assert "$SERVER 2>&1 --forbidden-ip 127.0.0.1/4a -m rc4-md5 -k 12345 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": Not a valid CIDR notation: 127.0.0.1/4a" 30 | $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop 31 | 32 | assert_end command 33 | -------------------------------------------------------------------------------- /tests/test_daemon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function run_test { 4 | expected=$1 5 | shift 6 | echo "running test: $command $@" 7 | $command $@ 8 | status=$? 9 | if [ $status -ne $expected ]; then 10 | echo "exit $status != $expected" 11 | exit 1 12 | fi 13 | echo "exit status $status == $expected" 14 | echo OK 15 | return 16 | } 17 | 18 | for module in local server 19 | do 20 | 21 | command="coverage run -p shadowsocks/$module.py" 22 | 23 | mkdir -p tmp 24 | 25 | run_test 0 -c tests/aes.json -d stop --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 26 | 27 | run_test 0 -c tests/aes.json -d start --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 28 | run_test 0 -c tests/aes.json -d stop --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 29 | 30 | run_test 0 -c tests/aes.json -d start --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 31 | run_test 1 -c tests/aes.json -d start --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 32 | run_test 0 -c tests/aes.json -d stop --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 33 | 34 | run_test 0 -c tests/aes.json -d start --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 35 | run_test 0 -c tests/aes.json -d restart --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 36 | run_test 0 -c tests/aes.json -d stop --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 37 | 38 | run_test 0 -c tests/aes.json -d restart --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 39 | run_test 0 -c tests/aes.json -d stop --pid-file tmp/shadowsocks.pid --log-file tmp/shadowsocks.log 40 | 41 | run_test 1 -c tests/aes.json -d start --pid-file tmp/not_exist/shadowsocks.pid --log-file tmp/shadowsocks.log 42 | 43 | done 44 | -------------------------------------------------------------------------------- /tests/test_large_file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PYTHON="coverage run -p" 4 | URL=http://127.0.0.1/file 5 | 6 | mkdir -p tmp 7 | 8 | $PYTHON shadowsocks/local.py -c tests/aes.json & 9 | LOCAL=$! 10 | 11 | $PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" & 12 | SERVER=$! 13 | 14 | sleep 3 15 | 16 | time curl -o tmp/expected $URL 17 | time curl -o tmp/result --socks5-hostname 127.0.0.1:1081 $URL 18 | 19 | kill -s SIGINT $LOCAL 20 | kill -s SIGINT $SERVER 21 | 22 | sleep 2 23 | 24 | diff tmp/expected tmp/result || exit 1 25 | -------------------------------------------------------------------------------- /tests/test_udp_src.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import socket 4 | import socks 5 | 6 | 7 | SERVER_IP = '127.0.0.1' 8 | SERVER_PORT = 1081 9 | 10 | 11 | if __name__ == '__main__': 12 | # Test 1: same source port IPv4 13 | sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, 14 | socket.SOL_UDP) 15 | sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) 16 | sock_out.bind(('127.0.0.1', 9000)) 17 | 18 | sock_in1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 19 | socket.SOL_UDP) 20 | sock_in2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 21 | socket.SOL_UDP) 22 | 23 | sock_in1.bind(('127.0.0.1', 9001)) 24 | sock_in2.bind(('127.0.0.1', 9002)) 25 | 26 | sock_out.sendto(b'data', ('127.0.0.1', 9001)) 27 | result1 = sock_in1.recvfrom(8) 28 | 29 | sock_out.sendto(b'data', ('127.0.0.1', 9002)) 30 | result2 = sock_in2.recvfrom(8) 31 | 32 | sock_out.close() 33 | sock_in1.close() 34 | sock_in2.close() 35 | 36 | # make sure they're from the same source port 37 | assert result1 == result2 38 | 39 | # Test 2: same source port IPv6 40 | # try again from the same port but IPv6 41 | sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, 42 | socket.SOL_UDP) 43 | sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) 44 | sock_out.bind(('127.0.0.1', 9000)) 45 | 46 | sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 47 | socket.SOL_UDP) 48 | sock_in2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 49 | socket.SOL_UDP) 50 | 51 | sock_in1.bind(('::1', 9001)) 52 | sock_in2.bind(('::1', 9002)) 53 | 54 | sock_out.sendto(b'data', ('::1', 9001)) 55 | result1 = sock_in1.recvfrom(8) 56 | 57 | sock_out.sendto(b'data', ('::1', 9002)) 58 | result2 = sock_in2.recvfrom(8) 59 | 60 | sock_out.close() 61 | sock_in1.close() 62 | sock_in2.close() 63 | 64 | # make sure they're from the same source port 65 | assert result1 == result2 66 | 67 | # Test 3: different source ports IPv6 68 | sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, 69 | socket.SOL_UDP) 70 | sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) 71 | sock_out.bind(('127.0.0.1', 9003)) 72 | 73 | sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 74 | socket.SOL_UDP) 75 | sock_in1.bind(('::1', 9001)) 76 | sock_out.sendto(b'data', ('::1', 9001)) 77 | result3 = sock_in1.recvfrom(8) 78 | 79 | # make sure they're from different source ports 80 | assert result1 != result3 81 | 82 | sock_out.close() 83 | sock_in1.close() 84 | -------------------------------------------------------------------------------- /tests/test_udp_src.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PYTHON="coverage run -p" 4 | 5 | mkdir -p tmp 6 | 7 | $PYTHON shadowsocks/local.py -c tests/aes.json -v & 8 | LOCAL=$! 9 | 10 | $PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" -v & 11 | SERVER=$! 12 | 13 | sleep 3 14 | 15 | python tests/test_udp_src.py 16 | r=$? 17 | 18 | kill -s SIGINT $LOCAL 19 | kill -s SIGINT $SERVER 20 | 21 | sleep 2 22 | 23 | exit $r 24 | -------------------------------------------------------------------------------- /tests/workers.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"127.0.0.1", 3 | "server_port":8388, 4 | "local_port":1081, 5 | "password":"workers_password", 6 | "timeout":60, 7 | "method":"aes-256-cfb", 8 | "local_address":"127.0.0.1", 9 | "workers": 4 10 | } 11 | -------------------------------------------------------------------------------- /utils/README.md: -------------------------------------------------------------------------------- 1 | Useful Tools 2 | =========== 3 | 4 | autoban.py 5 | ---------- 6 | 7 | Automatically ban IPs that try to brute force crack the server. 8 | 9 | See https://github.com/shadowsocks/shadowsocks/wiki/Ban-Brute-Force-Crackers 10 | -------------------------------------------------------------------------------- /utils/autoban.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2015 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | from __future__ import absolute_import, division, print_function, \ 25 | with_statement 26 | 27 | import os 28 | import sys 29 | import argparse 30 | 31 | if __name__ == '__main__': 32 | parser = argparse.ArgumentParser(description='See README') 33 | parser.add_argument('-c', '--count', default=3, type=int, 34 | help='with how many failure times it should be ' 35 | 'considered as an attack') 36 | config = parser.parse_args() 37 | ips = {} 38 | banned = set() 39 | for line in sys.stdin: 40 | if 'can not parse header when' in line: 41 | ip = line.split()[-1].split(':')[0] 42 | if ip not in ips: 43 | ips[ip] = 1 44 | print(ip) 45 | sys.stdout.flush() 46 | else: 47 | ips[ip] += 1 48 | if ip not in banned and ips[ip] >= config.count: 49 | banned.add(ip) 50 | cmd = 'iptables -A INPUT -s %s -j DROP' % ip 51 | print(cmd, file=sys.stderr) 52 | sys.stderr.flush() 53 | os.system(cmd) 54 | -------------------------------------------------------------------------------- /utils/fail2ban/shadowsocks.conf: -------------------------------------------------------------------------------- 1 | [Definition] 2 | 3 | _daemon = shadowsocks 4 | 5 | failregex = ^\s+ERROR\s+can not parse header when handling connection from :\d+$ 6 | -------------------------------------------------------------------------------- /webapi_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import requests 6 | from configloader import load_config, get_config 7 | from collections import OrderedDict 8 | 9 | class WebApi(object): 10 | 11 | def __init__(self): 12 | self.session_pool = requests.Session() 13 | 14 | def getApi(self, uri, params={}): 15 | res = None 16 | try: 17 | uri_params = params.copy() 18 | uri_params['key'] = get_config().WEBAPI_TOKEN 19 | res = self.session_pool.get( 20 | '%s/mod_mu/%s' % 21 | (get_config().WEBAPI_URL, uri), 22 | params=uri_params, 23 | timeout=10) 24 | try: 25 | data = res.json() 26 | except Exception: 27 | if res: 28 | logging.error("Error data:%s" % (res.text)) 29 | raise Exception('error data!') 30 | if data['ret'] == 0: 31 | logging.error("Error data:%s" % (res.text)) 32 | logging.error("request %s error!wrong ret!"%(uri)) 33 | raise Exception('wrong ret!') 34 | return data['data'] 35 | except Exception: 36 | import traceback 37 | trace = traceback.format_exc() 38 | logging.error(trace) 39 | raise Exception('network issue or server error!') 40 | 41 | 42 | def postApi(self, uri, params={}, raw_data={}): 43 | res = None 44 | try: 45 | uri_params = params.copy() 46 | uri_params['key'] = get_config().WEBAPI_TOKEN 47 | res = self.session_pool.post( 48 | '%s/mod_mu/%s' % 49 | (get_config().WEBAPI_URL, 50 | uri), 51 | params=uri_params, 52 | json=raw_data, 53 | timeout=10) 54 | try: 55 | data = res.json() 56 | except Exception: 57 | if res: 58 | logging.error("Error data:%s" % (res.text)) 59 | raise Exception('error data!') 60 | if data['ret'] == 0: 61 | logging.error("Error data:%s" % (res.text)) 62 | logging.error("request %s error!wrong ret!"%(uri)) 63 | raise Exception('wrong ret!') 64 | return data['data'] 65 | except Exception: 66 | import traceback 67 | trace = traceback.format_exc() 68 | logging.error(trace) 69 | raise Exception('network issue or server error!') 70 | --------------------------------------------------------------------------------