diff --git a/.gitignore b/.gitignore index e6f83bd..fb96264 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ pip-log.txt # Unit test / coverage reports htmlcov -.coverage* +.coverage .tox #Translations @@ -29,17 +29,3 @@ htmlcov .DS_Store .idea -tags - -#Emacs -.#* -venv/ - -# VS-code -.vscode/ - -# Pycharm -.idea - -#ss -config.json diff --git a/.jenkins.sh b/.jenkins.sh new file mode 100755 index 0000000..4c85f1c --- /dev/null +++ b/.jenkins.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +result=0 + +function run_test { + printf '\e[0;36m' + echo "running test: $command $@" + printf '\e[0m' + + $command "$@" + status=$? + if [ $status -ne 0 ]; then + printf '\e[0;31m' + echo "test failed: $command $@" + printf '\e[0m' + echo + result=1 + else + printf '\e[0;32m' + echo OK + printf '\e[0m' + echo + fi + return 0 +} + +coverage erase +mkdir tmp +run_test pep8 . +run_test pyflakes . +run_test coverage run tests/nose_plugin.py -v +run_test python setup.py sdist +run_test tests/test_daemon.sh +run_test python tests/test.py --with-coverage -c tests/aes.json +run_test python tests/test.py --with-coverage -c tests/aes-ctr.json +run_test python tests/test.py --with-coverage -c tests/aes-cfb1.json +run_test python tests/test.py --with-coverage -c tests/aes-cfb8.json +run_test python tests/test.py --with-coverage -c tests/rc4-md5.json +run_test python tests/test.py --with-coverage -c tests/salsa20.json +run_test python tests/test.py --with-coverage -c tests/chacha20.json +run_test python tests/test.py --with-coverage -c tests/salsa20-ctr.json +run_test python tests/test.py --with-coverage -c tests/table.json +run_test python tests/test.py --with-coverage -c tests/server-multi-ports.json +run_test python tests/test.py --with-coverage -s tests/server-multi-passwd.json -c tests/server-multi-passwd-client-side.json +run_test python tests/test.py --with-coverage -c tests/workers.json +run_test python tests/test.py --with-coverage -s tests/ipv6.json -c tests/ipv6-client-side.json +run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -q" -a "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -vv" +run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" + +if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then + if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then + # we have to run it twice: + # the first time there's no syn cookie + # the second time there is syn cookie + run_test python tests/test.py --with-coverage -c tests/fastopen.json + run_test python tests/test.py --with-coverage -c tests/fastopen.json + fi +fi + +run_test tests/test_large_file.sh + +run_test tests/test_command.sh + +coverage combine && coverage report --include=shadowsocks/* +rm -rf htmlcov +rm -rf tmp +coverage html --include=shadowsocks/* + +coverage report --include=shadowsocks/* | tail -n1 | rev | cut -d' ' -f 1 | rev > /tmp/shadowsocks-coverage + +exit $result diff --git a/.travis.yml b/.travis.yml index f11e179..4fbe78c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,15 +9,12 @@ cache: - dante-1.4.0 before_install: - sudo apt-get update -qq - - sudo apt-get install -qq build-essential dnsutils iproute nginx bc - - sudo dd if=/dev/urandom of=/usr/share/nginx/html/file bs=1M count=10 - - sudo sh -c "echo '127.0.0.1 localhost' > /etc/hosts" + - sudo apt-get install -qq build-essential libssl-dev swig python-m2crypto python-numpy dnsutils iproute nginx bc + - sudo dd if=/dev/urandom of=/usr/share/nginx/www/file bs=1M count=10 - sudo service nginx restart - - pip install pep8 pyflakes nose coverage PySocks + - pip install m2crypto salsa20 pep8 pyflakes nose coverage - sudo tests/socksify/install.sh - sudo tests/libsodium/install.sh - - sudo tests/libmbedtls/install.sh - - sudo tests/libopenssl/install.sh - sudo tests/setup_tc.sh script: - - tests/jenkins.sh + - ./.jenkins.sh diff --git a/CHANGES b/CHANGES index d6fe932..6822082 100644 --- a/CHANGES +++ b/CHANGES @@ -1,52 +1,3 @@ -2.8.2 2015-08-10 -- Fix a encoding problem in manager - -2.8.1 2015-08-06 -- Respond ok to add and remove commands - -2.8 2015-08-06 -- Add Shadowsocks manager - -2.7 2015-08-02 -- Optimize speed for multiple ports - -2.6.11 2015-07-10 -- Fix a compatibility issue in UDP Relay - -2.6.10 2015-06-08 -- Optimize LRU cache -- Refine logging - -2.6.9 2015-05-19 -- Fix a stability issue on Windows - -2.6.8 2015-02-10 -- Support multiple server ip on client side -- Support --version -- Minor fixes - -2.6.7 2015-02-02 -- Support --user -- Support CIDR format in --forbidden-ip -- Minor fixes - -2.6.6 2015-01-23 -- Fix a crash in forbidden list - -2.6.5 2015-01-18 -- Try both 32 bit and 64 bit dll on Windows - -2.6.4 2015-01-14 -- Also search lib* when searching libraries - -2.6.3 2015-01-12 -- Support --forbidden-ip to ban some IP, i.e. localhost -- Search OpenSSL and libsodium harder -- Now works on OpenWRT - -2.6.2 2015-01-03 -- Log client IP - 2.6.1 2014-12-26 - Fix a problem with TCP Fast Open on local side - Fix sometimes daemon_start returns wrong exit status diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d94e0b..c164423 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,31 +1,38 @@ -How to Contribute +How to contribute ================= -Notice this is the repository for Shadowsocks Python version. If you have problems with Android / iOS / Windows etc clients, please post your questions in their issue trackers. +在你提交问题前,请先[自行诊断]一下。提交时附上诊断过程中的问题和下列结果, +否则如果我们无法重现你的问题,也就不能帮助你。 -Pull Requests -------------- +Before you submit issues, please read [Troubleshooting] and take a few minutes +to read this guide. -1. Pull requests are welcome. If you would like to add a large feature -or make a significant change, make sure to open an issue to discuss with -people first. -2. Follow PEP8. -3. Make sure to pass the unit tests. Write unit tests for new modules if -needed. +问题反馈 +------- + +请提交下面的信息: + +1. 你是如何搭建环境的?(操作系统,Shadowsocks 版本) +2. 操作步骤是什么? +3. 浏览器里的现象是什么?一直转菊花,还是有提示错误? +4. 发生错误时,客户端和服务端最后一部分日志。 +5. 其它你认为可能和问题有关的信息。 + +如果你不清楚其中某条的含义, 可以直接跳过那一条。 Issues ------ -1. Only bugs and feature requests are accepted here. -2. We'll only work on important features. If the feature you're asking only -benefits a few people, you'd better implement the feature yourself and send us -a pull request, or ask some of your friends to do so. -3. We don't answer questions of any other types here. Since very few people -are watching the issue tracker here, you'll probably get no help from here. -Read [Troubleshooting] and get help from forums or [mailing lists]. -4. Issues in languages other than English will be Google translated into English -later. +Please include the following information in your submission: +1. How did you set up your environment? (OS, version of Shadowsocks) +2. Steps to reproduce the problem. +3. What happened in your browser? Just no response, or any error message? +4. 10 lines of log on the local side of shadowsocks when the error happened. +5. 10 lines of log on the server side of shadowsocks when the error happened. +6. Any other useful information. + +Skip any of them if you don't know its meaning. [Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting -[mailing lists]: https://groups.google.com/forum/#!forum/shadowsocks +[自行诊断]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 510d422..0000000 --- a/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM ubuntu:14.04 - -RUN apt-get update && apt-get install -y \ - python-software-properties \ - software-properties-common \ - && add-apt-repository ppa:chris-lea/libsodium \ - && echo "deb http://ppa.launchpad.net/chris-lea/libsodium/ubuntu trusty main" >> /etc/apt/sources.list \ - && echo "deb-src http://ppa.launchpad.net/chris-lea/libsodium/ubuntu trusty main" >> /etc/apt/sources.list \ - && apt-get update \ - && apt-get install -y libsodium-dev python-pip - -RUN pip install shadowsocks - -ENTRYPOINT ["/usr/local/bin/ssserver"] - -# usage: -# docker run -d --restart=always -p 1314:1314 ficapy/shadowsocks -s 0.0.0.0 -p 1314 -k $PD -m chacha20 diff --git a/LICENSE b/LICENSE index d645695..98f608b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,202 +1,21 @@ +Shadowsocks - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Copyright (c) 2014 clowwindy - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - 1. Definitions. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 1882dd7..1dc4c8e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ -recursive-include shadowsocks *.py +recursive-include *.py include README.rst include LICENSE diff --git a/README.md b/README.md index d87232a..621d2d9 100644 --- a/README.md +++ b/README.md @@ -3,95 +3,138 @@ shadowsocks [![PyPI version]][PyPI] [![Build Status]][Travis CI] +[![Coverage Status]][Coverage] A fast tunnel proxy that helps you bypass firewalls. -Features: -- TCP & UDP support -- User management API -- TCP Fast Open -- Workers and graceful restart -- Destination IP blacklist +[中文说明][Chinese Readme] -Server ------- +Install +------- -### Install +You'll have a client on your local side, and setup a server on a +remote server. -Debian / Ubuntu: +### Client + +* [Windows] / [OS X] +* [Android] / [iOS] +* [OpenWRT] + +### Server + +#### Debian / Ubuntu: apt-get install python-pip - pip install git+https://github.com/shadowsocks/shadowsocks.git@master + pip install shadowsocks -CentOS: +Or simply `apt-get install shadowsocks` if you have [Debian sid] in your +source list. - yum install python-setuptools && easy_install pip - pip install git+https://github.com/shadowsocks/shadowsocks.git@master +#### CentOS: -For CentOS 7, if you need AEAD ciphers, you need install libsodium -``` -dnf install libsodium python34-pip -pip3 install git+https://github.com/shadowsocks/shadowsocks.git@master -``` -Linux distributions with [snap](http://snapcraft.io/): + yum install python-setuptools + easy_install pip + pip install shadowsocks - snap install shadowsocks +#### Windows: -Windows: +Download [OpenSSL for Windows] and install. Then install shadowsocks via +easy_install and pip as Linux. If you don't know how to use them, you can +directly download [the package], and use `python shadowsocks/server.py` +instead of `ssserver` command below. -See [Install Shadowsocks Server on Windows](https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows). +Configuration +------------- -### Usage +On your server create a config file `/etc/shadowsocks.json`. +Example: - ssserver -p 443 -k password -m aes-256-cfb + { + "server":"my_server_ip", + "server_port":8388, + "local_address": "127.0.0.1", + "local_port":1080, + "password":"mypassword", + "timeout":300, + "method":"aes-256-cfb", + "fast_open": false + } -To run in the background: +Explanation of the fields: - sudo ssserver -p 443 -k password -m aes-256-cfb --user nobody -d start +| Name | Explanation | +| ------------- | ----------------------------------------------- | +| server | the address your server listens | +| server_port | server port | +| local_address | the address your local listens | +| local_port | local port | +| password | password used for encryption | +| timeout | in seconds | +| method | default: "aes-256-cfb", see [Encryption] | +| fast_open | use [TCP_FASTOPEN], true / false | +| workers | number of workers, available on Unix/Linux | -To stop: +On your server: - sudo ssserver -d stop - -To check the log: - - sudo less /var/log/shadowsocks.log - -Check all the options via `-h`. You can also use a [Configuration] file -instead. - -If you installed the [snap](http://snapcraft.io/) package, you have to prefix the commands with `shadowsocks.`, -like this: - - shadowsocks.ssserver -p 443 -k password -m aes-256-cfb - -### Usage with Config File - -[Create configuration file and run](https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File) - -To start: +To run in the foreground: ssserver -c /etc/shadowsocks.json +To run in the background: + + ssserver -c /etc/shadowsocks.json -d start + ssserver -c /etc/shadowsocks.json -d stop + +On your client machine, use the same configuration as your server. Check the +README of your client for more information. + +Command Line Options +-------------------- + +Check the options via `-h`.You can use args to override settings from +`config.json`. + + sslocal -s server_name -p server_port -l local_port -k password -m bf-cfb + ssserver -p server_port -k password -m bf-cfb --workers 2 + ssserver -c /etc/shadowsocks/config.json -d start --pid-file=/tmp/shadowsocks.pid + ssserver -c /etc/shadowsocks/config.json -d stop --pid-file=/tmp/shadowsocks.pid Documentation ------------- -You can find all the documentation in the [Wiki](https://github.com/shadowsocks/shadowsocks/wiki). +You can find all the documentation in the wiki: +https://github.com/clowwindy/shadowsocks/wiki License ------- +MIT -Apache License +Bugs and Issues +---------------- + +* [Troubleshooting] +* [Issue Tracker] +* [Mailing list] - - - - - -[Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat +[Android]: https://github.com/clowwindy/shadowsocks/wiki/Ports-and-Clients#android +[Build Status]: https://img.shields.io/travis/clowwindy/shadowsocks/master.svg?style=flat +[Chinese Readme]: https://github.com/clowwindy/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E +[Coverage Status]: http://192.81.132.184/result/shadowsocks +[Coverage]: http://192.81.132.184/job/Shadowsocks/ws/htmlcov/index.html +[Debian sid]: https://packages.debian.org/unstable/python/shadowsocks +[the package]: https://pypi.python.org/pypi/shadowsocks +[Encryption]: https://github.com/clowwindy/shadowsocks/wiki/Encryption +[iOS]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help +[Issue Tracker]: https://github.com/clowwindy/shadowsocks/issues?state=open +[Mailing list]: http://groups.google.com/group/shadowsocks +[OpenSSL for Windows]: http://slproweb.com/products/Win32OpenSSL.html +[OpenWRT]: https://github.com/clowwindy/shadowsocks/wiki/Ports-and-Clients#openwrt +[OS X]: https://github.com/shadowsocks/shadowsocks-iOS/wiki/Shadowsocks-for-OSX-Help [PyPI]: https://pypi.python.org/pypi/shadowsocks [PyPI version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat -[Travis CI]: https://travis-ci.org/shadowsocks/shadowsocks - +[TCP_FASTOPEN]: https://github.com/clowwindy/shadowsocks/wiki/TCP-Fast-Open +[Travis CI]: https://travis-ci.org/clowwindy/shadowsocks +[Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting +[Windows]: https://github.com/clowwindy/shadowsocks/wiki/Ports-and-Clients#windows diff --git a/README.rst b/README.rst index fe6fd51..003b358 100644 --- a/README.rst +++ b/README.rst @@ -1,118 +1,155 @@ -About shadowsocks-rm ---------------- - -This project is https://github.com/shadowsocks/shadowsocks clone. I JUST fix bug on the original code. Unless it is necessary to have additional features. - shadowsocks =========== -|PyPI version| |Build Status| |Coverage Status| +|PyPI version| |Build Status| A fast tunnel proxy that helps you bypass firewalls. -Server ------- +`中文说明 `__ Install -~~~~~~~ +------- + +You'll have a client on your local side, and setup a server on a remote +server. + +Client +~~~~~~ + +- `Windows `__ + / `OS + X `__ +- `Android `__ + / `iOS `__ +- `OpenWRT `__ + +Server +~~~~~~ Debian / Ubuntu: +^^^^^^^^^^^^^^^^ :: apt-get install python-pip pip install shadowsocks +Or simply ``apt-get install shadowsocks`` if you have `Debian +sid `__ in your +source list. + CentOS: +^^^^^^^ :: - yum install python-setuptools && easy_install pip + yum install python-setuptools + easy_install pip pip install shadowsocks Windows: +^^^^^^^^ -See `Install Server on -Windows `__ +Download `OpenSSL for +Windows `__ and install. +Then install shadowsocks via easy\_install and pip as Linux. If you +don't know how to use them, you can directly download `the +package `__, and use +``python shadowsocks/server.py`` instead of ``ssserver`` command below. -Usage -~~~~~ +Configuration +------------- + +On your server create a config file ``/etc/shadowsocks.json``. Example: :: - ssserver -p 443 -k password -m rc4-md5 + { + "server":"my_server_ip", + "server_port":8388, + "local_address": "127.0.0.1", + "local_port":1080, + "password":"mypassword", + "timeout":300, + "method":"aes-256-cfb", + "fast_open": false + } + +Explanation of the fields: + ++------------------+---------------------------------------------------------------------------------------------------------+ +| Name | Explanation | ++==================+=========================================================================================================+ +| server | the address your server listens | ++------------------+---------------------------------------------------------------------------------------------------------+ +| server\_port | server port | ++------------------+---------------------------------------------------------------------------------------------------------+ +| local\_address | the address your local listens | ++------------------+---------------------------------------------------------------------------------------------------------+ +| local\_port | local port | ++------------------+---------------------------------------------------------------------------------------------------------+ +| password | password used for encryption | ++------------------+---------------------------------------------------------------------------------------------------------+ +| timeout | in seconds | ++------------------+---------------------------------------------------------------------------------------------------------+ +| method | default: "aes-256-cfb", see `Encryption `__ | ++------------------+---------------------------------------------------------------------------------------------------------+ +| fast\_open | use `TCP\_FASTOPEN `__, true / false | ++------------------+---------------------------------------------------------------------------------------------------------+ +| workers | number of workers, available on Unix/Linux | ++------------------+---------------------------------------------------------------------------------------------------------+ + +On your server: + +To run in the foreground: + +:: + + ssserver -c /etc/shadowsocks.json To run in the background: :: - sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start + ssserver -c /etc/shadowsocks.json -d start + ssserver -c /etc/shadowsocks.json -d stop -To stop: +On your client machine, use the same configuration as your server. Check +the README of your client for more information. + +Command Line Options +-------------------- + +Check the options via ``-h``.You can use args to override settings from +``config.json``. :: - sudo ssserver -d stop - -To check the log: - -:: - - sudo less /var/log/shadowsocks.log - -Check all the options via ``-h``. You can also use a -`Configuration `__ -file instead. - -Client ------- - -- `Windows `__ - / `OS - X `__ -- `Android `__ - / `iOS `__ -- `OpenWRT `__ - -Use GUI clients on your local PC/phones. Check the README of your client -for more information. + sslocal -s server_name -p server_port -l local_port -k password -m bf-cfb + ssserver -p server_port -k password -m bf-cfb --workers 2 + ssserver -c /etc/shadowsocks/config.json -d start --pid-file=/tmp/shadowsocks.pid + ssserver -c /etc/shadowsocks/config.json -d stop --pid-file=/tmp/shadowsocks.pid Documentation ------------- -You can find all the documentation in the -`Wiki `__. +You can find all the documentation in the wiki: +https://github.com/clowwindy/shadowsocks/wiki License ------- -Copyright 2015 clowwindy - -Licensed under the Apache License, Version 2.0 (the "License"); you may -not use this file except in compliance with the License. You may obtain -a copy of the License at - -:: - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +MIT Bugs and Issues --------------- -- `Troubleshooting `__ +- `Troubleshooting `__ - `Issue - Tracker `__ -- `Mailing list `__ + Tracker `__ +- `Mailing list `__ .. |PyPI version| image:: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat :target: https://pypi.python.org/pypi/shadowsocks -.. |Build Status| image:: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat - :target: https://travis-ci.org/shadowsocks/shadowsocks -.. |Coverage Status| image:: https://jenkins.shadowvpn.org/result/shadowsocks - :target: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html +.. |Build Status| image:: https://img.shields.io/travis/clowwindy/shadowsocks/master.svg?style=flat + :target: https://travis-ci.org/clowwindy/shadowsocks diff --git a/config.json.example b/config.json.example deleted file mode 100644 index a1f9125..0000000 --- a/config.json.example +++ /dev/null @@ -1,17 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1080, - "password":"password", - "timeout":600, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false, - "tunnel_remote":"8.8.8.8", - "dns_server":["8.8.8.8", "8.8.4.4"], - "tunnel_remote_port":53, - "tunnel_port":53, - "libopenssl":"C:\\Program Files\\Git\\mingw64\\bin\\libeay32.dll", - "libsodium":"/usr/local/lib/libsodium.so", - "libmbedtls":"/usr/local/lib/libmbedcrypto.2.4.0.dylib" -} diff --git a/debian/changelog b/debian/changelog index 2bf239c..4e7ad16 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,26 +1,3 @@ -shadowsocks (2.9.0-2) unstable; urgency=medium - - [ Shell.Xu ] - * Fix compatible issue (Closes: #845016) - - -- Shell.Xu Sun, 20 Nov 2016 14:33:31 +0800 - -shadowsocks (2.9.0-1) unstable; urgency=medium - - [ Shell Xu ] - * Upstream update (Closes: #824640) - * Remove reference not exists (Closes: #810688) - - [ Thomas Goirand ] - * Added lsb-base as Depends:. - * Removed Pre-Depends: dpkg (>= 1.15.6~). - * Ran wrap-and-sort -t -a. - * Fixed VCS URLs to use https. - * Removed useless obsolete version of python-all build-depends. - * Fixed debian/copyright ordering. - - -- Shell.Xu Sat, 01 Oct 2016 16:14:47 +0800 - shadowsocks (2.1.0-1) unstable; urgency=low * Initial release (Closes: #758900) diff --git a/debian/config.json b/debian/config.json index 7846cb2..35cb14a 100644 --- a/debian/config.json +++ b/debian/config.json @@ -7,6 +7,5 @@ "timeout":300, "method":"aes-256-cfb", "fast_open": false, - "workers": 1, - "prefer_ipv6": false + "workers": 1 } \ No newline at end of file diff --git a/debian/control b/debian/control index fa0b1ec..da00920 100644 --- a/debian/control +++ b/debian/control @@ -2,23 +2,18 @@ Source: shadowsocks Section: python Priority: extra Maintainer: Shell.Xu -Build-Depends: debhelper (>= 8), - python-all, - python-setuptools, -Standards-Version: 3.9.8 -Homepage: https://github.com/shadowsocks/shadowsocks -Vcs-Git: https://github.com/shell909090/shadowsocks.git -Vcs-Browser: https://github.com/shell909090/shadowsocks +Build-Depends: debhelper (>= 8), python-all (>= 2.6.6-3~), python-setuptools +Standards-Version: 3.9.5 +Homepage: https://github.com/clowwindy/shadowsocks +Vcs-Git: git://github.com/shell909090/shadowsocks.git +Vcs-Browser: http://github.com/shell909090/shadowsocks Package: shadowsocks Architecture: all -Depends: lsb-base (>= 3.0-6), - python-m2crypto, - python-pkg-resources, - ${misc:Depends}, - ${python:Depends}, +Pre-Depends: dpkg (>= 1.15.6~) +Depends: ${misc:Depends}, ${python:Depends}, python-pkg-resources, python-m2crypto Description: Fast tunnel proxy that helps you bypass firewalls A secure socks5 proxy, designed to protect your Internet traffic. . This package contain local and server part of shadowsocks, a fast, - powerful tunnel proxy to bypass firewalls. + powerful tunnel proxy to bypass firewalls. \ No newline at end of file diff --git a/debian/copyright b/debian/copyright index 14f0851..7be8162 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,27 +1,30 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: shadowsocks -Source: https://github.com/shadowsocks/shadowsocks +Source: https://github.com/clowwindy/shadowsocks + +Files: debian/* +Copyright: 2014 Shell.Xu +License: Expat Files: * Copyright: 2014 clowwindy -License: Apache-2.0 +License: Expat -Files: debian/* -Copyright: 2016 Shell.Xu -License: Apache-2.0 - -License: Apache-2.0 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. . - http://www.apache.org/licenses/LICENSE-2.0 - . - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - . - On Debian systems, the complete text of the Apache License 2.0 can - be found in "/usr/share/common-licenses/Apache-2.0" + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/debian/install b/debian/install index a16a450..a614864 100644 --- a/debian/install +++ b/debian/install @@ -1 +1 @@ -debian/config.json etc/shadowsocks/ +debian/config.json etc/shadowsocks/ \ No newline at end of file diff --git a/debian/shadowsocks.manpages b/debian/shadowsocks.manpages index 98b6847..3df8a33 100644 --- a/debian/shadowsocks.manpages +++ b/debian/shadowsocks.manpages @@ -1,2 +1,2 @@ debian/sslocal.1 -debian/ssserver.1 +debian/ssserver.1 \ No newline at end of file diff --git a/debian/sslocal.1 b/debian/sslocal.1 index 05ef510..0c2cf51 100644 --- a/debian/sslocal.1 +++ b/debian/sslocal.1 @@ -55,5 +55,5 @@ Quiet mode, only show warnings/errors. The programs are documented fully by .IR "Shell Xu " and -.IR "Clowwindy " -. +.IR "Clowwindy ", +available via the Info system. diff --git a/debian/ssserver.1 b/debian/ssserver.1 index 05ef510..0c2cf51 100644 --- a/debian/ssserver.1 +++ b/debian/ssserver.1 @@ -55,5 +55,5 @@ Quiet mode, only show warnings/errors. The programs are documented fully by .IR "Shell Xu " and -.IR "Clowwindy " -. +.IR "Clowwindy ", +available via the Info system. diff --git a/setup.py b/setup.py index f41f481..442a3ac 100644 --- a/setup.py +++ b/setup.py @@ -7,12 +7,12 @@ with codecs.open('README.rst', encoding='utf-8') as f: setup( name="shadowsocks", - version="3.0.0", - license='http://www.apache.org/licenses/LICENSE-2.0', + version="2.6.1", + license='MIT', description="A fast tunnel proxy that help you get through firewalls", author='clowwindy', author_email='clowwindy42@gmail.com', - url='https://github.com/shadowsocks/shadowsocks', + url='https://github.com/clowwindy/shadowsocks', packages=['shadowsocks', 'shadowsocks.crypto'], package_data={ 'shadowsocks': ['README.rst', 'LICENSE'] @@ -24,7 +24,7 @@ setup( ssserver = shadowsocks.server:main """, classifiers=[ - 'License :: OSI Approved :: Apache Software License', + 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', diff --git a/shadowsocks/__init__.py b/shadowsocks/__init__.py index dc3abd4..5ba5908 100644 --- a/shadowsocks/__init__.py +++ b/shadowsocks/__init__.py @@ -1,18 +1,24 @@ #!/usr/bin/python + +# Copyright (c) 2014 clowwindy # -# Copyright 2012-2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/asyncdns.py b/shadowsocks/asyncdns.py index fa5be41..18222a6 100644 --- a/shadowsocks/asyncdns.py +++ b/shadowsocks/asyncdns.py @@ -1,35 +1,42 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2014-2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement +import time import os import socket import struct import re import logging -from shadowsocks import common, lru_cache, eventloop, shell +from shadowsocks import common, lru_cache, eventloop CACHE_SWEEP_INTERVAL = 30 -VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d\-_]{1,63}(? 255: return False @@ -242,29 +262,24 @@ class DNSResponse(object): return '%s: %s' % (self.hostname, str(self.answers)) -STATUS_FIRST = 0 -STATUS_SECOND = 1 +STATUS_IPV4 = 0 +STATUS_IPV6 = 1 class DNSResolver(object): - def __init__(self, server_list=None, prefer_ipv6=False): + def __init__(self): self._loop = None + self._request_id = 1 self._hosts = {} self._hostname_status = {} self._hostname_to_cb = {} self._cb_to_hostname = {} self._cache = lru_cache.LRUCache(timeout=300) + self._last_time = time.time() self._sock = None - if server_list is None: - self._servers = None - self._parse_resolv() - else: - self._servers = server_list - if prefer_ipv6: - self._QTYPES = [QTYPE_AAAA, QTYPE_A] - else: - self._QTYPES = [QTYPE_A, QTYPE_AAAA] + self._servers = None + self._parse_resolv() self._parse_hosts() # TODO monitor hosts change and reload hosts # TODO parse /etc/gai.conf and follow its rules @@ -276,18 +291,15 @@ class DNSResolver(object): content = f.readlines() for line in content: line = line.strip() - if not (line and line.startswith(b'nameserver')): - continue - - parts = line.split() - if len(parts) < 2: - continue - - server = parts[1] - if common.is_ip(server) == socket.AF_INET: - if type(server) != str: - server = server.decode('utf8') - self._servers.append(server) + if line: + if line.startswith(b'nameserver'): + parts = line.split() + if len(parts) >= 2: + server = parts[1] + if is_ip(server) == socket.AF_INET: + if type(server) != str: + server = server.decode('utf8') + self._servers.append(server) except IOError: pass if not self._servers: @@ -302,21 +314,17 @@ class DNSResolver(object): for line in f.readlines(): line = line.strip() parts = line.split() - if len(parts) < 2: - continue - - ip = parts[0] - if not common.is_ip(ip): - continue - - for i in range(1, len(parts)): - hostname = parts[i] - if hostname: - self._hosts[hostname] = ip + if len(parts) >= 2: + ip = parts[0] + if is_ip(ip): + for i in range(1, len(parts)): + hostname = parts[i] + if hostname: + self._hosts[hostname] = ip except IOError: self._hosts['localhost'] = '127.0.0.1' - def add_to_loop(self, loop): + def add_to_loop(self, loop, ref=False): if self._loop: raise Exception('already add to loop') self._loop = loop @@ -324,8 +332,8 @@ class DNSResolver(object): self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) self._sock.setblocking(False) - loop.add(self._sock, eventloop.POLL_IN, self) - loop.add_periodic(self.handle_periodic) + loop.add(self._sock, eventloop.POLL_IN) + loop.add_handler(self.handle_events, ref=ref) def _call_callback(self, hostname, ip, error=None): callbacks = self._hostname_to_cb.get(hostname, []) @@ -352,42 +360,44 @@ class DNSResolver(object): answer[2] == QCLASS_IN: ip = answer[0] break - if not ip and self._hostname_status.get(hostname, STATUS_SECOND) \ - == STATUS_FIRST: - self._hostname_status[hostname] = STATUS_SECOND - self._send_req(hostname, self._QTYPES[1]) + if not ip and self._hostname_status.get(hostname, STATUS_IPV6) \ + == STATUS_IPV4: + self._hostname_status[hostname] = STATUS_IPV6 + self._send_req(hostname, QTYPE_AAAA) else: if ip: self._cache[hostname] = ip self._call_callback(hostname, ip) - elif self._hostname_status.get(hostname, None) \ - == STATUS_SECOND: + elif self._hostname_status.get(hostname, None) == STATUS_IPV6: for question in response.questions: - if question[1] == self._QTYPES[1]: + if question[1] == QTYPE_AAAA: self._call_callback(hostname, None) break - def handle_event(self, sock, fd, event): - if sock != self._sock: - return - if event & eventloop.POLL_ERR: - logging.error('dns socket err') - self._loop.remove(self._sock) - self._sock.close() - # TODO when dns server is IPv6 - self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.SOL_UDP) - self._sock.setblocking(False) - self._loop.add(self._sock, eventloop.POLL_IN, self) - else: - data, addr = sock.recvfrom(1024) - if addr[0] not in self._servers: - logging.warn('received a packet other than our dns') - return - self._handle_data(data) - - def handle_periodic(self): - self._cache.sweep() + def handle_events(self, events): + for sock, fd, event in events: + if sock != self._sock: + continue + if event & eventloop.POLL_ERR: + logging.error('dns socket err') + self._loop.remove(self._sock) + self._sock.close() + # TODO when dns server is IPv6 + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.SOL_UDP) + self._sock.setblocking(False) + self._loop.add(self._sock, eventloop.POLL_IN) + else: + data, addr = sock.recvfrom(1024) + if addr[0] not in self._servers: + logging.warn('received a packet other than our dns') + break + self._handle_data(data) + break + now = time.time() + if now - self._last_time > CACHE_SWEEP_INTERVAL: + self._cache.sweep() + self._last_time = now def remove_callback(self, callback): hostname = self._cb_to_hostname.get(callback) @@ -402,7 +412,10 @@ class DNSResolver(object): del self._hostname_status[hostname] def _send_req(self, hostname, qtype): - req = build_request(hostname, qtype) + self._request_id += 1 + if self._request_id > 32768: + self._request_id = 1 + req = build_request(hostname, qtype, self._request_id) for server in self._servers: logging.debug('resolving %s with type %d using server %s', hostname, qtype, server) @@ -413,7 +426,7 @@ class DNSResolver(object): hostname = hostname.encode('utf8') if not hostname: callback(None, Exception('empty hostname')) - elif common.is_ip(hostname): + elif is_ip(hostname): callback((hostname, hostname), None) elif hostname in self._hosts: logging.debug('hit hosts: %s', hostname) @@ -429,20 +442,17 @@ class DNSResolver(object): return arr = self._hostname_to_cb.get(hostname, None) if not arr: - self._hostname_status[hostname] = STATUS_FIRST - self._send_req(hostname, self._QTYPES[0]) + self._hostname_status[hostname] = STATUS_IPV4 + self._send_req(hostname, QTYPE_A) self._hostname_to_cb[hostname] = [callback] self._cb_to_hostname[callback] = hostname else: arr.append(callback) # TODO send again only if waited too long - self._send_req(hostname, self._QTYPES[0]) + self._send_req(hostname, QTYPE_A) def close(self): if self._sock: - if self._loop: - self._loop.remove_periodic(self.handle_periodic) - self._loop.remove(self._sock) self._sock.close() self._sock = None @@ -450,7 +460,7 @@ class DNSResolver(object): def test(): dns_resolver = DNSResolver() loop = eventloop.EventLoop() - dns_resolver.add_to_loop(loop) + dns_resolver.add_to_loop(loop, ref=True) global counter counter = 0 @@ -464,8 +474,8 @@ def test(): print(result, error) counter += 1 if counter == 9: + loop.remove_handler(dns_resolver.handle_events) dns_resolver.close() - loop.stop() a_callback = callback return a_callback diff --git a/shadowsocks/common.py b/shadowsocks/common.py index 1a58457..e4f698c 100644 --- a/shadowsocks/common.py +++ b/shadowsocks/common.py @@ -1,19 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2013-2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement @@ -21,25 +27,6 @@ from __future__ import absolute_import, division, print_function, \ import socket import struct import logging -import hashlib -import hmac - - -ONETIMEAUTH_BYTES = 10 -ONETIMEAUTH_CHUNK_BYTES = 12 -ONETIMEAUTH_CHUNK_DATA_LEN = 2 - - -def sha1_hmac(secret, data): - return hmac.new(secret, data, hashlib.sha1).digest() - - -def onetimeauth_verify(_hash, data, key): - return _hash == sha1_hmac(key, data)[:ONETIMEAUTH_BYTES] - - -def onetimeauth_gen(data, key): - return sha1_hmac(key, data)[:ONETIMEAUTH_BYTES] def compat_ord(s): @@ -114,18 +101,6 @@ def inet_pton(family, addr): raise RuntimeError("What family?") -def is_ip(address): - for family in (socket.AF_INET, socket.AF_INET6): - try: - if type(address) != str: - address = address.decode('utf8') - inet_pton(family, address) - return family - except (TypeError, ValueError, OSError, IOError): - pass - return False - - def patch_socket(): if not hasattr(socket, 'inet_pton'): socket.inet_pton = inet_pton @@ -137,16 +112,13 @@ def patch_socket(): patch_socket() -ADDRTYPE_IPV4 = 0x01 -ADDRTYPE_IPV6 = 0x04 -ADDRTYPE_HOST = 0x03 -ADDRTYPE_AUTH = 0x10 -ADDRTYPE_MASK = 0xF +ADDRTYPE_IPV4 = 1 +ADDRTYPE_IPV6 = 4 +ADDRTYPE_HOST = 3 def pack_addr(address): address_str = to_str(address) - address = to_bytes(address) for family in (socket.AF_INET, socket.AF_INET6): try: r = socket.inet_pton(family, address_str) @@ -161,38 +133,31 @@ def pack_addr(address): return b'\x03' + chr(len(address)) + address -# add ss header -def add_header(address, port, data=b''): - _data = b'' - _data = pack_addr(address) + struct.pack('>H', port) + data - return _data - - def parse_header(data): addrtype = ord(data[0]) dest_addr = None dest_port = None header_length = 0 - if addrtype & ADDRTYPE_MASK == ADDRTYPE_IPV4: + if addrtype == ADDRTYPE_IPV4: if len(data) >= 7: dest_addr = socket.inet_ntoa(data[1:5]) dest_port = struct.unpack('>H', data[5:7])[0] header_length = 7 else: logging.warn('header is too short') - elif addrtype & ADDRTYPE_MASK == ADDRTYPE_HOST: + elif addrtype == ADDRTYPE_HOST: if len(data) > 2: addrlen = ord(data[1]) - if len(data) >= 4 + addrlen: + if len(data) >= 2 + addrlen: dest_addr = data[2:2 + addrlen] dest_port = struct.unpack('>H', data[2 + addrlen:4 + - addrlen])[0] + addrlen])[0] header_length = 4 + addrlen else: logging.warn('header is too short') else: logging.warn('header is too short') - elif addrtype & ADDRTYPE_MASK == ADDRTYPE_IPV6: + elif addrtype == ADDRTYPE_IPV6: if len(data) >= 19: dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17]) dest_port = struct.unpack('>H', data[17:19])[0] @@ -200,68 +165,13 @@ def parse_header(data): else: logging.warn('header is too short') else: - logging.warn('unsupported addrtype %d, maybe wrong password or ' - 'encryption method' % addrtype) + logging.warn('unsupported addrtype %d, maybe wrong password' % + addrtype) if dest_addr is None: return None return addrtype, to_bytes(dest_addr), dest_port, header_length -class IPNetwork(object): - ADDRLENGTH = {socket.AF_INET: 32, socket.AF_INET6: 128, False: 0} - - def __init__(self, addrs): - self._network_list_v4 = [] - self._network_list_v6 = [] - if type(addrs) == str: - addrs = addrs.split(',') - list(map(self.add_network, addrs)) - - def add_network(self, addr): - if addr is "": - return - block = addr.split('/') - addr_family = is_ip(block[0]) - addr_len = IPNetwork.ADDRLENGTH[addr_family] - if addr_family is socket.AF_INET: - ip, = struct.unpack("!I", socket.inet_aton(block[0])) - elif addr_family is socket.AF_INET6: - hi, lo = struct.unpack("!QQ", inet_pton(addr_family, block[0])) - ip = (hi << 64) | lo - else: - raise Exception("Not a valid CIDR notation: %s" % addr) - if len(block) is 1: - prefix_size = 0 - while (ip & 1) == 0 and ip is not 0: - ip >>= 1 - prefix_size += 1 - logging.warn("You did't specify CIDR routing prefix size for %s, " - "implicit treated as %s/%d" % (addr, addr, addr_len)) - elif block[1].isdigit() and int(block[1]) <= addr_len: - prefix_size = addr_len - int(block[1]) - ip >>= prefix_size - else: - raise Exception("Not a valid CIDR notation: %s" % addr) - if addr_family is socket.AF_INET: - self._network_list_v4.append((ip, prefix_size)) - else: - self._network_list_v6.append((ip, prefix_size)) - - def __contains__(self, addr): - addr_family = is_ip(addr) - if addr_family is socket.AF_INET: - ip, = struct.unpack("!I", socket.inet_aton(addr)) - return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1], - self._network_list_v4)) - elif addr_family is socket.AF_INET6: - hi, lo = struct.unpack("!QQ", inet_pton(addr_family, addr)) - ip = (hi << 64) | lo - return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1], - self._network_list_v6)) - else: - return False - - def test_inet_conv(): ipv4 = b'8.8.4.4' b = inet_pton(socket.AF_INET, ipv4) @@ -288,23 +198,7 @@ def test_pack_header(): assert pack_addr(b'www.google.com') == b'\x03\x0ewww.google.com' -def test_ip_network(): - ip_network = IPNetwork('127.0.0.0/24,::ff:1/112,::1,192.168.1.1,192.0.2.0') - assert '127.0.0.1' in ip_network - assert '127.0.1.1' not in ip_network - assert ':ff:ffff' in ip_network - assert '::ffff:1' not in ip_network - assert '::1' in ip_network - assert '::2' not in ip_network - assert '192.168.1.1' in ip_network - assert '192.168.1.2' not in ip_network - assert '192.0.2.1' in ip_network - assert '192.0.3.1' in ip_network # 192.0.2.0 is treated as 192.0.2.0/23 - assert 'www.google.com' not in ip_network - - if __name__ == '__main__': test_inet_conv() test_parse_header() test_pack_header() - test_ip_network() diff --git a/shadowsocks/crypto/__init__.py b/shadowsocks/crypto/__init__.py index 401c7b7..6251321 100644 --- a/shadowsocks/crypto/__init__.py +++ b/shadowsocks/crypto/__init__.py @@ -1,18 +1,24 @@ #!/usr/bin/env python + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement diff --git a/shadowsocks/crypto/aead.py b/shadowsocks/crypto/aead.py deleted file mode 100644 index c7240b3..0000000 --- a/shadowsocks/crypto/aead.py +++ /dev/null @@ -1,340 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Void Copyright NO ONE -# -# Void License -# -# The code belongs to no one. Do whatever you want. -# Forget about boring open source license. -# -# AEAD cipher for shadowsocks -# - -from __future__ import absolute_import, division, print_function, \ - with_statement - -from ctypes import c_int, create_string_buffer, byref, c_void_p - -import hashlib -from struct import pack, unpack - -from shadowsocks.crypto import util -from shadowsocks.crypto import hkdf -from shadowsocks.common import ord, chr - - -EVP_CTRL_GCM_SET_IVLEN = 0x9 -EVP_CTRL_GCM_GET_TAG = 0x10 -EVP_CTRL_GCM_SET_TAG = 0x11 -EVP_CTRL_CCM_SET_IVLEN = EVP_CTRL_GCM_SET_IVLEN -EVP_CTRL_CCM_GET_TAG = EVP_CTRL_GCM_GET_TAG -EVP_CTRL_CCM_SET_TAG = EVP_CTRL_GCM_SET_TAG - -EVP_CTRL_AEAD_SET_IVLEN = EVP_CTRL_GCM_SET_IVLEN -EVP_CTRL_AEAD_SET_TAG = EVP_CTRL_GCM_SET_TAG -EVP_CTRL_AEAD_GET_TAG = EVP_CTRL_GCM_GET_TAG - -AEAD_MSG_LEN_UNKNOWN = 0 -AEAD_CHUNK_SIZE_LEN = 2 -AEAD_CHUNK_SIZE_MASK = 0x3FFF - -CIPHER_NONCE_LEN = { - 'aes-128-gcm': 12, - 'aes-192-gcm': 12, - 'aes-256-gcm': 12, - 'aes-128-ocb': 12, # requires openssl 1.1 - 'aes-192-ocb': 12, - 'aes-256-ocb': 12, - 'chacha20-poly1305': 12, - 'chacha20-ietf-poly1305': 12, - 'xchacha20-ietf-poly1305': 24, - 'sodium:aes-256-gcm': 12, -} - -CIPHER_TAG_LEN = { - 'aes-128-gcm': 16, - 'aes-192-gcm': 16, - 'aes-256-gcm': 16, - 'aes-128-ocb': 16, # requires openssl 1.1 - 'aes-192-ocb': 16, - 'aes-256-ocb': 16, - 'chacha20-poly1305': 16, - 'chacha20-ietf-poly1305': 16, - 'xchacha20-ietf-poly1305': 16, - 'sodium:aes-256-gcm': 16, -} - -SUBKEY_INFO = b"ss-subkey" - -libsodium = None -sodium_loaded = False - - -def load_sodium(path=None): - """ - Load libsodium helpers for nonce increment - :return: None - """ - global libsodium, sodium_loaded - - libsodium = util.find_library('sodium', 'sodium_increment', - 'libsodium', path) - if libsodium is None: - print('load libsodium failed with path %s' % path) - return - - if libsodium.sodium_init() < 0: - libsodium = None - print('sodium init failed') - return - - libsodium.sodium_increment.restype = c_void_p - libsodium.sodium_increment.argtypes = ( - c_void_p, c_int - ) - - sodium_loaded = True - return - - -def nonce_increment(nonce, nlen): - """ - Increase nonce by 1 in little endian - From libsodium sodium_increment(): - for (; i < nlen; i++) { - c += (uint_fast16_t) n[i]; - n[i] = (unsigned char) c; - c >>= 8; - } - :param nonce: string_buffer nonce - :param nlen: nonce length - :return: nonce plus by 1 - """ - c = 1 - i = 0 - # n = create_string_buffer(nlen) - while i < nlen: - c += ord(nonce[i]) - nonce[i] = chr(c & 0xFF) - c >>= 8 - i += 1 - return # n.raw - - -class AeadCryptoBase(object): - """ - Handles basic aead process of shadowsocks protocol - - TCP Chunk (after encryption, *ciphertext*) - +--------------+---------------+--------------+------------+ - | *DataLen* | DataLen_TAG | *Data* | Data_TAG | - +--------------+---------------+--------------+------------+ - | 2 | Fixed | Variable | Fixed | - +--------------+---------------+--------------+------------+ - - UDP (after encryption, *ciphertext*) - +--------+-----------+-----------+ - | NONCE | *Data* | Data_TAG | - +-------+-----------+-----------+ - | Fixed | Variable | Fixed | - +--------+-----------+-----------+ - """ - - def __init__(self, cipher_name, key, iv, op, crypto_path=None): - self._op = int(op) - self._salt = iv - self._nlen = CIPHER_NONCE_LEN[cipher_name] - self._nonce = create_string_buffer(self._nlen) - self._tlen = CIPHER_TAG_LEN[cipher_name] - - crypto_hkdf = hkdf.Hkdf(iv, key, algorithm=hashlib.sha1) - self._skey = crypto_hkdf.expand(info=SUBKEY_INFO, length=len(key)) - # _chunk['mlen']: - # -1, waiting data len header - # n, n > 0, waiting data - self._chunk = {'mlen': AEAD_MSG_LEN_UNKNOWN, 'data': b''} - - # load libsodium for nonce increment - if not sodium_loaded: - crypto_path = dict(crypto_path) if crypto_path else dict() - path = crypto_path.get('sodium', None) - load_sodium(path) - - def nonce_increment(self): - """ - AEAD ciphers need nonce to be unique per key - TODO: cache and check unique - :return: None - """ - global libsodium, sodium_loaded - if sodium_loaded: - libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen)) - else: - nonce_increment(self._nonce, self._nlen) - # print("".join("%02x" % ord(b) for b in self._nonce)) - - def cipher_ctx_init(self): - """ - Increase nonce to make it unique for the same key - :return: None - """ - self.nonce_increment() - - def aead_encrypt(self, data): - """ - Encrypt data with authenticate tag - - :param data: plain text - :return: str [payload][tag] cipher text with tag - """ - raise Exception("Must implement aead_encrypt method") - - def encrypt_chunk(self, data): - """ - Encrypt a chunk for TCP chunks - - :param data: str - :return: str [len][tag][payload][tag] - """ - plen = len(data) - # l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2 - - # network byte order - ctext = [self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK))] - if len(ctext[0]) != AEAD_CHUNK_SIZE_LEN + self._tlen: - self.clean() - raise Exception("size length invalid") - - ctext.append(self.aead_encrypt(data)) - if len(ctext[1]) != plen + self._tlen: - self.clean() - raise Exception("data length invalid") - - return b''.join(ctext) - - def encrypt(self, data): - """ - Encrypt data, for TCP divided into chunks - For UDP data, call aead_encrypt instead - - :param data: str data bytes - :return: str encrypted data - """ - plen = len(data) - if plen <= AEAD_CHUNK_SIZE_MASK: - ctext = self.encrypt_chunk(data) - return ctext - ctext = [] - while plen > 0: - mlen = plen if plen < AEAD_CHUNK_SIZE_MASK \ - else AEAD_CHUNK_SIZE_MASK - c = self.encrypt_chunk(data[:mlen]) - ctext.append(c) - data = data[mlen:] - plen -= mlen - - return b''.join(ctext) - - def aead_decrypt(self, data): - """ - Decrypt data and authenticate tag - - :param data: str [len][tag][payload][tag] cipher text with tag - :return: str plain text - """ - raise Exception("Must implement aead_decrypt method") - - def decrypt_chunk_size(self, data): - """ - Decrypt chunk size - - :param data: str [size][tag] encrypted chunk payload len - :return: (int, str) msg length and remaining encrypted data - """ - if self._chunk['mlen'] > 0: - return self._chunk['mlen'], data - data = self._chunk['data'] + data - self._chunk['data'] = b"" - - hlen = AEAD_CHUNK_SIZE_LEN + self._tlen - if hlen > len(data): - self._chunk['data'] = data - return 0, b"" - plen = self.aead_decrypt(data[:hlen]) - plen, = unpack("!H", plen) - if plen & AEAD_CHUNK_SIZE_MASK != plen or plen <= 0: - self.clean() - raise Exception('Invalid message length') - - return plen, data[hlen:] - - def decrypt_chunk_payload(self, plen, data): - """ - Decrypted encrypted msg payload - - :param plen: int payload length - :param data: str [payload][tag][[len][tag]....] encrypted data - :return: (str, str) plain text and remaining encrypted data - """ - data = self._chunk['data'] + data - if len(data) < plen + self._tlen: - self._chunk['mlen'] = plen - self._chunk['data'] = data - return b"", b"" - self._chunk['mlen'] = AEAD_MSG_LEN_UNKNOWN - self._chunk['data'] = b"" - - plaintext = self.aead_decrypt(data[:plen + self._tlen]) - - if len(plaintext) != plen: - self.clean() - raise Exception("plaintext length invalid") - - return plaintext, data[plen + self._tlen:] - - def decrypt_chunk(self, data): - """ - Decrypt a TCP chunk - - :param data: str [len][tag][payload][tag][[len][tag]...] encrypted msg - :return: (str, str) decrypted msg and remaining encrypted data - """ - plen, data = self.decrypt_chunk_size(data) - if plen <= 0: - return b"", b"" - return self.decrypt_chunk_payload(plen, data) - - def decrypt(self, data): - """ - Decrypt data for TCP data divided into chunks - For UDP data, call aead_decrypt instead - - :param data: str - :return: str - """ - ptext = [] - pnext, left = self.decrypt_chunk(data) - ptext.append(pnext) - while len(left) > 0: - pnext, left = self.decrypt_chunk(left) - ptext.append(pnext) - return b''.join(ptext) - - -def test_nonce_increment(): - buf = create_string_buffer(12) - print("".join("%02x" % ord(b) for b in buf)) - nonce_increment(buf, 12) - nonce_increment(buf, 12) - nonce_increment(buf, 12) - nonce_increment(buf, 12) - print("".join("%02x" % ord(b) for b in buf)) - for i in range(256): - nonce_increment(buf, 12) - print("".join("%02x" % ord(b) for b in buf)) - - -if __name__ == '__main__': - load_sodium() - test_nonce_increment() diff --git a/shadowsocks/crypto/ctypes_libsodium.py b/shadowsocks/crypto/ctypes_libsodium.py new file mode 100644 index 0000000..efecfd4 --- /dev/null +++ b/shadowsocks/crypto/ctypes_libsodium.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 clowwindy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import logging +from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \ + create_string_buffer, c_void_p + +__all__ = ['ciphers'] + +libsodium = None +loaded = False + +buf_size = 2048 + +# for salsa20 and chacha20 +BLOCK_SIZE = 64 + + +def load_libsodium(): + global loaded, libsodium, buf + + from ctypes.util import find_library + for p in ('sodium',): + libsodium_path = find_library(p) + if libsodium_path: + break + else: + raise Exception('libsodium not found') + logging.info('loading libsodium from %s', libsodium_path) + libsodium = CDLL(libsodium_path) + libsodium.sodium_init.restype = c_int + libsodium.crypto_stream_salsa20_xor_ic.restype = c_int + libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, + c_ulonglong, + c_char_p, c_ulonglong, + c_char_p) + libsodium.crypto_stream_chacha20_xor_ic.restype = c_int + libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p, + c_ulonglong, + c_char_p, c_ulonglong, + c_char_p) + + libsodium.sodium_init() + + buf = create_string_buffer(buf_size) + loaded = True + + +class Salsa20Crypto(object): + def __init__(self, cipher_name, key, iv, op): + if not loaded: + load_libsodium() + self.key = key + self.iv = iv + self.key_ptr = c_char_p(key) + self.iv_ptr = c_char_p(iv) + if cipher_name == b'salsa20': + self.cipher = libsodium.crypto_stream_salsa20_xor_ic + elif cipher_name == b'chacha20': + self.cipher = libsodium.crypto_stream_chacha20_xor_ic + else: + raise Exception('Unknown cipher') + # byte counter, not block counter + self.counter = 0 + + def update(self, data): + global buf_size, buf + l = len(data) + + # we can only prepend some padding to make the encryption align to + # blocks + padding = self.counter % BLOCK_SIZE + if buf_size < padding + l: + buf_size = (padding + l) * 2 + buf = create_string_buffer(buf_size) + + if padding: + data = (b'\0' * padding) + data + self.cipher(byref(buf), c_char_p(data), padding + l, + self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr) + self.counter += l + # buf is copied to a str object when we access buf.raw + # strip off the padding + return buf.raw[padding:padding + l] + + +ciphers = { + b'salsa20': (32, 8, Salsa20Crypto), + b'chacha20': (32, 8, Salsa20Crypto), +} + + +def test_salsa20(): + from shadowsocks.crypto import util + + cipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 1) + decipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 0) + + util.run_cipher(cipher, decipher) + + +def test_chacha20(): + from shadowsocks.crypto import util + + cipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 1) + decipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 0) + + util.run_cipher(cipher, decipher) + + +if __name__ == '__main__': + test_chacha20() + test_salsa20() diff --git a/shadowsocks/crypto/ctypes_openssl.py b/shadowsocks/crypto/ctypes_openssl.py new file mode 100644 index 0000000..0ef8ce0 --- /dev/null +++ b/shadowsocks/crypto/ctypes_openssl.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 clowwindy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import logging +from ctypes import CDLL, c_char_p, c_int, c_long, byref,\ + create_string_buffer, c_void_p + +__all__ = ['ciphers'] + +libcrypto = None +loaded = False + +buf_size = 2048 + + +def load_openssl(): + global loaded, libcrypto, buf + + from ctypes.util import find_library + for p in ('crypto', 'eay32', 'libeay32'): + libcrypto_path = find_library(p) + if libcrypto_path: + break + else: + raise Exception('libcrypto(OpenSSL) not found') + logging.info('loading libcrypto from %s', libcrypto_path) + libcrypto = CDLL(libcrypto_path) + libcrypto.EVP_get_cipherbyname.restype = c_void_p + libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p + + libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, + c_char_p, c_char_p, c_int) + + libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, + c_char_p, c_int) + + libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) + libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) + if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): + libcrypto.OpenSSL_add_all_ciphers() + + buf = create_string_buffer(buf_size) + loaded = True + + +def load_cipher(cipher_name): + func_name = b'EVP_' + cipher_name.replace(b'-', b'_') + if bytes != str: + func_name = str(func_name, 'utf-8') + cipher = getattr(libcrypto, func_name, None) + if cipher: + cipher.restype = c_void_p + return cipher() + return None + + +class CtypesCrypto(object): + def __init__(self, cipher_name, key, iv, op): + if not loaded: + load_openssl() + self._ctx = None + cipher = libcrypto.EVP_get_cipherbyname(cipher_name) + if not cipher: + cipher = load_cipher(cipher_name) + if not cipher: + raise Exception('cipher %s not found in libcrypto' % cipher_name) + key_ptr = c_char_p(key) + iv_ptr = c_char_p(iv) + self._ctx = libcrypto.EVP_CIPHER_CTX_new() + if not self._ctx: + raise Exception('can not create cipher context') + r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None, + key_ptr, iv_ptr, c_int(op)) + if not r: + self.clean() + raise Exception('can not initialize cipher context') + + def update(self, data): + global buf_size, buf + cipher_out_len = c_long(0) + l = len(data) + if buf_size < l: + buf_size = l * 2 + buf = create_string_buffer(buf_size) + libcrypto.EVP_CipherUpdate(self._ctx, byref(buf), + byref(cipher_out_len), c_char_p(data), l) + # buf is copied to a str object when we access buf.raw + return buf.raw[:cipher_out_len.value] + + def __del__(self): + self.clean() + + def clean(self): + if self._ctx: + libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx) + libcrypto.EVP_CIPHER_CTX_free(self._ctx) + + +ciphers = { + b'aes-128-cfb': (16, 16, CtypesCrypto), + b'aes-192-cfb': (24, 16, CtypesCrypto), + b'aes-256-cfb': (32, 16, CtypesCrypto), + b'aes-128-ofb': (16, 16, CtypesCrypto), + b'aes-192-ofb': (24, 16, CtypesCrypto), + b'aes-256-ofb': (32, 16, CtypesCrypto), + b'aes-128-ctr': (16, 16, CtypesCrypto), + b'aes-192-ctr': (24, 16, CtypesCrypto), + b'aes-256-ctr': (32, 16, CtypesCrypto), + b'aes-128-cfb8': (16, 16, CtypesCrypto), + b'aes-192-cfb8': (24, 16, CtypesCrypto), + b'aes-256-cfb8': (32, 16, CtypesCrypto), + b'aes-128-cfb1': (16, 16, CtypesCrypto), + b'aes-192-cfb1': (24, 16, CtypesCrypto), + b'aes-256-cfb1': (32, 16, CtypesCrypto), + b'bf-cfb': (16, 8, CtypesCrypto), + b'camellia-128-cfb': (16, 16, CtypesCrypto), + b'camellia-192-cfb': (24, 16, CtypesCrypto), + b'camellia-256-cfb': (32, 16, CtypesCrypto), + b'cast5-cfb': (16, 8, CtypesCrypto), + b'des-cfb': (8, 8, CtypesCrypto), + b'idea-cfb': (16, 8, CtypesCrypto), + b'rc2-cfb': (16, 8, CtypesCrypto), + b'rc4': (16, 0, CtypesCrypto), + b'seed-cfb': (16, 16, CtypesCrypto), +} + + +def run_method(method): + from shadowsocks.crypto import util + + cipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 1) + decipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 0) + + util.run_cipher(cipher, decipher) + + +def test_aes_128_cfb(): + run_method(b'aes-128-cfb') + + +def test_aes_256_cfb(): + run_method(b'aes-256-cfb') + + +def test_aes_128_cfb8(): + run_method(b'aes-128-cfb8') + + +def test_aes_256_ofb(): + run_method(b'aes-256-ofb') + + +def test_aes_256_ctr(): + run_method(b'aes-256-ctr') + + +def test_bf_cfb(): + run_method(b'bf-cfb') + + +def test_rc4(): + run_method(b'rc4') + + +if __name__ == '__main__': + test_aes_128_cfb() diff --git a/shadowsocks/crypto/hkdf.py b/shadowsocks/crypto/hkdf.py deleted file mode 100644 index 11998e6..0000000 --- a/shadowsocks/crypto/hkdf.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Void Copyright NO ONE -# -# Void License -# -# The code belongs to no one. Do whatever you want. -# Forget about boring open source license. -# -# HKDF for AEAD ciphers -# - -from __future__ import division - -import hmac -import hashlib -import sys - -if sys.version_info[0] == 3: - def buffer(x): - return x - - -def hkdf_extract(salt, input_key_material, algorithm=hashlib.sha256): - """ - Extract a pseudorandom key suitable for use with hkdf_expand - from the input_key_material and a salt using HMAC with the - provided hash (default SHA-256). - - salt should be a random, application-specific byte string. If - salt is None or the empty string, an all-zeros string of the same - length as the hash's block size will be used instead per the RFC. - - See the HKDF draft RFC and paper for usage notes. - """ - hash_len = algorithm().digest_size - if salt is None or len(salt) == 0: - salt = bytearray((0,) * hash_len) - return hmac.new(bytes(salt), buffer(input_key_material), algorithm)\ - .digest() - - -def hkdf_expand(pseudo_random_key, info=b"", length=32, - algorithm=hashlib.sha256): - """ - Expand `pseudo_random_key` and `info` into a key of length `bytes` using - HKDF's expand function based on HMAC with the provided hash (default - SHA-256). See the HKDF draft RFC and paper for usage notes. - """ - hash_len = algorithm().digest_size - length = int(length) - if length > 255 * hash_len: - raise Exception("Cannot expand to more than 255 * %d = %d " - "bytes using the specified hash function" % - (hash_len, 255 * hash_len)) - blocks_needed = length // hash_len \ - + (0 if length % hash_len == 0 else 1) # ceil - okm = b"" - output_block = b"" - for counter in range(blocks_needed): - output_block = hmac.new( - pseudo_random_key, - buffer(output_block + info + bytearray((counter + 1,))), - algorithm - ).digest() - okm += output_block - return okm[:length] - - -class Hkdf(object): - """ - Wrapper class for HKDF extract and expand functions - """ - - def __init__(self, salt, input_key_material, algorithm=hashlib.sha256): - """ - Extract a pseudorandom key from `salt` and `input_key_material` - arguments. - - See the HKDF draft RFC for guidance on setting these values. - The constructor optionally takes a `algorithm` argument defining - the hash function use, defaulting to hashlib.sha256. - """ - self._hash = algorithm - self._prk = hkdf_extract(salt, input_key_material, self._hash) - - def expand(self, info, length=32): - """ - Generate output key material based on an `info` value - - Arguments: - - info - context to generate the OKM - - length - length in bytes of the key to generate - - See the HKDF draft RFC for guidance. - """ - return hkdf_expand(self._prk, info, length, self._hash) diff --git a/shadowsocks/crypto/m2.py b/shadowsocks/crypto/m2.py new file mode 100644 index 0000000..5ad48a8 --- /dev/null +++ b/shadowsocks/crypto/m2.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 clowwindy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import sys +import logging + +__all__ = ['ciphers'] + +has_m2 = True +try: + __import__('M2Crypto') +except ImportError: + has_m2 = False +if bytes != str: + has_m2 = False + + +def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, + padding=1): + + import M2Crypto.EVP + return M2Crypto.EVP.Cipher(alg.replace('-', '_'), key, iv, op, + key_as_bytes=0, d='md5', salt=None, i=1, + padding=1) + + +def err(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, padding=1): + logging.error(('M2Crypto is required to use %s, please run' + ' `apt-get install python-m2crypto`') % alg) + sys.exit(1) + + +if has_m2: + ciphers = { + b'aes-128-cfb': (16, 16, create_cipher), + b'aes-192-cfb': (24, 16, create_cipher), + b'aes-256-cfb': (32, 16, create_cipher), + b'bf-cfb': (16, 8, create_cipher), + b'camellia-128-cfb': (16, 16, create_cipher), + b'camellia-192-cfb': (24, 16, create_cipher), + b'camellia-256-cfb': (32, 16, create_cipher), + b'cast5-cfb': (16, 8, create_cipher), + b'des-cfb': (8, 8, create_cipher), + b'idea-cfb': (16, 8, create_cipher), + b'rc2-cfb': (16, 8, create_cipher), + b'rc4': (16, 0, create_cipher), + b'seed-cfb': (16, 16, create_cipher), + } +else: + ciphers = {} + + +def run_method(method): + from shadowsocks.crypto import util + + cipher = create_cipher(method, b'k' * 32, b'i' * 16, 1) + decipher = create_cipher(method, b'k' * 32, b'i' * 16, 0) + + util.run_cipher(cipher, decipher) + + +def check_env(): + # skip this test on pypy and Python 3 + try: + import __pypy__ + del __pypy__ + from nose.plugins.skip import SkipTest + raise SkipTest + except ImportError: + pass + if bytes != str: + from nose.plugins.skip import SkipTest + raise SkipTest + + +def test_aes_128_cfb(): + check_env() + run_method(b'aes-128-cfb') + + +def test_aes_256_cfb(): + check_env() + run_method(b'aes-256-cfb') + + +def test_bf_cfb(): + check_env() + run_method(b'bf-cfb') + + +def test_rc4(): + check_env() + run_method(b'rc4') + + +if __name__ == '__main__': + test_aes_128_cfb() diff --git a/shadowsocks/crypto/mbedtls.py b/shadowsocks/crypto/mbedtls.py deleted file mode 100644 index 1954a86..0000000 --- a/shadowsocks/crypto/mbedtls.py +++ /dev/null @@ -1,478 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Void Copyright NO ONE -# -# Void License -# -# The code belongs to no one. Do whatever you want. -# Forget about boring open source license. -# -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 - - -from __future__ import absolute_import, division, print_function, \ - with_statement - -from ctypes import c_char_p, c_int, c_size_t, byref,\ - create_string_buffer, c_void_p - -from shadowsocks import common -from shadowsocks.crypto import util -from shadowsocks.crypto.aead import AeadCryptoBase - -__all__ = ['ciphers'] - -libmbedtls = None -loaded = False - -buf = None -buf_size = 2048 - -CIPHER_ENC_UNCHANGED = -1 - -# define MAX_KEY_LENGTH 64 -# define MAX_NONCE_LENGTH 32 -# typedef struct { -# uint32_t init; -# uint64_t counter; -# cipher_evp_t *evp; -# cipher_t *cipher; -# buffer_t *chunk; -# uint8_t salt[MAX_KEY_LENGTH]; -# uint8_t skey[MAX_KEY_LENGTH]; -# uint8_t nonce[MAX_NONCE_LENGTH]; -# } cipher_ctx_t; -# -# sizeof(cipher_ctx_t) = 196 - -CIPHER_CTX_SIZE = 256 - - -def load_mbedtls(crypto_path=None): - global loaded, libmbedtls, buf - - crypto_path = dict(crypto_path) if crypto_path else dict() - path = crypto_path.get('mbedtls', None) - libmbedtls = util.find_library('mbedcrypto', - 'mbedtls_cipher_init', - 'libmbedcrypto', path) - if libmbedtls is None: - raise Exception('libmbedcrypto(mbedtls) not found with path %s' - % path) - - libmbedtls.mbedtls_cipher_init.restype = None - libmbedtls.mbedtls_cipher_free.restype = None - - libmbedtls.mbedtls_cipher_info_from_string.restype = c_void_p - libmbedtls.mbedtls_cipher_info_from_string.argtypes = (c_char_p,) - - libmbedtls.mbedtls_cipher_setup.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_setup.argtypes = (c_void_p, c_void_p) - - libmbedtls.mbedtls_cipher_setkey.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_setkey.argtypes = ( - c_void_p, # ctx - c_char_p, # key - c_int, # key_bitlen, not bytes - c_int # op: 1 enc, 0 dec, -1 none - ) - - libmbedtls.mbedtls_cipher_set_iv.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_set_iv.argtypes = ( - c_void_p, # ctx - c_char_p, # iv - c_size_t # iv_len - ) - - libmbedtls.mbedtls_cipher_reset.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_reset.argtypes = (c_void_p,) # ctx - - if hasattr(libmbedtls, 'mbedtls_cipher_update_ad'): - libmbedtls.mbedtls_cipher_update_ad.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_update_ad.argtypes = ( - c_void_p, # ctx - c_char_p, # ad - c_size_t # ad_len - ) - - libmbedtls.mbedtls_cipher_update.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_update.argtypes = ( - c_void_p, # ctx - c_char_p, # input - c_size_t, # ilen, must be multiple of block size except last one - c_void_p, # *output - c_void_p # *olen - ) - - libmbedtls.mbedtls_cipher_finish.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_finish.argtypes = ( - c_void_p, # ctx - c_void_p, # *output - c_void_p # *olen - ) - - if hasattr(libmbedtls, 'mbedtls_cipher_write_tag'): - libmbedtls.mbedtls_cipher_write_tag.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_write_tag.argtypes = ( - c_void_p, # ctx - c_void_p, # *tag - c_size_t # tag_len - ) - libmbedtls.mbedtls_cipher_check_tag.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_check_tag.argtypes = ( - c_void_p, # ctx - c_char_p, # tag - c_size_t # tag_len - ) - - libmbedtls.mbedtls_cipher_crypt.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_crypt.argtypes = ( - c_void_p, # ctx - c_char_p, # iv - c_size_t, # iv_len, = 0 if iv = NULL - c_char_p, # input - c_size_t, # ilen - c_void_p, # *output, no less than ilen + block_size - c_void_p # *olen - ) - - if hasattr(libmbedtls, 'mbedtls_cipher_auth_encrypt'): - libmbedtls.mbedtls_cipher_auth_encrypt.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_auth_encrypt.argtypes = ( - c_void_p, # ctx - c_char_p, # iv - c_size_t, # iv_len - c_char_p, # ad - c_size_t, # ad_len - c_char_p, # input - c_size_t, # ilen - c_void_p, # *output, no less than ilen + block_size - c_void_p, # *olen - c_void_p, # *tag - c_size_t # tag_len - ) - libmbedtls.mbedtls_cipher_auth_decrypt.restype = c_int # 0 on success - libmbedtls.mbedtls_cipher_auth_decrypt.argtypes = ( - c_void_p, # ctx - c_char_p, # iv - c_size_t, # iv_len - c_char_p, # ad - c_size_t, # ad_len - c_char_p, # input - c_size_t, # ilen - c_void_p, # *output, no less than ilen + block_size - c_void_p, # *olen - c_char_p, # tag - c_size_t, # tag_len - ) - - buf = create_string_buffer(buf_size) - loaded = True - - -class MbedTLSCryptoBase(object): - """ - MbedTLS crypto base class - """ - def __init__(self, cipher_name, crypto_path=None): - global loaded - self._ctx = create_string_buffer(b'\0' * CIPHER_CTX_SIZE) - self._cipher = None - if not loaded: - load_mbedtls(crypto_path) - cipher_name = common.to_bytes(cipher_name.upper()) - cipher = libmbedtls.mbedtls_cipher_info_from_string(cipher_name) - if not cipher: - raise Exception('cipher %s not found in libmbedtls' % cipher_name) - libmbedtls.mbedtls_cipher_init(byref(self._ctx)) - if libmbedtls.mbedtls_cipher_setup(byref(self._ctx), cipher): - raise Exception('can not setup cipher') - self._cipher = cipher - - self.encrypt_once = self.update - self.decrypt_once = self.update - - def update(self, data): - """ - Encrypt/decrypt data - :param data: str - :return: str - """ - global buf_size, buf - cipher_out_len = c_size_t(0) - l = len(data) - if buf_size < l: - buf_size = l * 2 - buf = create_string_buffer(buf_size) - libmbedtls.mbedtls_cipher_update( - byref(self._ctx), - c_char_p(data), c_size_t(l), - byref(buf), byref(cipher_out_len) - ) - # buf is copied to a str object when we access buf.raw - return buf.raw[:cipher_out_len.value] - - def __del__(self): - self.clean() - - def clean(self): - if self._ctx: - libmbedtls.mbedtls_cipher_free(byref(self._ctx)) - - -class MbedTLSAeadCrypto(MbedTLSCryptoBase, AeadCryptoBase): - """ - Implement mbedtls Aead mode: gcm - """ - def __init__(self, cipher_name, key, iv, op, crypto_path=None): - if cipher_name[:len('mbedtls:')] == 'mbedtls:': - cipher_name = cipher_name[len('mbedtls:'):] - MbedTLSCryptoBase.__init__(self, cipher_name, crypto_path) - AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path) - - key_ptr = c_char_p(self._skey) - r = libmbedtls.mbedtls_cipher_setkey( - byref(self._ctx), - key_ptr, c_int(len(key) * 8), - c_int(op) - ) - if r: - self.clean() - raise Exception('can not initialize cipher context') - - r = libmbedtls.mbedtls_cipher_reset(byref(self._ctx)) - if r: - self.clean() - raise Exception('can not finish preparation of mbed TLS ' - 'cipher context') - - def cipher_ctx_init(self): - """ - Nonce + 1 - :return: None - """ - AeadCryptoBase.nonce_increment(self) - - def set_tag(self, tag): - """ - Set tag before decrypt any data (update) - :param tag: authenticated tag - :return: None - """ - tag_len = self._tlen - r = libmbedtls.mbedtls_cipher_check_tag( - byref(self._ctx), - c_char_p(tag), c_size_t(tag_len) - ) - if not r: - raise Exception('Set tag failed') - - def get_tag(self): - """ - Get authenticated tag, called after EVP_CipherFinal_ex - :return: str - """ - tag_len = self._tlen - tag_buf = create_string_buffer(tag_len) - r = libmbedtls.mbedtls_cipher_write_tag( - byref(self._ctx), - byref(tag_buf), c_size_t(tag_len) - ) - if not r: - raise Exception('Get tag failed') - return tag_buf.raw[:tag_len] - - def final(self): - """ - Finish encrypt/decrypt a chunk (<= 0x3FFF) - :return: str - """ - global buf_size, buf - cipher_out_len = c_size_t(0) - r = libmbedtls.mbedtls_cipher_finish( - byref(self._ctx), - byref(buf), byref(cipher_out_len) - ) - if not r: - # print(self._nonce.raw, r, cipher_out_len) - raise Exception('Finalize cipher failed') - return buf.raw[:cipher_out_len.value] - - def aead_encrypt(self, data): - """ - Encrypt data with authenticate tag - - :param data: plain text - :return: cipher text with tag - """ - global buf_size, buf - plen = len(data) - if buf_size < plen + self._tlen: - buf_size = (plen + self._tlen) * 2 - buf = create_string_buffer(buf_size) - cipher_out_len = c_size_t(0) - tag_buf = create_string_buffer(self._tlen) - - r = libmbedtls.mbedtls_cipher_auth_encrypt( - byref(self._ctx), - c_char_p(self._nonce.raw), c_size_t(self._nlen), - None, c_size_t(0), - c_char_p(data), c_size_t(plen), - byref(buf), byref(cipher_out_len), - byref(tag_buf), c_size_t(self._tlen) - ) - assert cipher_out_len.value == plen - if r: - raise Exception('AEAD encrypt failed {0:#x}'.format(r)) - self.cipher_ctx_init() - return buf.raw[:cipher_out_len.value] + tag_buf.raw[:self._tlen] - - def aead_decrypt(self, data): - """ - Decrypt data and authenticate tag - - :param data: cipher text with tag - :return: plain text - """ - global buf_size, buf - cipher_out_len = c_size_t(0) - plen = len(data) - self._tlen - if buf_size < plen: - buf_size = plen * 2 - buf = create_string_buffer(buf_size) - tag = data[plen:] - r = libmbedtls.mbedtls_cipher_auth_decrypt( - byref(self._ctx), - c_char_p(self._nonce.raw), c_size_t(self._nlen), - None, c_size_t(0), - c_char_p(data), c_size_t(plen), - byref(buf), byref(cipher_out_len), - c_char_p(tag), c_size_t(self._tlen) - ) - if r: - raise Exception('AEAD encrypt failed {0:#x}'.format(r)) - self.cipher_ctx_init() - return buf.raw[:cipher_out_len.value] - - -class MbedTLSStreamCrypto(MbedTLSCryptoBase): - """ - Crypto for stream modes: cfb, ofb, ctr - """ - def __init__(self, cipher_name, key, iv, op, crypto_path=None): - if cipher_name[:len('mbedtls:')] == 'mbedtls:': - cipher_name = cipher_name[len('mbedtls:'):] - MbedTLSCryptoBase.__init__(self, cipher_name, crypto_path) - key_ptr = c_char_p(key) - iv_ptr = c_char_p(iv) - r = libmbedtls.mbedtls_cipher_setkey( - byref(self._ctx), - key_ptr, c_int(len(key) * 8), - c_int(op) - ) - if r: - self.clean() - raise Exception('can not set cipher key') - r = libmbedtls.mbedtls_cipher_set_iv( - byref(self._ctx), - iv_ptr, c_size_t(len(iv)) - ) - if r: - self.clean() - raise Exception('can not set cipher iv') - r = libmbedtls.mbedtls_cipher_reset(byref(self._ctx)) - if r: - self.clean() - raise Exception('can not reset cipher') - - self.encrypt = self.update - self.decrypt = self.update - - -ciphers = { - 'mbedtls:aes-128-cfb128': (16, 16, MbedTLSStreamCrypto), - 'mbedtls:aes-192-cfb128': (24, 16, MbedTLSStreamCrypto), - 'mbedtls:aes-256-cfb128': (32, 16, MbedTLSStreamCrypto), - 'mbedtls:aes-128-ctr': (16, 16, MbedTLSStreamCrypto), - 'mbedtls:aes-192-ctr': (24, 16, MbedTLSStreamCrypto), - 'mbedtls:aes-256-ctr': (32, 16, MbedTLSStreamCrypto), - 'mbedtls:camellia-128-cfb128': (16, 16, MbedTLSStreamCrypto), - 'mbedtls:camellia-192-cfb128': (24, 16, MbedTLSStreamCrypto), - 'mbedtls:camellia-256-cfb128': (32, 16, MbedTLSStreamCrypto), - # AEAD: iv_len = salt_len = key_len - 'mbedtls:aes-128-gcm': (16, 16, MbedTLSAeadCrypto), - 'mbedtls:aes-192-gcm': (24, 24, MbedTLSAeadCrypto), - 'mbedtls:aes-256-gcm': (32, 32, MbedTLSAeadCrypto), -} - - -def run_method(method): - - print(method, ': [stream]', 32) - cipher = MbedTLSStreamCrypto(method, b'k' * 32, b'i' * 16, 1) - decipher = MbedTLSStreamCrypto(method, b'k' * 32, b'i' * 16, 0) - - util.run_cipher(cipher, decipher) - - -def run_aead_method(method, key_len=16): - - print(method, ': [payload][tag]', key_len) - key_len = int(key_len) - cipher = MbedTLSAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) - decipher = MbedTLSAeadCrypto( - method, - b'k' * key_len, b'i' * key_len, 0 - ) - - util.run_cipher(cipher, decipher) - - -def run_aead_method_chunk(method, key_len=16): - - print(method, ': chunk([size][tag][payload][tag]', key_len) - key_len = int(key_len) - cipher = MbedTLSAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) - decipher = MbedTLSAeadCrypto( - method, - b'k' * key_len, b'i' * key_len, 0 - ) - - cipher.encrypt_once = cipher.encrypt - decipher.decrypt_once = decipher.decrypt - util.run_cipher(cipher, decipher) - - -def test_camellia_256_cfb(): - run_method('camellia-256-cfb128') - - -def test_aes_gcm(bits=128): - method = "aes-{0}-gcm".format(bits) - run_aead_method(method, bits / 8) - - -def test_aes_gcm_chunk(bits=128): - method = "aes-{0}-gcm".format(bits) - run_aead_method_chunk(method, bits / 8) - - -def test_aes_256_cfb(): - run_method('aes-256-cfb128') - - -def test_aes_256_ctr(): - run_method('aes-256-ctr') - - -if __name__ == '__main__': - test_aes_256_cfb() - test_camellia_256_cfb() - test_aes_256_ctr() - test_aes_gcm(128) - test_aes_gcm(192) - test_aes_gcm(256) - test_aes_gcm_chunk(128) - test_aes_gcm_chunk(192) - test_aes_gcm_chunk(256) diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py deleted file mode 100644 index ff63541..0000000 --- a/shadowsocks/crypto/openssl.py +++ /dev/null @@ -1,448 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 clowwindy -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -from ctypes import c_char_p, c_int, c_long, byref,\ - create_string_buffer, c_void_p - -from shadowsocks import common -from shadowsocks.crypto import util -from shadowsocks.crypto.aead import AeadCryptoBase, EVP_CTRL_AEAD_SET_IVLEN, \ - EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG - -__all__ = ['ciphers'] - -libcrypto = None -loaded = False -libsodium = None - -buf = None -buf_size = 2048 - -ctx_cleanup = None - -CIPHER_ENC_UNCHANGED = -1 - - -def load_openssl(crypto_path=None): - global loaded, libcrypto, libsodium, buf, ctx_cleanup - - crypto_path = dict(crypto_path) if crypto_path else dict() - path = crypto_path.get('openssl', None) - libcrypto = util.find_library(('crypto', 'eay32'), - 'EVP_get_cipherbyname', - 'libcrypto', path) - if libcrypto is None: - raise Exception('libcrypto(OpenSSL) not found with path %s' % path) - - libcrypto.EVP_get_cipherbyname.restype = c_void_p - libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p - - libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, - c_char_p, c_char_p, c_int) - libcrypto.EVP_CIPHER_CTX_ctrl.argtypes = (c_void_p, c_int, c_int, c_void_p) - - libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, - c_char_p, c_int) - - libcrypto.EVP_CipherFinal_ex.argtypes = (c_void_p, c_void_p, c_void_p) - - try: - libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) - ctx_cleanup = libcrypto.EVP_CIPHER_CTX_cleanup - except AttributeError: - libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,) - ctx_cleanup = libcrypto.EVP_CIPHER_CTX_reset - libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) - if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): - libcrypto.OpenSSL_add_all_ciphers() - - buf = create_string_buffer(buf_size) - loaded = True - - -def load_cipher(cipher_name): - func_name = b'EVP_' + cipher_name.replace(b'-', b'_') - if bytes != str: - func_name = str(func_name, 'utf-8') - cipher = getattr(libcrypto, func_name, None) - if cipher: - cipher.restype = c_void_p - return cipher() - return None - - -class OpenSSLCryptoBase(object): - """ - OpenSSL crypto base class - """ - def __init__(self, cipher_name, crypto_path=None): - self._ctx = None - self._cipher = None - if not loaded: - load_openssl(crypto_path) - cipher_name = common.to_bytes(cipher_name) - cipher = libcrypto.EVP_get_cipherbyname(cipher_name) - if not cipher: - cipher = load_cipher(cipher_name) - if not cipher: - raise Exception('cipher %s not found in libcrypto' % cipher_name) - self._ctx = libcrypto.EVP_CIPHER_CTX_new() - self._cipher = cipher - if not self._ctx: - raise Exception('can not create cipher context') - - def encrypt_once(self, data): - return self.update(data) - - def decrypt_once(self, data): - return self.update(data) - - def update(self, data): - """ - Encrypt/decrypt data - :param data: str - :return: str - """ - global buf_size, buf - cipher_out_len = c_long(0) - l = len(data) - if buf_size < l: - buf_size = l * 2 - buf = create_string_buffer(buf_size) - libcrypto.EVP_CipherUpdate( - self._ctx, byref(buf), - byref(cipher_out_len), c_char_p(data), l - ) - # buf is copied to a str object when we access buf.raw - return buf.raw[:cipher_out_len.value] - - def __del__(self): - self.clean() - - def clean(self): - if self._ctx: - ctx_cleanup(self._ctx) - libcrypto.EVP_CIPHER_CTX_free(self._ctx) - self._ctx = None - - -class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase): - """ - Implement OpenSSL Aead mode: gcm, ocb - """ - def __init__(self, cipher_name, key, iv, op, crypto_path=None): - OpenSSLCryptoBase.__init__(self, cipher_name, crypto_path) - AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path) - - key_ptr = c_char_p(self._skey) - r = libcrypto.EVP_CipherInit_ex( - self._ctx, - self._cipher, - None, - key_ptr, None, - c_int(op) - ) - if not r: - self.clean() - raise Exception('can not initialize cipher context') - - r = libcrypto.EVP_CIPHER_CTX_ctrl( - self._ctx, - c_int(EVP_CTRL_AEAD_SET_IVLEN), - c_int(self._nlen), - None - ) - if not r: - self.clean() - raise Exception('Set ivlen failed') - - self.cipher_ctx_init() - - def cipher_ctx_init(self): - """ - Need init cipher context after EVP_CipherFinal_ex to reuse context - :return: None - """ - iv_ptr = c_char_p(self._nonce.raw) - r = libcrypto.EVP_CipherInit_ex( - self._ctx, - None, - None, - None, iv_ptr, - c_int(CIPHER_ENC_UNCHANGED) - ) - if not r: - self.clean() - raise Exception('can not initialize cipher context') - - AeadCryptoBase.nonce_increment(self) - - def set_tag(self, tag): - """ - Set tag before decrypt any data (update) - :param tag: authenticated tag - :return: None - """ - tag_len = self._tlen - r = libcrypto.EVP_CIPHER_CTX_ctrl( - self._ctx, - c_int(EVP_CTRL_AEAD_SET_TAG), - c_int(tag_len), c_char_p(tag) - ) - if not r: - self.clean() - raise Exception('Set tag failed') - - def get_tag(self): - """ - Get authenticated tag, called after EVP_CipherFinal_ex - :return: str - """ - tag_len = self._tlen - tag_buf = create_string_buffer(tag_len) - r = libcrypto.EVP_CIPHER_CTX_ctrl( - self._ctx, - c_int(EVP_CTRL_AEAD_GET_TAG), - c_int(tag_len), byref(tag_buf) - ) - if not r: - self.clean() - raise Exception('Get tag failed') - return tag_buf.raw[:tag_len] - - def final(self): - """ - Finish encrypt/decrypt a chunk (<= 0x3FFF) - :return: str - """ - global buf_size, buf - cipher_out_len = c_long(0) - r = libcrypto.EVP_CipherFinal_ex( - self._ctx, - byref(buf), byref(cipher_out_len) - ) - if not r: - self.clean() - # print(self._nonce.raw, r, cipher_out_len) - raise Exception('Finalize cipher failed') - return buf.raw[:cipher_out_len.value] - - def aead_encrypt(self, data): - """ - Encrypt data with authenticate tag - - :param data: plain text - :return: cipher text with tag - """ - ctext = self.update(data) + self.final() + self.get_tag() - self.cipher_ctx_init() - return ctext - - def aead_decrypt(self, data): - """ - Decrypt data and authenticate tag - - :param data: cipher text with tag - :return: plain text - """ - clen = len(data) - if clen < self._tlen: - self.clean() - raise Exception('Data too short') - - self.set_tag(data[clen - self._tlen:]) - plaintext = self.update(data[:clen - self._tlen]) + self.final() - self.cipher_ctx_init() - return plaintext - - def encrypt_once(self, data): - return self.aead_encrypt(data) - - def decrypt_once(self, data): - return self.aead_decrypt(data) - - -class OpenSSLStreamCrypto(OpenSSLCryptoBase): - """ - Crypto for stream modes: cfb, ofb, ctr - """ - def __init__(self, cipher_name, key, iv, op, crypto_path=None): - OpenSSLCryptoBase.__init__(self, cipher_name, crypto_path) - key_ptr = c_char_p(key) - iv_ptr = c_char_p(iv) - r = libcrypto.EVP_CipherInit_ex(self._ctx, self._cipher, None, - key_ptr, iv_ptr, c_int(op)) - if not r: - self.clean() - raise Exception('can not initialize cipher context') - - def encrypt(self, data): - return self.update(data) - - def decrypt(self, data): - return self.update(data) - - -ciphers = { - 'aes-128-cfb': (16, 16, OpenSSLStreamCrypto), - 'aes-192-cfb': (24, 16, OpenSSLStreamCrypto), - 'aes-256-cfb': (32, 16, OpenSSLStreamCrypto), - 'aes-128-ofb': (16, 16, OpenSSLStreamCrypto), - 'aes-192-ofb': (24, 16, OpenSSLStreamCrypto), - 'aes-256-ofb': (32, 16, OpenSSLStreamCrypto), - 'aes-128-ctr': (16, 16, OpenSSLStreamCrypto), - 'aes-192-ctr': (24, 16, OpenSSLStreamCrypto), - 'aes-256-ctr': (32, 16, OpenSSLStreamCrypto), - 'aes-128-cfb8': (16, 16, OpenSSLStreamCrypto), - 'aes-192-cfb8': (24, 16, OpenSSLStreamCrypto), - 'aes-256-cfb8': (32, 16, OpenSSLStreamCrypto), - 'aes-128-cfb1': (16, 16, OpenSSLStreamCrypto), - 'aes-192-cfb1': (24, 16, OpenSSLStreamCrypto), - 'aes-256-cfb1': (32, 16, OpenSSLStreamCrypto), - 'bf-cfb': (16, 8, OpenSSLStreamCrypto), - 'camellia-128-cfb': (16, 16, OpenSSLStreamCrypto), - 'camellia-192-cfb': (24, 16, OpenSSLStreamCrypto), - 'camellia-256-cfb': (32, 16, OpenSSLStreamCrypto), - 'cast5-cfb': (16, 8, OpenSSLStreamCrypto), - 'des-cfb': (8, 8, OpenSSLStreamCrypto), - 'idea-cfb': (16, 8, OpenSSLStreamCrypto), - 'rc2-cfb': (16, 8, OpenSSLStreamCrypto), - 'rc4': (16, 0, OpenSSLStreamCrypto), - 'seed-cfb': (16, 16, OpenSSLStreamCrypto), - # AEAD: iv_len = salt_len = key_len - 'aes-128-gcm': (16, 16, OpenSSLAeadCrypto), - 'aes-192-gcm': (24, 24, OpenSSLAeadCrypto), - 'aes-256-gcm': (32, 32, OpenSSLAeadCrypto), - 'aes-128-ocb': (16, 16, OpenSSLAeadCrypto), - 'aes-192-ocb': (24, 24, OpenSSLAeadCrypto), - 'aes-256-ocb': (32, 32, OpenSSLAeadCrypto), -} - - -def run_method(method): - - print(method, ': [stream]', 32) - cipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 1) - decipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0) - - util.run_cipher(cipher, decipher) - - -def run_aead_method(method, key_len=16): - - if not loaded: - load_openssl(None) - print(method, ': [payload][tag]', key_len) - cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method)) - if not cipher: - cipher = load_cipher(common.to_bytes(method)) - if not cipher: - print('cipher not avaiable, please upgrade openssl') - return - key_len = int(key_len) - cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) - decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0) - - util.run_cipher(cipher, decipher) - - -def run_aead_method_chunk(method, key_len=16): - - if not loaded: - load_openssl(None) - print(method, ': chunk([size][tag][payload][tag]', key_len) - cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method)) - if not cipher: - cipher = load_cipher(common.to_bytes(method)) - if not cipher: - print('cipher not avaiable, please upgrade openssl') - return - key_len = int(key_len) - cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) - decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0) - - cipher.encrypt_once = cipher.encrypt - decipher.decrypt_once = decipher.decrypt - util.run_cipher(cipher, decipher) - - -def test_aes_gcm(bits=128): - method = "aes-{0}-gcm".format(bits) - run_aead_method(method, bits / 8) - - -def test_aes_ocb(bits=128): - method = "aes-{0}-ocb".format(bits) - run_aead_method(method, bits / 8) - - -def test_aes_gcm_chunk(bits=128): - method = "aes-{0}-gcm".format(bits) - run_aead_method_chunk(method, bits / 8) - - -def test_aes_ocb_chunk(bits=128): - method = "aes-{0}-ocb".format(bits) - run_aead_method_chunk(method, bits / 8) - - -def test_aes_128_cfb(): - run_method('aes-128-cfb') - - -def test_aes_256_cfb(): - run_method('aes-256-cfb') - - -def test_aes_128_cfb8(): - run_method('aes-128-cfb8') - - -def test_aes_256_ofb(): - run_method('aes-256-ofb') - - -def test_aes_256_ctr(): - run_method('aes-256-ctr') - - -def test_bf_cfb(): - run_method('bf-cfb') - - -def test_rc4(): - run_method('rc4') - - -if __name__ == '__main__': - test_aes_128_cfb() - test_aes_256_cfb() - test_aes_256_ofb() - test_aes_gcm(128) - test_aes_gcm(192) - test_aes_gcm(256) - test_aes_gcm_chunk(128) - test_aes_gcm_chunk(192) - test_aes_gcm_chunk(256) - test_aes_ocb(128) - test_aes_ocb(192) - test_aes_ocb(256) - test_aes_ocb_chunk(128) - test_aes_ocb_chunk(192) - test_aes_ocb_chunk(256) diff --git a/shadowsocks/crypto/rc4_md5.py b/shadowsocks/crypto/rc4_md5.py index 014fa3c..3062dcc 100644 --- a/shadowsocks/crypto/rc4_md5.py +++ b/shadowsocks/crypto/rc4_md5.py @@ -1,48 +1,61 @@ #!/usr/bin/env python + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement import hashlib -from shadowsocks.crypto import openssl + __all__ = ['ciphers'] -def create_cipher(alg, key, iv, op, crypto_path=None, - key_as_bytes=0, d=None, salt=None, +def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, padding=1): md5 = hashlib.md5() md5.update(key) md5.update(iv) rc4_key = md5.digest() - return openssl.OpenSSLStreamCrypto(b'rc4', rc4_key, b'', op, crypto_path) + + try: + from shadowsocks.crypto import ctypes_openssl + return ctypes_openssl.CtypesCrypto(b'rc4', rc4_key, b'', op) + except: + import M2Crypto.EVP + return M2Crypto.EVP.Cipher(b'rc4', rc4_key, b'', op, + key_as_bytes=0, d='md5', salt=None, i=1, + padding=1) ciphers = { - 'rc4-md5': (16, 16, create_cipher), + b'rc4-md5': (16, 16, create_cipher), } def test(): from shadowsocks.crypto import util - cipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 1) - decipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 0) + cipher = create_cipher(b'rc4-md5', b'k' * 32, b'i' * 16, 1) + decipher = create_cipher(b'rc4-md5', b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) diff --git a/shadowsocks/crypto/salsa20_ctr.py b/shadowsocks/crypto/salsa20_ctr.py new file mode 100644 index 0000000..0ea13b8 --- /dev/null +++ b/shadowsocks/crypto/salsa20_ctr.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 clowwindy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import struct +import logging +import sys + +slow_xor = False +imported = False + +salsa20 = None +numpy = None + +BLOCK_SIZE = 16384 + + +def run_imports(): + global imported, slow_xor, salsa20, numpy + if not imported: + imported = True + try: + numpy = __import__('numpy') + except ImportError: + logging.error('can not import numpy, using SLOW XOR') + logging.error('please install numpy if you use salsa20') + slow_xor = True + try: + salsa20 = __import__('salsa20') + except ImportError: + logging.error('you have to install salsa20 before you use salsa20') + sys.exit(1) + + +def numpy_xor(a, b): + if slow_xor: + return py_xor_str(a, b) + dtype = numpy.byte + if len(a) % 4 == 0: + dtype = numpy.uint32 + elif len(a) % 2 == 0: + dtype = numpy.uint16 + + ab = numpy.frombuffer(a, dtype=dtype) + bb = numpy.frombuffer(b, dtype=dtype) + c = numpy.bitwise_xor(ab, bb) + r = c.tostring() + return r + + +def py_xor_str(a, b): + c = [] + if bytes == str: + for i in range(0, len(a)): + c.append(chr(ord(a[i]) ^ ord(b[i]))) + return ''.join(c) + else: + for i in range(0, len(a)): + c.append(a[i] ^ b[i]) + return bytes(c) + + +class Salsa20Cipher(object): + """a salsa20 CTR implemetation, provides m2crypto like cipher API""" + + def __init__(self, alg, key, iv, op, key_as_bytes=0, d=None, salt=None, + i=1, padding=1): + run_imports() + if alg != b'salsa20-ctr': + raise Exception('unknown algorithm') + self._key = key + self._nonce = struct.unpack('= BLOCK_SIZE: + self._next_stream() + self._pos = 0 + if not data: + break + return b''.join(results) + + +ciphers = { + b'salsa20-ctr': (32, 8, Salsa20Cipher), +} + + +def test(): + from shadowsocks.crypto import util + + cipher = Salsa20Cipher(b'salsa20-ctr', b'k' * 32, b'i' * 8, 1) + decipher = Salsa20Cipher(b'salsa20-ctr', b'k' * 32, b'i' * 8, 1) + + util.run_cipher(cipher, decipher) + + +if __name__ == '__main__': + test() diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py deleted file mode 100644 index 981321e..0000000 --- a/shadowsocks/crypto/sodium.py +++ /dev/null @@ -1,442 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 clowwindy -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -from ctypes import c_char_p, c_int, c_uint, c_ulonglong, byref, \ - create_string_buffer, c_void_p - -from shadowsocks.crypto import util -from shadowsocks.crypto import aead -from shadowsocks.crypto.aead import AeadCryptoBase - -__all__ = ['ciphers'] - -libsodium = None -loaded = False - -buf = None -buf_size = 2048 - -# for salsa20 and chacha20 and chacha20-ietf -BLOCK_SIZE = 64 - - -def load_libsodium(crypto_path=None): - global loaded, libsodium, buf - - crypto_path = dict(crypto_path) if crypto_path else dict() - path = crypto_path.get('sodium', None) - - if not aead.sodium_loaded: - aead.load_sodium(path) - - if aead.sodium_loaded: - libsodium = aead.libsodium - else: - print('load libsodium again with path %s' % path) - libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', - 'libsodium', path) - if libsodium is None: - raise Exception('libsodium not found') - - if libsodium.sodium_init() < 0: - raise Exception('libsodium init failed') - - libsodium.crypto_stream_salsa20_xor_ic.restype = c_int - libsodium.crypto_stream_salsa20_xor_ic.argtypes = ( - c_void_p, c_char_p, # cipher output, msg - c_ulonglong, # msg len - c_char_p, c_ulonglong, # nonce, uint64_t initial block counter - c_char_p # key - ) - libsodium.crypto_stream_chacha20_xor_ic.restype = c_int - libsodium.crypto_stream_chacha20_xor_ic.argtypes = ( - c_void_p, c_char_p, - c_ulonglong, - c_char_p, c_ulonglong, - c_char_p - ) - if hasattr(libsodium, 'crypto_stream_xchacha20_xor_ic'): - libsodium.crypto_stream_xchacha20_xor_ic.restype = c_int - libsodium.crypto_stream_xchacha20_xor_ic.argtypes = ( - c_void_p, c_char_p, - c_ulonglong, - c_char_p, c_ulonglong, - c_char_p - ) - libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int - libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = ( - c_void_p, c_char_p, - c_ulonglong, - c_char_p, - c_uint, # uint32_t initial counter - c_char_p - ) - - # chacha20-poly1305 - libsodium.crypto_aead_chacha20poly1305_encrypt.restype = c_int - libsodium.crypto_aead_chacha20poly1305_encrypt.argtypes = ( - c_void_p, c_void_p, # c, clen - c_char_p, c_ulonglong, # m, mlen - c_char_p, c_ulonglong, # ad, adlen - c_char_p, # nsec, not used - c_char_p, c_char_p # npub, k - ) - libsodium.crypto_aead_chacha20poly1305_decrypt.restype = c_int - libsodium.crypto_aead_chacha20poly1305_decrypt.argtypes = ( - c_void_p, c_void_p, # m, mlen - c_char_p, # nsec, not used - c_char_p, c_ulonglong, # c, clen - c_char_p, c_ulonglong, # ad, adlen - c_char_p, c_char_p # npub, k - ) - - # chacha20-ietf-poly1305, same api structure as above - libsodium.crypto_aead_chacha20poly1305_ietf_encrypt.restype = c_int - libsodium.crypto_aead_chacha20poly1305_ietf_encrypt.argtypes = ( - c_void_p, c_void_p, - c_char_p, c_ulonglong, - c_char_p, c_ulonglong, - c_char_p, - c_char_p, c_char_p - ) - libsodium.crypto_aead_chacha20poly1305_ietf_decrypt.restype = c_int - libsodium.crypto_aead_chacha20poly1305_ietf_decrypt.argtypes = ( - c_void_p, c_void_p, - c_char_p, - c_char_p, c_ulonglong, - c_char_p, c_ulonglong, - c_char_p, c_char_p - ) - - # xchacha20-ietf-poly1305, same api structure as above - if hasattr(libsodium, 'crypto_aead_xchacha20poly1305_ietf_encrypt'): - libsodium.crypto_aead_xchacha20poly1305_ietf_encrypt.restype = c_int - libsodium.crypto_aead_xchacha20poly1305_ietf_encrypt.argtypes = ( - c_void_p, c_void_p, - c_char_p, c_ulonglong, - c_char_p, c_ulonglong, - c_char_p, - c_char_p, c_char_p - ) - - libsodium.crypto_aead_xchacha20poly1305_ietf_decrypt.restype = c_int - libsodium.crypto_aead_xchacha20poly1305_ietf_decrypt.argtypes = ( - c_void_p, c_void_p, - c_char_p, - c_char_p, c_ulonglong, - c_char_p, c_ulonglong, - c_char_p, c_char_p - ) - - # aes-256-gcm, same api structure as above - libsodium.crypto_aead_aes256gcm_is_available.restype = c_int - - if libsodium.crypto_aead_aes256gcm_is_available(): - libsodium.crypto_aead_aes256gcm_encrypt.restype = c_int - libsodium.crypto_aead_aes256gcm_encrypt.argtypes = ( - c_void_p, c_void_p, - c_char_p, c_ulonglong, - c_char_p, c_ulonglong, - c_char_p, - c_char_p, c_char_p - ) - libsodium.crypto_aead_aes256gcm_decrypt.restype = c_int - libsodium.crypto_aead_aes256gcm_decrypt.argtypes = ( - c_void_p, c_void_p, - c_char_p, - c_char_p, c_ulonglong, - c_char_p, c_ulonglong, - c_char_p, c_char_p - ) - - buf = create_string_buffer(buf_size) - loaded = True - - -class SodiumCrypto(object): - def __init__(self, cipher_name, key, iv, op, crypto_path=None): - if not loaded: - load_libsodium(crypto_path) - self.key = key - self.iv = iv - self.key_ptr = c_char_p(key) - self.iv_ptr = c_char_p(iv) - if cipher_name == 'salsa20': - self.cipher = libsodium.crypto_stream_salsa20_xor_ic - elif cipher_name == 'chacha20': - self.cipher = libsodium.crypto_stream_chacha20_xor_ic - elif cipher_name == 'xchacha20': - if hasattr(libsodium, 'crypto_stream_xchacha20_xor_ic'): - self.cipher = libsodium.crypto_stream_xchacha20_xor_ic - else: - raise Exception('Unsupported cipher') - elif cipher_name == 'chacha20-ietf': - self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic - else: - raise Exception('Unknown cipher') - # byte counter, not block counter - self.counter = 0 - - def encrypt(self, data): - return self.update(data) - - def decrypt(self, data): - return self.update(data) - - def encrypt_once(self, data): - return self.update(data) - - def decrypt_once(self, data): - return self.update(data) - - def update(self, data): - global buf_size, buf - l = len(data) - - # we can only prepend some padding to make the encryption align to - # blocks - padding = self.counter % BLOCK_SIZE - if buf_size < padding + l: - buf_size = (padding + l) * 2 - buf = create_string_buffer(buf_size) - - if padding: - data = (b'\0' * padding) + data - self.cipher(byref(buf), c_char_p(data), padding + l, - self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr) - self.counter += l - # buf is copied to a str object when we access buf.raw - # strip off the padding - return buf.raw[padding:padding + l] - - def clean(self): - pass - - -class SodiumAeadCrypto(AeadCryptoBase): - def __init__(self, cipher_name, key, iv, op, crypto_path=None): - if not loaded: - load_libsodium(crypto_path) - AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path) - - if cipher_name == 'chacha20-poly1305': - self.encryptor = libsodium.crypto_aead_chacha20poly1305_encrypt - self.decryptor = libsodium.crypto_aead_chacha20poly1305_decrypt - elif cipher_name == 'chacha20-ietf-poly1305': - self.encryptor = libsodium. \ - crypto_aead_chacha20poly1305_ietf_encrypt - self.decryptor = libsodium. \ - crypto_aead_chacha20poly1305_ietf_decrypt - elif cipher_name == 'xchacha20-ietf-poly1305': - if hasattr(libsodium, - 'crypto_aead_xchacha20poly1305_ietf_encrypt'): - self.encryptor = libsodium. \ - crypto_aead_xchacha20poly1305_ietf_encrypt - self.decryptor = libsodium. \ - crypto_aead_xchacha20poly1305_ietf_decrypt - else: - raise Exception('Unsupported cipher') - elif cipher_name == 'sodium:aes-256-gcm': - if hasattr(libsodium, 'crypto_aead_aes256gcm_encrypt'): - self.encryptor = libsodium.crypto_aead_aes256gcm_encrypt - self.decryptor = libsodium.crypto_aead_aes256gcm_decrypt - else: - raise Exception('Unsupported cipher') - else: - raise Exception('Unknown cipher') - - def cipher_ctx_init(self): - global libsodium - libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen)) - # print("".join("%02x" % ord(b) for b in self._nonce)) - - def aead_encrypt(self, data): - global buf, buf_size - plen = len(data) - if buf_size < plen + self._tlen: - buf_size = (plen + self._tlen) * 2 - buf = create_string_buffer(buf_size) - cipher_out_len = c_ulonglong(0) - self.encryptor( - byref(buf), byref(cipher_out_len), - c_char_p(data), c_ulonglong(plen), - None, c_ulonglong(0), None, - c_char_p(self._nonce.raw), c_char_p(self._skey) - ) - if cipher_out_len.value != plen + self._tlen: - raise Exception("Encrypt failed") - - self.cipher_ctx_init() - return buf.raw[:cipher_out_len.value] - - def aead_decrypt(self, data): - global buf, buf_size - clen = len(data) - if buf_size < clen: - buf_size = clen * 2 - buf = create_string_buffer(buf_size) - cipher_out_len = c_ulonglong(0) - r = self.decryptor( - byref(buf), byref(cipher_out_len), - None, - c_char_p(data), c_ulonglong(clen), - None, c_ulonglong(0), - c_char_p(self._nonce.raw), c_char_p(self._skey) - ) - if r != 0: - raise Exception("Decrypt failed") - - if cipher_out_len.value != clen - self._tlen: - raise Exception("Decrypt failed") - - self.cipher_ctx_init() - return buf.raw[:cipher_out_len.value] - - def encrypt_once(self, data): - return self.aead_encrypt(data) - - def decrypt_once(self, data): - return self.aead_decrypt(data) - - -ciphers = { - 'salsa20': (32, 8, SodiumCrypto), - 'chacha20': (32, 8, SodiumCrypto), - 'xchacha20': (32, 24, SodiumCrypto), - 'chacha20-ietf': (32, 12, SodiumCrypto), - # AEAD: iv_len = salt_len = key_len - 'chacha20-poly1305': (32, 32, SodiumAeadCrypto), - 'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), - 'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), - 'sodium:aes-256-gcm': (32, 32, SodiumAeadCrypto), -} - - -def test_chacha20(): - print("Test chacha20") - cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) - decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) - - util.run_cipher(cipher, decipher) - - -def test_xchacha20(): - print("Test xchacha20") - cipher = SodiumCrypto('xchacha20', b'k' * 32, b'i' * 24, 1) - decipher = SodiumCrypto('xchacha20', b'k' * 32, b'i' * 24, 0) - - util.run_cipher(cipher, decipher) - - -def test_salsa20(): - print("Test salsa20") - cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1) - decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0) - - util.run_cipher(cipher, decipher) - - -def test_chacha20_ietf(): - print("Test chacha20-ietf") - cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) - decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) - - util.run_cipher(cipher, decipher) - - -def test_chacha20_poly1305(): - print("Test chacha20-poly1305 [payload][tag]") - cipher = SodiumAeadCrypto('chacha20-poly1305', - b'k' * 32, b'i' * 32, 1) - decipher = SodiumAeadCrypto('chacha20-poly1305', - b'k' * 32, b'i' * 32, 0) - - util.run_cipher(cipher, decipher) - - -def test_chacha20_poly1305_chunk(): - print("Test chacha20-poly1305 chunk [size][tag][payload][tag]") - cipher = SodiumAeadCrypto('chacha20-poly1305', - b'k' * 32, b'i' * 32, 1) - decipher = SodiumAeadCrypto('chacha20-poly1305', - b'k' * 32, b'i' * 32, 0) - - cipher.encrypt_once = cipher.encrypt - decipher.decrypt_once = decipher.decrypt - - util.run_cipher(cipher, decipher) - - -def test_chacha20_ietf_poly1305(): - print("Test chacha20-ietf-poly1305 [payload][tag]") - cipher = SodiumAeadCrypto('chacha20-ietf-poly1305', - b'k' * 32, b'i' * 32, 1) - decipher = SodiumAeadCrypto('chacha20-ietf-poly1305', - b'k' * 32, b'i' * 32, 0) - - util.run_cipher(cipher, decipher) - - -def test_chacha20_ietf_poly1305_chunk(): - print("Test chacha20-ietf-poly1305 chunk [size][tag][payload][tag]") - cipher = SodiumAeadCrypto('chacha20-ietf-poly1305', - b'k' * 32, b'i' * 32, 1) - decipher = SodiumAeadCrypto('chacha20-ietf-poly1305', - b'k' * 32, b'i' * 32, 0) - - cipher.encrypt_once = cipher.encrypt - decipher.decrypt_once = decipher.decrypt - - util.run_cipher(cipher, decipher) - - -def test_aes_256_gcm(): - print("Test sodium:aes-256-gcm [payload][tag]") - cipher = SodiumAeadCrypto('sodium:aes-256-gcm', - b'k' * 32, b'i' * 32, 1) - decipher = SodiumAeadCrypto('sodium:aes-256-gcm', - b'k' * 32, b'i' * 32, 0) - - util.run_cipher(cipher, decipher) - - -def test_aes_256_gcm_chunk(): - print("Test sodium:aes-256-gcm chunk [size][tag][payload][tag]") - cipher = SodiumAeadCrypto('sodium:aes-256-gcm', - b'k' * 32, b'i' * 32, 1) - decipher = SodiumAeadCrypto('sodium:aes-256-gcm', - b'k' * 32, b'i' * 32, 0) - - cipher.encrypt_once = cipher.encrypt - decipher.decrypt_once = decipher.decrypt - - util.run_cipher(cipher, decipher) - - -if __name__ == '__main__': - test_chacha20() - test_xchacha20() - test_salsa20() - test_chacha20_ietf() - test_chacha20_poly1305() - test_chacha20_poly1305_chunk() - test_chacha20_ietf_poly1305() - test_chacha20_ietf_poly1305_chunk() - test_aes_256_gcm() - test_aes_256_gcm_chunk() diff --git a/shadowsocks/crypto/table.py b/shadowsocks/crypto/table.py index 1752be5..08c1205 100644 --- a/shadowsocks/crypto/table.py +++ b/shadowsocks/crypto/table.py @@ -1,18 +1,24 @@ # !/usr/bin/env python + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement @@ -55,13 +61,9 @@ def init_table(key): class TableCipher(object): - def __init__(self, cipher_name, key, iv, op, crypto_path=None): + def __init__(self, cipher_name, key, iv, op): self._encrypt_table, self._decrypt_table = init_table(key) self._op = op - self.encrypt = self.update - self.decrypt = self.update - self.encrypt_once = self.update - self.decrypt_once = self.update def update(self, data): if self._op: @@ -71,7 +73,7 @@ class TableCipher(object): ciphers = { - 'table': (0, 0, TableCipher) + b'table': (0, 0, TableCipher) } @@ -167,8 +169,8 @@ def test_table_result(): def test_encryption(): from shadowsocks.crypto import util - cipher = TableCipher('table', b'test', b'', 1) - decipher = TableCipher('table', b'test', b'', 0) + cipher = TableCipher(b'table', b'test', b'', 1) + decipher = TableCipher(b'table', b'test', b'', 0) util.run_cipher(cipher, decipher) diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index f52e7ba..3bac1db 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -1,121 +1,24 @@ #!/usr/bin/env python + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import os -import logging - - -def find_library_nt(name): - # modified from ctypes.util - # ctypes.util.find_library just returns first result he found - # but we want to try them all - # because on Windows, users may have both 32bit and 64bit version installed - import glob - results = [] - for directory in os.environ['PATH'].split(os.pathsep): - fname = os.path.join(directory, name) - if os.path.isfile(fname): - results.append(fname) - if fname.lower().endswith(".dll"): - continue - fname += "*.dll" - files = glob.glob(fname) - if files: - results.extend(files) - return results - - -def load_library(path, search_symbol, library_name): - from ctypes import CDLL - try: - lib = CDLL(path) - if hasattr(lib, search_symbol): - logging.info('loading %s from %s', library_name, path) - return lib - else: - logging.warn('can\'t find symbol %s in %s', search_symbol, - path) - except Exception: - pass - return None - - -def find_library(possible_lib_names, search_symbol, library_name, - custom_path=None): - import ctypes.util - - if custom_path: - return load_library(custom_path, search_symbol, library_name) - - paths = [] - - if type(possible_lib_names) not in (list, tuple): - possible_lib_names = [possible_lib_names] - - lib_names = [] - for lib_name in possible_lib_names: - lib_names.append(lib_name) - lib_names.append('lib' + lib_name) - - for name in lib_names: - if os.name == "nt": - paths.extend(find_library_nt(name)) - else: - path = ctypes.util.find_library(name) - if path: - paths.append(path) - - if not paths: - # We may get here when find_library fails because, for example, - # the user does not have sufficient privileges to access those - # tools underlying find_library on linux. - import glob - - for name in lib_names: - patterns = [ - '/usr/local/lib*/lib%s.*' % name, - '/usr/lib*/lib%s.*' % name, - 'lib%s.*' % name, - '%s.dll' % name] - - for pat in patterns: - files = glob.glob(pat) - if files: - paths.extend(files) - for path in paths: - lib = load_library(path, search_symbol, library_name) - if lib: - return lib - return None - - -def parse_mode(cipher_nme): - """ - Parse the cipher mode from cipher name - e.g. aes-128-gcm, the mode is gcm - :param cipher_nme: str cipher name, aes-128-cfb, aes-128-gcm ... - :return: str/None The mode, cfb, gcm ... - """ - hyphen = cipher_nme.rfind('-') - if hyphen > 0: - return cipher_nme[hyphen:] - return None +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. def run_cipher(cipher, decipher): @@ -123,44 +26,26 @@ def run_cipher(cipher, decipher): import random import time - block_size = 16384 + BLOCK_SIZE = 16384 rounds = 1 * 1024 - plain = urandom(block_size * rounds) + plain = urandom(BLOCK_SIZE * rounds) - cipher_results = [] + results = [] pos = 0 print('test start') start = time.time() while pos < len(plain): l = random.randint(100, 32768) - # print(pos, l) - c = cipher.encrypt_once(plain[pos:pos + l]) - cipher_results.append(c) + c = cipher.update(plain[pos:pos + l]) + results.append(c) pos += l pos = 0 - # c = b''.join(cipher_results) - plain_results = [] - for c in cipher_results: - # l = random.randint(100, 32768) - l = len(c) - plain_results.append(decipher.decrypt_once(c)) + c = b''.join(results) + results = [] + while pos < len(plain): + l = random.randint(100, 32768) + results.append(decipher.update(c[pos:pos + l])) pos += l end = time.time() - print('speed: %d bytes/s' % (block_size * rounds / (end - start))) - assert b''.join(plain_results) == plain - - -def test_find_library(): - assert find_library('c', 'strcpy', 'libc') is not None - assert find_library(['c'], 'strcpy', 'libc') is not None - assert find_library(('c',), 'strcpy', 'libc') is not None - assert find_library(('crypto', 'eay32'), 'EVP_CipherUpdate', - 'libcrypto') is not None - assert find_library('notexist', 'strcpy', 'libnotexist') is None - assert find_library('c', 'symbol_not_exist', 'c') is None - assert find_library(('notexist', 'c', 'crypto', 'eay32'), - 'EVP_CipherUpdate', 'libc') is not None - - -if __name__ == '__main__': - test_find_library() + print('speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start))) + assert b''.join(results) == plain diff --git a/shadowsocks/cryptor.py b/shadowsocks/cryptor.py deleted file mode 100644 index 4eae9e8..0000000 --- a/shadowsocks/cryptor.py +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012-2015 clowwindy -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import os -import sys -import hashlib -import logging - -from shadowsocks import common -from shadowsocks.crypto import rc4_md5, openssl, mbedtls, sodium, table - - -CIPHER_ENC_ENCRYPTION = 1 -CIPHER_ENC_DECRYPTION = 0 - -METHOD_INFO_KEY_LEN = 0 -METHOD_INFO_IV_LEN = 1 -METHOD_INFO_CRYPTO = 2 - -method_supported = {} -method_supported.update(rc4_md5.ciphers) -method_supported.update(openssl.ciphers) -method_supported.update(mbedtls.ciphers) -method_supported.update(sodium.ciphers) -method_supported.update(table.ciphers) - - -def random_string(length): - return os.urandom(length) - -cached_keys = {} - - -def try_cipher(key, method=None, crypto_path=None): - Cryptor(key, method, crypto_path) - - -def EVP_BytesToKey(password, key_len, iv_len): - # equivalent to OpenSSL's EVP_BytesToKey() with count 1 - # so that we make the same key and iv as nodejs version - cached_key = '%s-%d-%d' % (password, key_len, iv_len) - r = cached_keys.get(cached_key, None) - if r: - return r - m = [] - i = 0 - while len(b''.join(m)) < (key_len + iv_len): - md5 = hashlib.md5() - data = password - if i > 0: - data = m[i - 1] + password - md5.update(data) - m.append(md5.digest()) - i += 1 - ms = b''.join(m) - key = ms[:key_len] - iv = ms[key_len:key_len + iv_len] - cached_keys[cached_key] = (key, iv) - return key, iv - - -class Cryptor(object): - def __init__(self, password, method, crypto_path=None): - """ - Crypto wrapper - :param password: str cipher password - :param method: str cipher - :param crypto_path: dict or none - {'openssl': path, 'sodium': path, 'mbedtls': path} - """ - self.password = password - self.key = None - self.method = method - self.iv_sent = False - self.cipher_iv = b'' - self.decipher = None - self.decipher_iv = None - self.crypto_path = crypto_path - method = method.lower() - self._method_info = Cryptor.get_method_info(method) - if self._method_info: - self.cipher = self.get_cipher( - password, method, CIPHER_ENC_ENCRYPTION, - random_string(self._method_info[METHOD_INFO_IV_LEN]) - ) - else: - logging.error('method %s not supported' % method) - sys.exit(1) - - @staticmethod - def get_method_info(method): - method = method.lower() - m = method_supported.get(method) - return m - - def iv_len(self): - return len(self.cipher_iv) - - def get_cipher(self, password, method, op, iv): - password = common.to_bytes(password) - m = self._method_info - if m[METHOD_INFO_KEY_LEN] > 0: - key, _ = EVP_BytesToKey(password, - m[METHOD_INFO_KEY_LEN], - m[METHOD_INFO_IV_LEN]) - else: - # key_length == 0 indicates we should use the key directly - key, iv = password, b'' - self.key = key - iv = iv[:m[METHOD_INFO_IV_LEN]] - if op == CIPHER_ENC_ENCRYPTION: - # this iv is for cipher not decipher - self.cipher_iv = iv - return m[METHOD_INFO_CRYPTO](method, key, iv, op, self.crypto_path) - - def encrypt(self, buf): - if len(buf) == 0: - return buf - if self.iv_sent: - return self.cipher.encrypt(buf) - else: - self.iv_sent = True - return self.cipher_iv + self.cipher.encrypt(buf) - - def decrypt(self, buf): - if len(buf) == 0: - return buf - if self.decipher is None: - decipher_iv_len = self._method_info[METHOD_INFO_IV_LEN] - decipher_iv = buf[:decipher_iv_len] - self.decipher_iv = decipher_iv - self.decipher = self.get_cipher( - self.password, self.method, - CIPHER_ENC_DECRYPTION, - decipher_iv - ) - buf = buf[decipher_iv_len:] - if len(buf) == 0: - return buf - return self.decipher.decrypt(buf) - - -def gen_key_iv(password, method): - method = method.lower() - (key_len, iv_len, m) = method_supported[method] - if key_len > 0: - key, _ = EVP_BytesToKey(password, key_len, iv_len) - else: - key = password - iv = random_string(iv_len) - return key, iv, m - - -def encrypt_all_m(key, iv, m, method, data, crypto_path=None): - result = [iv] - cipher = m(method, key, iv, 1, crypto_path) - result.append(cipher.encrypt_once(data)) - return b''.join(result) - - -def decrypt_all(password, method, data, crypto_path=None): - result = [] - method = method.lower() - (key, iv, m) = gen_key_iv(password, method) - iv = data[:len(iv)] - data = data[len(iv):] - cipher = m(method, key, iv, CIPHER_ENC_DECRYPTION, crypto_path) - result.append(cipher.decrypt_once(data)) - return b''.join(result), key, iv - - -def encrypt_all(password, method, data, crypto_path=None): - result = [] - method = method.lower() - (key, iv, m) = gen_key_iv(password, method) - result.append(iv) - cipher = m(method, key, iv, CIPHER_ENC_ENCRYPTION, crypto_path) - result.append(cipher.encrypt_once(data)) - return b''.join(result) - - -CIPHERS_TO_TEST = [ - 'aes-128-cfb', - 'aes-256-cfb', - 'aes-256-gcm', - 'rc4-md5', - 'salsa20', - 'chacha20', - 'table', -] - - -def test_encryptor(): - from os import urandom - plain = urandom(10240) - for method in CIPHERS_TO_TEST: - logging.warn(method) - encryptor = Cryptor(b'key', method) - decryptor = Cryptor(b'key', method) - cipher = encryptor.encrypt(plain) - plain2 = decryptor.decrypt(cipher) - assert plain == plain2 - - -def test_encrypt_all(): - from os import urandom - plain = urandom(10240) - for method in CIPHERS_TO_TEST: - logging.warn(method) - cipher = encrypt_all(b'key', method, plain) - plain2, key, iv = decrypt_all(b'key', method, cipher) - assert plain == plain2 - - -def test_encrypt_all_m(): - from os import urandom - plain = urandom(10240) - for method in CIPHERS_TO_TEST: - logging.warn(method) - key, iv, m = gen_key_iv(b'key', method) - cipher = encrypt_all_m(key, iv, m, method, plain) - plain2, key, iv = decrypt_all(b'key', method, cipher) - assert plain == plain2 - - -if __name__ == '__main__': - test_encrypt_all() - test_encryptor() - test_encrypt_all_m() diff --git a/shadowsocks/daemon.py b/shadowsocks/daemon.py index 77ed323..d206ccf 100644 --- a/shadowsocks/daemon.py +++ b/shadowsocks/daemon.py @@ -1,19 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2014-2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement @@ -23,7 +29,7 @@ import sys import logging import signal import time -from shadowsocks import common, shell +from shadowsocks import common # this module is ported from ShadowVPN daemon.c @@ -37,6 +43,9 @@ def daemon_exec(config): command = 'start' pid_file = config['pid-file'] log_file = config['log-file'] + command = common.to_str(command) + pid_file = common.to_str(pid_file) + log_file = common.to_str(log_file) if command == 'start': daemon_start(pid_file, log_file) elif command == 'stop': @@ -58,7 +67,7 @@ def write_pid_file(pid_file, pid): fd = os.open(pid_file, os.O_RDWR | os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR) except OSError as e: - shell.print_exception(e) + logging.error(e) return -1 flags = fcntl.fcntl(fd, fcntl.F_GETFD) assert flags != -1 @@ -117,7 +126,7 @@ def daemon_start(pid_file, log_file): sys.exit(1) os.setsid() - signal.signal(signal.SIGHUP, signal.SIG_IGN) + signal.signal(signal.SIG_IGN, signal.SIGHUP) print('started') os.kill(ppid, signal.SIGTERM) @@ -127,7 +136,7 @@ def daemon_start(pid_file, log_file): freopen(log_file, 'a', sys.stdout) freopen(log_file, 'a', sys.stderr) except IOError as e: - shell.print_exception(e) + logging.error(e) sys.exit(1) @@ -140,7 +149,7 @@ def daemon_stop(pid_file): if not buf: logging.error('not running') except IOError as e: - shell.print_exception(e) + logging.error(e) if e.errno == errno.ENOENT: # always exit 0 if we are sure daemon is not running logging.error('not running') @@ -155,7 +164,7 @@ def daemon_stop(pid_file): logging.error('not running') # always exit 0 if we are sure daemon is not running return - shell.print_exception(e) + logging.error(e) sys.exit(1) else: logging.error('pid is not positive: %d', pid) @@ -174,35 +183,3 @@ def daemon_stop(pid_file): sys.exit(1) print('stopped') os.unlink(pid_file) - - -def set_user(username): - if username is None: - return - - import pwd - import grp - - try: - pwrec = pwd.getpwnam(username) - except KeyError: - logging.error('user not found: %s' % username) - raise - user = pwrec[0] - uid = pwrec[2] - gid = pwrec[3] - - cur_uid = os.getuid() - if uid == cur_uid: - return - if cur_uid != 0: - logging.error('can not set user as nonroot user') - # will raise later - - # inspired by supervisor - if hasattr(os, 'setgroups'): - groups = [grprec[2] for grprec in grp.getgrall() if user in grprec[3]] - groups.insert(0, gid) - os.setgroups(groups) - os.setgid(gid) - os.setuid(uid) diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py new file mode 100644 index 0000000..ba02101 --- /dev/null +++ b/shadowsocks/encrypt.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python + +# Copyright (c) 2014 clowwindy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import os +import sys +import hashlib +import logging + +from shadowsocks.crypto import m2, rc4_md5, salsa20_ctr,\ + ctypes_openssl, ctypes_libsodium, table + + +method_supported = {} +method_supported.update(rc4_md5.ciphers) +method_supported.update(salsa20_ctr.ciphers) +method_supported.update(ctypes_openssl.ciphers) +method_supported.update(ctypes_libsodium.ciphers) +# let M2Crypto override ctypes_openssl +method_supported.update(m2.ciphers) +method_supported.update(table.ciphers) + + +def random_string(length): + try: + import M2Crypto.Rand + return M2Crypto.Rand.rand_bytes(length) + except ImportError: + return os.urandom(length) + + +cached_keys = {} + + +def try_cipher(key, method=None): + Encryptor(key, method) + + +def EVP_BytesToKey(password, key_len, iv_len): + # equivalent to OpenSSL's EVP_BytesToKey() with count 1 + # so that we make the same key and iv as nodejs version + if hasattr(password, 'encode'): + password = password.encode('utf-8') + cached_key = '%s-%d-%d' % (password, key_len, iv_len) + r = cached_keys.get(cached_key, None) + if r: + return r + m = [] + i = 0 + while len(b''.join(m)) < (key_len + iv_len): + md5 = hashlib.md5() + data = password + if i > 0: + data = m[i - 1] + password + md5.update(data) + m.append(md5.digest()) + i += 1 + ms = b''.join(m) + key = ms[:key_len] + iv = ms[key_len:key_len + iv_len] + cached_keys[cached_key] = (key, iv) + return key, iv + + +class Encryptor(object): + def __init__(self, key, method): + self.key = key + self.method = method + self.iv = None + self.iv_sent = False + self.cipher_iv = b'' + self.decipher = None + method = method.lower() + self._method_info = self.get_method_info(method) + if self._method_info: + self.cipher = self.get_cipher(key, method, 1, + random_string(self._method_info[1])) + else: + logging.error('method %s not supported' % method) + sys.exit(1) + + def get_method_info(self, method): + method = method.lower() + m = method_supported.get(method) + return m + + def iv_len(self): + return len(self.cipher_iv) + + def get_cipher(self, password, method, op, iv): + if hasattr(password, 'encode'): + password = password.encode('utf-8') + m = self._method_info + if m[0] > 0: + key, iv_ = EVP_BytesToKey(password, m[0], m[1]) + else: + # key_length == 0 indicates we should use the key directly + key, iv = password, b'' + + iv = iv[:m[1]] + if op == 1: + # this iv is for cipher not decipher + self.cipher_iv = iv[:m[1]] + return m[2](method, key, iv, op) + + def encrypt(self, buf): + if len(buf) == 0: + return buf + if self.iv_sent: + return self.cipher.update(buf) + else: + self.iv_sent = True + return self.cipher_iv + self.cipher.update(buf) + + def decrypt(self, buf): + if len(buf) == 0: + return buf + if self.decipher is None: + decipher_iv_len = self._method_info[1] + decipher_iv = buf[:decipher_iv_len] + self.decipher = self.get_cipher(self.key, self.method, 0, + iv=decipher_iv) + buf = buf[decipher_iv_len:] + if len(buf) == 0: + return buf + return self.decipher.update(buf) + + +def encrypt_all(password, method, op, data): + result = [] + method = method.lower() + (key_len, iv_len, m) = method_supported[method] + if key_len > 0: + key, _ = EVP_BytesToKey(password, key_len, iv_len) + else: + key = password + if op: + iv = random_string(iv_len) + result.append(iv) + else: + iv = data[:iv_len] + data = data[iv_len:] + cipher = m(method, key, iv, op) + result.append(cipher.update(data)) + return b''.join(result) + + +CIPHERS_TO_TEST = [ + b'aes-128-cfb', + b'aes-256-cfb', + b'rc4-md5', + b'salsa20', + b'chacha20', + b'table', +] + + +def test_encryptor(): + from os import urandom + plain = urandom(10240) + for method in CIPHERS_TO_TEST: + logging.warn(method) + encryptor = Encryptor(b'key', method) + decryptor = Encryptor(b'key', method) + cipher = encryptor.encrypt(plain) + plain2 = decryptor.decrypt(cipher) + assert plain == plain2 + + +def test_encrypt_all(): + from os import urandom + plain = urandom(10240) + for method in CIPHERS_TO_TEST: + logging.warn(method) + cipher = encrypt_all(b'key', method, 1, plain) + plain2 = encrypt_all(b'key', method, 0, cipher) + assert plain == plain2 + + +if __name__ == '__main__': + test_encrypt_all() + test_encryptor() diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index ce5da37..55c30bb 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -1,19 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2013-2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. # from ssloop # https://github.com/clowwindy/ssloop @@ -22,16 +28,12 @@ from __future__ import absolute_import, division, print_function, \ with_statement import os -import time import socket import select -import traceback import errno import logging from collections import defaultdict -from shadowsocks import shell - __all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR', 'POLL_HUP', 'POLL_NVAL', 'EVENT_NAMES'] @@ -53,8 +55,23 @@ EVENT_NAMES = { POLL_NVAL: 'POLL_NVAL', } -# we check timeouts every TIMEOUT_PRECISION seconds -TIMEOUT_PRECISION = 10 + +class EpollLoop(object): + + def __init__(self): + self._epoll = select.epoll() + + def poll(self, timeout): + return self._epoll.poll(timeout) + + def add_fd(self, fd, mode): + self._epoll.register(fd, mode) + + def remove_fd(self, fd): + self._epoll.unregister(fd) + + def modify_fd(self, fd, mode): + self._epoll.modify(fd, mode) class KqueueLoop(object): @@ -87,20 +104,17 @@ class KqueueLoop(object): results[fd] |= POLL_OUT return results.items() - def register(self, fd, mode): + def add_fd(self, fd, mode): self._fds[fd] = mode self._control(fd, mode, select.KQ_EV_ADD) - def unregister(self, fd): + def remove_fd(self, fd): self._control(fd, self._fds[fd], select.KQ_EV_DELETE) del self._fds[fd] - def modify(self, fd, mode): - self.unregister(fd) - self.register(fd, mode) - - def close(self): - self._kqueue.close() + def modify_fd(self, fd, mode): + self.remove_fd(fd) + self.add_fd(fd, mode) class SelectLoop(object): @@ -119,7 +133,7 @@ class SelectLoop(object): results[fd] |= p[1] return results.items() - def register(self, fd, mode): + def add_fd(self, fd, mode): if mode & POLL_IN: self._r_list.add(fd) if mode & POLL_OUT: @@ -127,7 +141,7 @@ class SelectLoop(object): if mode & POLL_ERR: self._x_list.add(fd) - def unregister(self, fd): + def remove_fd(self, fd): if fd in self._r_list: self._r_list.remove(fd) if fd in self._w_list: @@ -135,18 +149,16 @@ class SelectLoop(object): if fd in self._x_list: self._x_list.remove(fd) - def modify(self, fd, mode): - self.unregister(fd) - self.register(fd, mode) - - def close(self): - pass + def modify_fd(self, fd, mode): + self.remove_fd(fd) + self.add_fd(fd, mode) class EventLoop(object): def __init__(self): + self._iterating = False if hasattr(select, 'epoll'): - self._impl = select.epoll() + self._impl = EpollLoop() model = 'epoll' elif hasattr(select, 'kqueue'): self._impl = KqueueLoop() @@ -157,73 +169,73 @@ class EventLoop(object): else: raise Exception('can not find any available functions in select ' 'package') - self._fdmap = {} # (f, handler) - self._last_time = time.time() - self._periodic_callbacks = [] - self._stopping = False + self._fd_to_f = {} + self._handlers = [] + self._ref_handlers = [] + self._handlers_to_remove = [] logging.debug('using event model: %s', model) def poll(self, timeout=None): events = self._impl.poll(timeout) - return [(self._fdmap[fd][0], fd, event) for fd, event in events] + return [(self._fd_to_f[fd], fd, event) for fd, event in events] - def add(self, f, mode, handler): + def add(self, f, mode): fd = f.fileno() - self._fdmap[fd] = (f, handler) - self._impl.register(fd, mode) + self._fd_to_f[fd] = f + self._impl.add_fd(fd, mode) def remove(self, f): fd = f.fileno() - del self._fdmap[fd] - self._impl.unregister(fd) - - def add_periodic(self, callback): - self._periodic_callbacks.append(callback) - - def remove_periodic(self, callback): - self._periodic_callbacks.remove(callback) + del self._fd_to_f[fd] + self._impl.remove_fd(fd) def modify(self, f, mode): fd = f.fileno() - self._impl.modify(fd, mode) + self._impl.modify_fd(fd, mode) - def stop(self): - self._stopping = True + def add_handler(self, handler, ref=True): + self._handlers.append(handler) + if ref: + # when all ref handlers are removed, loop stops + self._ref_handlers.append(handler) + + def remove_handler(self, handler): + if handler in self._ref_handlers: + self._ref_handlers.remove(handler) + if self._iterating: + self._handlers_to_remove.append(handler) + else: + self._handlers.remove(handler) def run(self): events = [] - while not self._stopping: - asap = False + while self._ref_handlers: try: - events = self.poll(TIMEOUT_PRECISION) + events = self.poll(1) except (OSError, IOError) as e: if errno_from_exception(e) in (errno.EPIPE, errno.EINTR): # EPIPE: Happens when the client closes the connection # EINTR: Happens when received a signal # handles them as soon as possible - asap = True logging.debug('poll:%s', e) else: logging.error('poll:%s', e) + import traceback traceback.print_exc() continue - - for sock, fd, event in events: - handler = self._fdmap.get(fd, None) - if handler is not None: - handler = handler[1] - try: - handler.handle_event(sock, fd, event) - except (OSError, IOError) as e: - shell.print_exception(e) - now = time.time() - if asap or now - self._last_time >= TIMEOUT_PRECISION: - for callback in self._periodic_callbacks: - callback() - self._last_time = now - - def __del__(self): - self._impl.close() + self._iterating = True + for handler in self._handlers: + # TODO when there are a lot of handlers + try: + handler(events) + except (OSError, IOError) as e: + logging.error(e) + import traceback + traceback.print_exc() + for handler in self._handlers_to_remove: + self._handlers.remove(handler) + self._handlers_to_remove = [] + self._iterating = False # from tornado diff --git a/shadowsocks/local.py b/shadowsocks/local.py index dfc8032..994b6d8 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -1,19 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2012-2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement @@ -24,12 +30,12 @@ import logging import signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns +from shadowsocks import utils, daemon, encrypt, eventloop, tcprelay, udprelay,\ + asyncdns -@shell.exception_handle(self_=False, exit_code=1) def main(): - shell.check_python() + utils.check_python() # fix py2exe if hasattr(sys, "frozen") and sys.frozen in \ @@ -37,32 +43,43 @@ def main(): p = os.path.dirname(os.path.abspath(sys.executable)) os.chdir(p) - config = shell.get_config(True) + config = utils.get_config(True) + daemon.daemon_exec(config) - logging.info("starting local at %s:%d" % - (config['local_address'], config['local_port'])) + utils.print_shadowsocks() - dns_resolver = asyncdns.DNSResolver() - tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) - udp_server = udprelay.UDPRelay(config, dns_resolver, True) - loop = eventloop.EventLoop() - dns_resolver.add_to_loop(loop) - tcp_server.add_to_loop(loop) - udp_server.add_to_loop(loop) + encrypt.try_cipher(config['password'], config['method']) - def handler(signum, _): - logging.warn('received SIGQUIT, doing graceful shutting down..') - tcp_server.close(next_tick=True) - udp_server.close(next_tick=True) - signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) + try: + logging.info("starting local at %s:%d" % + (config['local_address'], config['local_port'])) - def int_handler(signum, _): - sys.exit(1) - signal.signal(signal.SIGINT, int_handler) + dns_resolver = asyncdns.DNSResolver() + tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) + udp_server = udprelay.UDPRelay(config, dns_resolver, True) + loop = eventloop.EventLoop() + dns_resolver.add_to_loop(loop) + tcp_server.add_to_loop(loop) + udp_server.add_to_loop(loop) - daemon.set_user(config.get('user', None)) - loop.run() + def handler(signum, _): + logging.warn('received SIGQUIT, doing graceful shutting down..') + tcp_server.close(next_tick=True) + udp_server.close(next_tick=True) + signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) + + def int_handler(signum, _): + sys.exit(1) + signal.signal(signal.SIGINT, int_handler) + + loop.run() + except (KeyboardInterrupt, IOError, OSError) as e: + logging.error(e) + if config['verbose']: + import traceback + traceback.print_exc() + os._exit(1) if __name__ == '__main__': main() diff --git a/shadowsocks/lru_cache.py b/shadowsocks/lru_cache.py index 55cb346..4523399 100644 --- a/shadowsocks/lru_cache.py +++ b/shadowsocks/lru_cache.py @@ -1,19 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement @@ -41,7 +47,6 @@ class LRUCache(collections.MutableMapping): self._time_to_keys = collections.defaultdict(list) self._keys_to_last_time = {} self._last_visits = collections.deque() - self._closed_values = set() self.update(dict(*args, **kwargs)) # use the free update to set keys def __getitem__(self, key): @@ -79,21 +84,21 @@ class LRUCache(collections.MutableMapping): least = self._last_visits[0] if now - least <= self.timeout: break - self._last_visits.popleft() + if self.close_callback is not None: + for key in self._time_to_keys[least]: + if key in self._store: + if now - self._keys_to_last_time[key] > self.timeout: + value = self._store[key] + self.close_callback(value) for key in self._time_to_keys[least]: + self._last_visits.popleft() if key in self._store: if now - self._keys_to_last_time[key] > self.timeout: - if self.close_callback is not None: - value = self._store[key] - if value not in self._closed_values: - self.close_callback(value) - self._closed_values.add(value) del self._store[key] del self._keys_to_last_time[key] c += 1 del self._time_to_keys[least] if c: - self._closed_values.clear() logging.debug('%d keys swept' % c) @@ -127,22 +132,5 @@ def test(): assert 'a' not in c assert 'b' not in c - global close_cb_called - close_cb_called = False - - def close_cb(t): - global close_cb_called - assert not close_cb_called - close_cb_called = True - - c = LRUCache(timeout=0.1, close_callback=close_cb) - c['s'] = 1 - c['t'] = 1 - c['s'] - time.sleep(0.1) - c['s'] - time.sleep(0.3) - c.sweep() - if __name__ == '__main__': test() diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py deleted file mode 100644 index 62f187e..0000000 --- a/shadowsocks/manager.py +++ /dev/null @@ -1,293 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright 2015 clowwindy -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import errno -import traceback -import socket -import logging -import json -import collections - -from shadowsocks import common, eventloop, tcprelay, udprelay, asyncdns, shell - - -BUF_SIZE = 1506 -STAT_SEND_LIMIT = 50 - - -class Manager(object): - - def __init__(self, config): - self._config = config - self._relays = {} # (tcprelay, udprelay) - self._loop = eventloop.EventLoop() - self._dns_resolver = asyncdns.DNSResolver() - self._dns_resolver.add_to_loop(self._loop) - - self._statistics = collections.defaultdict(int) - self._control_client_addr = None - try: - manager_address = config['manager_address'] - if ':' in manager_address: - addr = manager_address.rsplit(':', 1) - addr = addr[0], int(addr[1]) - addrs = socket.getaddrinfo(addr[0], addr[1]) - if addrs: - family = addrs[0][0] - else: - logging.error('invalid address: %s', manager_address) - exit(1) - else: - addr = manager_address - family = socket.AF_UNIX - self._control_socket = socket.socket(family, - socket.SOCK_DGRAM) - self._control_socket.bind(addr) - self._control_socket.setblocking(False) - except (OSError, IOError) as e: - logging.error(e) - logging.error('can not bind to manager address') - exit(1) - self._loop.add(self._control_socket, - eventloop.POLL_IN, self) - self._loop.add_periodic(self.handle_periodic) - - port_password = config['port_password'] - del config['port_password'] - config['crypto_path'] = config.get('crypto_path', dict()) - for port, password in port_password.items(): - a_config = config.copy() - a_config['server_port'] = int(port) - a_config['password'] = password - self.add_port(a_config) - - def add_port(self, config): - port = int(config['server_port']) - servers = self._relays.get(port, None) - if servers: - logging.error("server already exists at %s:%d" % (config['server'], - port)) - return - logging.info("adding server at %s:%d" % (config['server'], port)) - t = tcprelay.TCPRelay(config, self._dns_resolver, False, - self.stat_callback) - u = udprelay.UDPRelay(config, self._dns_resolver, False, - self.stat_callback) - t.add_to_loop(self._loop) - u.add_to_loop(self._loop) - self._relays[port] = (t, u) - - def remove_port(self, config): - port = int(config['server_port']) - servers = self._relays.get(port, None) - if servers: - logging.info("removing server at %s:%d" % (config['server'], port)) - t, u = servers - t.close(next_tick=False) - u.close(next_tick=False) - del self._relays[port] - else: - logging.error("server not exist at %s:%d" % (config['server'], - port)) - - def handle_event(self, sock, fd, event): - if sock == self._control_socket and event == eventloop.POLL_IN: - data, self._control_client_addr = sock.recvfrom(BUF_SIZE) - parsed = self._parse_command(data) - if parsed: - command, config = parsed - a_config = self._config.copy() - if config: - # let the command override the configuration file - a_config.update(config) - if 'server_port' not in a_config: - logging.error('can not find server_port in config') - else: - if command == 'add': - self.add_port(a_config) - self._send_control_data(b'ok') - elif command == 'remove': - self.remove_port(a_config) - self._send_control_data(b'ok') - elif command == 'ping': - self._send_control_data(b'pong') - else: - logging.error('unknown command %s', command) - - def _parse_command(self, data): - # commands: - # add: {"server_port": 8000, "password": "foobar"} - # remove: {"server_port": 8000"} - data = common.to_str(data) - parts = data.split(':', 1) - if len(parts) < 2: - return data, None - command, config_json = parts - try: - config = shell.parse_json_in_str(config_json) - if 'method' in config: - config['method'] = common.to_str(config['method']) - return command, config - except Exception as e: - logging.error(e) - return None - - def stat_callback(self, port, data_len): - self._statistics[port] += data_len - - def handle_periodic(self): - r = {} - i = 0 - - def send_data(data_dict): - if data_dict: - # use compact JSON format (without space) - data = common.to_bytes(json.dumps(data_dict, - separators=(',', ':'))) - self._send_control_data(b'stat: ' + data) - - for k, v in self._statistics.items(): - r[k] = v - i += 1 - # split the data into segments that fit in UDP packets - if i >= STAT_SEND_LIMIT: - send_data(r) - r.clear() - i = 0 - if len(r) > 0: - send_data(r) - self._statistics.clear() - - def _send_control_data(self, data): - if not self._control_client_addr: - return - - try: - self._control_socket.sendto(data, self._control_client_addr) - except (socket.error, OSError, IOError) as e: - error_no = eventloop.errno_from_exception(e) - if error_no in (errno.EAGAIN, errno.EINPROGRESS, - errno.EWOULDBLOCK): - return - else: - shell.print_exception(e) - if self._config['verbose']: - traceback.print_exc() - - def run(self): - self._loop.run() - - -def run(config): - Manager(config).run() - - -def test(): - import time - import threading - import struct - from shadowsocks import cryptor - - logging.basicConfig(level=5, - format='%(asctime)s %(levelname)-8s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S') - enc = [] - eventloop.TIMEOUT_PRECISION = 1 - - def run_server(): - config = { - 'server': '127.0.0.1', - 'local_port': 1081, - 'port_password': { - '8381': 'foobar1', - '8382': 'foobar2' - }, - 'method': 'aes-256-cfb', - 'manager_address': '127.0.0.1:6001', - 'timeout': 60, - 'fast_open': False, - 'verbose': 2 - } - manager = Manager(config) - enc.append(manager) - manager.run() - - t = threading.Thread(target=run_server) - t.start() - time.sleep(1) - manager = enc[0] - cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - cli.connect(('127.0.0.1', 6001)) - - # test add and remove - time.sleep(1) - cli.send(b'add: {"server_port":7001, "password":"asdfadsfasdf"}') - time.sleep(1) - assert 7001 in manager._relays - data, addr = cli.recvfrom(1506) - assert b'ok' in data - - cli.send(b'remove: {"server_port":8381}') - time.sleep(1) - assert 8381 not in manager._relays - data, addr = cli.recvfrom(1506) - assert b'ok' in data - logging.info('add and remove test passed') - - # test statistics for TCP - header = common.pack_addr(b'google.com') + struct.pack('>H', 80) - data = cryptor.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', - header + b'GET /\r\n\r\n') - tcp_cli = socket.socket() - tcp_cli.connect(('127.0.0.1', 7001)) - tcp_cli.send(data) - tcp_cli.recv(4096) - tcp_cli.close() - - data, addr = cli.recvfrom(1506) - data = common.to_str(data) - assert data.startswith('stat: ') - data = data.split('stat:')[1] - stats = shell.parse_json_in_str(data) - assert '7001' in stats - logging.info('TCP statistics test passed') - - # test statistics for UDP - header = common.pack_addr(b'127.0.0.1') + struct.pack('>H', 80) - data = cryptor.encrypt_all(b'foobar2', 'aes-256-cfb', - header + b'test') - udp_cli = socket.socket(type=socket.SOCK_DGRAM) - udp_cli.sendto(data, ('127.0.0.1', 8382)) - tcp_cli.close() - - data, addr = cli.recvfrom(1506) - data = common.to_str(data) - assert data.startswith('stat: ') - data = data.split('stat:')[1] - stats = json.loads(data) - assert '8382' in stats - logging.info('UDP statistics test passed') - - manager._loop.stop() - t.join() - - -if __name__ == '__main__': - test() diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 4dc5621..8eed4ad 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -1,19 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement @@ -24,17 +30,19 @@ import logging import signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, \ - asyncdns, manager +from shadowsocks import utils, daemon, encrypt, eventloop, tcprelay, udprelay,\ + asyncdns def main(): - shell.check_python() + utils.check_python() - config = shell.get_config(False) + config = utils.get_config(False) daemon.daemon_exec(config) + utils.print_shadowsocks() + if config['port_password']: if config['password']: logging.warn('warning: port_password should not be used with ' @@ -49,23 +57,11 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] - if config.get('manager_address', 0): - logging.info('entering manager mode') - manager.run(config) - return - + encrypt.try_cipher(config['password'], config['method']) tcp_servers = [] udp_servers = [] - - if 'dns_server' in config: # allow override settings in resolv.conf - dns_resolver = asyncdns.DNSResolver(config['dns_server'], - config['prefer_ipv6']) - else: - dns_resolver = asyncdns.DNSResolver(prefer_ipv6=config['prefer_ipv6']) - - port_password = config['port_password'] - del config['port_password'] - for port, password in port_password.items(): + dns_resolver = asyncdns.DNSResolver() + for port, password in config['port_password'].items(): a_config = config.copy() a_config['server_port'] = int(port) a_config['password'] = password @@ -90,12 +86,13 @@ def main(): loop = eventloop.EventLoop() dns_resolver.add_to_loop(loop) list(map(lambda s: s.add_to_loop(loop), tcp_servers + udp_servers)) - - daemon.set_user(config.get('user', None)) loop.run() - except Exception as e: - shell.print_exception(e) - sys.exit(1) + except (KeyboardInterrupt, IOError, OSError) as e: + logging.error(e) + if config['verbose']: + import traceback + traceback.print_exc() + os._exit(1) if int(config['workers']) > 1: if os.name == 'posix': diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py deleted file mode 100644 index d508049..0000000 --- a/shadowsocks/shell.py +++ /dev/null @@ -1,509 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright 2015 clowwindy -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import os -import json -import sys -import getopt -import logging -import traceback - -from functools import wraps - -from shadowsocks.common import to_bytes, to_str, IPNetwork -from shadowsocks import cryptor - - -VERBOSE_LEVEL = 5 - -verbose = 0 - - -def check_python(): - info = sys.version_info - if info[0] == 2 and not info[1] >= 6: - print('Python 2.6+ required') - sys.exit(1) - elif info[0] == 3 and not info[1] >= 3: - print('Python 3.3+ required') - sys.exit(1) - elif info[0] not in [2, 3]: - print('Python version not supported') - sys.exit(1) - - -def print_exception(e): - global verbose - logging.error(e) - if verbose > 0: - import traceback - traceback.print_exc() - - -def exception_handle(self_, err_msg=None, exit_code=None, - destroy=False, conn_err=False): - # self_: if function passes self as first arg - - def process_exception(e, self=None): - print_exception(e) - if err_msg: - logging.error(err_msg) - if exit_code: - sys.exit(1) - - if not self_: - return - - if conn_err: - addr, port = self._client_address[0], self._client_address[1] - logging.error('%s when handling connection from %s:%d' % - (e, addr, port)) - if self._config['verbose']: - traceback.print_exc() - if destroy: - self.destroy() - - def decorator(func): - if self_: - @wraps(func) - def wrapper(self, *args, **kwargs): - try: - func(self, *args, **kwargs) - except Exception as e: - process_exception(e, self) - else: - @wraps(func) - def wrapper(*args, **kwargs): - try: - func(*args, **kwargs) - except Exception as e: - process_exception(e) - - return wrapper - return decorator - - -def print_shadowsocks(): - version = '' - try: - import pkg_resources - version = pkg_resources.get_distribution('shadowsocks').version - except Exception: - pass - print('Shadowsocks %s' % version) - - -def find_config(): - config_path = 'config.json' - if os.path.exists(config_path): - return config_path - config_path = os.path.join(os.path.dirname(__file__), '../', 'config.json') - if os.path.exists(config_path): - return config_path - return None - - -def check_config(config, is_local): - if config.get('daemon', None) == 'stop': - # no need to specify configuration for daemon stop - return - - if is_local: - if config.get('server', None) is None: - logging.error('server addr not specified') - print_local_help() - sys.exit(2) - else: - config['server'] = to_str(config['server']) - - if config.get('tunnel_remote', None) is None: - logging.error('tunnel_remote addr not specified') - print_local_help() - sys.exit(2) - else: - config['tunnel_remote'] = to_str(config['tunnel_remote']) - else: - config['server'] = to_str(config.get('server', '0.0.0.0')) - try: - config['forbidden_ip'] = \ - IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) - except Exception as e: - logging.error(e) - sys.exit(2) - - if is_local and not config.get('password', None): - logging.error('password not specified') - print_help(is_local) - sys.exit(2) - - if not is_local and not config.get('password', None) \ - and not config.get('port_password', None) \ - and not config.get('manager_address'): - logging.error('password or port_password not specified') - print_help(is_local) - sys.exit(2) - - if 'local_port' in config: - config['local_port'] = int(config['local_port']) - - if 'server_port' in config and type(config['server_port']) != list: - config['server_port'] = int(config['server_port']) - - if 'tunnel_remote_port' in config: - config['tunnel_remote_port'] = int(config['tunnel_remote_port']) - if 'tunnel_port' in config: - config['tunnel_port'] = int(config['tunnel_port']) - - if config.get('local_address', '') in [b'0.0.0.0']: - logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe') - if config.get('server', '') in ['127.0.0.1', 'localhost']: - logging.warn('warning: server set to listen on %s:%s, are you sure?' % - (to_str(config['server']), config['server_port'])) - if (config.get('method', '') or '').lower() == 'table': - logging.warn('warning: table is not safe; please use a safer cipher, ' - 'like AES-256-CFB') - if (config.get('method', '') or '').lower() == 'rc4': - logging.warn('warning: RC4 is not safe; please use a safer cipher, ' - 'like AES-256-CFB') - if config.get('timeout', 300) < 100: - logging.warn('warning: your timeout %d seems too short' % - int(config.get('timeout'))) - if config.get('timeout', 300) > 600: - logging.warn('warning: your timeout %d seems too long' % - int(config.get('timeout'))) - if config.get('password') in [b'mypassword']: - logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' - 'config.json!') - sys.exit(1) - if config.get('user', None) is not None: - if os.name != 'posix': - logging.error('user can be used only on Unix') - sys.exit(1) - if config.get('dns_server', None) is not None: - if type(config['dns_server']) != list: - config['dns_server'] = to_str(config['dns_server']) - else: - config['dns_server'] = [to_str(ds) for ds in config['dns_server']] - logging.info('Specified DNS server: %s' % config['dns_server']) - - config['crypto_path'] = {'openssl': config['libopenssl'], - 'mbedtls': config['libmbedtls'], - 'sodium': config['libsodium']} - - cryptor.try_cipher(config['password'], config['method'], - config['crypto_path']) - - -def get_config(is_local): - global verbose - - logging.basicConfig(level=logging.INFO, - format='%(levelname)-s: %(message)s') - if is_local: - shortopts = 'hd:s:b:p:k:l:m:c:t:vqa' - longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=', - 'libopenssl=', 'libmbedtls=', 'libsodium=', 'version'] - else: - shortopts = 'hd:s:p:k:m:c:t:vqa' - longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', - 'forbidden-ip=', 'user=', 'manager-address=', 'version', - 'libopenssl=', 'libmbedtls=', 'libsodium=', 'prefer-ipv6'] - try: - config_path = find_config() - optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) - for key, value in optlist: - if key == '-c': - config_path = value - - if config_path: - logging.info('loading config from %s' % config_path) - with open(config_path, 'rb') as f: - try: - config = parse_json_in_str(f.read().decode('utf8')) - except ValueError as e: - logging.error('found an error in config.json: %s', - e.message) - sys.exit(1) - else: - config = {} - - v_count = 0 - for key, value in optlist: - if key == '-p': - config['server_port'] = int(value) - elif key == '-k': - config['password'] = to_bytes(value) - elif key == '-l': - config['local_port'] = int(value) - elif key == '-s': - config['server'] = to_str(value) - elif key == '-m': - config['method'] = to_str(value) - elif key == '-b': - config['local_address'] = to_str(value) - elif key == '-v': - v_count += 1 - # '-vv' turns on more verbose mode - config['verbose'] = v_count - elif key == '-a': - config['one_time_auth'] = True - elif key == '-t': - config['timeout'] = int(value) - elif key == '--fast-open': - config['fast_open'] = True - elif key == '--libopenssl': - config['libopenssl'] = to_str(value) - elif key == '--libmbedtls': - config['libmbedtls'] = to_str(value) - elif key == '--libsodium': - config['libsodium'] = to_str(value) - elif key == '--workers': - config['workers'] = int(value) - elif key == '--manager-address': - config['manager_address'] = to_str(value) - elif key == '--user': - config['user'] = to_str(value) - elif key == '--forbidden-ip': - config['forbidden_ip'] = to_str(value).split(',') - elif key in ('-h', '--help'): - if is_local: - print_local_help() - else: - print_server_help() - sys.exit(0) - elif key == '--version': - print_shadowsocks() - sys.exit(0) - elif key == '-d': - config['daemon'] = to_str(value) - elif key == '--pid-file': - config['pid-file'] = to_str(value) - elif key == '--log-file': - config['log-file'] = to_str(value) - elif key == '-q': - v_count -= 1 - config['verbose'] = v_count - elif key == '--prefer-ipv6': - config['prefer_ipv6'] = True - except getopt.GetoptError as e: - print(e, file=sys.stderr) - print_help(is_local) - sys.exit(2) - - if not config: - logging.error('config not specified') - print_help(is_local) - sys.exit(2) - - config['password'] = to_bytes(config.get('password', b'')) - config['method'] = to_str(config.get('method', 'aes-256-cfb')) - config['port_password'] = config.get('port_password', None) - config['timeout'] = int(config.get('timeout', 300)) - config['fast_open'] = config.get('fast_open', False) - config['workers'] = config.get('workers', 1) - config['pid-file'] = config.get('pid-file', '/var/run/shadowsocks.pid') - config['log-file'] = config.get('log-file', '/var/log/shadowsocks.log') - config['verbose'] = config.get('verbose', False) - config['local_address'] = to_str(config.get('local_address', '127.0.0.1')) - config['local_port'] = config.get('local_port', 1080) - config['one_time_auth'] = config.get('one_time_auth', False) - config['prefer_ipv6'] = config.get('prefer_ipv6', False) - config['server_port'] = config.get('server_port', 8388) - config['dns_server'] = config.get('dns_server', None) - config['libopenssl'] = config.get('libopenssl', None) - config['libmbedtls'] = config.get('libmbedtls', None) - config['libsodium'] = config.get('libsodium', None) - - config['tunnel_remote'] = to_str(config.get('tunnel_remote', '8.8.8.8')) - config['tunnel_remote_port'] = config.get('tunnel_remote_port', 53) - config['tunnel_port'] = config.get('tunnel_port', 53) - - logging.getLogger('').handlers = [] - logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') - if config['verbose'] >= 2: - level = VERBOSE_LEVEL - elif config['verbose'] == 1: - level = logging.DEBUG - elif config['verbose'] == -1: - level = logging.WARN - elif config['verbose'] <= -2: - level = logging.ERROR - else: - level = logging.INFO - verbose = config['verbose'] - logging.basicConfig(level=level, - format='%(asctime)s %(levelname)-8s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S') - - check_config(config, is_local) - - return config - - -def print_help(is_local): - if is_local: - print_local_help() - else: - print_server_help() - - -def print_local_help(): - print('''usage: sslocal [OPTION]... -A fast tunnel proxy that helps you bypass firewalls. - -You can supply configurations via either config file or command line arguments. - -Proxy options: - -c CONFIG path to config file - -s SERVER_ADDR server address - -p SERVER_PORT server port, default: 8388 - -b LOCAL_ADDR local binding address, default: 127.0.0.1 - -l LOCAL_PORT local port, default: 1080 - -k PASSWORD password - -m METHOD encryption method, default: aes-256-cfb - Sodium: - chacha20-poly1305, chacha20-ietf-poly1305, - xchacha20-ietf-poly1305, - sodium:aes-256-gcm, - salsa20, chacha20, chacha20-ietf. - Sodium 1.0.12: - xchacha20 - OpenSSL: - aes-{128|192|256}-gcm, aes-{128|192|256}-cfb, - aes-{128|192|256}-ofb, aes-{128|192|256}-ctr, - camellia-{128|192|256}-cfb, - bf-cfb, cast5-cfb, des-cfb, idea-cfb, - rc2-cfb, seed-cfb, - rc4, rc4-md5, table. - OpenSSL 1.1: - aes-{128|192|256}-ocb - mbedTLS: - mbedtls:aes-{128|192|256}-cfb128, - mbedtls:aes-{128|192|256}-ctr, - mbedtls:camellia-{128|192|256}-cfb128, - mbedtls:aes-{128|192|256}-gcm - -t TIMEOUT timeout in seconds, default: 300 - -a ONE_TIME_AUTH one time auth - --fast-open use TCP_FASTOPEN, requires Linux 3.7+ - --libopenssl=PATH custom openssl crypto lib path - --libmbedtls=PATH custom mbedtls crypto lib path - --libsodium=PATH custom sodium crypto lib path - -General options: - -h, --help show this help message and exit - -d start/stop/restart daemon mode - --pid-file=PID_FILE pid file for daemon mode - --log-file=LOG_FILE log file for daemon mode - --user=USER username to run as - -v, -vv verbose mode - -q, -qq quiet mode, only show warnings/errors - --version show version information - -Online help: -''') - - -def print_server_help(): - print('''usage: ssserver [OPTION]... -A fast tunnel proxy that helps you bypass firewalls. - -You can supply configurations via either config file or command line arguments. - -Proxy options: - -c CONFIG path to config file - -s SERVER_ADDR server address, default: 0.0.0.0 - -p SERVER_PORT server port, default: 8388 - -k PASSWORD password - -m METHOD encryption method, default: aes-256-cfb - Sodium: - chacha20-poly1305, chacha20-ietf-poly1305, - xchacha20-ietf-poly1305, - sodium:aes-256-gcm, - salsa20, chacha20, chacha20-ietf. - Sodium 1.0.12: - xchacha20 - OpenSSL: - aes-{128|192|256}-gcm, aes-{128|192|256}-cfb, - aes-{128|192|256}-ofb, aes-{128|192|256}-ctr, - camellia-{128|192|256}-cfb, - bf-cfb, cast5-cfb, des-cfb, idea-cfb, - rc2-cfb, seed-cfb, - rc4, rc4-md5, table. - OpenSSL 1.1: - aes-{128|192|256}-ocb - mbedTLS: - mbedtls:aes-{128|192|256}-cfb128, - mbedtls:aes-{128|192|256}-ctr, - mbedtls:camellia-{128|192|256}-cfb128, - mbedtls:aes-{128|192|256}-gcm - -t TIMEOUT timeout in seconds, default: 300 - -a ONE_TIME_AUTH one time auth - --fast-open use TCP_FASTOPEN, requires Linux 3.7+ - --workers=WORKERS number of workers, available on Unix/Linux - --forbidden-ip=IPLIST comma seperated IP list forbidden to connect - --manager-address=ADDR optional server manager UDP address, see wiki - --prefer-ipv6 resolve ipv6 address first - --libopenssl=PATH custom openssl crypto lib path - --libmbedtls=PATH custom mbedtls crypto lib path - --libsodium=PATH custom sodium crypto lib path - -General options: - -h, --help show this help message and exit - -d start/stop/restart daemon mode - --pid-file PID_FILE pid file for daemon mode - --log-file LOG_FILE log file for daemon mode - --user USER username to run as - -v, -vv verbose mode - -q, -qq quiet mode, only show warnings/errors - --version show version information - -Online help: -''') - - -def _decode_list(data): - rv = [] - for item in data: - if hasattr(item, 'encode'): - item = item.encode('utf-8') - elif isinstance(item, list): - item = _decode_list(item) - elif isinstance(item, dict): - item = _decode_dict(item) - rv.append(item) - return rv - - -def _decode_dict(data): - rv = {} - for key, value in data.items(): - if hasattr(value, 'encode'): - value = value.encode('utf-8') - elif isinstance(value, list): - value = _decode_list(value) - elif isinstance(value, dict): - value = _decode_dict(value) - rv[key] = value - return rv - - -def parse_json_in_str(data): - # parse json and convert everything from unicode to str - return json.loads(data, object_hook=_decode_dict) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 0ef913f..567f515 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -1,19 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement @@ -26,43 +32,49 @@ import logging import traceback import random -from shadowsocks import cryptor, eventloop, shell, common -from shadowsocks.common import parse_header, onetimeauth_verify, \ - onetimeauth_gen, ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, \ - ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH +from shadowsocks import encrypt, eventloop, utils, common +from shadowsocks.common import parse_header # we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time TIMEOUTS_CLEAN_SIZE = 512 +# we check timeouts every TIMEOUT_PRECISION seconds +TIMEOUT_PRECISION = 4 + MSG_FASTOPEN = 0x20000000 -# SOCKS METHOD definition -METHOD_NOAUTH = 0 - -# SOCKS command definition +# SOCKS CMD defination CMD_CONNECT = 1 CMD_BIND = 2 CMD_UDP_ASSOCIATE = 3 -# for each opening port, we have a TCP Relay +# TCP Relay can be either sslocal or ssserver +# for sslocal it is called is_local=True +# for each opening port, we have a TCP Relay # for each connection, we have a TCP Relay Handler to handle the connection # for each handler, we have 2 sockets: # local: connected to the client # remote: connected to remote server +# for each handler, we have 2 streams: +# upstream: from client to server direction +# read local and write to remote +# downstream: from server to client direction +# read remote and write to local + # for each handler, it could be at one of several stages: -# as sslocal: -# stage 0 auth METHOD received from local, reply with selection message +# sslocal: +# stage 0 SOCKS hello received from local, send hello to local # stage 1 addr received from local, query DNS for remote # stage 2 UDP assoc # stage 3 DNS resolved, connect to remote # stage 4 still connecting, more data from local received # stage 5 remote connected, piping local and remote -# as ssserver: +# ssserver: # stage 0 just jump to stage 1 # stage 1 addr received from local, query DNS for remote # stage 3 DNS resolved, connect to remote @@ -77,38 +89,20 @@ STAGE_CONNECTING = 4 STAGE_STREAM = 5 STAGE_DESTROYED = -1 -# for each handler, we have 2 stream directions: -# upstream: from client to server direction -# read local and write to remote -# downstream: from server to client direction -# read remote and write to local - +# stream direction STREAM_UP = 0 STREAM_DOWN = 1 -# for each stream, it's waiting for reading, or writing, or both +# stream wait status, indicating it's waiting for reading, etc WAIT_STATUS_INIT = 0 WAIT_STATUS_READING = 1 WAIT_STATUS_WRITING = 2 WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING BUF_SIZE = 32 * 1024 -UP_STREAM_BUF_SIZE = 16 * 1024 -DOWN_STREAM_BUF_SIZE = 32 * 1024 - -# helper exceptions for TCPRelayHandler - - -class BadSocksHeader(Exception): - pass - - -class NoAcceptableMethods(Exception): - pass class TCPRelayHandler(object): - def __init__(self, server, fd_to_handlers, loop, local_sock, config, dns_resolver, is_local): self._server = server @@ -118,39 +112,22 @@ class TCPRelayHandler(object): self._remote_sock = None self._config = config self._dns_resolver = dns_resolver - self.tunnel_remote = config.get('tunnel_remote', "8.8.8.8") - self.tunnel_remote_port = config.get('tunnel_remote_port', 53) - self.tunnel_port = config.get('tunnel_port', 53) - self._is_tunnel = server._is_tunnel - - # TCP Relay works as either sslocal or ssserver - # if is_local, this is sslocal self._is_local = is_local self._stage = STAGE_INIT - self._cryptor = cryptor.Cryptor(config['password'], - config['method'], - config['crypto_path']) - self._ota_enable = config.get('one_time_auth', False) - self._ota_enable_session = self._ota_enable - self._ota_buff_head = b'' - self._ota_buff_data = b'' - self._ota_len = 0 - self._ota_chunk_idx = 0 + self._encryptor = encrypt.Encryptor(config['password'], + config['method']) self._fastopen_connected = False self._data_to_write_to_local = [] self._data_to_write_to_remote = [] self._upstream_status = WAIT_STATUS_READING self._downstream_status = WAIT_STATUS_INIT - self._client_address = local_sock.getpeername()[:2] self._remote_address = None - self._forbidden_iplist = config.get('forbidden_ip') if is_local: self._chosen_server = self._get_a_server() fd_to_handlers[local_sock.fileno()] = self local_sock.setblocking(False) local_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) - loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR, - self._server) + loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR) self.last_activity = 0 self._update_activity() @@ -168,15 +145,14 @@ class TCPRelayHandler(object): server_port = self._config['server_port'] if type(server_port) == list: server_port = random.choice(server_port) - if type(server) == list: - server = random.choice(server) logging.debug('chosen server: %s:%d', server, server_port) + # TODO support multiple server IP return server, server_port - def _update_activity(self, data_len=0): + def _update_activity(self): # tell the TCP Relay we have activities recently # else it will think we are inactive and timed out - self._server.update_activity(self, data_len) + self._server.update_activity(self) def _update_stream(self, stream, status): # update a stream to a new waiting status @@ -192,23 +168,21 @@ class TCPRelayHandler(object): if self._upstream_status != status: self._upstream_status = status dirty = True - if not dirty: - return - - if self._local_sock: - event = eventloop.POLL_ERR - if self._downstream_status & WAIT_STATUS_WRITING: - event |= eventloop.POLL_OUT - if self._upstream_status & WAIT_STATUS_READING: - event |= eventloop.POLL_IN - self._loop.modify(self._local_sock, event) - if self._remote_sock: - event = eventloop.POLL_ERR - if self._downstream_status & WAIT_STATUS_READING: - event |= eventloop.POLL_IN - if self._upstream_status & WAIT_STATUS_WRITING: - event |= eventloop.POLL_OUT - self._loop.modify(self._remote_sock, event) + if dirty: + if self._local_sock: + event = eventloop.POLL_ERR + if self._downstream_status & WAIT_STATUS_WRITING: + event |= eventloop.POLL_OUT + if self._upstream_status & WAIT_STATUS_READING: + event |= eventloop.POLL_IN + self._loop.modify(self._local_sock, event) + if self._remote_sock: + event = eventloop.POLL_ERR + if self._downstream_status & WAIT_STATUS_READING: + event |= eventloop.POLL_IN + if self._upstream_status & WAIT_STATUS_WRITING: + event |= eventloop.POLL_OUT + self._loop.modify(self._remote_sock, event) def _write_to_sock(self, data, sock): # write data to sock @@ -229,7 +203,9 @@ class TCPRelayHandler(object): errno.EWOULDBLOCK): uncomplete = True else: - shell.print_exception(e) + logging.error(e) + if self._config['verbose']: + traceback.print_exc() self.destroy() return False if uncomplete: @@ -251,19 +227,11 @@ class TCPRelayHandler(object): return True def _handle_stage_connecting(self, data): - if not self._is_local: - if self._ota_enable_session: - self._ota_chunk_data(data, - self._data_to_write_to_remote.append) - else: - self._data_to_write_to_remote.append(data) - return - if self._ota_enable_session: - data = self._ota_chunk_data_gen(data) - data = self._cryptor.encrypt(data) + if self._is_local: + data = self._encryptor.encrypt(data) self._data_to_write_to_remote.append(data) - - if self._config['fast_open'] and not self._fastopen_connected: + if self._is_local and not self._fastopen_connected and \ + self._config['fast_open']: # for sslocal and fastopen, we basically wait for data and use # sendto to connect try: @@ -272,11 +240,10 @@ class TCPRelayHandler(object): remote_sock = \ self._create_remote_socket(self._chosen_server[0], self._chosen_server[1]) - self._loop.add(remote_sock, eventloop.POLL_ERR, self._server) + self._loop.add(remote_sock, eventloop.POLL_ERR) data = b''.join(self._data_to_write_to_remote) l = len(data) - s = remote_sock.sendto(data, MSG_FASTOPEN, - self._chosen_server) + s = remote_sock.sendto(data, MSG_FASTOPEN, self._chosen_server) if s < l: data = data[s:] self._data_to_write_to_remote = [data] @@ -292,21 +259,14 @@ class TCPRelayHandler(object): self._config['fast_open'] = False self.destroy() else: - shell.print_exception(e) + logging.error(e) if self._config['verbose']: traceback.print_exc() self.destroy() - @shell.exception_handle(self_=True, destroy=True, conn_err=True) def _handle_stage_addr(self, data): - if self._is_local: - if self._is_tunnel: - # add ss header to data - tunnel_remote = self.tunnel_remote - tunnel_remote_port = self.tunnel_remote_port - data = common.add_header(tunnel_remote, - tunnel_remote_port, data) - else: + try: + if self._is_local: cmd = common.ord(data[1]) if cmd == CMD_UDP_ASSOCIATE: logging.debug('UDP associate') @@ -330,77 +290,45 @@ class TCPRelayHandler(object): logging.error('unknown command %d', cmd) self.destroy() return - header_result = parse_header(data) - if header_result is None: - raise Exception('can not parse header') - addrtype, remote_addr, remote_port, header_length = header_result - logging.info('connecting %s:%d from %s:%d' % - (common.to_str(remote_addr), remote_port, - self._client_address[0], self._client_address[1])) - if self._is_local is False: - # spec https://shadowsocks.org/en/spec/one-time-auth.html - self._ota_enable_session = addrtype & ADDRTYPE_AUTH - if self._ota_enable and not self._ota_enable_session: - logging.warn('client one time auth is required') - return - if self._ota_enable_session: - if len(data) < header_length + ONETIMEAUTH_BYTES: - logging.warn('one time auth header is too short') - return None - offset = header_length + ONETIMEAUTH_BYTES - _hash = data[header_length: offset] - _data = data[:header_length] - key = self._cryptor.decipher_iv + self._cryptor.key - if onetimeauth_verify(_hash, _data, key) is False: - logging.warn('one time auth fail') - self.destroy() - return - header_length += ONETIMEAUTH_BYTES - self._remote_address = (common.to_str(remote_addr), remote_port) - # pause reading - self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) - self._stage = STAGE_DNS - if self._is_local: - # jump over socks5 response - if not self._is_tunnel: + header_result = parse_header(data) + if header_result is None: + raise Exception('can not parse header') + addrtype, remote_addr, remote_port, header_length = header_result + logging.info('connecting %s:%d' % (common.to_str(remote_addr), + remote_port)) + self._remote_address = (remote_addr, remote_port) + # pause reading + self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) + self._stage = STAGE_DNS + if self._is_local: # forward address to remote self._write_to_sock((b'\x05\x00\x00\x01' b'\x00\x00\x00\x00\x10\x10'), self._local_sock) - # spec https://shadowsocks.org/en/spec/one-time-auth.html - # ATYP & 0x10 == 0x10, then OTA is enabled. - if self._ota_enable_session: - data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] - key = self._cryptor.cipher_iv + self._cryptor.key - _header = data[:header_length] - sha110 = onetimeauth_gen(data, key) - data = _header + sha110 + data[header_length:] - data_to_send = self._cryptor.encrypt(data) - self._data_to_write_to_remote.append(data_to_send) - # notice here may go into _handle_dns_resolved directly - self._dns_resolver.resolve(self._chosen_server[0], - self._handle_dns_resolved) - else: - if self._ota_enable_session: - data = data[header_length:] - self._ota_chunk_data(data, - self._data_to_write_to_remote.append) - elif len(data) > header_length: - self._data_to_write_to_remote.append(data[header_length:]) - # notice here may go into _handle_dns_resolved directly - self._dns_resolver.resolve(remote_addr, - self._handle_dns_resolved) + data_to_send = self._encryptor.encrypt(data) + self._data_to_write_to_remote.append(data_to_send) + # notice here may go into _handle_dns_resolved directly + self._dns_resolver.resolve(self._chosen_server[0], + self._handle_dns_resolved) + else: + if len(data) > header_length: + self._data_to_write_to_remote.append(data[header_length:]) + # notice here may go into _handle_dns_resolved directly + self._dns_resolver.resolve(remote_addr, + self._handle_dns_resolved) + except Exception as e: + logging.error(e) + if self._config['verbose']: + traceback.print_exc() + # TODO use logging when debug completed + self.destroy() def _create_remote_socket(self, ip, port): addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, socket.SOL_TCP) if len(addrs) == 0: - raise Exception("getaddrinfo failed for %s:%d" % (ip, port)) + raise Exception("getaddrinfo failed for %s:%d" % (ip, port)) af, socktype, proto, canonname, sa = addrs[0] - if self._forbidden_iplist: - if common.to_str(sa[0]) in self._forbidden_iplist: - raise Exception('IP %s is in forbidden list, reject' % - common.to_str(sa[0])) remote_sock = socket.socket(af, socktype, proto) self._remote_sock = remote_sock self._fd_to_handlers[remote_sock.fileno()] = self @@ -408,160 +336,62 @@ class TCPRelayHandler(object): remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) return remote_sock - @shell.exception_handle(self_=True) def _handle_dns_resolved(self, result, error): if error: - addr, port = self._client_address[0], self._client_address[1] - logging.error('%s when handling connection from %s:%d' % - (error, addr, port)) - self.destroy() - return - if not (result and result[1]): + logging.error(error) self.destroy() return + if result: + ip = result[1] + if ip: + try: + self._stage = STAGE_CONNECTING + remote_addr = ip + if self._is_local: + remote_port = self._chosen_server[1] + else: + remote_port = self._remote_address[1] - ip = result[1] - self._stage = STAGE_CONNECTING - remote_addr = ip - if self._is_local: - remote_port = self._chosen_server[1] - else: - remote_port = self._remote_address[1] - - if self._is_local and self._config['fast_open']: - # for fastopen: - # wait for more data arrive and send them in one SYN - self._stage = STAGE_CONNECTING - # we don't have to wait for remote since it's not - # created - self._update_stream(STREAM_UP, WAIT_STATUS_READING) - # TODO when there is already data in this packet - else: - # else do connect - remote_sock = self._create_remote_socket(remote_addr, - remote_port) - try: - remote_sock.connect((remote_addr, remote_port)) - except (OSError, IOError) as e: - if eventloop.errno_from_exception(e) == \ - errno.EINPROGRESS: - pass - self._loop.add(remote_sock, - eventloop.POLL_ERR | eventloop.POLL_OUT, - self._server) - self._stage = STAGE_CONNECTING - self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) - self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) - - def _write_to_sock_remote(self, data): - self._write_to_sock(data, self._remote_sock) - - def _ota_chunk_data(self, data, data_cb): - # spec https://shadowsocks.org/en/spec/one-time-auth.html - unchunk_data = b'' - while len(data) > 0: - if self._ota_len == 0: - # get DATA.LEN + HMAC-SHA1 - length = ONETIMEAUTH_CHUNK_BYTES - len(self._ota_buff_head) - self._ota_buff_head += data[:length] - data = data[length:] - if len(self._ota_buff_head) < ONETIMEAUTH_CHUNK_BYTES: - # wait more data + if self._is_local and self._config['fast_open']: + # for fastopen: + # wait for more data to arrive and send them in one SYN + self._stage = STAGE_CONNECTING + # we don't have to wait for remote since it's not + # created + self._update_stream(STREAM_UP, WAIT_STATUS_READING) + # TODO when there is already data in this packet + else: + # else do connect + remote_sock = self._create_remote_socket(remote_addr, + remote_port) + try: + remote_sock.connect((remote_addr, remote_port)) + except (OSError, IOError) as e: + if eventloop.errno_from_exception(e) == \ + errno.EINPROGRESS: + pass + self._loop.add(remote_sock, + eventloop.POLL_ERR | eventloop.POLL_OUT) + self._stage = STAGE_CONNECTING + self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) + self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) return - data_len = self._ota_buff_head[:ONETIMEAUTH_CHUNK_DATA_LEN] - self._ota_len = struct.unpack('>H', data_len)[0] - length = min(self._ota_len - len(self._ota_buff_data), len(data)) - self._ota_buff_data += data[:length] - data = data[length:] - if len(self._ota_buff_data) == self._ota_len: - # get a chunk data - _hash = self._ota_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:] - _data = self._ota_buff_data - index = struct.pack('>I', self._ota_chunk_idx) - key = self._cryptor.decipher_iv + index - if onetimeauth_verify(_hash, _data, key) is False: - logging.warn('one time auth fail, drop chunk !') - else: - unchunk_data += _data - self._ota_chunk_idx += 1 - self._ota_buff_head = b'' - self._ota_buff_data = b'' - self._ota_len = 0 - data_cb(unchunk_data) - return - - def _ota_chunk_data_gen(self, data): - data_len = struct.pack(">H", len(data)) - index = struct.pack('>I', self._ota_chunk_idx) - key = self._cryptor.cipher_iv + index - sha110 = onetimeauth_gen(data, key) - self._ota_chunk_idx += 1 - return data_len + sha110 + data - - def _handle_stage_stream(self, data): - if self._is_local: - if self._ota_enable_session: - data = self._ota_chunk_data_gen(data) - data = self._cryptor.encrypt(data) - self._write_to_sock(data, self._remote_sock) - else: - if self._ota_enable_session: - self._ota_chunk_data(data, self._write_to_sock_remote) - else: - self._write_to_sock(data, self._remote_sock) - return - - def _check_auth_method(self, data): - # VER, NMETHODS, and at least 1 METHODS - if len(data) < 3: - logging.warning('method selection header too short') - raise BadSocksHeader - socks_version = common.ord(data[0]) - nmethods = common.ord(data[1]) - if socks_version != 5: - logging.warning('unsupported SOCKS protocol version ' + - str(socks_version)) - raise BadSocksHeader - if nmethods < 1 or len(data) != nmethods + 2: - logging.warning('NMETHODS and number of METHODS mismatch') - raise BadSocksHeader - noauth_exist = False - for method in data[2:]: - if common.ord(method) == METHOD_NOAUTH: - noauth_exist = True - break - if not noauth_exist: - logging.warning('none of SOCKS METHOD\'s ' - 'requested by client is supported') - raise NoAcceptableMethods - - def _handle_stage_init(self, data): - try: - self._check_auth_method(data) - except BadSocksHeader: - self.destroy() - return - except NoAcceptableMethods: - self._write_to_sock(b'\x05\xff', self._local_sock) - self.destroy() - return - - self._write_to_sock(b'\x05\00', self._local_sock) - self._stage = STAGE_ADDR + except (OSError, IOError) as e: + logging.error(e) + if self._config['verbose']: + traceback.print_exc() + self.destroy() def _on_local_read(self): # handle all local read events and dispatch them to methods for # each stage + self._update_activity() if not self._local_sock: return is_local = self._is_local data = None - if is_local: - buf_size = UP_STREAM_BUF_SIZE - else: - buf_size = DOWN_STREAM_BUF_SIZE try: - data = self._local_sock.recv(buf_size) + data = self._local_sock.recv(BUF_SIZE) except (OSError, IOError) as e: if eventloop.errno_from_exception(e) in \ (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): @@ -569,21 +399,20 @@ class TCPRelayHandler(object): if not data: self.destroy() return - self._update_activity(len(data)) if not is_local: - data = self._cryptor.decrypt(data) + data = self._encryptor.decrypt(data) if not data: return if self._stage == STAGE_STREAM: - self._handle_stage_stream(data) + if self._is_local: + data = self._encryptor.encrypt(data) + self._write_to_sock(data, self._remote_sock) return elif is_local and self._stage == STAGE_INIT: - # jump over socks5 init - if self._is_tunnel: - self._handle_stage_addr(data) - return - else: - self._handle_stage_init(data) + # TODO check auth method + self._write_to_sock(b'\x05\00', self._local_sock) + self._stage = STAGE_ADDR + return elif self._stage == STAGE_CONNECTING: self._handle_stage_connecting(data) elif (is_local and self._stage == STAGE_ADDR) or \ @@ -592,14 +421,10 @@ class TCPRelayHandler(object): def _on_remote_read(self): # handle all remote read events + self._update_activity() data = None - if self._is_local: - buf_size = UP_STREAM_BUF_SIZE - else: - buf_size = DOWN_STREAM_BUF_SIZE try: - data = self._remote_sock.recv(buf_size) - + data = self._remote_sock.recv(BUF_SIZE) except (OSError, IOError) as e: if eventloop.errno_from_exception(e) in \ (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): @@ -607,15 +432,14 @@ class TCPRelayHandler(object): if not data: self.destroy() return - self._update_activity(len(data)) if self._is_local: - data = self._cryptor.decrypt(data) + data = self._encryptor.decrypt(data) else: - data = self._cryptor.encrypt(data) + data = self._encryptor.encrypt(data) try: self._write_to_sock(data, self._local_sock) except Exception as e: - shell.print_exception(e) + logging.error(e) if self._config['verbose']: traceback.print_exc() # TODO use logging when debug completed @@ -652,7 +476,6 @@ class TCPRelayHandler(object): logging.error(eventloop.get_sock_error(self._remote_sock)) self.destroy() - @shell.exception_handle(self_=True, destroy=True) def handle_event(self, sock, event): # handle all events in this handler and dispatch them to methods if self._stage == STAGE_DESTROYED: @@ -719,15 +542,14 @@ class TCPRelayHandler(object): class TCPRelay(object): - - def __init__(self, config, dns_resolver, is_local, stat_callback=None): + def __init__(self, config, dns_resolver, is_local): self._config = config self._is_local = is_local self._dns_resolver = dns_resolver self._closed = False self._eventloop = None self._fd_to_handlers = {} - self._is_tunnel = False + self._last_time = time.time() self._timeout = config['timeout'] self._timeouts = [] # a list for all the handlers @@ -761,7 +583,6 @@ class TCPRelay(object): self._config['fast_open'] = False server_socket.listen(1024) self._server_socket = server_socket - self._stat_callback = stat_callback def add_to_loop(self, loop): if self._eventloop: @@ -769,9 +590,10 @@ class TCPRelay(object): if self._closed: raise Exception('already closed') self._eventloop = loop + loop.add_handler(self._handle_events) + self._eventloop.add(self._server_socket, - eventloop.POLL_IN | eventloop.POLL_ERR, self) - self._eventloop.add_periodic(self.handle_periodic) + eventloop.POLL_IN | eventloop.POLL_ERR) def remove_handler(self, handler): index = self._handler_to_timeouts.get(hash(handler), -1) @@ -780,13 +602,10 @@ class TCPRelay(object): self._timeouts[index] = None del self._handler_to_timeouts[hash(handler)] - def update_activity(self, handler, data_len): - if data_len and self._stat_callback: - self._stat_callback(self._listen_port, data_len) - + def update_activity(self, handler): # set handler to active now = int(time.time()) - if now - handler.last_activity < eventloop.TIMEOUT_PRECISION: + if now - handler.last_activity < TIMEOUT_PRECISION: # thus we can lower timeout modification frequency return handler.last_activity = now @@ -803,7 +622,7 @@ class TCPRelay(object): # we just need a sorted last_activity queue and it's faster than heapq # in fact we can do O(1) insertion/remove so we invent our own if self._timeouts: - logging.log(shell.VERBOSE_LEVEL, 'sweeping timeouts') + logging.log(utils.VERBOSE_LEVEL, 'sweeping timeouts') now = time.time() length = len(self._timeouts) pos = self._timeout_offset @@ -832,57 +651,53 @@ class TCPRelay(object): pos = 0 self._timeout_offset = pos - def handle_event(self, sock, fd, event): + def _handle_events(self, events): # handle events and dispatch to handlers - if sock: - logging.log(shell.VERBOSE_LEVEL, 'fd %d %s', fd, - eventloop.EVENT_NAMES.get(event, event)) - if sock == self._server_socket: - if event & eventloop.POLL_ERR: - # TODO - raise Exception('server_socket error') - try: - logging.debug('accept') - conn = self._server_socket.accept() - TCPRelayHandler(self, self._fd_to_handlers, - self._eventloop, conn[0], self._config, - self._dns_resolver, self._is_local) - except (OSError, IOError) as e: - error_no = eventloop.errno_from_exception(e) - if error_no in (errno.EAGAIN, errno.EINPROGRESS, - errno.EWOULDBLOCK): - return - else: - shell.print_exception(e) - if self._config['verbose']: - traceback.print_exc() - else: + for sock, fd, event in events: if sock: - handler = self._fd_to_handlers.get(fd, None) - if handler: - handler.handle_event(sock, event) + logging.log(utils.VERBOSE_LEVEL, 'fd %d %s', fd, + eventloop.EVENT_NAMES.get(event, event)) + if sock == self._server_socket: + if event & eventloop.POLL_ERR: + # TODO + raise Exception('server_socket error') + try: + logging.debug('accept') + conn = self._server_socket.accept() + TCPRelayHandler(self, self._fd_to_handlers, + self._eventloop, conn[0], self._config, + self._dns_resolver, self._is_local) + except (OSError, IOError) as e: + error_no = eventloop.errno_from_exception(e) + if error_no in (errno.EAGAIN, errno.EINPROGRESS, + errno.EWOULDBLOCK): + continue + else: + logging.error(e) + if self._config['verbose']: + traceback.print_exc() else: - logging.warn('poll removed fd') + if sock: + handler = self._fd_to_handlers.get(fd, None) + if handler: + handler.handle_event(sock, event) + else: + logging.warn('poll removed fd') - def handle_periodic(self): + now = time.time() + if now - self._last_time > TIMEOUT_PRECISION: + self._sweep_timeout() + self._last_time = now if self._closed: if self._server_socket: self._eventloop.remove(self._server_socket) self._server_socket.close() self._server_socket = None - logging.info('closed TCP port %d', self._listen_port) + logging.info('closed listen port %d', self._listen_port) if not self._fd_to_handlers: - logging.info('stopping') - self._eventloop.stop() - self._sweep_timeout() + self._eventloop.remove_handler(self._handle_events) def close(self, next_tick=False): - logging.debug('TCP close') self._closed = True if not next_tick: - if self._eventloop: - self._eventloop.remove_periodic(self.handle_periodic) - self._eventloop.remove(self._server_socket) self._server_socket.close() - for handler in list(self._fd_to_handlers.values()): - handler.destroy() diff --git a/shadowsocks/tunnel.py b/shadowsocks/tunnel.py deleted file mode 100755 index dbfb438..0000000 --- a/shadowsocks/tunnel.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2012-2015 clowwindy -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import sys -import os -import logging -import signal - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) -from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns - - -@shell.exception_handle(self_=False, exit_code=1) -def main(): - shell.check_python() - - # fix py2exe - if hasattr(sys, "frozen") and sys.frozen in \ - ("windows_exe", "console_exe"): - p = os.path.dirname(os.path.abspath(sys.executable)) - os.chdir(p) - - config = shell.get_config(True) - daemon.daemon_exec(config) - dns_resolver = asyncdns.DNSResolver() - loop = eventloop.EventLoop() - dns_resolver.add_to_loop(loop) - _config = config.copy() - _config["local_port"] = _config["tunnel_port"] - logging.info("starting tcp tunnel at %s:%d forward to %s:%d" % - (_config['local_address'], _config['local_port'], - _config['tunnel_remote'], _config['tunnel_remote_port'])) - tunnel_tcp_server = tcprelay.TCPRelay(_config, dns_resolver, True) - tunnel_tcp_server._is_tunnel = True - tunnel_tcp_server.add_to_loop(loop) - logging.info("starting udp tunnel at %s:%d forward to %s:%d" % - (_config['local_address'], _config['local_port'], - _config['tunnel_remote'], _config['tunnel_remote_port'])) - tunnel_udp_server = udprelay.UDPRelay(_config, dns_resolver, True) - tunnel_udp_server._is_tunnel = True - tunnel_udp_server.add_to_loop(loop) - - def handler(signum, _): - logging.warn('received SIGQUIT, doing graceful shutting down..') - tunnel_tcp_server.close(next_tick=True) - tunnel_udp_server.close(next_tick=True) - signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) - - def int_handler(signum, _): - sys.exit(1) - signal.signal(signal.SIGINT, int_handler) - - daemon.set_user(config.get('user', None)) - loop.run() - -if __name__ == '__main__': - main() diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index f726717..2b8b12f 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -1,19 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. # SOCKS5 UDP Request # +----+------+------+----------+----------+----------+ @@ -62,28 +68,26 @@ from __future__ import absolute_import, division, print_function, \ with_statement +import time import socket import logging import struct import errno import random -from shadowsocks import cryptor, eventloop, lru_cache, common, shell -from shadowsocks.common import parse_header, pack_addr, onetimeauth_verify, \ - onetimeauth_gen, ONETIMEAUTH_BYTES, ADDRTYPE_AUTH +from shadowsocks import encrypt, eventloop, lru_cache, common +from shadowsocks.common import parse_header, pack_addr BUF_SIZE = 65536 -def client_key(source_addr, server_af): - # notice this is server af, not dest af - return '%s:%s:%d' % (source_addr[0], source_addr[1], server_af) +def client_key(a, b, c, d): + return '%s:%s:%s:%s' % (a, b, c, d) class UDPRelay(object): - - def __init__(self, config, dns_resolver, is_local, stat_callback=None): + def __init__(self, config, dns_resolver, is_local): self._config = config if is_local: self._listen_addr = config['local_address'] @@ -95,48 +99,38 @@ class UDPRelay(object): self._listen_port = config['server_port'] self._remote_addr = None self._remote_port = None - self.tunnel_remote = config.get('tunnel_remote', "8.8.8.8") - self.tunnel_remote_port = config.get('tunnel_remote_port', 53) - self.tunnel_port = config.get('tunnel_port', 53) - self._is_tunnel = False self._dns_resolver = dns_resolver - self._password = common.to_bytes(config['password']) + self._password = config['password'] self._method = config['method'] self._timeout = config['timeout'] - self._ota_enable = config.get('one_time_auth', False) - self._ota_enable_session = self._ota_enable self._is_local = is_local self._cache = lru_cache.LRUCache(timeout=config['timeout'], close_callback=self._close_client) self._client_fd_to_server_addr = \ lru_cache.LRUCache(timeout=config['timeout']) - self._dns_cache = lru_cache.LRUCache(timeout=300) self._eventloop = None self._closed = False + self._last_time = time.time() self._sockets = set() - self._forbidden_iplist = config.get('forbidden_ip') - self._crypto_path = config['crypto_path'] addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) if len(addrs) == 0: - raise Exception("UDP can't get addrinfo for %s:%d" % + raise Exception("can't get addrinfo for %s:%d" % (self._listen_addr, self._listen_port)) af, socktype, proto, canonname, sa = addrs[0] server_socket = socket.socket(af, socktype, proto) server_socket.bind((self._listen_addr, self._listen_port)) server_socket.setblocking(False) self._server_socket = server_socket - self._stat_callback = stat_callback def _get_a_server(self): server = self._config['server'] server_port = self._config['server_port'] if type(server_port) == list: server_port = random.choice(server_port) - if type(server) == list: - server = random.choice(server) logging.debug('chosen server: %s:%d', server, server_port) + # TODO support multiple server IP return server, server_port def _close_client(self, client): @@ -151,35 +145,18 @@ class UDPRelay(object): def _handle_server(self): server = self._server_socket data, r_addr = server.recvfrom(BUF_SIZE) - key = None - iv = None if not data: logging.debug('UDP handle_server: data is empty') - if self._stat_callback: - self._stat_callback(self._listen_port, len(data)) if self._is_local: - if self._is_tunnel: - # add ss header to data - tunnel_remote = self.tunnel_remote - tunnel_remote_port = self.tunnel_remote_port - data = common.add_header(tunnel_remote, - tunnel_remote_port, data) - else: - frag = common.ord(data[2]) - if frag != 0: - logging.warn('UDP drop a message since frag is not 0') - return - else: - data = data[3:] - else: - # decrypt data - try: - data, key, iv = cryptor.decrypt_all(self._password, - self._method, - data, self._crypto_path) - except Exception: - logging.debug('UDP handle_server: decrypt data failed') + frag = common.ord(data[2]) + if frag != 0: + logging.warn('drop a message since frag is not 0') return + else: + data = data[3:] + else: + data = encrypt.encrypt_all(self._password, self._method, 0, data) + # decrypt data if not data: logging.debug('UDP handle_server: data is empty after decrypt') return @@ -187,67 +164,32 @@ class UDPRelay(object): if header_result is None: return addrtype, dest_addr, dest_port, header_length = header_result - logging.info("udp data to %s:%d from %s:%d" - % (dest_addr, dest_port, r_addr[0], r_addr[1])) + if self._is_local: server_addr, server_port = self._get_a_server() else: server_addr, server_port = dest_addr, dest_port - # spec https://shadowsocks.org/en/spec/one-time-auth.html - self._ota_enable_session = addrtype & ADDRTYPE_AUTH - if self._ota_enable and not self._ota_enable_session: - logging.warn('client one time auth is required') - return - if self._ota_enable_session: - if len(data) < header_length + ONETIMEAUTH_BYTES: - logging.warn('UDP one time auth header is too short') - return - _hash = data[-ONETIMEAUTH_BYTES:] - data = data[: -ONETIMEAUTH_BYTES] - _key = iv + key - if onetimeauth_verify(_hash, data, _key) is False: - logging.warn('UDP one time auth fail') - return - addrs = self._dns_cache.get(server_addr, None) - if addrs is None: - addrs = socket.getaddrinfo(server_addr, server_port, 0, - socket.SOCK_DGRAM, socket.SOL_UDP) - if not addrs: - # drop - return - else: - self._dns_cache[server_addr] = addrs - af, socktype, proto, canonname, sa = addrs[0] - key = client_key(r_addr, af) + key = client_key(r_addr[0], r_addr[1], dest_addr, dest_port) client = self._cache.get(key, None) if not client: # TODO async getaddrinfo - if self._forbidden_iplist: - if common.to_str(sa[0]) in self._forbidden_iplist: - logging.debug('IP %s is in forbidden list, drop' % - common.to_str(sa[0])) - # drop - return - client = socket.socket(af, socktype, proto) - client.setblocking(False) - self._cache[key] = client - self._client_fd_to_server_addr[client.fileno()] = r_addr - + addrs = socket.getaddrinfo(server_addr, server_port, 0, + socket.SOCK_DGRAM, socket.SOL_UDP) + if addrs: + af, socktype, proto, canonname, sa = addrs[0] + client = socket.socket(af, socktype, proto) + client.setblocking(False) + self._cache[key] = client + self._client_fd_to_server_addr[client.fileno()] = r_addr + else: + # drop + return self._sockets.add(client.fileno()) - self._eventloop.add(client, eventloop.POLL_IN, self) + self._eventloop.add(client, eventloop.POLL_IN) if self._is_local: - key, iv, m = cryptor.gen_key_iv(self._password, self._method) - # spec https://shadowsocks.org/en/spec/one-time-auth.html - if self._ota_enable_session: - data = self._ota_chunk_data_gen(key, iv, data) - try: - data = cryptor.encrypt_all_m(key, iv, m, self._method, data, - self._crypto_path) - except Exception: - logging.debug("UDP handle_server: encrypt data failed") - return + data = encrypt.encrypt_all(self._password, self._method, 1, data) if not data: return else: @@ -261,105 +203,75 @@ class UDPRelay(object): if err in (errno.EINPROGRESS, errno.EAGAIN): pass else: - shell.print_exception(e) + logging.error(e) def _handle_client(self, sock): data, r_addr = sock.recvfrom(BUF_SIZE) if not data: logging.debug('UDP handle_client: data is empty') return - if self._stat_callback: - self._stat_callback(self._listen_port, len(data)) if not self._is_local: addrlen = len(r_addr[0]) if addrlen > 255: # drop return data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data - try: - response = cryptor.encrypt_all(self._password, - self._method, data, - self._crypto_path) - except Exception: - logging.debug("UDP handle_client: encrypt data failed") - return + response = encrypt.encrypt_all(self._password, self._method, 1, + data) if not response: return else: - try: - data, key, iv = cryptor.decrypt_all(self._password, - self._method, data, - self._crypto_path) - except Exception: - logging.debug('UDP handle_client: decrypt data failed') - return + data = encrypt.encrypt_all(self._password, self._method, 0, + data) if not data: return header_result = parse_header(data) if header_result is None: return - addrtype, dest_addr, dest_port, header_length = header_result - if self._is_tunnel: - # remove ss header - response = data[header_length:] - else: - response = b'\x00\x00\x00' + data + # addrtype, dest_addr, dest_port, header_length = header_result + response = b'\x00\x00\x00' + data client_addr = self._client_fd_to_server_addr.get(sock.fileno()) if client_addr: - logging.debug("send udp response to %s:%d" - % (client_addr[0], client_addr[1])) self._server_socket.sendto(response, client_addr) else: # this packet is from somewhere else we know # simply drop that packet pass - def _ota_chunk_data_gen(self, key, iv, data): - data = common.chr(common.ord(data[0]) | ADDRTYPE_AUTH) + data[1:] - key = iv + key - return data + onetimeauth_gen(data, key) - def add_to_loop(self, loop): if self._eventloop: raise Exception('already add to loop') if self._closed: raise Exception('already closed') self._eventloop = loop + loop.add_handler(self._handle_events) server_socket = self._server_socket self._eventloop.add(server_socket, - eventloop.POLL_IN | eventloop.POLL_ERR, self) - loop.add_periodic(self.handle_periodic) + eventloop.POLL_IN | eventloop.POLL_ERR) - def handle_event(self, sock, fd, event): - if sock == self._server_socket: - if event & eventloop.POLL_ERR: - logging.error('UDP server_socket err') - self._handle_server() - elif sock and (fd in self._sockets): - if event & eventloop.POLL_ERR: - logging.error('UDP client_socket err') - self._handle_client(sock) - - def handle_periodic(self): + def _handle_events(self, events): + for sock, fd, event in events: + if sock == self._server_socket: + if event & eventloop.POLL_ERR: + logging.error('UDP server_socket err') + self._handle_server() + elif sock and (fd in self._sockets): + if event & eventloop.POLL_ERR: + logging.error('UDP client_socket err') + self._handle_client(sock) + now = time.time() + if now - self._last_time > 3: + self._cache.sweep() + self._client_fd_to_server_addr.sweep() + self._last_time = now if self._closed: - if self._server_socket: - self._server_socket.close() - self._server_socket = None - for sock in self._sockets: - sock.close() - logging.info('closed UDP port %d', self._listen_port) - self._cache.sweep() - self._client_fd_to_server_addr.sweep() - self._dns_cache.sweep() + self._server_socket.close() + for sock in self._sockets: + sock.close() + self._eventloop.remove_handler(self._handle_events) def close(self, next_tick=False): - logging.debug('UDP close') self._closed = True if not next_tick: - if self._eventloop: - self._eventloop.remove_periodic(self.handle_periodic) - self._eventloop.remove(self._server_socket) self._server_socket.close() - for client in list(self._cache.values()): - client.close() diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py new file mode 100644 index 0000000..ff6801c --- /dev/null +++ b/shadowsocks/utils.py @@ -0,0 +1,324 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import os +import json +import sys +import getopt +import logging +from shadowsocks.common import to_bytes, to_str + + +VERBOSE_LEVEL = 5 + + +def check_python(): + info = sys.version_info + if info[0] == 2 and not info[1] >= 6: + print('Python 2.6+ required') + sys.exit(1) + elif info[0] == 3 and not info[1] >= 3: + print('Python 3.3+ required') + sys.exit(1) + elif info[0] not in [2, 3]: + print('Python version not supported') + sys.exit(1) + + +def print_shadowsocks(): + version = '' + try: + import pkg_resources + version = pkg_resources.get_distribution('shadowsocks').version + except Exception: + pass + print('shadowsocks %s' % version) + + +def find_config(): + config_path = 'config.json' + if os.path.exists(config_path): + return config_path + config_path = os.path.join(os.path.dirname(__file__), '../', 'config.json') + if os.path.exists(config_path): + return config_path + return None + + +def check_config(config): + if config.get('local_address', '') in [b'0.0.0.0']: + logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe') + if config.get('server', '') in [b'127.0.0.1', b'localhost']: + logging.warn('warning: server set to listen on %s:%s, are you sure?' % + (to_str(config['server']), config['server_port'])) + if (config.get('method', '') or '').lower() == b'table': + logging.warn('warning: table is not safe; please use a safer cipher, ' + 'like AES-256-CFB') + if (config.get('method', '') or '').lower() == b'rc4': + logging.warn('warning: RC4 is not safe; please use a safer cipher, ' + 'like AES-256-CFB') + if config.get('timeout', 300) < 100: + logging.warn('warning: your timeout %d seems too short' % + int(config.get('timeout'))) + if config.get('timeout', 300) > 600: + logging.warn('warning: your timeout %d seems too long' % + int(config.get('timeout'))) + if config.get('password') in [b'mypassword']: + logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' + 'config.json!') + exit(1) + + +def get_config(is_local): + logging.basicConfig(level=logging.INFO, + format='%(levelname)-s: %(message)s') + if is_local: + shortopts = 'hd:s:b:p:k:l:m:c:t:vq' + longopts = ['help', 'fast-open', 'pid-file=', 'log-file='] + else: + shortopts = 'hd:s:p:k:m:c:t:vq' + longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers='] + try: + config_path = find_config() + optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) + for key, value in optlist: + if key == '-c': + config_path = value + + if config_path: + logging.info('loading config from %s' % config_path) + with open(config_path, 'rb') as f: + try: + config = json.loads(f.read().decode('utf8'), + object_hook=_decode_dict) + except ValueError as e: + logging.error('found an error in config.json: %s', + e.message) + sys.exit(1) + else: + config = {} + + optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) + v_count = 0 + for key, value in optlist: + if key == '-p': + config['server_port'] = int(value) + elif key == '-k': + config['password'] = to_bytes(value) + elif key == '-l': + config['local_port'] = int(value) + elif key == '-s': + config['server'] = to_bytes(value) + elif key == '-m': + config['method'] = to_bytes(value) + elif key == '-b': + config['local_address'] = to_bytes(value) + elif key == '-v': + v_count += 1 + # '-vv' turns on more verbose mode + config['verbose'] = v_count + elif key == '-t': + config['timeout'] = int(value) + elif key == '--fast-open': + config['fast_open'] = True + elif key == '--workers': + config['workers'] = int(value) + elif key in ('-h', '--help'): + if is_local: + print_local_help() + else: + print_server_help() + sys.exit(0) + elif key == '-d': + config['daemon'] = value + elif key == '--pid-file': + config['pid-file'] = value + elif key == '--log-file': + config['log-file'] = value + elif key == '-q': + v_count -= 1 + config['verbose'] = v_count + except getopt.GetoptError as e: + print(e, file=sys.stderr) + print_help(is_local) + sys.exit(2) + + if not config: + logging.error('config not specified') + print_help(is_local) + sys.exit(2) + + config['password'] = config.get('password', '') + config['method'] = config.get('method', 'aes-256-cfb') + config['port_password'] = config.get('port_password', None) + config['timeout'] = int(config.get('timeout', 300)) + config['fast_open'] = config.get('fast_open', False) + config['workers'] = config.get('workers', 1) + config['pid-file'] = config.get('pid-file', '/var/run/shadowsocks.pid') + config['log-file'] = config.get('log-file', '/var/log/shadowsocks.log') + config['workers'] = config.get('workers', 1) + config['verbose'] = config.get('verbose', False) + config['local_address'] = config.get('local_address', '127.0.0.1') + config['local_port'] = config.get('local_port', 1080) + if is_local: + if config.get('server', None) is None: + logging.error('server addr not specified') + print_local_help() + sys.exit(2) + else: + config['server'] = config.get('server', '0.0.0.0') + config['server_port'] = config.get('server_port', 8388) + + if is_local and not config.get('password', None): + logging.error('password not specified') + print_help(is_local) + sys.exit(2) + + if not is_local and not config.get('password', None) \ + and not config.get('port_password', None): + logging.error('password or port_password not specified') + print_help(is_local) + sys.exit(2) + + if 'local_port' in config: + config['local_port'] = int(config['local_port']) + + if 'server_port' in config and type(config['server_port']) != list: + config['server_port'] = int(config['server_port']) + + logging.getLogger('').handlers = [] + logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') + if config['verbose'] >= 2: + level = VERBOSE_LEVEL + elif config['verbose'] == 1: + level = logging.DEBUG + elif config['verbose'] == -1: + level = logging.WARN + elif config['verbose'] <= -2: + level = logging.ERROR + else: + level = logging.INFO + logging.basicConfig(level=level, + format='%(asctime)s %(levelname)-8s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + + check_config(config) + + return config + + +def print_help(is_local): + if is_local: + print_local_help() + else: + print_server_help() + + +def print_local_help(): + print('''usage: sslocal [-h] -s SERVER_ADDR [-p SERVER_PORT] + [-b LOCAL_ADDR] [-l LOCAL_PORT] -k PASSWORD [-m METHOD] + [-t TIMEOUT] [-c CONFIG] [--fast-open] [-v] -[d] [-q] +A fast tunnel proxy that helps you bypass firewalls. + +You can supply configurations via either config file or command line arguments. + +Proxy options: + -h, --help show this help message and exit + -c CONFIG path to config file + -s SERVER_ADDR server address + -p SERVER_PORT server port, default: 8388 + -b LOCAL_ADDR local binding address, default: 127.0.0.1 + -l LOCAL_PORT local port, default: 1080 + -k PASSWORD password + -m METHOD encryption method, default: aes-256-cfb + -t TIMEOUT timeout in seconds, default: 300 + --fast-open use TCP_FASTOPEN, requires Linux 3.7+ + +General options: + -d start/stop/restart daemon mode + --pid-file PID_FILE pid file for daemon mode + --log-file LOG_FILE log file for daemon mode + -v, -vv verbose mode + -q, -qq quiet mode, only show warnings/errors + +Online help: +''') + + +def print_server_help(): + print('''usage: ssserver [-h] [-s SERVER_ADDR] [-p SERVER_PORT] -k PASSWORD + -m METHOD [-t TIMEOUT] [-c CONFIG] [--fast-open] + [--workers WORKERS] [-v] [-d start] [-q] +A fast tunnel proxy that helps you bypass firewalls. + +You can supply configurations via either config file or command line arguments. + +Proxy options: + -h, --help show this help message and exit + -c CONFIG path to config file + -s SERVER_ADDR server address, default: 0.0.0.0 + -p SERVER_PORT server port, default: 8388 + -k PASSWORD password + -m METHOD encryption method, default: aes-256-cfb + -t TIMEOUT timeout in seconds, default: 300 + --fast-open use TCP_FASTOPEN, requires Linux 3.7+ + --workers WORKERS number of workers, available on Unix/Linux + +General options: + -d start/stop/restart daemon mode + --pid-file PID_FILE pid file for daemon mode + --log-file LOG_FILE log file for daemon mode + -v, -vv verbose mode + -q, -qq quiet mode, only show warnings/errors + +Online help: +''') + + +def _decode_list(data): + rv = [] + for item in data: + if hasattr(item, 'encode'): + item = item.encode('utf-8') + elif isinstance(item, list): + item = _decode_list(item) + elif isinstance(item, dict): + item = _decode_dict(item) + rv.append(item) + return rv + + +def _decode_dict(data): + rv = {} + for key, value in data.items(): + if hasattr(value, 'encode'): + value = value.encode('utf-8') + elif isinstance(value, list): + value = _decode_list(value) + elif isinstance(value, dict): + value = _decode_dict(value) + rv[key] = value + return rv diff --git a/snapcraft.yaml b/snapcraft.yaml deleted file mode 100644 index 6860270..0000000 --- a/snapcraft.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: shadowsocks -version: 2.9.1-1 -summary: A fast tunnel proxy that helps you bypass firewalls -description: A fast tunnel proxy that helps you bypass firewalls -confinement: strict -grade: stable - -apps: - sslocal: - command: bin/sslocal - plugs: [network, network-bind] - aliases: [sslocal] - - ssserver: - command: bin/ssserver - plugs: [network, network-bind] - aliases: [ssserver] - -parts: - shadowsocks: - plugin: python - python-version: python2 - source: https://github.com/shadowsocks/shadowsocks/archive/2.9.1.tar.gz diff --git a/tests/aes-cfb1.json b/tests/aes-cfb1.json index 70ae670..40d0b21 100644 --- a/tests/aes-cfb1.json +++ b/tests/aes-cfb1.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb1", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb1", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/aes-cfb8.json b/tests/aes-cfb8.json index fe8b715..fb7014b 100644 --- a/tests/aes-cfb8.json +++ b/tests/aes-cfb8.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb8", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb8", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/aes-ctr.json b/tests/aes-ctr.json index 1f5a1d7..1fed8a8 100644 --- a/tests/aes-ctr.json +++ b/tests/aes-ctr.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-ctr", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-ctr", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/aes-gcm.json b/tests/aes-gcm.json deleted file mode 100644 index 271d721..0000000 --- a/tests/aes-gcm.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-gcm", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/aes-ocb.json b/tests/aes-ocb.json deleted file mode 100644 index 9c1d5b8..0000000 --- a/tests/aes-ocb.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-ocb", - "local_address":"127.0.0.1", - "fast_open":false, - "libopenssl":"/usr/local/lib/libcrypto.so.1.1" -} diff --git a/tests/aes-ofb.json b/tests/aes-ofb.json deleted file mode 100644 index 0e2003f..0000000 --- a/tests/aes-ofb.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-ofb", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/aes.json b/tests/aes.json index 2fc29f3..a3d95b9 100644 --- a/tests/aes.json +++ b/tests/aes.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/camellia.json b/tests/camellia.json deleted file mode 100644 index d7ffd8c..0000000 --- a/tests/camellia.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"camellia_password", - "timeout":60, - "method":"camellia-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/chacha20-ietf-poly1305.json b/tests/chacha20-ietf-poly1305.json deleted file mode 100644 index 074dccb..0000000 --- a/tests/chacha20-ietf-poly1305.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"chacha20-ietf-poly1305", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/chacha20-ietf.json b/tests/chacha20-ietf.json deleted file mode 100644 index a27987c..0000000 --- a/tests/chacha20-ietf.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"chacha20-ietf", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/chacha20-poly1305.json b/tests/chacha20-poly1305.json deleted file mode 100644 index d9f2191..0000000 --- a/tests/chacha20-poly1305.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"chacha20-poly1305", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/chacha20.json b/tests/chacha20.json index 6def51b..541a9be 100644 --- a/tests/chacha20.json +++ b/tests/chacha20.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"chacha20_password", - "timeout":60, - "method":"chacha20", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"chacha20", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/client-multi-server-ip.json b/tests/client-multi-server-ip.json deleted file mode 100644 index 3050c11..0000000 --- a/tests/client-multi-server-ip.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":["127.0.0.1", "127.0.0.1"], - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/coverage_server.py b/tests/coverage_server.py index 23cc8cd..7a135c5 100644 --- a/tests/coverage_server.py +++ b/tests/coverage_server.py @@ -1,18 +1,4 @@ #!/usr/bin/env python -# -# Copyright 2015 clowwindy -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. if __name__ == '__main__': import tornado.ioloop @@ -20,24 +6,16 @@ if __name__ == '__main__': import urllib class MainHandler(tornado.web.RequestHandler): - def get(self, project): - try: - with open('/tmp/%s-coverage' % project, 'rb') as f: - coverage = f.read().strip() - n = int(coverage.strip('%')) - if n >= 80: - color = 'brightgreen' - else: - color = 'yellow' - self.redirect(('https://img.shields.io/badge/' - 'coverage-%s-%s.svg' - '?style=flat') % - (urllib.quote(coverage), color)) - except IOError: - raise tornado.web.HTTPError(404) + def get(self): + with open('/tmp/shadowsocks-coverage', 'rb') as f: + coverage = f.read().strip() + self.redirect(('https://img.shields.io/badge/' + 'coverage-%s-brightgreen.svg' + '?style=flat') % + urllib.quote(coverage)) application = tornado.web.Application([ - (r"/([a-zA-Z0-9\-_]+)", MainHandler), + (r"/shadowsocks", MainHandler), ]) if __name__ == "__main__": diff --git a/tests/fastopen.json b/tests/fastopen.json index b4f60d4..f3980b6 100644 --- a/tests/fastopen.json +++ b/tests/fastopen.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"fastopen_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":true -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"fastopen_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":true +} diff --git a/tests/gen_multiple_passwd.py b/tests/gen_multiple_passwd.py deleted file mode 100644 index 62586c2..0000000 --- a/tests/gen_multiple_passwd.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/python - -import json - -with open('server-multi-passwd-performance.json', 'wb') as f: - r = { - 'server': '127.0.0.1', - 'local_port': 1081, - 'timeout': 60, - 'method': 'aes-256-cfb' - } - ports = {} - for i in range(7000, 9000): - ports[str(i)] = 'aes_password' - - r['port_password'] = ports - print(r) - f.write(json.dumps(r, indent=4).encode('utf-8')) diff --git a/tests/graceful.json b/tests/graceful.json deleted file mode 100644 index c00c5df..0000000 --- a/tests/graceful.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":15, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/graceful_cli.py b/tests/graceful_cli.py deleted file mode 100644 index e58674b..0000000 --- a/tests/graceful_cli.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/python - -import socks -import time - - -SERVER_IP = '127.0.0.1' -SERVER_PORT = 8001 - - -if __name__ == '__main__': - s = socks.socksocket() - s.set_proxy(socks.SOCKS5, SERVER_IP, 1081) - s.connect((SERVER_IP, SERVER_PORT)) - s.send(b'test') - time.sleep(30) - s.close() diff --git a/tests/graceful_server.py b/tests/graceful_server.py deleted file mode 100644 index d7038f1..0000000 --- a/tests/graceful_server.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/python - -import socket - - -if __name__ == '__main__': - s = socket.socket() - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(('127.0.0.1', 8001)) - s.listen(1024) - c = None - while True: - c = s.accept() diff --git a/tests/ipv6-client-side.json b/tests/ipv6-client-side.json index e14bb07..6c3cfaf 100644 --- a/tests/ipv6-client-side.json +++ b/tests/ipv6-client-side.json @@ -1,10 +1,10 @@ -{ - "server":"::1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"::1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/ipv6.json b/tests/ipv6.json index 18263ee..d855f9c 100644 --- a/tests/ipv6.json +++ b/tests/ipv6.json @@ -1,10 +1,10 @@ -{ - "server":"::", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"::", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/jenkins.sh b/tests/jenkins.sh deleted file mode 100755 index 85dd01a..0000000 --- a/tests/jenkins.sh +++ /dev/null @@ -1,111 +0,0 @@ -#!/bin/bash - -result=0 - -function run_test { - printf '\e[0;36m' - echo "running test: $command $@" - printf '\e[0m' - - $command "$@" - status=$? - if [ $status -ne 0 ]; then - printf '\e[0;31m' - echo "test failed: $command $@" - printf '\e[0m' - echo - result=1 - else - printf '\e[0;32m' - echo OK - printf '\e[0m' - echo - fi - return 0 -} - -python --version -coverage erase -mkdir tmp -run_test pep8 --ignore=E402 . -run_test pyflakes . -run_test coverage run tests/nose_plugin.py -v -run_test python setup.py sdist -run_test tests/test_daemon.sh -run_test python tests/test.py --with-coverage -c tests/aes.json -run_test python tests/test.py --with-coverage -c tests/mbedtls-aes.json -run_test python tests/test.py --with-coverage -c tests/aes-gcm.json -run_test python tests/test.py --with-coverage -c tests/aes-ocb.json -run_test python tests/test.py --with-coverage -c tests/mbedtls-aes-gcm.json -run_test python tests/test.py --with-coverage -c tests/aes-ctr.json -run_test python tests/test.py --with-coverage -c tests/mbedtls-aes-ctr.json -run_test python tests/test.py --with-coverage -c tests/aes-cfb1.json -run_test python tests/test.py --with-coverage -c tests/aes-cfb8.json -run_test python tests/test.py --with-coverage -c tests/aes-ofb.json -run_test python tests/test.py --with-coverage -c tests/camellia.json -run_test python tests/test.py --with-coverage -c tests/mbedtls-camellia.json -run_test python tests/test.py --with-coverage -c tests/rc4-md5.json -run_test python tests/test.py --with-coverage -c tests/salsa20.json -run_test python tests/test.py --with-coverage -c tests/chacha20.json -run_test python tests/test.py --with-coverage -c tests/xchacha20.json -run_test python tests/test.py --with-coverage -c tests/chacha20-ietf.json -run_test python tests/test.py --with-coverage -c tests/chacha20-poly1305.json -run_test python tests/test.py --with-coverage -c tests/xchacha20-ietf-poly1305.json -run_test python tests/test.py --with-coverage -c tests/chacha20-ietf-poly1305.json -run_test python tests/test.py --with-coverage -c tests/table.json -run_test python tests/test.py --with-coverage -c tests/server-multi-ports.json -run_test python tests/test.py --with-coverage -s tests/aes.json -c tests/client-multi-server-ip.json -run_test python tests/test.py --with-coverage -s tests/server-dnsserver.json -c tests/client-multi-server-ip.json -run_test python tests/test.py --with-coverage -s tests/server-multi-passwd.json -c tests/server-multi-passwd-client-side.json -run_test python tests/test.py --with-coverage -c tests/workers.json -run_test python tests/test.py --with-coverage -c tests/rc4-md5-ota.json -# travis-ci not support IPv6 -# run_test python tests/test.py --with-coverage -s tests/ipv6.json -c tests/ipv6-client-side.json -run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -q" -a "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -vv" -run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" -run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" - -# test custom lib path - -run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libopenssl=/usr/local/lib/libcrypto.so" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libopenssl=/usr/local/lib/libcrypto.so" -run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m mbedtls:aes-256-cfb128 -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libmbedtls=/usr/local/lib/libmbedcrypto.so" -a "-m mbedtls:aes-256-cfb128 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libmbedtls=/usr/local/lib/libmbedcrypto.so" -run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libsodium=/usr/local/lib/libsodium.so" -a "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libsodium=/usr/local/lib/libsodium.so" -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= --libopenssl=invalid_path" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libopenssl=invalid_path" -run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libsodium=invalid_path" -a "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libsodium=invalid_path" -run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m mbedtls:aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libmbedtls=invalid_path" -a "-m mbedtls:aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libmbedtls=invalid_path" - -# test if DNS works -run_test python tests/test.py --with-coverage -c tests/aes.json --url="https://clients1.google.com/generate_204" - -# test localhost is in the forbidden list by default -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" - -# test localhost is available when forbidden list is empty -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" - -if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then - if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then - # we have to run it twice: - # the first time there's no syn cookie - # the second time there is syn cookie - run_test python tests/test.py --with-coverage -c tests/fastopen.json - run_test python tests/test.py --with-coverage -c tests/fastopen.json - fi -fi - -run_test tests/test_large_file.sh - -if [ "a$JENKINS" != "a1" ] ; then - # jenkins blocked SIGQUIT with sigprocmask(), we have to skip this test on Jenkins - run_test tests/test_graceful_restart.sh -fi -run_test tests/test_udp_src.sh -run_test tests/test_command.sh - -# coverage combine && coverage report --include=shadowsocks/* -# rm -rf htmlcov -# rm -rf tmp -# coverage html --include=shadowsocks/* -# coverage report --include=shadowsocks/* | tail -n1 | rev | cut -d' ' -f 1 | rev > /tmp/shadowsocks-coverage - -exit $result diff --git a/tests/libmbedtls/install.sh b/tests/libmbedtls/install.sh deleted file mode 100755 index 59fb512..0000000 --- a/tests/libmbedtls/install.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -MBEDTLS_VER=2.4.2 -if [ ! -d mbedtls-$MBEDTLS_VER ]; then - wget https://tls.mbed.org/download/mbedtls-$MBEDTLS_VER-gpl.tgz || exit 1 - tar xf mbedtls-$MBEDTLS_VER-gpl.tgz || exit 1 -fi -pushd mbedtls-$MBEDTLS_VER -make SHARED=1 CFLAGS=-fPIC && sudo make install || exit 1 -sudo ldconfig -popd -rm -rf mbedtls-$MBEDTLS_VER || exit 1 diff --git a/tests/libopenssl/install.sh b/tests/libopenssl/install.sh deleted file mode 100755 index 480c772..0000000 --- a/tests/libopenssl/install.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -OPENSSL_VER=1.1.0e -if [ ! -d openssl-$OPENSSL_VER ]; then - wget https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz || exit 1 - tar xf openssl-$OPENSSL_VER.tar.gz || exit 1 -fi -pushd openssl-$OPENSSL_VER -./config && make && sudo make install || exit 1 -# sudo ldconfig # test multiple libcrypto -popd -rm -rf openssl-$OPENSSL_VER || exit 1 - -rm /usr/bin/openssl || exit 1 -rm -r /usr/include/openssl || exit 1 -ln -s /usr/local/bin/openssl /usr/bin/openssl || exit 1 -ln -s /usr/local/include/openssl /usr/include/openssl || exit 1 -echo /usr/local/lib >> /etc/ld.so.conf || exit 1 -ldconfig -v || exit 1 diff --git a/tests/libsodium/install.sh b/tests/libsodium/install.sh index 215ef72..b0e35fa 100755 --- a/tests/libsodium/install.sh +++ b/tests/libsodium/install.sh @@ -1,11 +1,10 @@ #!/bin/bash -if [ ! -d libsodium-1.0.12 ]; then - wget https://github.com/jedisct1/libsodium/releases/download/1.0.12/libsodium-1.0.12.tar.gz || exit 1 - tar xf libsodium-1.0.12.tar.gz || exit 1 +if [ ! -d libsodium-1.0.1 ]; then + wget https://github.com/jedisct1/libsodium/releases/download/1.0.1/libsodium-1.0.1.tar.gz || exit 1 + tar xf libsodium-1.0.1.tar.gz || exit 1 fi -pushd libsodium-1.0.12 +pushd libsodium-1.0.1 ./configure && make -j2 && make install || exit 1 sudo ldconfig popd -rm -rf libsodium-1.0.12 || exit 1 diff --git a/tests/mbedtls-aes-ctr.json b/tests/mbedtls-aes-ctr.json deleted file mode 100644 index 0740eea..0000000 --- a/tests/mbedtls-aes-ctr.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"mbedtls:aes-256-ctr", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/mbedtls-aes-gcm.json b/tests/mbedtls-aes-gcm.json deleted file mode 100644 index c504e1c..0000000 --- a/tests/mbedtls-aes-gcm.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"mbedtls:aes-256-gcm", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/mbedtls-aes.json b/tests/mbedtls-aes.json deleted file mode 100644 index c0b097b..0000000 --- a/tests/mbedtls-aes.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"mbedtls:aes-256-cfb128", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/mbedtls-camellia.json b/tests/mbedtls-camellia.json deleted file mode 100644 index 480e2ed..0000000 --- a/tests/mbedtls-camellia.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"camellia_password", - "timeout":60, - "method":"mbedtls:camellia-256-cfb128", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/nose_plugin.py b/tests/nose_plugin.py index 86b1a86..ad32cf0 100644 --- a/tests/nose_plugin.py +++ b/tests/nose_plugin.py @@ -1,19 +1,3 @@ -#!/usr/bin/env python -# -# Copyright 2015 clowwindy -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - import nose from nose.plugins.base import Plugin diff --git a/tests/rc4-md5-ota.json b/tests/rc4-md5-ota.json deleted file mode 100644 index 6c312c7..0000000 --- a/tests/rc4-md5-ota.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"rc4-md5", - "local_address":"127.0.0.1", - "fast_open":false, - "one_time_auth":true -} diff --git a/tests/rc4-md5.json b/tests/rc4-md5.json index e69b433..26ba0df 100644 --- a/tests/rc4-md5.json +++ b/tests/rc4-md5.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"rc4-md5", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"aes_password", + "timeout":60, + "method":"rc4-md5", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/salsa20-ctr.json b/tests/salsa20-ctr.json index 8b77d07..5ca6c45 100644 --- a/tests/salsa20-ctr.json +++ b/tests/salsa20-ctr.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"salsa20-ctr", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"salsa20-ctr", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/salsa20.json b/tests/salsa20.json index a4a664f..7e30380 100644 --- a/tests/salsa20.json +++ b/tests/salsa20.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"salsa20", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"salsa20_password", + "timeout":60, + "method":"salsa20", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/server-dnsserver.json b/tests/server-dnsserver.json deleted file mode 100644 index f8d5902..0000000 --- a/tests/server-dnsserver.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"aes_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "fast_open":false, - "dns_server": ["8.8.8.8","8.8.4.4"] -} diff --git a/tests/server-multi-passwd-empty.json b/tests/server-multi-passwd-empty.json deleted file mode 100644 index de354e5..0000000 --- a/tests/server-multi-passwd-empty.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "server": "127.0.0.1", - "local_port": 1081, - "port_password": { - }, - "timeout": 60, - "method": "aes-256-cfb" -} diff --git a/tests/server-multi-passwd-performance.json b/tests/server-multi-passwd-performance.json deleted file mode 100644 index c9fbc37..0000000 --- a/tests/server-multi-passwd-performance.json +++ /dev/null @@ -1,2008 +0,0 @@ -{ - "server": "127.0.0.1", - "local_port": 1081, - "port_password": { - "7582": "aes_password", - "7672": "aes_password", - "8923": "aes_password", - "8502": "aes_password", - "8282": "aes_password", - "8871": "aes_password", - "7732": "aes_password", - "8671": "aes_password", - "7018": "aes_password", - "8492": "aes_password", - "7748": "aes_password", - "8992": "aes_password", - "8246": "aes_password", - "7127": "aes_password", - "7775": "aes_password", - "8542": "aes_password", - "8488": "aes_password", - "7515": "aes_password", - "7659": "aes_password", - "8892": "aes_password", - "8028": "aes_password", - "7276": "aes_password", - "7959": "aes_password", - "7457": "aes_password", - "8635": "aes_password", - "7592": "aes_password", - "8764": "aes_password", - "8861": "aes_password", - "8842": "aes_password", - "8135": "aes_password", - "8140": "aes_password", - "8376": "aes_password", - "7733": "aes_password", - "8174": "aes_password", - "7265": "aes_password", - "8314": "aes_password", - "8772": "aes_password", - "8991": "aes_password", - "7183": "aes_password", - "7067": "aes_password", - "7730": "aes_password", - "8694": "aes_password", - "7629": "aes_password", - "7041": "aes_password", - "8507": "aes_password", - "8112": "aes_password", - "8491": "aes_password", - "7273": "aes_password", - "8811": "aes_password", - "8947": "aes_password", - "8612": "aes_password", - "8134": "aes_password", - "8422": "aes_password", - "8970": "aes_password", - "7051": "aes_password", - "8158": "aes_password", - "8934": "aes_password", - "7579": "aes_password", - "7140": "aes_password", - "8448": "aes_password", - "8536": "aes_password", - "7554": "aes_password", - "8168": "aes_password", - "8307": "aes_password", - "8946": "aes_password", - "7872": "aes_password", - "7330": "aes_password", - "8208": "aes_password", - "7955": "aes_password", - "8597": "aes_password", - "7025": "aes_password", - "7086": "aes_password", - "7534": "aes_password", - "7311": "aes_password", - "7758": "aes_password", - "7103": "aes_password", - "8408": "aes_password", - "7688": "aes_password", - "7073": "aes_password", - "8963": "aes_password", - "8578": "aes_password", - "7735": "aes_password", - "7657": "aes_password", - "7763": "aes_password", - "7680": "aes_password", - "8627": "aes_password", - "8205": "aes_password", - "7188": "aes_password", - "8743": "aes_password", - "8472": "aes_password", - "8823": "aes_password", - "7167": "aes_password", - "7008": "aes_password", - "7601": "aes_password", - "8603": "aes_password", - "8467": "aes_password", - "8803": "aes_password", - "7014": "aes_password", - "7233": "aes_password", - "7199": "aes_password", - "7192": "aes_password", - "7329": "aes_password", - "8031": "aes_password", - "8584": "aes_password", - "8041": "aes_password", - "8962": "aes_password", - "8824": "aes_password", - "8079": "aes_password", - "8049": "aes_password", - "7743": "aes_password", - "8035": "aes_password", - "8212": "aes_password", - "8452": "aes_password", - "8484": "aes_password", - "8232": "aes_password", - "8444": "aes_password", - "8410": "aes_password", - "7110": "aes_password", - "7505": "aes_password", - "8856": "aes_password", - "8293": "aes_password", - "7967": "aes_password", - "8267": "aes_password", - "7772": "aes_password", - "8864": "aes_password", - "8518": "aes_password", - "7520": "aes_password", - "7976": "aes_password", - "8407": "aes_password", - "8971": "aes_password", - "7389": "aes_password", - "7510": "aes_password", - "7373": "aes_password", - "8013": "aes_password", - "8310": "aes_password", - "7028": "aes_password", - "7874": "aes_password", - "7356": "aes_password", - "7729": "aes_password", - "7427": "aes_password", - "8312": "aes_password", - "7721": "aes_password", - "7020": "aes_password", - "8231": "aes_password", - "8188": "aes_password", - "8869": "aes_password", - "8595": "aes_password", - "8022": "aes_password", - "8911": "aes_password", - "7957": "aes_password", - "7141": "aes_password", - "7157": "aes_password", - "8471": "aes_password", - "8157": "aes_password", - "8795": "aes_password", - "7087": "aes_password", - "7470": "aes_password", - "7266": "aes_password", - "8072": "aes_password", - "8346": "aes_password", - "7163": "aes_password", - "8954": "aes_password", - "7046": "aes_password", - "7856": "aes_password", - "7883": "aes_password", - "8198": "aes_password", - "8443": "aes_password", - "8496": "aes_password", - "8900": "aes_password", - "8354": "aes_password", - "8758": "aes_password", - "8287": "aes_password", - "7574": "aes_password", - "8316": "aes_password", - "7539": "aes_password", - "8460": "aes_password", - "7616": "aes_password", - "8599": "aes_password", - "7795": "aes_password", - "7079": "aes_password", - "8468": "aes_password", - "8462": "aes_password", - "8645": "aes_password", - "8347": "aes_password", - "8776": "aes_password", - "7072": "aes_password", - "8781": "aes_password", - "7765": "aes_password", - "8048": "aes_password", - "7401": "aes_password", - "8718": "aes_password", - "8712": "aes_password", - "7801": "aes_password", - "8673": "aes_password", - "8791": "aes_password", - "7567": "aes_password", - "7003": "aes_password", - "7358": "aes_password", - "8916": "aes_password", - "7021": "aes_password", - "7487": "aes_password", - "7499": "aes_password", - "7108": "aes_password", - "7501": "aes_password", - "7313": "aes_password", - "8887": "aes_password", - "8724": "aes_password", - "7376": "aes_password", - "7153": "aes_password", - "7377": "aes_password", - "8426": "aes_password", - "8831": "aes_password", - "7380": "aes_password", - "7958": "aes_password", - "8250": "aes_password", - "8155": "aes_password", - "8435": "aes_password", - "7630": "aes_password", - "8026": "aes_password", - "7533": "aes_password", - "8704": "aes_password", - "8411": "aes_password", - "7645": "aes_password", - "7937": "aes_password", - "7488": "aes_password", - "8750": "aes_password", - "7196": "aes_password", - "8714": "aes_password", - "8677": "aes_password", - "7475": "aes_password", - "7625": "aes_password", - "8234": "aes_password", - "8870": "aes_password", - "7147": "aes_password", - "8417": "aes_password", - "7362": "aes_password", - "7341": "aes_password", - "8896": "aes_password", - "8423": "aes_password", - "8884": "aes_password", - "7220": "aes_password", - "8615": "aes_password", - "8719": "aes_password", - "8575": "aes_password", - "8891": "aes_password", - "8210": "aes_password", - "8289": "aes_password", - "7406": "aes_password", - "7692": "aes_password", - "7518": "aes_password", - "7244": "aes_password", - "8561": "aes_password", - "7325": "aes_password", - "7306": "aes_password", - "8266": "aes_password", - "8136": "aes_password", - "7991": "aes_password", - "8844": "aes_password", - "8259": "aes_password", - "7749": "aes_password", - "7238": "aes_password", - "7952": "aes_password", - "8528": "aes_password", - "8477": "aes_password", - "7555": "aes_password", - "7544": "aes_password", - "7478": "aes_password", - "7112": "aes_password", - "8931": "aes_password", - "8082": "aes_password", - "8189": "aes_password", - "8461": "aes_password", - "7740": "aes_password", - "8633": "aes_password", - "8322": "aes_password", - "7093": "aes_password", - "8415": "aes_password", - "8093": "aes_password", - "8682": "aes_password", - "7860": "aes_password", - "8580": "aes_password", - "8503": "aes_password", - "7794": "aes_password", - "8394": "aes_password", - "8487": "aes_password", - "8053": "aes_password", - "7277": "aes_password", - "7241": "aes_password", - "8430": "aes_password", - "8428": "aes_password", - "7805": "aes_password", - "8393": "aes_password", - "7711": "aes_password", - "7807": "aes_password", - "7600": "aes_password", - "8403": "aes_password", - "8141": "aes_password", - "8937": "aes_password", - "7559": "aes_password", - "7834": "aes_password", - "8837": "aes_password", - "7823": "aes_password", - "8928": "aes_password", - "7083": "aes_password", - "7590": "aes_password", - "8806": "aes_password", - "7130": "aes_password", - "7929": "aes_password", - "8684": "aes_password", - "8195": "aes_password", - "8706": "aes_password", - "7044": "aes_password", - "7403": "aes_password", - "8890": "aes_password", - "8364": "aes_password", - "8206": "aes_password", - "7882": "aes_password", - "8522": "aes_password", - "8958": "aes_password", - "7429": "aes_password", - "8586": "aes_password", - "8330": "aes_password", - "8922": "aes_password", - "7940": "aes_password", - "7379": "aes_password", - "8955": "aes_password", - "7168": "aes_password", - "7294": "aes_password", - "7949": "aes_password", - "7384": "aes_password", - "8832": "aes_password", - "7423": "aes_password", - "8763": "aes_password", - "7148": "aes_password", - "7029": "aes_password", - "7969": "aes_password", - "8190": "aes_password", - "8807": "aes_password", - "8889": "aes_password", - "7750": "aes_password", - "7348": "aes_password", - "7193": "aes_password", - "7459": "aes_password", - "7507": "aes_password", - "7536": "aes_password", - "8734": "aes_password", - "7174": "aes_password", - "8400": "aes_password", - "8630": "aes_password", - "7128": "aes_password", - "7261": "aes_password", - "7527": "aes_password", - "7232": "aes_password", - "7843": "aes_password", - "7326": "aes_password", - "8639": "aes_password", - "7830": "aes_password", - "7981": "aes_password", - "8404": "aes_password", - "8888": "aes_password", - "7920": "aes_password", - "7410": "aes_password", - "7204": "aes_password", - "8382": "aes_password", - "8355": "aes_password", - "7700": "aes_password", - "7606": "aes_password", - "7372": "aes_password", - "8106": "aes_password", - "8160": "aes_password", - "7511": "aes_password", - "8204": "aes_password", - "8732": "aes_password", - "8751": "aes_password", - "7727": "aes_password", - "7137": "aes_password", - "8311": "aes_password", - "8587": "aes_password", - "7336": "aes_password", - "7674": "aes_password", - "8009": "aes_password", - "7230": "aes_password", - "7383": "aes_password", - "8867": "aes_password", - "7260": "aes_password", - "7497": "aes_password", - "7390": "aes_password", - "8821": "aes_password", - "7274": "aes_password", - "7285": "aes_password", - "7857": "aes_password", - "8137": "aes_password", - "7114": "aes_password", - "7979": "aes_password", - "8726": "aes_password", - "7227": "aes_password", - "7714": "aes_password", - "8012": "aes_password", - "7613": "aes_password", - "8876": "aes_password", - "7622": "aes_password", - "8582": "aes_password", - "7120": "aes_password", - "7104": "aes_password", - "8785": "aes_password", - "8096": "aes_password", - "8129": "aes_password", - "8481": "aes_password", - "8695": "aes_password", - "7473": "aes_password", - "8163": "aes_password", - "8357": "aes_password", - "8501": "aes_password", - "7177": "aes_password", - "7931": "aes_password", - "8220": "aes_password", - "7399": "aes_password", - "7956": "aes_password", - "8801": "aes_password", - "7719": "aes_password", - "8042": "aes_password", - "7433": "aes_password", - "7827": "aes_password", - "8377": "aes_password", - "7745": "aes_password", - "7302": "aes_password", - "8399": "aes_password", - "7766": "aes_password", - "8720": "aes_password", - "8685": "aes_password", - "8558": "aes_password", - "7796": "aes_password", - "7319": "aes_password", - "7170": "aes_password", - "7342": "aes_password", - "7191": "aes_password", - "8747": "aes_password", - "7231": "aes_password", - "7817": "aes_password", - "7352": "aes_password", - "7057": "aes_password", - "8177": "aes_password", - "7221": "aes_password", - "7297": "aes_password", - "7686": "aes_password", - "7082": "aes_password", - "8414": "aes_password", - "8529": "aes_password", - "7257": "aes_password", - "7300": "aes_password", - "7159": "aes_password", - "8901": "aes_password", - "7578": "aes_password", - "8479": "aes_password", - "7225": "aes_password", - "8286": "aes_password", - "7182": "aes_password", - "8194": "aes_password", - "8850": "aes_password", - "7847": "aes_password", - "7665": "aes_password", - "8011": "aes_password", - "7702": "aes_password", - "8638": "aes_password", - "7116": "aes_password", - "7301": "aes_password", - "8936": "aes_password", - "8661": "aes_password", - "8333": "aes_password", - "8025": "aes_password", - "7368": "aes_password", - "8634": "aes_password", - "7154": "aes_password", - "8365": "aes_password", - "8736": "aes_password", - "8478": "aes_password", - "7436": "aes_password", - "7411": "aes_password", - "7913": "aes_password", - "8236": "aes_password", - "8854": "aes_password", - "8722": "aes_password", - "8227": "aes_password", - "7757": "aes_password", - "8835": "aes_password", - "8651": "aes_password", - "7417": "aes_password", - "7877": "aes_password", - "7200": "aes_password", - "8622": "aes_password", - "7004": "aes_password", - "8845": "aes_password", - "8159": "aes_password", - "8741": "aes_password", - "7106": "aes_password", - "8897": "aes_password", - "7968": "aes_password", - "7047": "aes_password", - "8860": "aes_password", - "8777": "aes_password", - "7597": "aes_password", - "8859": "aes_password", - "7117": "aes_password", - "8178": "aes_password", - "8642": "aes_password", - "7246": "aes_password", - "7557": "aes_password", - "7965": "aes_password", - "7699": "aes_password", - "8658": "aes_password", - "7442": "aes_password", - "8272": "aes_password", - "7821": "aes_password", - "7893": "aes_password", - "8665": "aes_password", - "8499": "aes_password", - "7897": "aes_password", - "7173": "aes_password", - "7007": "aes_password", - "8219": "aes_password", - "8040": "aes_password", - "7571": "aes_password", - "7526": "aes_password", - "8203": "aes_password", - "7810": "aes_password", - "8974": "aes_password", - "8200": "aes_password", - "7778": "aes_password", - "7987": "aes_password", - "7701": "aes_password", - "7443": "aes_password", - "7798": "aes_password", - "8995": "aes_password", - "8473": "aes_password", - "7132": "aes_password", - "7262": "aes_password", - "7720": "aes_password", - "7282": "aes_password", - "8066": "aes_password", - "7006": "aes_password", - "7197": "aes_password", - "7815": "aes_password", - "7933": "aes_password", - "8138": "aes_password", - "8418": "aes_password", - "7365": "aes_password", - "7786": "aes_password", - "7891": "aes_password", - "8317": "aes_password", - "8207": "aes_password", - "8416": "aes_password", - "7448": "aes_password", - "8843": "aes_password", - "7371": "aes_password", - "8780": "aes_password", - "7989": "aes_password", - "8043": "aes_password", - "7363": "aes_password", - "7550": "aes_password", - "8678": "aes_password", - "7837": "aes_password", - "8302": "aes_password", - "7907": "aes_password", - "8865": "aes_password", - "8153": "aes_password", - "8090": "aes_password", - "7268": "aes_password", - "8292": "aes_password", - "7919": "aes_password", - "8131": "aes_password", - "8815": "aes_password", - "8154": "aes_password", - "7777": "aes_password", - "8369": "aes_password", - "8929": "aes_password", - "7670": "aes_password", - "7484": "aes_password", - "8353": "aes_password", - "8017": "aes_password", - "8833": "aes_password", - "8001": "aes_password", - "8058": "aes_password", - "7918": "aes_password", - "7694": "aes_password", - "7485": "aes_password", - "8592": "aes_password", - "7584": "aes_password", - "8527": "aes_password", - "8285": "aes_password", - "8264": "aes_password", - "8879": "aes_password", - "7944": "aes_password", - "7000": "aes_password", - "7187": "aes_password", - "7039": "aes_password", - "7769": "aes_password", - "8063": "aes_password", - "8701": "aes_password", - "7432": "aes_password", - "8594": "aes_password", - "7052": "aes_password", - "7369": "aes_password", - "8474": "aes_password", - "7709": "aes_password", - "8296": "aes_password", - "8278": "aes_password", - "8395": "aes_password", - "8674": "aes_password", - "8733": "aes_password", - "7890": "aes_password", - "7080": "aes_password", - "7528": "aes_password", - "7782": "aes_password", - "7466": "aes_password", - "7903": "aes_password", - "8105": "aes_password", - "7144": "aes_password", - "8069": "aes_password", - "8254": "aes_password", - "8573": "aes_password", - "7787": "aes_password", - "7577": "aes_password", - "8918": "aes_password", - "8767": "aes_password", - "7611": "aes_password", - "8233": "aes_password", - "7802": "aes_password", - "7129": "aes_password", - "8968": "aes_password", - "8217": "aes_password", - "8176": "aes_password", - "8027": "aes_password", - "7941": "aes_password", - "8070": "aes_password", - "8697": "aes_password", - "8798": "aes_password", - "8553": "aes_password", - "8510": "aes_password", - "7254": "aes_password", - "7386": "aes_password", - "8800": "aes_password", - "7712": "aes_password", - "7844": "aes_password", - "7535": "aes_password", - "8273": "aes_password", - "8875": "aes_password", - "8675": "aes_password", - "7396": "aes_password", - "7649": "aes_password", - "8623": "aes_password", - "7186": "aes_password", - "8125": "aes_password", - "7734": "aes_password", - "8275": "aes_password", - "7521": "aes_password", - "7135": "aes_password", - "8546": "aes_password", - "7867": "aes_password", - "8989": "aes_password", - "8787": "aes_password", - "8225": "aes_password", - "8457": "aes_password", - "7405": "aes_password", - "7588": "aes_password", - "7854": "aes_password", - "7789": "aes_password", - "8133": "aes_password", - "7641": "aes_password", - "8535": "aes_password", - "7849": "aes_password", - "7594": "aes_password", - "8313": "aes_password", - "8097": "aes_password", - "8030": "aes_password", - "8737": "aes_password", - "8260": "aes_password", - "8950": "aes_password", - "7249": "aes_password", - "7644": "aes_password", - "7912": "aes_password", - "8979": "aes_password", - "8998": "aes_password", - "8731": "aes_password", - "8662": "aes_password", - "7983": "aes_password", - "7035": "aes_password", - "7841": "aes_password", - "8600": "aes_password", - "7228": "aes_password", - "7071": "aes_password", - "8080": "aes_password", - "8713": "aes_password", - "7210": "aes_password", - "7935": "aes_password", - "8057": "aes_password", - "8242": "aes_password", - "7084": "aes_password", - "7070": "aes_password", - "7494": "aes_password", - "8451": "aes_password", - "8626": "aes_password", - "8618": "aes_password", - "7741": "aes_password", - "7118": "aes_password", - "7387": "aes_password", - "7715": "aes_password", - "8143": "aes_password", - "7668": "aes_password", - "7716": "aes_password", - "8101": "aes_password", - "7234": "aes_password", - "8021": "aes_password", - "7156": "aes_password", - "8392": "aes_password", - "7900": "aes_password", - "7055": "aes_password", - "8566": "aes_password", - "7869": "aes_password", - "7864": "aes_password", - "8265": "aes_password", - "8216": "aes_password", - "7738": "aes_password", - "7467": "aes_password", - "8447": "aes_password", - "8564": "aes_password", - "7767": "aes_password", - "7811": "aes_password", - "7898": "aes_password", - "8182": "aes_password", - "8065": "aes_password", - "7561": "aes_password", - "8545": "aes_password", - "7253": "aes_password", - "8173": "aes_password", - "7922": "aes_password", - "7951": "aes_password", - "7612": "aes_password", - "7324": "aes_password", - "7549": "aes_password", - "7858": "aes_password", - "8655": "aes_password", - "8211": "aes_password", - "8469": "aes_password", - "7298": "aes_password", - "8380": "aes_password", - "7394": "aes_password", - "7089": "aes_password", - "8060": "aes_password", - "7591": "aes_password", - "7542": "aes_password", - "7540": "aes_password", - "7456": "aes_password", - "7768": "aes_password", - "8489": "aes_password", - "8089": "aes_password", - "7838": "aes_password", - "8644": "aes_password", - "8344": "aes_password", - "7739": "aes_password", - "7984": "aes_password", - "7909": "aes_password", - "8517": "aes_password", - "8056": "aes_password", - "8297": "aes_password", - "8647": "aes_password", - "8334": "aes_password", - "7056": "aes_password", - "7164": "aes_password", - "8878": "aes_password", - "7816": "aes_password", - "7444": "aes_password", - "8996": "aes_password", - "8359": "aes_password", - "7901": "aes_password", - "8127": "aes_password", - "7424": "aes_password", - "8116": "aes_password", - "7354": "aes_password", - "8919": "aes_password", - "7214": "aes_password", - "7589": "aes_password", - "8982": "aes_password", - "8244": "aes_password", - "8295": "aes_password", - "7669": "aes_password", - "7562": "aes_password", - "8470": "aes_password", - "7474": "aes_password", - "7345": "aes_password", - "7799": "aes_password", - "8269": "aes_password", - "8213": "aes_password", - "8786": "aes_password", - "7482": "aes_password", - "8261": "aes_password", - "8755": "aes_password", - "8882": "aes_password", - "7166": "aes_password", - "7428": "aes_password", - "8766": "aes_password", - "7458": "aes_password", - "8372": "aes_password", - "8045": "aes_password", - "8185": "aes_password", - "7602": "aes_password", - "8373": "aes_password", - "7826": "aes_password", - "8249": "aes_password", - "8881": "aes_password", - "8830": "aes_password", - "8044": "aes_password", - "7563": "aes_password", - "7509": "aes_password", - "7290": "aes_password", - "7019": "aes_password", - "8454": "aes_password", - "8637": "aes_password", - "7876": "aes_password", - "8500": "aes_password", - "8226": "aes_password", - "7744": "aes_password", - "7753": "aes_password", - "7017": "aes_password", - "8362": "aes_password", - "7642": "aes_password", - "8091": "aes_password", - "7512": "aes_password", - "7708": "aes_password", - "8335": "aes_password", - "7292": "aes_password", - "8281": "aes_password", - "8132": "aes_password", - "7683": "aes_password", - "7048": "aes_password", - "7316": "aes_password", - "7011": "aes_password", - "8299": "aes_password", - "8440": "aes_password", - "8147": "aes_password", - "8280": "aes_password", - "8130": "aes_password", - "8303": "aes_password", - "8610": "aes_password", - "8361": "aes_password", - "8339": "aes_password", - "8037": "aes_password", - "8102": "aes_password", - "7845": "aes_password", - "7307": "aes_password", - "8607": "aes_password", - "8523": "aes_password", - "7839": "aes_password", - "7279": "aes_password", - "7321": "aes_password", - "8032": "aes_password", - "8894": "aes_password", - "8166": "aes_password", - "7381": "aes_password", - "8113": "aes_password", - "8139": "aes_password", - "8290": "aes_password", - "7990": "aes_password", - "7388": "aes_password", - "8571": "aes_password", - "8730": "aes_password", - "8441": "aes_password", - "8074": "aes_password", - "7813": "aes_password", - "8555": "aes_password", - "8978": "aes_password", - "7835": "aes_password", - "7323": "aes_password", - "7293": "aes_password", - "8550": "aes_password", - "7617": "aes_password", - "8071": "aes_password", - "7998": "aes_password", - "8115": "aes_password", - "7419": "aes_password", - "8825": "aes_password", - "8412": "aes_password", - "8019": "aes_password", - "8142": "aes_password", - "8186": "aes_password", - "8909": "aes_password", - "8078": "aes_password", - "8952": "aes_password", - "8360": "aes_password", - "8336": "aes_password", - "7953": "aes_password", - "7005": "aes_password", - "8663": "aes_password", - "8866": "aes_password", - "7950": "aes_password", - "7248": "aes_password", - "8519": "aes_password", - "8099": "aes_password", - "8151": "aes_password", - "8959": "aes_password", - "7042": "aes_password", - "7939": "aes_password", - "8064": "aes_password", - "8165": "aes_password", - "8836": "aes_password", - "8965": "aes_password", - "7431": "aes_password", - "7223": "aes_password", - "7999": "aes_password", - "8913": "aes_password", - "7921": "aes_password", - "8883": "aes_password", - "8169": "aes_password", - "7441": "aes_password", - "7469": "aes_password", - "7666": "aes_password", - "8547": "aes_password", - "7993": "aes_password", - "7705": "aes_password", - "8103": "aes_password", - "8524": "aes_password", - "8240": "aes_password", - "7779": "aes_password", - "7344": "aes_password", - "7395": "aes_password", - "8420": "aes_password", - "7287": "aes_password", - "7926": "aes_password", - "8100": "aes_password", - "8874": "aes_password", - "7496": "aes_password", - "7626": "aes_password", - "7784": "aes_password", - "7880": "aes_password", - "7226": "aes_password", - "8405": "aes_password", - "7910": "aes_password", - "8693": "aes_password", - "7997": "aes_password", - "8585": "aes_password", - "8383": "aes_password", - "8429": "aes_password", - "7878": "aes_password", - "8098": "aes_password", - "7288": "aes_password", - "8509": "aes_password", - "8809": "aes_password", - "7973": "aes_password", - "7620": "aes_password", - "7115": "aes_password", - "8445": "aes_password", - "8977": "aes_password", - "8341": "aes_password", - "7859": "aes_password", - "8256": "aes_password", - "7119": "aes_password", - "8442": "aes_password", - "8606": "aes_password", - "7992": "aes_password", - "7270": "aes_password", - "8988": "aes_password", - "7375": "aes_password", - "7747": "aes_password", - "7100": "aes_password", - "7639": "aes_password", - "7296": "aes_password", - "7435": "aes_password", - "7889": "aes_password", - "7636": "aes_password", - "7946": "aes_password", - "7819": "aes_password", - "7978": "aes_password", - "7728": "aes_password", - "8152": "aes_password", - "7660": "aes_password", - "7464": "aes_password", - "8398": "aes_password", - "8804": "aes_password", - "7450": "aes_password", - "8020": "aes_password", - "7988": "aes_password", - "7398": "aes_password", - "7171": "aes_password", - "8315": "aes_password", - "7871": "aes_password", - "8513": "aes_password", - "7250": "aes_password", - "8181": "aes_password", - "7793": "aes_password", - "7414": "aes_password", - "7179": "aes_password", - "7445": "aes_password", - "7259": "aes_password", - "8779": "aes_password", - "7695": "aes_password", - "7149": "aes_password", - "8973": "aes_password", - "8258": "aes_password", - "7291": "aes_password", - "8301": "aes_password", - "7447": "aes_password", - "8276": "aes_password", - "8279": "aes_password", - "7572": "aes_password", - "7146": "aes_password", - "7764": "aes_password", - "7504": "aes_password", - "7604": "aes_password", - "7465": "aes_password", - "7565": "aes_password", - "8976": "aes_password", - "8016": "aes_password", - "7707": "aes_password", - "7627": "aes_password", - "8379": "aes_password", - "7523": "aes_password", - "7145": "aes_password", - "7971": "aes_password", - "8790": "aes_password", - "8475": "aes_password", - "7718": "aes_password", - "7481": "aes_password", - "7650": "aes_password", - "7808": "aes_password", - "7142": "aes_password", - "8816": "aes_password", - "7676": "aes_password", - "7873": "aes_password", - "7885": "aes_password", - "7049": "aes_password", - "8994": "aes_password", - "8863": "aes_password", - "7178": "aes_password", - "8625": "aes_password", - "7237": "aes_password", - "7936": "aes_password", - "7575": "aes_password", - "7673": "aes_password", - "7689": "aes_password", - "7455": "aes_password", - "7780": "aes_password", - "8687": "aes_password", - "7675": "aes_password", - "8431": "aes_password", - "7030": "aes_password", - "8756": "aes_password", - "8318": "aes_password", - "7088": "aes_password", - "8792": "aes_password", - "7454": "aes_password", - "7776": "aes_password", - "8531": "aes_password", - "8829": "aes_password", - "8350": "aes_password", - "7198": "aes_password", - "7438": "aes_password", - "8543": "aes_password", - "8981": "aes_password", - "8915": "aes_password", - "8446": "aes_password", - "7812": "aes_password", - "7570": "aes_password", - "7054": "aes_password", - "8903": "aes_password", - "7213": "aes_password", - "8670": "aes_password", - "7211": "aes_password", - "7327": "aes_password", - "7121": "aes_password", - "8926": "aes_password", - "7430": "aes_password", - "8413": "aes_password", - "7337": "aes_password", - "7771": "aes_password", - "7350": "aes_password", - "7284": "aes_password", - "8252": "aes_password", - "7138": "aes_password", - "7491": "aes_password", - "8774": "aes_password", - "8828": "aes_password", - "7069": "aes_password", - "7545": "aes_password", - "7360": "aes_password", - "8771": "aes_password", - "7915": "aes_password", - "8485": "aes_password", - "7002": "aes_password", - "8838": "aes_password", - "7040": "aes_password", - "8820": "aes_password", - "8033": "aes_password", - "7053": "aes_password", - "8640": "aes_password", - "8552": "aes_password", - "8180": "aes_password", - "7361": "aes_password", - "7560": "aes_password", - "8277": "aes_password", - "7243": "aes_password", - "8999": "aes_password", - "7391": "aes_password", - "8930": "aes_password", - "8957": "aes_password", - "8504": "aes_password", - "7122": "aes_password", - "7139": "aes_password", - "8789": "aes_password", - "8245": "aes_password", - "7493": "aes_password", - "8810": "aes_password", - "8521": "aes_password", - "7317": "aes_password", - "7476": "aes_password", - "8562": "aes_password", - "7724": "aes_password", - "8652": "aes_password", - "7434": "aes_password", - "8110": "aes_password", - "8533": "aes_password", - "7970": "aes_password", - "8745": "aes_password", - "7585": "aes_password", - "7085": "aes_password", - "8421": "aes_password", - "8370": "aes_password", - "7289": "aes_password", - "7996": "aes_password", - "8840": "aes_password", - "7194": "aes_password", - "7256": "aes_password", - "8609": "aes_password", - "8604": "aes_password", - "7062": "aes_password", - "7252": "aes_password", - "8076": "aes_password", - "7647": "aes_password", - "8271": "aes_password", - "7684": "aes_password", - "8539": "aes_password", - "8588": "aes_password", - "8611": "aes_password", - "8654": "aes_password", - "7836": "aes_password", - "8351": "aes_password", - "7962": "aes_password", - "8230": "aes_password", - "7332": "aes_password", - "7573": "aes_password", - "7825": "aes_password", - "7934": "aes_password", - "8572": "aes_password", - "8700": "aes_password", - "8328": "aes_password", - "8046": "aes_password", - "7425": "aes_password", - "8729": "aes_password", - "8886": "aes_password", - "7806": "aes_password", - "7239": "aes_password", - "8601": "aes_password", - "8717": "aes_password", - "7697": "aes_password", - "8352": "aes_password", - "8123": "aes_password", - "7205": "aes_password", - "8298": "aes_password", - "7490": "aes_password", - "7023": "aes_password", - "7964": "aes_password", - "8986": "aes_password", - "7101": "aes_password", - "7634": "aes_password", - "7269": "aes_password", - "8000": "aes_password", - "8172": "aes_password", - "7172": "aes_password", - "8107": "aes_password", - "8486": "aes_password", - "8716": "aes_password", - "8961": "aes_password", - "7685": "aes_password", - "7667": "aes_password", - "7359": "aes_password", - "7804": "aes_password", - "7012": "aes_password", - "8975": "aes_password", - "7598": "aes_password", - "7662": "aes_password", - "7043": "aes_password", - "8331": "aes_password", - "8617": "aes_password", - "7015": "aes_password", - "8390": "aes_password", - "8608": "aes_password", - "7346": "aes_password", - "7331": "aes_password", - "8984": "aes_password", - "7339": "aes_password", - "8465": "aes_password", - "7184": "aes_password", - "7851": "aes_password", - "7868": "aes_password", - "7663": "aes_password", - "8849": "aes_password", - "8667": "aes_password", - "7506": "aes_password", - "7351": "aes_password", - "7109": "aes_password", - "7426": "aes_password", - "7453": "aes_password", - "7737": "aes_password", - "7870": "aes_password", - "8308": "aes_password", - "7543": "aes_password", - "7299": "aes_password", - "8340": "aes_password", - "8243": "aes_password", - "7357": "aes_password", - "8641": "aes_password", - "7553": "aes_password", - "8464": "aes_password", - "7966": "aes_password", - "8581": "aes_password", - "7343": "aes_password", - "8759": "aes_password", - "7631": "aes_password", - "7181": "aes_password", - "7415": "aes_password", - "8187": "aes_password", - "8924": "aes_password", - "7452": "aes_password", - "8797": "aes_password", - "8525": "aes_password", - "7026": "aes_password", - "7322": "aes_password", - "7982": "aes_password", - "8906": "aes_password", - "8342": "aes_password", - "7977": "aes_password", - "7059": "aes_password", - "7986": "aes_password", - "7513": "aes_password", - "7134": "aes_password", - "7462": "aes_password", - "7800": "aes_password", - "8966": "aes_password", - "8453": "aes_password", - "7760": "aes_password", - "7155": "aes_password", - "8632": "aes_password", - "7679": "aes_password", - "8808": "aes_password", - "8508": "aes_password", - "7479": "aes_password", - "7263": "aes_password", - "8648": "aes_password", - "7972": "aes_password", - "8215": "aes_password", - "8948": "aes_password", - "7894": "aes_password", - "7364": "aes_password", - "8520": "aes_password", - "8754": "aes_password", - "7773": "aes_password", - "7255": "aes_password", - "7413": "aes_password", - "7524": "aes_password", - "8511": "aes_password", - "8914": "aes_password", - "8557": "aes_password", - "8085": "aes_password", - "7938": "aes_password", - "8549": "aes_password", - "8839": "aes_password", - "7628": "aes_password", - "7852": "aes_password", - "8788": "aes_password", - "8680": "aes_password", - "8371": "aes_password", - "8378": "aes_password", - "7525": "aes_password", - "7338": "aes_password", - "8969": "aes_password", - "8895": "aes_password", - "7280": "aes_password", - "8827": "aes_password", - "7502": "aes_password", - "8126": "aes_password", - "7208": "aes_password", - "8643": "aes_password", - "7175": "aes_password", - "8183": "aes_password", - "7314": "aes_password", - "8818": "aes_password", - "8039": "aes_password", - "7392": "aes_password", - "7902": "aes_password", - "7576": "aes_password", - "8537": "aes_password", - "7618": "aes_password", - "7203": "aes_password", - "8306": "aes_password", - "7855": "aes_password", - "8778": "aes_password", - "7928": "aes_password", - "7866": "aes_password", - "8483": "aes_password", - "7152": "aes_password", - "7309": "aes_password", - "7222": "aes_password", - "8023": "aes_password", - "7619": "aes_password", - "8985": "aes_password", - "7643": "aes_password", - "7440": "aes_password", - "7646": "aes_password", - "7892": "aes_password", - "7061": "aes_password", - "8941": "aes_password", - "7846": "aes_password", - "8683": "aes_password", - "7829": "aes_password", - "8711": "aes_password", - "8235": "aes_password", - "8463": "aes_password", - "7899": "aes_password", - "8602": "aes_password", - "8114": "aes_password", - "7593": "aes_password", - "7385": "aes_password", - "7624": "aes_password", - "8659": "aes_password", - "7460": "aes_password", - "7917": "aes_password", - "8705": "aes_password", - "7713": "aes_password", - "7875": "aes_password", - "7788": "aes_password", - "7548": "aes_password", - "7076": "aes_password", - "8770": "aes_password", - "8506": "aes_password", - "8769": "aes_password", - "8197": "aes_password", - "7655": "aes_password", - "7247": "aes_password", - "8432": "aes_password", - "7783": "aes_password", - "8327": "aes_password", - "8120": "aes_password", - "7131": "aes_password", - "8268": "aes_password", - "7696": "aes_password", - "8783": "aes_password", - "8406": "aes_password", - "8433": "aes_password", - "8024": "aes_password", - "8005": "aes_password", - "7097": "aes_password", - "8554": "aes_password", - "8967": "aes_password", - "8943": "aes_password", - "7792": "aes_password", - "8003": "aes_password", - "8698": "aes_password", - "7229": "aes_password", - "7687": "aes_password", - "8532": "aes_password", - "7304": "aes_password", - "8122": "aes_password", - "8202": "aes_password", - "7833": "aes_password", - "8679": "aes_password", - "7580": "aes_password", - "8498": "aes_password", - "8345": "aes_password", - "8696": "aes_password", - "7422": "aes_password", - "8650": "aes_password", - "8905": "aes_password", - "7495": "aes_password", - "7075": "aes_password", - "8912": "aes_password", - "8570": "aes_password", - "8847": "aes_password", - "8613": "aes_password", - "7551": "aes_password", - "7150": "aes_password", - "7218": "aes_password", - "7514": "aes_password", - "7195": "aes_password", - "8775": "aes_password", - "8757": "aes_password", - "8251": "aes_password", - "7209": "aes_password", - "7162": "aes_password", - "7564": "aes_password", - "8121": "aes_password", - "7960": "aes_password", - "7656": "aes_password", - "7397": "aes_password", - "8699": "aes_password", - "7421": "aes_password", - "8263": "aes_password", - "8598": "aes_password", - "7009": "aes_password", - "7861": "aes_password", - "7001": "aes_password", - "8744": "aes_password", - "8368": "aes_password", - "7975": "aes_password", - "7165": "aes_password", - "8530": "aes_password", - "8614": "aes_password", - "7258": "aes_password", - "8034": "aes_password", - "7449": "aes_password", - "7932": "aes_password", - "7906": "aes_password", - "8124": "aes_password", - "8526": "aes_password", - "8568": "aes_password", - "7754": "aes_password", - "8228": "aes_password", - "8904": "aes_password", - "8161": "aes_password", - "7547": "aes_password", - "7310": "aes_password", - "7558": "aes_password", - "8338": "aes_password", - "8799": "aes_password", - "8224": "aes_password", - "8563": "aes_password", - "7863": "aes_password", - "7682": "aes_password", - "8944": "aes_password", - "7914": "aes_password", - "7541": "aes_password", - "8953": "aes_password", - "7318": "aes_password", - "8560": "aes_password", - "8972": "aes_password", - "7608": "aes_password", - "8455": "aes_password", - "7468": "aes_password", - "7037": "aes_password", - "7690": "aes_password", - "8814": "aes_password", - "8664": "aes_password", - "7756": "aes_password", - "7569": "aes_password", - "7102": "aes_password", - "8119": "aes_password", - "7161": "aes_password", - "7378": "aes_password", - "7790": "aes_password", - "7638": "aes_password", - "7678": "aes_password", - "7483": "aes_password", - "7063": "aes_password", - "8348": "aes_password", - "7974": "aes_password", - "8596": "aes_password", - "8653": "aes_password", - "8319": "aes_password", - "8933": "aes_password", - "8877": "aes_password", - "7886": "aes_password", - "8459": "aes_password", - "8008": "aes_password", - "8631": "aes_password", - "8482": "aes_password", - "8868": "aes_password", - "7031": "aes_password", - "8036": "aes_password", - "7530": "aes_password", - "7498": "aes_password", - "8497": "aes_password", - "8505": "aes_password", - "8323": "aes_password", - "7566": "aes_password", - "7038": "aes_password", - "7640": "aes_password", - "8690": "aes_password", - "8150": "aes_password", - "7235": "aes_password", - "8921": "aes_password", - "8196": "aes_password", - "8938": "aes_password", - "7531": "aes_password", - "8002": "aes_password", - "7439": "aes_password", - "7451": "aes_password", - "7583": "aes_password", - "7400": "aes_password", - "7242": "aes_password", - "8589": "aes_password", - "8826": "aes_password", - "8050": "aes_password", - "8960": "aes_password", - "7409": "aes_password", - "8873": "aes_password", - "8710": "aes_password", - "8932": "aes_password", - "7751": "aes_password", - "8846": "aes_password", - "7420": "aes_password", - "8817": "aes_password", - "7884": "aes_password", - "7124": "aes_password", - "8047": "aes_password", - "8081": "aes_password", - "8144": "aes_password", - "7477": "aes_password", - "7066": "aes_password", - "7862": "aes_password", - "8389": "aes_password", - "8321": "aes_password", - "8239": "aes_password", - "8987": "aes_password", - "7032": "aes_password", - "7522": "aes_password", - "7691": "aes_password", - "8567": "aes_password", - "8425": "aes_password", - "8128": "aes_password", - "8853": "aes_password", - "7092": "aes_password", - "8624": "aes_password", - "7416": "aes_password", - "7461": "aes_password", - "7283": "aes_password", - "7463": "aes_password", - "8956": "aes_password", - "8574": "aes_password", - "8872": "aes_password", - "8480": "aes_password", - "8384": "aes_password", - "8540": "aes_password", - "8381": "aes_password", - "7064": "aes_password", - "8095": "aes_password", - "8349": "aes_password", - "8855": "aes_password", - "7881": "aes_password", - "7519": "aes_password", - "7961": "aes_password", - "8728": "aes_password", - "8222": "aes_password", - "8326": "aes_password", - "8218": "aes_password", - "8723": "aes_password", - "7068": "aes_password", - "7202": "aes_password", - "7923": "aes_password", - "7347": "aes_password", - "8794": "aes_password", - "7264": "aes_password", - "7437": "aes_password", - "8494": "aes_password", - "7335": "aes_password", - "7717": "aes_password", - "7295": "aes_password", - "8061": "aes_password", - "8702": "aes_password", - "7824": "aes_password", - "8111": "aes_password", - "7176": "aes_password", - "8332": "aes_password", - "8556": "aes_password", - "7818": "aes_password", - "8583": "aes_password", - "8920": "aes_password", - "8006": "aes_password", - "8108": "aes_password", - "7308": "aes_password", - "8708": "aes_password", - "7587": "aes_password", - "7219": "aes_password", - "7367": "aes_password", - "8681": "aes_password", - "8945": "aes_password", - "8337": "aes_password", - "7586": "aes_password", - "7095": "aes_password", - "8880": "aes_password", - "8388": "aes_password", - "7065": "aes_password", - "7045": "aes_password", - "7111": "aes_password", - "8762": "aes_password", - "7703": "aes_password", - "8686": "aes_password", - "8367": "aes_password", - "8796": "aes_password", - "7840": "aes_password", - "7945": "aes_password", - "8329": "aes_password", - "7107": "aes_password", - "8167": "aes_password", - "8862": "aes_password", - "8951": "aes_password", - "7963": "aes_password", - "8073": "aes_password", - "8068": "aes_password", - "7759": "aes_password", - "8175": "aes_password", - "7704": "aes_password", - "8083": "aes_password", - "7206": "aes_password", - "8054": "aes_password", - "7160": "aes_password", - "8727": "aes_password", - "7333": "aes_password", - "7094": "aes_password", - "8753": "aes_password", - "7755": "aes_password", - "8577": "aes_password", - "7099": "aes_password", - "7334": "aes_password", - "8055": "aes_password", - "8397": "aes_password", - "8214": "aes_password", - "8366": "aes_password", - "8541": "aes_password", - "7312": "aes_password", - "8283": "aes_password", - "8052": "aes_password", - "7653": "aes_password", - "7402": "aes_password", - "7224": "aes_password", - "7217": "aes_password", - "7126": "aes_password", - "7710": "aes_password", - "8668": "aes_password", - "8401": "aes_password", - "8628": "aes_password", - "8424": "aes_password", - "7658": "aes_password", - "7471": "aes_password", - "8813": "aes_password", - "7374": "aes_password", - "8067": "aes_password", - "8703": "aes_password", - "7681": "aes_password", - "7382": "aes_password", - "8538": "aes_password", - "8676": "aes_password", - "7033": "aes_password", - "7480": "aes_password", - "8841": "aes_password", - "8834": "aes_password", - "8666": "aes_password", - "8084": "aes_password", - "7925": "aes_password", - "7742": "aes_password", - "8669": "aes_password", - "7532": "aes_password", - "8657": "aes_password", - "8358": "aes_password", - "8590": "aes_password", - "7746": "aes_password", - "7605": "aes_password", - "7905": "aes_password", - "8294": "aes_password", - "8980": "aes_password", - "8735": "aes_password", - "8179": "aes_password", - "8760": "aes_password", - "7036": "aes_password", - "7404": "aes_password", - "8062": "aes_password", - "8201": "aes_password", - "7320": "aes_password", - "8964": "aes_password", - "8402": "aes_password", - "7303": "aes_password", - "8765": "aes_password", - "7803": "aes_password", - "8247": "aes_password", - "8449": "aes_password", - "7278": "aes_password", - "7189": "aes_password", - "8284": "aes_password", - "8848": "aes_password", - "7809": "aes_password", - "7503": "aes_password", - "8419": "aes_password", - "7723": "aes_password", - "7446": "aes_password", - "7556": "aes_password", - "8739": "aes_password", - "8514": "aes_password", - "7486": "aes_password", - "7143": "aes_password", - "8709": "aes_password", - "7105": "aes_password", - "7654": "aes_password", - "8436": "aes_password", - "8761": "aes_password", - "7595": "aes_password", - "8209": "aes_password", - "8029": "aes_password", - "7603": "aes_password", - "8257": "aes_password", - "8649": "aes_password", - "7370": "aes_password", - "8534": "aes_password", - "7267": "aes_password", - "8565": "aes_password", - "7664": "aes_password", - "8551": "aes_password", - "7888": "aes_password", - "8576": "aes_password", - "8893": "aes_password", - "8857": "aes_password", - "7599": "aes_password", - "7568": "aes_password", - "7791": "aes_password", - "7916": "aes_password", - "8885": "aes_password", - "7418": "aes_password", - "7245": "aes_password", - "8170": "aes_password", - "8466": "aes_password", - "7098": "aes_password", - "7489": "aes_password", - "8997": "aes_password", - "8014": "aes_password", - "8805": "aes_password", - "7774": "aes_password", - "8325": "aes_password", - "8262": "aes_password", - "8038": "aes_password", - "8512": "aes_password", - "7546": "aes_password", - "7614": "aes_password", - "8309": "aes_password", - "7123": "aes_password", - "8324": "aes_password", - "8591": "aes_password", - "8255": "aes_password", - "7623": "aes_password", - "8270": "aes_password", - "8715": "aes_password", - "8515": "aes_password", - "8088": "aes_password", - "8375": "aes_password", - "7693": "aes_password", - "8752": "aes_password", - "8059": "aes_password", - "7271": "aes_password", - "8917": "aes_password", - "7500": "aes_password", - "8656": "aes_password", - "7022": "aes_password", - "8812": "aes_password", - "8858": "aes_password", - "7980": "aes_password", - "7090": "aes_password", - "7637": "aes_password", - "7671": "aes_password", - "7904": "aes_password", - "8629": "aes_password", - "7034": "aes_password", - "7207": "aes_password", - "7096": "aes_password", - "7621": "aes_password", - "7770": "aes_password", - "8221": "aes_password", - "7077": "aes_password", - "7661": "aes_password", - "7994": "aes_password", - "7632": "aes_password", - "7848": "aes_password", - "8822": "aes_password", - "7328": "aes_password", - "8438": "aes_password", - "8939": "aes_password", - "8636": "aes_password", - "7995": "aes_password", - "8363": "aes_password", - "7010": "aes_password", - "7212": "aes_password", - "8559": "aes_password", - "8548": "aes_password", - "7275": "aes_password", - "8819": "aes_password", - "7927": "aes_password", - "8291": "aes_password", - "7652": "aes_password", - "7954": "aes_password", - "7832": "aes_password", - "8087": "aes_password", - "8646": "aes_password", - "7736": "aes_password", - "7581": "aes_password", - "7216": "aes_password", - "7190": "aes_password", - "7180": "aes_password", - "7412": "aes_password", - "8569": "aes_password", - "8010": "aes_password", - "7529": "aes_password", - "8118": "aes_password", - "8162": "aes_password", - "8925": "aes_password", - "8490": "aes_password", - "7762": "aes_password", - "7610": "aes_password", - "8439": "aes_password", - "7698": "aes_password", - "7648": "aes_password", - "8742": "aes_password", - "8993": "aes_password", - "7785": "aes_password", - "7609": "aes_password", - "7896": "aes_password", - "8851": "aes_password", - "7731": "aes_password", - "8248": "aes_password", - "8386": "aes_password", - "8621": "aes_password", - "7349": "aes_password", - "7366": "aes_password", - "7169": "aes_password", - "7942": "aes_password", - "7633": "aes_password", - "7781": "aes_password", - "8387": "aes_password", - "8117": "aes_password", - "8782": "aes_password", - "8192": "aes_password", - "8104": "aes_password", - "7615": "aes_password", - "7050": "aes_password", - "7315": "aes_password", - "7725": "aes_password", - "7058": "aes_password", - "7865": "aes_password", - "7133": "aes_password", - "8458": "aes_password", - "7552": "aes_password", - "7761": "aes_password", - "8229": "aes_password", - "7677": "aes_password", - "8619": "aes_password", - "8193": "aes_password", - "8164": "aes_password", - "8493": "aes_password", - "7943": "aes_password", - "8476": "aes_password", - "8427": "aes_password", - "7240": "aes_password", - "8773": "aes_password", - "8749": "aes_password", - "8983": "aes_password", - "8910": "aes_password", - "7722": "aes_password", - "8092": "aes_password", - "8086": "aes_password", - "7911": "aes_password", - "8793": "aes_password", - "8725": "aes_password", - "7201": "aes_password", - "7538": "aes_password", - "7016": "aes_password", - "8396": "aes_password", - "7726": "aes_password", - "8495": "aes_password", - "8300": "aes_password", - "7948": "aes_password", - "8237": "aes_password", - "8802": "aes_password", - "7074": "aes_password", - "8077": "aes_password", - "7516": "aes_password", - "8908": "aes_password", - "8616": "aes_password", - "7814": "aes_password", - "8688": "aes_password", - "7236": "aes_password", - "7537": "aes_password", - "8691": "aes_password", - "8109": "aes_password", - "8223": "aes_password", - "7895": "aes_password", - "8018": "aes_password", - "7820": "aes_password", - "7407": "aes_password", - "8544": "aes_password", - "7651": "aes_password", - "8288": "aes_password", - "7930": "aes_password", - "8004": "aes_password", - "8935": "aes_password", - "7272": "aes_password", - "8707": "aes_password", - "8907": "aes_password", - "8146": "aes_password", - "7850": "aes_password", - "8579": "aes_password", - "7125": "aes_password", - "8692": "aes_password", - "7828": "aes_password", - "7113": "aes_password", - "7027": "aes_password", - "8148": "aes_password", - "7078": "aes_password", - "7508": "aes_password", - "8434": "aes_password", - "8374": "aes_password", - "8356": "aes_password", - "8942": "aes_password", - "8437": "aes_password", - "8902": "aes_password", - "7887": "aes_password", - "7281": "aes_password", - "8593": "aes_password", - "7853": "aes_password", - "8898": "aes_password", - "8927": "aes_password", - "8184": "aes_password", - "7879": "aes_password", - "8409": "aes_password", - "8241": "aes_password", - "7013": "aes_password", - "8145": "aes_password", - "8094": "aes_password", - "8450": "aes_password", - "7353": "aes_password", - "7286": "aes_password", - "7393": "aes_password", - "8852": "aes_password", - "8456": "aes_password", - "8660": "aes_password", - "7908": "aes_password", - "7408": "aes_password", - "8784": "aes_password", - "8990": "aes_password", - "8899": "aes_password", - "8748": "aes_password", - "8385": "aes_password", - "7151": "aes_password", - "7355": "aes_password", - "8051": "aes_password", - "7842": "aes_password", - "7985": "aes_password", - "7081": "aes_password", - "8620": "aes_password", - "7924": "aes_password", - "7305": "aes_password", - "8949": "aes_password", - "7091": "aes_password", - "7947": "aes_password", - "8075": "aes_password", - "8721": "aes_password", - "8605": "aes_password", - "8305": "aes_password", - "7185": "aes_password", - "7831": "aes_password", - "8672": "aes_password", - "8253": "aes_password", - "8274": "aes_password", - "8746": "aes_password", - "8738": "aes_password", - "8015": "aes_password", - "8007": "aes_password", - "8768": "aes_password", - "7492": "aes_password", - "7752": "aes_password", - "8391": "aes_password", - "7596": "aes_password", - "7797": "aes_password", - "8940": "aes_password", - "8304": "aes_password", - "7706": "aes_password", - "7060": "aes_password", - "7251": "aes_password", - "7472": "aes_password", - "7340": "aes_password", - "7607": "aes_password", - "7215": "aes_password", - "7136": "aes_password", - "8171": "aes_password", - "8740": "aes_password", - "8343": "aes_password", - "7024": "aes_password", - "7635": "aes_password", - "8689": "aes_password", - "7158": "aes_password", - "8320": "aes_password", - "8516": "aes_password", - "8238": "aes_password", - "8156": "aes_password", - "8149": "aes_password", - "8199": "aes_password", - "7822": "aes_password", - "7517": "aes_password", - "8191": "aes_password" - }, - "method": "aes-256-cfb", - "timeout": 60 -} \ No newline at end of file diff --git a/tests/socksify/install.sh b/tests/socksify/install.sh index 86c4e1d..8eff72d 100755 --- a/tests/socksify/install.sh +++ b/tests/socksify/install.sh @@ -1,12 +1,8 @@ #!/bin/bash -if [ ! -d dante-1.4.0 ] || [ ! -d dante-1.4.0/configure ]; then - rm dante-1.4.0 -rf - #wget http://www.inet.no/dante/files/dante-1.4.0.tar.gz || exit 1 - wget https://codeload.github.com/notpeter/dante/tar.gz/dante-1.4.0 -O dante-1.4.0.tar.gz || exit 1 +if [ ! -d dante-1.4.0 ]; then + wget http://www.inet.no/dante/files/dante-1.4.0.tar.gz || exit 1 tar xf dante-1.4.0.tar.gz || exit 1 - # - mv dante-dante-1.4.0 dante-1.4.0 fi pushd dante-1.4.0 ./configure && make -j4 && make install || exit 1 diff --git a/tests/table.json b/tests/table.json index 49c2c01..cca6ac2 100644 --- a/tests/table.json +++ b/tests/table.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"table_password", - "timeout":60, - "method":"table", - "local_address":"127.0.0.1", - "fast_open":false -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"table_password", + "timeout":60, + "method":"table", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/test.py b/tests/test.py index 85bd007..0b63a18 100755 --- a/tests/test.py +++ b/tests/test.py @@ -1,19 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- + +# Copyright (c) 2014 clowwindy # -# Copyright 2015 clowwindy +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. from __future__ import absolute_import, division, print_function, \ with_statement @@ -28,23 +34,17 @@ from subprocess import Popen, PIPE python = ['python'] -default_url = 'http://localhost/' - parser = argparse.ArgumentParser(description='test Shadowsocks') parser.add_argument('-c', '--client-conf', type=str, default=None) parser.add_argument('-s', '--server-conf', type=str, default=None) parser.add_argument('-a', '--client-args', type=str, default=None) parser.add_argument('-b', '--server-args', type=str, default=None) parser.add_argument('--with-coverage', action='store_true', default=None) -parser.add_argument('--should-fail', action='store_true', default=None) -parser.add_argument('--tcp-only', action='store_true', default=None) -parser.add_argument('--url', type=str, default=default_url) -parser.add_argument('--dns', type=str, default='8.8.8.8') config = parser.parse_args() if config.with_coverage: - python = ['coverage', 'run', '-a'] + python = ['coverage', 'run', '-p', '-a'] client_args = python + ['shadowsocks/local.py', '-v'] server_args = python + ['shadowsocks/server.py', '-v'] @@ -61,8 +61,6 @@ if config.client_args: server_args.extend(config.server_args.split()) else: server_args.extend(config.client_args.split()) -if config.url == default_url: - server_args.extend(['--forbidden-ip', '']) p1 = Popen(server_args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) p2 = Popen(client_args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) @@ -96,7 +94,7 @@ try: stage = 5 if bytes != str: line = str(line, 'utf8') - sys.stderr.write(line) + sys.stdout.write(line) if line.find('starting local') >= 0: local_ready = True if line.find('starting server') >= 0: @@ -105,7 +103,7 @@ try: if stage == 1: time.sleep(2) - p3 = Popen(['curl', config.url, '-v', '-L', + p3 = Popen(['curl', 'http://www.example.com/', '-v', '-L', '--socks5-hostname', '127.0.0.1:1081', '-m', '15', '--connect-timeout', '10'], stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) @@ -120,16 +118,9 @@ try: fdset.remove(p3.stdout) fdset.remove(p3.stderr) r = p3.wait() - if config.should_fail: - if r == 0: - sys.exit(1) - else: - if r != 0: - sys.exit(1) - if config.tcp_only: - break - p4 = Popen(['socksify', 'dig', '@%s' % config.dns, - 'www.google.com'], + if r != 0: + sys.exit(1) + p4 = Popen(['socksify', 'dig', '@8.8.8.8', 'www.google.com'], stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) if p4 is not None: fdset.append(p4.stdout) @@ -140,14 +131,9 @@ try: if stage == 5: r = p4.wait() - if config.should_fail: - if r == 0: - sys.exit(1) - print('test passed (expecting failure)') - else: - if r != 0: - sys.exit(1) - print('test passed') + if r != 0: + sys.exit(1) + print('test passed') break finally: for p in [p1, p2]: diff --git a/tests/test_command.sh b/tests/test_command.sh index 9af91c3..eba4c8c 100755 --- a/tests/test_command.sh +++ b/tests/test_command.sh @@ -2,13 +2,10 @@ . tests/assert.sh -PYTHON="coverage run -a" +PYTHON="coverage run -a -p" LOCAL="$PYTHON shadowsocks/local.py" SERVER="$PYTHON shadowsocks/server.py" -assert "$LOCAL --version 2>&1 | grep Shadowsocks | awk -F\" \" '{print \$1}'" "Shadowsocks" -assert "$SERVER --version 2>&1 | grep Shadowsocks | awk -F\" \" '{print \$1}'" "Shadowsocks" - assert "$LOCAL 2>&1 | grep ERROR" "ERROR: config not specified" assert "$LOCAL 2>&1 | grep usage | cut -d: -f1" "usage" @@ -30,16 +27,14 @@ $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d sto assert "$LOCAL 2>&1 -m rc4-md5 -k mypassword -s 0.0.0.0 -p 8388 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " DON'T USE DEFAULT PASSWORD! Please change it in your config.json!" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop -assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " server addr not specified" +assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": server addr not specified" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop -assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password not specified" +assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password not specified" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop -assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password or port_password not specified" +assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password or port_password not specified" $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop -assert "$SERVER 2>&1 --forbidden-ip 127.0.0.1/4a -m rc4-md5 -k 12345 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " Not a valid CIDR notation: 127.0.0.1/4a" -$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop assert_end command diff --git a/tests/test_daemon.sh b/tests/test_daemon.sh index 1acddc0..40f35ef 100755 --- a/tests/test_daemon.sh +++ b/tests/test_daemon.sh @@ -18,7 +18,7 @@ function run_test { for module in local server do -command="coverage run -a shadowsocks/$module.py" +command="coverage run -p -a shadowsocks/$module.py" mkdir -p tmp diff --git a/tests/test_graceful_restart.sh b/tests/test_graceful_restart.sh deleted file mode 100755 index eb4bc0f..0000000 --- a/tests/test_graceful_restart.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -PYTHON="coverage run -a" -URL=http://127.0.0.1/file - - -# setup processes -$PYTHON shadowsocks/local.py -c tests/graceful.json & -LOCAL=$! - -$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & -SERVER=$! - -python tests/graceful_server.py & -GSERVER=$! - -sleep 1 - -python tests/graceful_cli.py & -GCLI=$! - -sleep 1 - -# graceful restart server: send SIGQUIT to old process and start a new one -kill -s SIGQUIT $SERVER -sleep 0.5 -$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & -NEWSERVER=$! - -sleep 1 - -# check old server -ps x | grep -v grep | grep $SERVER -OLD_SERVER_RUNNING1=$? -# old server should not quit at this moment -echo old server running: $OLD_SERVER_RUNNING1 - -sleep 1 - -# close connections on old server -kill -s SIGKILL $GCLI -kill -s SIGKILL $GSERVER -kill -s SIGINT $LOCAL - -sleep 11 - -# check old server -ps x | grep -v grep | grep $SERVER -OLD_SERVER_RUNNING2=$? -# old server should quit at this moment -echo old server running: $OLD_SERVER_RUNNING2 - -kill -s SIGINT $SERVER -# new server is expected running -kill -s SIGINT $NEWSERVER || exit 1 - -if [ $OLD_SERVER_RUNNING1 -ne 0 ]; then - exit 1 -fi - -if [ $OLD_SERVER_RUNNING2 -ne 1 ]; then - sleep 1 - exit 1 -fi diff --git a/tests/test_large_file.sh b/tests/test_large_file.sh index 7a6aec8..e8acd79 100755 --- a/tests/test_large_file.sh +++ b/tests/test_large_file.sh @@ -1,6 +1,6 @@ #!/bin/bash -PYTHON="coverage run -a" +PYTHON="coverage run -p -a" URL=http://127.0.0.1/file mkdir -p tmp @@ -8,7 +8,7 @@ mkdir -p tmp $PYTHON shadowsocks/local.py -c tests/aes.json & LOCAL=$! -$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" & +$PYTHON shadowsocks/server.py -c tests/aes.json & SERVER=$! sleep 3 diff --git a/tests/test_udp_src.py b/tests/test_udp_src.py deleted file mode 100644 index 585606d..0000000 --- a/tests/test_udp_src.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/python - -import socket -import socks - - -SERVER_IP = '127.0.0.1' -SERVER_PORT = 1081 - - -if __name__ == '__main__': - # Test 1: same source port IPv4 - sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, - socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) - sock_out.bind(('127.0.0.1', 9000)) - - sock_in1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.SOL_UDP) - sock_in2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.SOL_UDP) - - sock_in1.bind(('127.0.0.1', 9001)) - sock_in2.bind(('127.0.0.1', 9002)) - - sock_out.sendto(b'data', ('127.0.0.1', 9001)) - result1 = sock_in1.recvfrom(8) - - sock_out.sendto(b'data', ('127.0.0.1', 9002)) - result2 = sock_in2.recvfrom(8) - - sock_out.close() - sock_in1.close() - sock_in2.close() - - # make sure they're from the same source port - assert result1 == result2 - - """ - # Test 2: same source port IPv6 - # try again from the same port but IPv6 - sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, - socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) - sock_out.bind(('127.0.0.1', 9000)) - - sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, - socket.SOL_UDP) - sock_in2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, - socket.SOL_UDP) - - sock_in1.bind(('::1', 9001)) - sock_in2.bind(('::1', 9002)) - - sock_out.sendto(b'data', ('::1', 9001)) - result1 = sock_in1.recvfrom(8) - - sock_out.sendto(b'data', ('::1', 9002)) - result2 = sock_in2.recvfrom(8) - - sock_out.close() - sock_in1.close() - sock_in2.close() - - # make sure they're from the same source port - assert result1 == result2 - - # Test 3: different source ports IPv6 - sock_out = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM, - socket.SOL_UDP) - sock_out.set_proxy(socks.SOCKS5, SERVER_IP, SERVER_PORT) - sock_out.bind(('127.0.0.1', 9003)) - - sock_in1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, - socket.SOL_UDP) - sock_in1.bind(('::1', 9001)) - sock_out.sendto(b'data', ('::1', 9001)) - result3 = sock_in1.recvfrom(8) - - # make sure they're from different source ports - assert result1 != result3 - - sock_out.close() - sock_in1.close() - """ diff --git a/tests/test_udp_src.sh b/tests/test_udp_src.sh deleted file mode 100755 index 4a07a23..0000000 --- a/tests/test_udp_src.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -PYTHON="coverage run -a" - -mkdir -p tmp - -$PYTHON shadowsocks/local.py -c tests/aes.json -v & -LOCAL=$! - -$PYTHON shadowsocks/server.py -c tests/aes.json --forbidden-ip "" -v & -SERVER=$! - -sleep 3 - -python tests/test_udp_src.py -r=$? - -kill -s SIGINT $LOCAL -kill -s SIGINT $SERVER - -sleep 2 - -exit $r diff --git a/tests/workers.json b/tests/workers.json index 8e0943b..2015ff6 100644 --- a/tests/workers.json +++ b/tests/workers.json @@ -1,10 +1,10 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"workers_password", - "timeout":60, - "method":"aes-256-cfb", - "local_address":"127.0.0.1", - "workers": 4 -} +{ + "server":"127.0.0.1", + "server_port":8388, + "local_port":1081, + "password":"workers_password", + "timeout":60, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "workers": 4 +} diff --git a/tests/xchacha20-ietf-poly1305.json b/tests/xchacha20-ietf-poly1305.json deleted file mode 100644 index 91d27dd..0000000 --- a/tests/xchacha20-ietf-poly1305.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"salsa20_password", - "timeout":60, - "method":"xchacha20-ietf-poly1305", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/tests/xchacha20.json b/tests/xchacha20.json deleted file mode 100644 index bf6f365..0000000 --- a/tests/xchacha20.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "server":"127.0.0.1", - "server_port":8388, - "local_port":1081, - "password":"xchacha20_password", - "timeout":60, - "method":"xchacha20", - "local_address":"127.0.0.1", - "fast_open":false -} diff --git a/utils/README.md b/utils/README.md deleted file mode 100644 index f624309..0000000 --- a/utils/README.md +++ /dev/null @@ -1,9 +0,0 @@ -Useful Tools -=========== - -autoban.py ----------- - -Automatically ban IPs that try to brute force crack the server. - -See https://github.com/shadowsocks/shadowsocks/wiki/Ban-Brute-Force-Crackers diff --git a/utils/autoban.py b/utils/autoban.py deleted file mode 100755 index 52aa163..0000000 --- a/utils/autoban.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 2015 clowwindy -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import absolute_import, division, print_function, \ - with_statement - -import sys -import socket -import argparse -import subprocess - - -def inet_pton(str_ip): - try: - return socket.inet_pton(socket.AF_INET, str_ip) - except socket.error: - return None - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='See README') - parser.add_argument('-c', '--count', default=3, type=int, - help='with how many failure times it should be ' - 'considered as an attack') - config = parser.parse_args() - ips = {} - banned = set() - for line in sys.stdin: - if 'can not parse header when' not in line: - continue - ip_str = line.split()[-1].rsplit(':', 1)[0] - ip = inet_pton(ip_str) - if ip is None: - continue - if ip not in ips: - ips[ip] = 1 - sys.stdout.flush() - else: - ips[ip] += 1 - if ip not in banned and ips[ip] >= config.count: - banned.add(ip) - print('ban ip %s' % ip_str) - cmd = ['iptables', '-A', 'INPUT', '-s', ip_str, '-j', 'DROP', - '-m', 'comment', '--comment', 'autoban'] - print(' '.join(cmd), file=sys.stderr) - sys.stderr.flush() - subprocess.call(cmd) diff --git a/utils/fail2ban/shadowsocks.conf b/utils/fail2ban/shadowsocks.conf deleted file mode 100644 index 9b1c7ec..0000000 --- a/utils/fail2ban/shadowsocks.conf +++ /dev/null @@ -1,5 +0,0 @@ -[Definition] - -_daemon = shadowsocks - -failregex = ^\s+ERROR\s+can not parse header when handling connection from :\d+$