chendeben 8 vuotta sitten
sitoutus
d9c07e892d
100 muutettua tiedostoa jossa 12311 lisäystä ja 0 poistoa
  1. 30 0
      Dockerfile
  2. 289 0
      ssr/CHANGES
  3. 29 0
      ssr/CONTRIBUTING.md
  4. 32 0
      ssr/Dockerfile
  5. 202 0
      ssr/LICENSE
  6. 3 0
      ssr/MANIFEST.in
  7. 105 0
      ssr/README.md
  8. 113 0
      ssr/README.rst
  9. 15 0
      ssr/apiconfig.py
  10. 99 0
      ssr/asyncmgr.py
  11. 19 0
      ssr/config.json
  12. 15 0
      ssr/configloader.py
  13. BIN
      ssr/configloader.pyc
  14. 596 0
      ssr/db_transfer.py
  15. BIN
      ssr/db_transfer.pyc
  16. 5 0
      ssr/debian/changelog
  17. 1 0
      ssr/debian/compat
  18. 11 0
      ssr/debian/config.json
  19. 19 0
      ssr/debian/control
  20. 30 0
      ssr/debian/copyright
  21. 2 0
      ssr/debian/docs
  22. 149 0
      ssr/debian/init.d
  23. 1 0
      ssr/debian/install
  24. 5 0
      ssr/debian/rules
  25. 12 0
      ssr/debian/shadowsocks.default
  26. 2 0
      ssr/debian/shadowsocks.manpages
  27. 1 0
      ssr/debian/source/format
  28. 59 0
      ssr/debian/sslocal.1
  29. 59 0
      ssr/debian/ssserver.1
  30. 24 0
      ssr/importloader.py
  31. BIN
      ssr/importloader.pyc
  32. 4 0
      ssr/initcfg.bat
  33. 8 0
      ssr/initcfg.sh
  34. 9 0
      ssr/logrun.sh
  35. 2 0
      ssr/mudb.json
  36. 356 0
      ssr/mujson_mgr.py
  37. 13 0
      ssr/mysql.json
  38. 6 0
      ssr/run.sh
  39. 66 0
      ssr/server.py
  40. 294 0
      ssr/server_pool.py
  41. BIN
      ssr/server_pool.pyc
  42. 39 0
      ssr/setup.py
  43. 18 0
      ssr/shadowsocks/__init__.py
  44. BIN
      ssr/shadowsocks/__init__.pyc
  45. 553 0
      ssr/shadowsocks/asyncdns.py
  46. BIN
      ssr/shadowsocks/asyncdns.pyc
  47. 376 0
      ssr/shadowsocks/common.py
  48. BIN
      ssr/shadowsocks/common.pyc
  49. 18 0
      ssr/shadowsocks/crypto/__init__.py
  50. BIN
      ssr/shadowsocks/crypto/__init__.pyc
  51. 135 0
      ssr/shadowsocks/crypto/ctypes_libsodium.py
  52. 188 0
      ssr/shadowsocks/crypto/ctypes_openssl.py
  53. 202 0
      ssr/shadowsocks/crypto/openssl.py
  54. BIN
      ssr/shadowsocks/crypto/openssl.pyc
  55. 52 0
      ssr/shadowsocks/crypto/rc4_md5.py
  56. BIN
      ssr/shadowsocks/crypto/rc4_md5.pyc
  57. 140 0
      ssr/shadowsocks/crypto/sodium.py
  58. BIN
      ssr/shadowsocks/crypto/sodium.pyc
  59. 181 0
      ssr/shadowsocks/crypto/table.py
  60. BIN
      ssr/shadowsocks/crypto/table.pyc
  61. 139 0
      ssr/shadowsocks/crypto/util.py
  62. BIN
      ssr/shadowsocks/crypto/util.pyc
  63. 208 0
      ssr/shadowsocks/daemon.py
  64. 236 0
      ssr/shadowsocks/encrypt.py
  65. BIN
      ssr/shadowsocks/encrypt.pyc
  66. 51 0
      ssr/shadowsocks/encrypt_test.py
  67. 251 0
      ssr/shadowsocks/eventloop.py
  68. BIN
      ssr/shadowsocks/eventloop.pyc
  69. 81 0
      ssr/shadowsocks/local.py
  70. 6 0
      ssr/shadowsocks/logrun.sh
  71. 180 0
      ssr/shadowsocks/lru_cache.py
  72. BIN
      ssr/shadowsocks/lru_cache.pyc
  73. 291 0
      ssr/shadowsocks/manager.py
  74. 104 0
      ssr/shadowsocks/obfs.py
  75. BIN
      ssr/shadowsocks/obfs.pyc
  76. 18 0
      ssr/shadowsocks/obfsplugin/__init__.py
  77. BIN
      ssr/shadowsocks/obfsplugin/__init__.pyc
  78. 1462 0
      ssr/shadowsocks/obfsplugin/auth.py
  79. BIN
      ssr/shadowsocks/obfsplugin/auth.pyc
  80. 309 0
      ssr/shadowsocks/obfsplugin/http_simple.py
  81. BIN
      ssr/shadowsocks/obfsplugin/http_simple.pyc
  82. 277 0
      ssr/shadowsocks/obfsplugin/obfs_tls.py
  83. BIN
      ssr/shadowsocks/obfsplugin/obfs_tls.pyc
  84. 101 0
      ssr/shadowsocks/obfsplugin/plain.py
  85. BIN
      ssr/shadowsocks/obfsplugin/plain.pyc
  86. 360 0
      ssr/shadowsocks/obfsplugin/verify.py
  87. BIN
      ssr/shadowsocks/obfsplugin/verify.pyc
  88. 214 0
      ssr/shadowsocks/ordereddict.py
  89. 6 0
      ssr/shadowsocks/run.sh
  90. 215 0
      ssr/shadowsocks/server.py
  91. 457 0
      ssr/shadowsocks/shell.py
  92. BIN
      ssr/shadowsocks/shell.pyc
  93. 3 0
      ssr/shadowsocks/stop.sh
  94. 3 0
      ssr/shadowsocks/tail.sh
  95. 1255 0
      ssr/shadowsocks/tcprelay.py
  96. BIN
      ssr/shadowsocks/tcprelay.pyc
  97. 1477 0
      ssr/shadowsocks/udprelay.py
  98. BIN
      ssr/shadowsocks/udprelay.pyc
  99. 20 0
      ssr/shadowsocks/version.py
  100. BIN
      ssr/shadowsocks/version.pyc

+ 30 - 0
Dockerfile

@@ -0,0 +1,30 @@
+FROM alpine:3.6
+
+ENV SERVER_ADDR     0.0.0.0
+ENV SERVER_PORT     51348
+ENV PASSWORD        psw
+ENV METHOD          aes-128-ctr
+ENV PROTOCOL        auth_aes128_md5
+ENV PROTOCOLPARAM   32
+ENV OBFS            tls1.2_ticket_auth_compatible
+ENV TIMEOUT         300
+ENV DNS_ADDR        8.8.8.8
+ENV DNS_ADDR_2      8.8.4.4
+
+
+RUN apk --no-cache add python \
+    libsodium \
+    wget
+
+RUN mkdir -p $WORK/shadowsocksr && cd $WORK/shadowsocksr && \
+    wget --no-check-certificate -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \
+    && python /tmp/get-pip.py \
+    && rm /tmp/get-pip.py \
+   	&& pip install cymysql
+CP ./ssr $WORK/shadowsocksr
+
+WORKDIR $WORK/shadowsocksr
+
+
+EXPOSE $SERVER_PORT
+CMD sh logrun.sh

+ 289 - 0
ssr/CHANGES

@@ -0,0 +1,289 @@
+3.0.4 2017-01-08
+- multi-user in single port
+
+3.0.1 2017-01-03
+- remove auth_aes128_*_compatible
+
+3.0.0 2016-12-23
+- http_simple fix bugs
+- tls1.2_ticket_auth fix bug & defaule time diff set to 86400s
+
+2.9.7 2016-11-22
+- manage client with LRUCache
+- catch bind error
+- fix import error of resource on windows
+- print RLIMIT_NOFILE
+- always close cymysql objects
+- add init script
+
+2.9.6 2016-10-17
+- tls1.2_ticket_auth random packet size
+
+2.9.5.1 2016-10-16
+- UDP bind address
+
+2.9.5 2016-10-13
+- add auth_aes128_md5 and auth_aes128_sha1
+
+2.9.4 2016-10-11
+- sync client version
+
+2.6.13 2015-11-02
+- add protocol setting
+
+2.6.12 2015-10-27
+- IPv6 first
+- Fix mem leaks
+- auth_simple plugin
+- remove FORCE_NEW_PROTOCOL
+- optimize code
+
+2.6.11 2015-10-20
+- Obfs plugin
+- Obfs parameters
+- UDP over TCP
+- TCP over UDP (experimental)
+- Fix socket leaks
+- Catch abnormal UDP package
+
+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
+
+2.6 2014-12-21
+- Add daemon support
+
+2.5 2014-12-11
+- Add salsa20 and chacha20
+
+2.4.3 2014-11-10
+- Fix an issue on Python 3
+- Fix an issue with IPv6
+
+2.4.2 2014-11-06
+- Fix command line arguments on Python 3
+- Support table on Python 3
+- Fix TCP Fast Open on Python 3
+
+2.4.1 2014-11-01
+- Fix setup.py for non-utf8 locales on Python 3
+
+2.4 2014-11-01
+- Python 3 support
+- Performance improvement
+- Fix LRU cache behavior
+
+2.3.2 2014-10-11
+- Fix OpenSSL on Windows
+
+2.3.1 2014-10-09
+- Does not require M2Crypto any more
+
+2.3 2014-09-23
+- Support CFB1, CFB8 and CTR mode of AES
+- Do not require password config when using port_password
+- Use SIGTERM instead of SIGQUIT on Windows
+
+2.2.2 2014-09-14
+- Fix when multiple DNS set, IPv6 only sites are broken
+
+2.2.1 2014-09-10
+- Support graceful shutdown
+- Fix some bugs
+
+2.2.0 2014-09-09
+- Add RC4-MD5 encryption
+
+2.1.0 2014-08-10
+- Use only IPv4 DNS server
+- Does not ship config.json
+- Better error message
+
+2.0.12 2014-07-26
+- Support -q quiet mode
+- Exit 0 when showing help with -h
+
+2.0.11 2014-07-12
+- Prefers IP addresses over hostnames, more friendly with socksify and openvpn
+
+2.0.10 2014-07-11
+- Fix UDP on local
+
+2.0.9 2014-07-06
+- Fix EWOULDBLOCK on Windows
+- Fix Unicode config problem on some platforms
+
+2.0.8 2014-06-23
+- Use multiple DNS to query hostnames
+
+2.0.7 2014-06-21
+- Fix fastopen on local
+- Fallback when fastopen is not available
+- Add verbose logging mode -vv
+- Verify if hostname is valid
+
+2.0.6 2014-06-19
+- Fix CPU 100% on POLL_HUP
+- More friendly logging
+
+2.0.5 2014-06-18
+- Support a simple config format for multiple ports
+
+2.0.4 2014-06-12
+- Fix worker master
+
+2.0.3 2014-06-11
+- Fix table encryption with UDP
+
+2.0.2 2014-06-11
+- Add asynchronous DNS in TCP relay
+
+2.0.1 2014-06-05
+- Better logging
+- Maybe fix bad file descriptor
+
+2.0 2014-06-05
+- Use a new event model
+- Remove gevent
+- Refuse to use default password
+- Fix a problem when using multiple passwords with table encryption
+
+1.4.5 2014-05-24
+- Add timeout in TCP server
+- Close sockets in master process
+
+1.4.4 2014-05-17
+- Support multiple workers
+
+1.4.3 2014-05-13
+- Fix Windows
+
+1.4.2 2014-05-10
+- Add salsa20-ctr cipher
+
+1.4.1 2014-05-03
+- Fix error log
+- Fix EINPROGESS with some version of gevent
+
+1.4.0 2014-05-02
+- Adds UDP relay
+- TCP fast open support on Linux 3.7+
+
+1.3.7 2014-04-10
+- Fix a typo in help
+
+1.3.6 2014-04-10
+- Fix a typo in help
+
+1.3.5 2014-04-07
+- Add help
+- Change default local binding address into 127.0.0.1
+
+1.3.4 2014-02-17
+- Fix a bug when no config file exists
+- Client now support multiple server ports and multiple server/port pairs
+- Better error message with bad config.json format and wrong password
+
+1.3.3 2013-07-09
+- Fix default key length of rc2
+
+1.3.2 2013-07-04
+- Server will listen at server IP specified in config
+- Check config file and show some warning messages
+
+1.3.1 2013-06-29
+- Fix -c arg
+
+1.3.0 2013-06-22
+- Move to pypi
+
+1.2.3 2013-06-14
+- add bind address
+
+1.2.2 2013-05-31
+- local can listen at ::0 with -6 arg; bump 1.2.2
+
+1.2.1 2013-05-23
+- Fix an OpenSSL crash
+
+1.2 2013-05-22
+- Use random iv, we finally have strong encryption
+
+1.1.1 2013-05-21
+- Add encryption, AES, blowfish, etc.
+
+1.1 2013-05-16
+- Support IPv6 addresses (type 4)
+- Drop Python 2.5 support
+
+1.0 2013-04-03
+- Fix -6 IPv6
+
+0.9.4 2013-03-04
+- Support Python 2.5
+
+0.9.3 2013-01-14
+- Fix conn termination null data
+
+0.9.2 2013-01-05
+- Change default timeout
+
+0.9.1 2013-01-05
+- Add Travis-CI test
+
+0.9 2012-12-30
+- Replace send with sendall, fix FreeBSD
+
+0.6 2012-12-06
+- Support args
+
+0.5 2012-11-08
+- Fix encryption with negative md5sum
+
+0.4 2012-11-02
+- Move config into a JSON file
+- Auto-detect config path
+
+0.3 2012-06-06
+- Move socks5 negotiation to local
+
+0.2 2012-05-11
+- Add -6 arg for IPv6
+- Fix socket.error
+
+0.1 2012-04-20
+- Initial version

+ 29 - 0
ssr/CONTRIBUTING.md

@@ -0,0 +1,29 @@
+How to Contribute
+=================
+
+Pull Requests
+-------------
+
+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.
+
+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.
+
+
+[Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting
+[mailing lists]:   https://groups.google.com/forum/#!forum/shadowsocks

+ 32 - 0
ssr/Dockerfile

@@ -0,0 +1,32 @@
+FROM alpine
+
+ENV SERVER_ADDR 0.0.0.0
+ENV SERVER_PORT 51348
+ENV PASSWORD=
+ENV METHOD      aes-256-cfb
+ENV PROTOCOL    origin
+ENV OBFS      http_simple
+ENV TIMEOUT     300
+ENV DNS_ADDR    8.8.8.8
+ENV DNS_ADDR_2  8.8.4.4
+
+
+RUN apk update \
+    && apk add python \
+    libsodium \
+    unzip \
+    wget \
+  && rm -rf /var/cache/apk/*
+
+
+
+RUN wget --no-check-certificate https://github.com/breakwa11/shadowsocks/archive/manyuser.zip -O /tmp/manyuser.zip \
+    && unzip -d /tmp /tmp/manyuser.zip \
+    && mv /tmp/shadowsocks-manyuser/shadowsocks ~/shadowsocks \
+    && rm -rf /tmp/*
+
+
+WORKDIR ~/shadowsocks
+
+
+CMD python ~/shadowsocks/server.py -p $SERVER_PORT -k $PASSWORD -m $METHOD  -O $PROTOCOL -o $OBFS

+ 202 - 0
ssr/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "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.

+ 3 - 0
ssr/MANIFEST.in

@@ -0,0 +1,3 @@
+recursive-include shadowsocks *.py
+include README.rst
+include LICENSE

+ 105 - 0
ssr/README.md

@@ -0,0 +1,105 @@
+ShadowsocksR
+===========
+
+[![Build Status]][Travis CI]
+
+A fast tunnel proxy that helps you bypass firewalls.
+
+Server
+------
+
+### Install
+
+Debian / Ubuntu:
+
+    apt-get install git
+    git clone https://github.com/shadowsocksr/shadowsocksr.git
+
+CentOS:
+
+    yum install git
+    git clone https://github.com/shadowsocksr/shadowsocksr.git
+
+Windows:
+
+    git clone https://github.com/shadowsocksr/shadowsocksr.git
+
+### Usage for single user on linux platform
+
+If you clone it into "~/shadowsocksr"  
+move to "~/shadowsocksr", then run:
+
+    bash initcfg.sh
+
+move to "~/shadowsocksr/shadowsocks", then run:
+
+    python server.py -p 443 -k password -m aes-128-cfb -O auth_aes128_md5_compatible -o tls1.2_ticket_auth_compatible
+
+Check all the options via `-h`.
+
+You can also use a configuration file instead (recommand), move to "~/shadowsocksr" and edit the file "user-config.json", then move to "~/shadowsocksr/shadowsocks" again, just run:
+
+    python server.py
+
+To run in the background:
+
+    ./logrun.sh
+
+To stop:
+
+    ./stop.sh
+
+To monitor the log:
+
+    ./tail.sh
+
+
+Client
+------
+
+* [Windows] / [OS X]
+* [Android] / [iOS]
+* [OpenWRT]
+
+Use GUI clients on your local PC/phones. Check the README of your client
+for more information.
+
+Documentation
+-------------
+
+You can find all the documentation in the [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.
+
+Bugs and Issues
+----------------
+
+* [Issue Tracker]
+
+
+
+[Android]:           https://github.com/shadowsocksr/shadowsocksr-android
+[Build Status]:      https://travis-ci.org/shadowsocksr/shadowsocksr.svg?branch=manyuser
+[Debian sid]:        https://packages.debian.org/unstable/python/shadowsocks
+[iOS]:               https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help
+[Issue Tracker]:     https://github.com/shadowsocksr/shadowsocksr/issues?state=open
+[OpenWRT]:           https://github.com/shadowsocks/openwrt-shadowsocks
+[OS X]:              https://github.com/shadowsocks/shadowsocks-iOS/wiki/Shadowsocks-for-OSX-Help
+[Travis CI]:         https://travis-ci.org/shadowsocksr/shadowsocksr
+[Windows]:           https://github.com/shadowsocksr/shadowsocksr-csharp
+[Wiki]:              https://github.com/breakwa11/shadowsocks-rss/wiki

+ 113 - 0
ssr/README.rst

@@ -0,0 +1,113 @@
+shadowsocks
+===========
+
+|PyPI version| |Build Status| |Coverage Status|
+
+A fast tunnel proxy that helps you bypass firewalls.
+
+Server
+------
+
+Install
+~~~~~~~
+
+Debian / Ubuntu:
+
+::
+
+    apt-get install python-pip
+    pip install shadowsocks
+
+CentOS:
+
+::
+
+    yum install python-setuptools && easy_install pip
+    pip install shadowsocks
+
+Windows:
+
+See `Install Server on
+Windows <https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows>`__
+
+Usage
+~~~~~
+
+::
+
+    ssserver -p 443 -k password -m rc4-md5
+
+To run in the background:
+
+::
+
+    sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start
+
+To stop:
+
+::
+
+    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 <https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File>`__
+file instead.
+
+Client
+------
+
+-  `Windows <https://github.com/shadowsocks/shadowsocks/wiki/Ports-and-Clients#windows>`__
+   / `OS
+   X <https://github.com/shadowsocks/shadowsocks-iOS/wiki/Shadowsocks-for-OSX-Help>`__
+-  `Android <https://github.com/shadowsocks/shadowsocks/wiki/Ports-and-Clients#android>`__
+   / `iOS <https://github.com/shadowsocks/shadowsocks-iOS/wiki/Help>`__
+-  `OpenWRT <https://github.com/shadowsocks/openwrt-shadowsocks>`__
+
+Use GUI clients on your local PC/phones. Check the README of your client
+for more information.
+
+Documentation
+-------------
+
+You can find all the documentation in the
+`Wiki <https://github.com/shadowsocks/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.
+
+Bugs and Issues
+---------------
+
+-  `Troubleshooting <https://github.com/shadowsocks/shadowsocks/wiki/Troubleshooting>`__
+-  `Issue
+   Tracker <https://github.com/shadowsocks/shadowsocks/issues?state=open>`__
+-  `Mailing list <https://groups.google.com/group/shadowsocks>`__
+
+.. |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

+ 15 - 0
ssr/apiconfig.py

@@ -0,0 +1,15 @@
+# Config
+API_INTERFACE = 'sspanelv2' #mudbjson, sspanelv2, sspanelv3, sspanelv3ssr, muapiv2(not support)
+UPDATE_TIME = 60
+SERVER_PUB_ADDR = '127.0.0.1' # mujson_mgr need this to generate ssr link
+
+#mudb
+MUDB_FILE = 'mudb.json'
+
+# Mysql
+MYSQL_CONFIG = 'usermysql.json'
+
+# API
+MUAPI_CONFIG = 'usermuapi.json'
+
+

+ 99 - 0
ssr/asyncmgr.py

@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 clowwindy
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import time
+import os
+import socket
+import struct
+import re
+import logging
+from shadowsocks import common
+from shadowsocks import lru_cache
+from shadowsocks import eventloop
+import server_pool
+import Config
+
+class ServerMgr(object):
+
+    def __init__(self):
+        self._loop = None
+        self._request_id = 1
+        self._hosts = {}
+        self._hostname_status = {}
+        self._hostname_to_cb = {}
+        self._cb_to_hostname = {}
+        self._last_time = time.time()
+        self._sock = None
+        self._servers = None
+
+    def add_to_loop(self, loop):
+        if self._loop:
+            raise Exception('already add to loop')
+        self._loop = loop
+        # TODO when dns server is IPv6
+        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
+                                   socket.SOL_UDP)
+        self._sock.bind((Config.MANAGE_BIND_IP, Config.MANAGE_PORT))
+        self._sock.setblocking(False)
+        loop.add(self._sock, eventloop.POLL_IN, self)
+
+    def _handle_data(self, sock):
+        data, addr = sock.recvfrom(128)
+        #manage pwd:port:passwd:action
+        args = data.split(':')
+        if len(args) < 4:
+            return
+        if args[0] == Config.MANAGE_PASS:
+            if args[3] == '0':
+                server_pool.ServerPool.get_instance().cb_del_server(args[1])
+            elif args[3] == '1':
+                server_pool.ServerPool.get_instance().new_server(args[1], args[2])
+
+    def handle_event(self, sock, fd, event):
+        if sock != self._sock:
+            return
+        if event & eventloop.POLL_ERR:
+            logging.error('mgr 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:
+            self._handle_data(sock)
+
+    def close(self):
+        if self._sock:
+            if self._loop:
+                self._loop.remove(self._sock)
+            self._sock.close()
+            self._sock = None
+
+
+def test():
+    pass
+
+if __name__ == '__main__':
+    test()

+ 19 - 0
ssr/config.json

@@ -0,0 +1,19 @@
+{
+    "server": "0.0.0.0",
+    "server_ipv6": "::",
+    "server_port": 8388,
+    "local_address": "127.0.0.1",
+    "local_port": 1080,
+    "password": "m",
+    "timeout": 120,
+    "udp_timeout": 60,
+    "method": "aes-128-ctr",
+    "protocol": "auth_aes128_md5",
+    "protocol_param": "",
+    "obfs": "tls1.2_ticket_auth_compatible",
+    "obfs_param": "",
+    "dns_ipv6": false,
+    "connect_verbose_info": 0,
+    "redirect": "",
+    "fast_open": false
+}

+ 15 - 0
ssr/configloader.py

@@ -0,0 +1,15 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+import importloader
+
+g_config = None
+
+def load_config():
+	global g_config
+	g_config = importloader.loads(['userapiconfig', 'apiconfig'])
+
+def get_config():
+	return g_config
+
+load_config()
+

BIN
ssr/configloader.pyc


+ 596 - 0
ssr/db_transfer.py

@@ -0,0 +1,596 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+
+import logging
+import time
+import sys
+from server_pool import ServerPool
+import traceback
+from shadowsocks import common, shell, lru_cache
+from configloader import load_config, get_config
+import importloader
+
+switchrule = None
+db_instance = None
+
+class TransferBase(object):
+	def __init__(self):
+		import threading
+		self.event = threading.Event()
+		self.key_list = ['port', 'u', 'd', 'transfer_enable', 'passwd', 'enable']
+		self.last_get_transfer = {} #上一次的实际流量
+		self.last_update_transfer = {} #上一次更新到的流量(小于等于实际流量)
+		self.force_update_transfer = set() #强制推入数据库的ID
+		self.port_uid_table = {} #端口到uid的映射(仅v3以上有用)
+		self.onlineuser_cache = lru_cache.LRUCache(timeout=60*30) #用户在线状态记录
+		self.pull_ok = False #记录是否已经拉出过数据
+
+	def load_cfg(self):
+		pass
+
+	def push_db_all_user(self):
+		if self.pull_ok is False:
+			return
+		#更新用户流量到数据库
+		last_transfer = self.last_update_transfer
+		curr_transfer = ServerPool.get_instance().get_servers_transfer()
+		#上次和本次的增量
+		dt_transfer = {}
+		for id in self.force_update_transfer: #此表中的用户统计上次未计入的流量
+			if id in self.last_get_transfer and id in last_transfer:
+				dt_transfer[id] = [self.last_get_transfer[id][0] - last_transfer[id][0], self.last_get_transfer[id][1] - last_transfer[id][1]]
+
+		for id in curr_transfer.keys():
+			if id in self.force_update_transfer:
+				continue
+			#算出与上次记录的流量差值,保存于dt_transfer表
+			if id in last_transfer:
+				if curr_transfer[id][0] + curr_transfer[id][1] - last_transfer[id][0] - last_transfer[id][1] <= 0:
+					continue
+				dt_transfer[id] = [curr_transfer[id][0] - last_transfer[id][0],
+								curr_transfer[id][1] - last_transfer[id][1]]
+			else:
+				if curr_transfer[id][0] + curr_transfer[id][1] <= 0:
+					continue
+				dt_transfer[id] = [curr_transfer[id][0], curr_transfer[id][1]]
+
+			#有流量的,先记录在线状态
+			if id in self.last_get_transfer:
+				if curr_transfer[id][0] + curr_transfer[id][1] > self.last_get_transfer[id][0] + self.last_get_transfer[id][1]:
+					self.onlineuser_cache[id] = curr_transfer[id][0] + curr_transfer[id][1]
+			else:
+				self.onlineuser_cache[id] = curr_transfer[id][0] + curr_transfer[id][1]
+
+		self.onlineuser_cache.sweep()
+
+		update_transfer = self.update_all_user(dt_transfer) #返回有更新的表
+		for id in update_transfer.keys(): #其增量加在此表
+			if id not in self.force_update_transfer: #但排除在force_update_transfer内的
+				last = self.last_update_transfer.get(id, [0,0])
+				self.last_update_transfer[id] = [last[0] + update_transfer[id][0], last[1] + update_transfer[id][1]]
+		self.last_get_transfer = curr_transfer
+		for id in self.force_update_transfer:
+			if id in self.last_update_transfer:
+				del self.last_update_transfer[id]
+			if id in self.last_get_transfer:
+				del self.last_get_transfer[id]
+		self.force_update_transfer = set()
+
+	def del_server_out_of_bound_safe(self, last_rows, rows):
+		#停止超流量的服务
+		#启动没超流量的服务
+		try:
+			switchrule = importloader.load('switchrule')
+		except Exception as e:
+			logging.error('load switchrule.py fail')
+		cur_servers = {}
+		new_servers = {}
+		allow_users = {}
+		mu_servers  = {}
+		for row in rows:
+			try:
+				allow = switchrule.isTurnOn(row) and row['enable'] == 1 and row['u'] + row['d'] < row['transfer_enable']
+			except Exception as e:
+				allow = False
+
+			port = row['port']
+			passwd = common.to_bytes(row['passwd'])
+			cfg = {'password': passwd}
+			if 'id' in row:
+				self.port_uid_table[row['port']] = row['id']
+
+			read_config_keys = ['method', 'obfs', 'obfs_param', 'protocol', 'protocol_param', 'forbidden_ip', 'forbidden_port']
+			for name in read_config_keys:
+				if name in row and row[name]:
+					cfg[name] = row[name]
+
+			merge_config_keys = ['password'] + read_config_keys
+			for name in cfg.keys():
+				if hasattr(cfg[name], 'encode'):
+					cfg[name] = cfg[name].encode('utf-8')
+
+			if port not in cur_servers:
+				cur_servers[port] = passwd
+			else:
+				logging.error('more than one user use the same port [%s]' % (port,))
+				continue
+
+			if allow:
+				allow_users[port] = 1
+				if 'protocol' in cfg and 'protocol_param' in cfg and common.to_str(cfg['protocol']) in ['auth_aes128_md5', 'auth_aes128_sha1']:
+					if '#' in common.to_str(cfg['protocol_param']):
+						mu_servers[port] = 1
+
+				cfgchange = False
+				if port in ServerPool.get_instance().tcp_servers_pool:
+					relay = ServerPool.get_instance().tcp_servers_pool[port]
+					for name in merge_config_keys:
+						if name in cfg and not self.cmp(cfg[name], relay._config[name]):
+							cfgchange = True
+							break
+				if not cfgchange and port in ServerPool.get_instance().tcp_ipv6_servers_pool:
+					relay = ServerPool.get_instance().tcp_ipv6_servers_pool[port]
+					for name in merge_config_keys:
+						if name in cfg and not self.cmp(cfg[name], relay._config[name]):
+							cfgchange = True
+							break
+
+			if port in mu_servers:
+				if ServerPool.get_instance().server_is_run(port) > 0:
+					if cfgchange:
+						logging.info('db stop server at port [%s] reason: config changed: %s' % (port, cfg))
+						ServerPool.get_instance().cb_del_server(port)
+						self.force_update_transfer.add(port)
+						new_servers[port] = (passwd, cfg)
+				else:
+					self.new_server(port, passwd, cfg)
+			else:
+				if ServerPool.get_instance().server_is_run(port) > 0:
+					if not allow:
+						logging.info('db stop server at port [%s]' % (port,))
+						ServerPool.get_instance().cb_del_server(port)
+						self.force_update_transfer.add(port)
+					else:
+						if cfgchange:
+							logging.info('db stop server at port [%s] reason: config changed: %s' % (port, cfg))
+							ServerPool.get_instance().cb_del_server(port)
+							self.force_update_transfer.add(port)
+							new_servers[port] = (passwd, cfg)
+
+				elif allow and port > 0 and port < 65536 and ServerPool.get_instance().server_run_status(port) is False:
+					self.new_server(port, passwd, cfg)
+
+		for row in last_rows:
+			if row['port'] in cur_servers:
+				pass
+			else:
+				logging.info('db stop server at port [%s] reason: port not exist' % (row['port']))
+				ServerPool.get_instance().cb_del_server(row['port'])
+				self.clear_cache(row['port'])
+				if row['port'] in self.port_uid_table:
+					del self.port_uid_table[row['port']]
+
+		if len(new_servers) > 0:
+			from shadowsocks import eventloop
+			self.event.wait(eventloop.TIMEOUT_PRECISION + eventloop.TIMEOUT_PRECISION / 2)
+			for port in new_servers.keys():
+				passwd, cfg = new_servers[port]
+				self.new_server(port, passwd, cfg)
+
+		if isinstance(self, MuJsonTransfer): # works in MuJsonTransfer only
+			logging.debug('db allow users %s \nmu_servers %s' % (allow_users, mu_servers))
+			for port in mu_servers:
+				ServerPool.get_instance().update_mu_server(port, None, allow_users)
+
+	def clear_cache(self, port):
+		if port in self.force_update_transfer: del self.force_update_transfer[port]
+		if port in self.last_get_transfer: del self.last_get_transfer[port]
+		if port in self.last_update_transfer: del self.last_update_transfer[port]
+
+	def new_server(self, port, passwd, cfg):
+		protocol = cfg.get('protocol', ServerPool.get_instance().config.get('protocol', 'origin'))
+		method = cfg.get('method', ServerPool.get_instance().config.get('method', 'None'))
+		obfs = cfg.get('obfs', ServerPool.get_instance().config.get('obfs', 'plain'))
+		logging.info('db start server at port [%s] pass [%s] protocol [%s] method [%s] obfs [%s]' % (port, passwd, protocol, method, obfs))
+		ServerPool.get_instance().new_server(port, cfg)
+
+	def cmp(self, val1, val2):
+		if type(val1) is bytes:
+			val1 = common.to_str(val1)
+		if type(val2) is bytes:
+			val2 = common.to_str(val2)
+		return val1 == val2
+
+	@staticmethod
+	def del_servers():
+		for port in [v for v in ServerPool.get_instance().tcp_servers_pool.keys()]:
+			if ServerPool.get_instance().server_is_run(port) > 0:
+				ServerPool.get_instance().cb_del_server(port)
+		for port in [v for v in ServerPool.get_instance().tcp_ipv6_servers_pool.keys()]:
+			if ServerPool.get_instance().server_is_run(port) > 0:
+				ServerPool.get_instance().cb_del_server(port)
+
+	@staticmethod
+	def thread_db(obj):
+		import socket
+		import time
+		global db_instance
+		timeout = 60
+		socket.setdefaulttimeout(timeout)
+		last_rows = []
+		db_instance = obj()
+		ServerPool.get_instance()
+		shell.log_shadowsocks_version()
+
+		try:
+			import resource
+			logging.info('current process RLIMIT_NOFILE resource: soft %d hard %d'  % resource.getrlimit(resource.RLIMIT_NOFILE))
+		except:
+			pass
+
+		try:
+			while True:
+				load_config()
+				db_instance.load_cfg()
+				try:
+					db_instance.push_db_all_user()
+					rows = db_instance.pull_db_all_user()
+					if rows:
+						db_instance.pull_ok = True
+					db_instance.del_server_out_of_bound_safe(last_rows, rows)
+					last_rows = rows
+				except Exception as e:
+					trace = traceback.format_exc()
+					logging.error(trace)
+					#logging.warn('db thread except:%s' % e)
+				if db_instance.event.wait(get_config().UPDATE_TIME) or not ServerPool.get_instance().thread.is_alive():
+					break
+		except KeyboardInterrupt as e:
+			pass
+		db_instance.del_servers()
+		ServerPool.get_instance().stop()
+		db_instance = None
+
+	@staticmethod
+	def thread_db_stop():
+		global db_instance
+		db_instance.event.set()
+
+class DbTransfer(TransferBase):
+	def __init__(self):
+		super(DbTransfer, self).__init__()
+		self.user_pass = {} #记录更新此用户流量时被跳过多少次
+		self.cfg = {
+			"host": "127.0.0.1",
+			"port": 3306,
+			"user": "ss",
+			"password": "pass",
+			"db": "shadowsocks",
+			"node_id": 0,
+			"transfer_mul": 1.0,
+			"ssl_enable": 0,
+			"ssl_ca": "",
+			"ssl_cert": "",
+			"ssl_key": ""}
+		self.load_cfg()
+
+	def load_cfg(self):
+		import json
+		config_path = get_config().MYSQL_CONFIG
+		cfg = None
+		with open(config_path, 'rb+') as f:
+			cfg = json.loads(f.read().decode('utf8'))
+
+		if cfg:
+			self.cfg.update(cfg)
+
+	def update_all_user(self, dt_transfer):
+		import cymysql
+		update_transfer = {}
+		
+		query_head = 'UPDATE user'
+		query_sub_when = ''
+		query_sub_when2 = ''
+		query_sub_in = None
+		last_time = time.time()
+
+		for id in dt_transfer.keys():
+			transfer = dt_transfer[id]
+			#小于最低更新流量的先不更新
+			update_trs = 1024 * (2048 - self.user_pass.get(id, 0) * 64)
+			if transfer[0] + transfer[1] < update_trs and id not in self.force_update_transfer:
+				self.user_pass[id] = self.user_pass.get(id, 0) + 1
+				continue
+			if id in self.user_pass:
+				del self.user_pass[id]
+
+			query_sub_when += ' WHEN %s THEN u+%s' % (id, int(transfer[0] * self.cfg["transfer_mul"]))
+			query_sub_when2 += ' WHEN %s THEN d+%s' % (id, int(transfer[1] * self.cfg["transfer_mul"]))
+			update_transfer[id] = transfer
+
+			if query_sub_in is not None:
+				query_sub_in += ',%s' % id
+			else:
+				query_sub_in = '%s' % id
+
+		if query_sub_when == '':
+			return update_transfer
+		query_sql = query_head + ' SET u = CASE port' + query_sub_when + \
+					' END, d = CASE port' + query_sub_when2 + \
+					' END, t = ' + str(int(last_time)) + \
+					' WHERE port IN (%s)' % query_sub_in
+		if self.cfg["ssl_enable"] == 1:
+			conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
+					user=self.cfg["user"], passwd=self.cfg["password"],
+					db=self.cfg["db"], charset='utf8',
+					ssl={'ca':self.cfg["ssl_ca"],'cert':self.cfg["ssl_cert"],'key':self.cfg["ssl_key"]})
+		else:
+			conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
+					user=self.cfg["user"], passwd=self.cfg["password"],
+					db=self.cfg["db"], charset='utf8')
+
+		try:
+			cur = conn.cursor()
+			try:
+				cur.execute(query_sql)
+			except Exception as e:
+				logging.error(e)
+				update_transfer = {}
+
+			cur.close()
+			conn.commit()
+		except Exception as e:
+			logging.error(e)
+			update_transfer = {}
+		finally:
+			conn.close()
+
+		return update_transfer
+
+	def pull_db_all_user(self):
+		import cymysql
+		#数据库所有用户信息
+		if self.cfg["ssl_enable"] == 1:
+			conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
+					user=self.cfg["user"], passwd=self.cfg["password"],
+					db=self.cfg["db"], charset='utf8',
+					ssl={'ca':self.cfg["ssl_ca"],'cert':self.cfg["ssl_cert"],'key':self.cfg["ssl_key"]})
+		else:
+			conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
+					user=self.cfg["user"], passwd=self.cfg["password"],
+					db=self.cfg["db"], charset='utf8')
+
+		try:
+			rows = self.pull_db_users(conn)
+		finally:
+			conn.close()
+
+		if not rows:
+			logging.warn('no user in db')
+		return rows
+
+	def pull_db_users(self, conn):
+		try:
+			switchrule = importloader.load('switchrule')
+			keys = switchrule.getKeys(self.key_list)
+		except Exception as e:
+			keys = self.key_list
+
+		cur = conn.cursor()
+		cur.execute("SELECT " + ','.join(keys) + " FROM user")
+		rows = []
+		for r in cur.fetchall():
+			d = {}
+			for column in range(len(keys)):
+				d[keys[column]] = r[column]
+			rows.append(d)
+		cur.close()
+		return rows
+
+class Dbv3Transfer(DbTransfer):
+	def __init__(self):
+		super(Dbv3Transfer, self).__init__()
+		self.key_list += ['id', 'method']
+		if get_config().API_INTERFACE == 'sspanelv3ssr':
+			self.key_list += ['obfs', 'protocol']
+		self.start_time = time.time()
+
+	def update_all_user(self, dt_transfer):
+		import cymysql
+		update_transfer = {}
+
+		query_head = 'UPDATE user'
+		query_sub_when = ''
+		query_sub_when2 = ''
+		query_sub_in = None
+		last_time = time.time()
+
+		alive_user_count = len(self.onlineuser_cache)
+		bandwidth_thistime = 0
+
+		if self.cfg["ssl_enable"] == 1:
+			conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
+					user=self.cfg["user"], passwd=self.cfg["password"],
+					db=self.cfg["db"], charset='utf8',
+					ssl={'ca':self.cfg["ssl_ca"],'cert':self.cfg["ssl_cert"],'key':self.cfg["ssl_key"]})
+		else:
+			conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
+					user=self.cfg["user"], passwd=self.cfg["password"],
+					db=self.cfg["db"], charset='utf8')
+		conn.autocommit(True)
+
+		for id in dt_transfer.keys():
+			transfer = dt_transfer[id]
+			bandwidth_thistime = bandwidth_thistime + transfer[0] + transfer[1]
+
+			update_trs = 1024 * (2048 - self.user_pass.get(id, 0) * 64)
+			if transfer[0] + transfer[1] < update_trs:
+				self.user_pass[id] = self.user_pass.get(id, 0) + 1
+				continue
+			if id in self.user_pass:
+				del self.user_pass[id]
+
+			query_sub_when += ' WHEN %s THEN u+%s' % (id, int(transfer[0] * self.cfg["transfer_mul"]))
+			query_sub_when2 += ' WHEN %s THEN d+%s' % (id, int(transfer[1] * self.cfg["transfer_mul"]))
+			update_transfer[id] = transfer
+
+			cur = conn.cursor()
+			try:
+				if id in self.port_uid_table:
+					cur.execute("INSERT INTO `user_traffic_log` (`id`, `user_id`, `u`, `d`, `node_id`, `rate`, `traffic`, `log_time`) VALUES (NULL, '" + \
+						str(self.port_uid_table[id]) + "', '" + str(transfer[0]) + "', '" + str(transfer[1]) + "', '" + \
+						str(self.cfg["node_id"]) + "', '" + str(self.cfg["transfer_mul"]) + "', '" + \
+						self.traffic_format((transfer[0] + transfer[1]) * self.cfg["transfer_mul"]) + "', unix_timestamp()); ")
+			except:
+				logging.warn('no `user_traffic_log` in db')
+			cur.close()
+
+			if query_sub_in is not None:
+				query_sub_in += ',%s' % id
+			else:
+				query_sub_in = '%s' % id
+
+		if query_sub_when != '':
+			query_sql = query_head + ' SET u = CASE port' + query_sub_when + \
+						' END, d = CASE port' + query_sub_when2 + \
+						' END, t = ' + str(int(last_time)) + \
+						' WHERE port IN (%s)' % query_sub_in
+			cur = conn.cursor()
+			try:
+				cur.execute(query_sql)
+			except Exception as e:
+				logging.error(e)
+			cur.close()
+
+		try:
+			cur = conn.cursor()
+			try:
+				cur.execute("INSERT INTO `ss_node_online_log` (`id`, `node_id`, `online_user`, `log_time`) VALUES (NULL, '" + \
+					str(self.cfg["node_id"]) + "', '" + str(alive_user_count) + "', unix_timestamp()); ")
+			except Exception as e:
+				logging.error(e)
+			cur.close()
+
+			cur = conn.cursor()
+			try:
+				cur.execute("INSERT INTO `ss_node_info_log` (`id`, `node_id`, `uptime`, `load`, `log_time`) VALUES (NULL, '" + \
+					str(self.cfg["node_id"]) + "', '" + str(self.uptime()) + "', '" + \
+					str(self.load()) + "', unix_timestamp()); ")
+			except Exception as e:
+				logging.error(e)
+			cur.close()
+		except:
+			logging.warn('no `ss_node_online_log` or `ss_node_info_log` in db')
+
+		conn.close()
+		return update_transfer
+
+	def pull_db_users(self, conn):
+		try:
+			switchrule = importloader.load('switchrule')
+			keys = switchrule.getKeys(self.key_list)
+		except Exception as e:
+			keys = self.key_list
+
+		cur = conn.cursor()
+
+		node_info_keys = ['traffic_rate']
+		try:
+			cur.execute("SELECT " + ','.join(node_info_keys) +" FROM ss_node where `id`='" + str(self.cfg["node_id"]) + "'")
+			nodeinfo = cur.fetchone()
+		except Exception as e:
+			logging.error(e)
+			nodeinfo = None
+
+		if nodeinfo == None:
+			rows = []
+			cur.close()
+			conn.commit()
+			logging.warn('None result when select node info from ss_node in db, maybe you set the incorrect node id')
+			return rows
+		cur.close()
+
+		node_info_dict = {}
+		for column in range(len(nodeinfo)):
+			node_info_dict[node_info_keys[column]] = nodeinfo[column]
+		self.cfg['transfer_mul'] = float(node_info_dict['traffic_rate'])
+
+		cur = conn.cursor()
+		try:
+			cur.execute("SELECT " + ','.join(keys) + " FROM user")
+			rows = []
+			for r in cur.fetchall():
+				d = {}
+				for column in range(len(keys)):
+					d[keys[column]] = r[column]
+				rows.append(d)
+		except Exception as e:
+			logging.error(e)
+		cur.close()
+		return rows
+
+	def load(self):
+		import os
+		return os.popen("cat /proc/loadavg | awk '{ print $1\" \"$2\" \"$3 }'").readlines()[0]
+
+	def uptime(self):
+		return time.time() - self.start_time
+
+	def traffic_format(self, traffic):
+		if traffic < 1024 * 8:
+			return str(int(traffic)) + "B";
+
+		if traffic < 1024 * 1024 * 2:
+			return str(round((traffic / 1024.0), 2)) + "KB";
+
+		return str(round((traffic / 1048576.0), 2)) + "MB";
+
+class MuJsonTransfer(TransferBase):
+	def __init__(self):
+		super(MuJsonTransfer, self).__init__()
+
+	def update_all_user(self, dt_transfer):
+		import json
+		rows = None
+
+		config_path = get_config().MUDB_FILE
+		with open(config_path, 'rb+') as f:
+			rows = json.loads(f.read().decode('utf8'))
+			for row in rows:
+				if "port" in row:
+					port = row["port"]
+					if port in dt_transfer:
+						row["u"] += dt_transfer[port][0]
+						row["d"] += dt_transfer[port][1]
+
+		if rows:
+			output = json.dumps(rows, sort_keys=True, indent=4, separators=(',', ': '))
+			with open(config_path, 'r+') as f:
+				f.write(output)
+				f.truncate()
+
+		return dt_transfer
+
+	def pull_db_all_user(self):
+		import json
+		rows = None
+
+		config_path = get_config().MUDB_FILE
+		with open(config_path, 'rb+') as f:
+			rows = json.loads(f.read().decode('utf8'))
+			for row in rows:
+				try:
+					if 'forbidden_ip' in row:
+						row['forbidden_ip'] = common.IPNetwork(row['forbidden_ip'])
+				except Exception as e:
+					logging.error(e)
+				try:
+					if 'forbidden_port' in row:
+						row['forbidden_port'] = common.PortRange(row['forbidden_port'])
+				except Exception as e:
+					logging.error(e)
+
+		if not rows:
+			logging.warn('no user in json file')
+		return rows
+

BIN
ssr/db_transfer.pyc


+ 5 - 0
ssr/debian/changelog

@@ -0,0 +1,5 @@
+shadowsocks (2.1.0-1) unstable; urgency=low
+
+  * Initial release (Closes: #758900)
+
+ -- Shell.Xu <shell909090@gmail.com>  Sat, 23 Aug 2014 00:56:04 +0800

+ 1 - 0
ssr/debian/compat

@@ -0,0 +1 @@
+8

+ 11 - 0
ssr/debian/config.json

@@ -0,0 +1,11 @@
+{
+    "server":"my_server_ip",
+    "server_port":8388,
+    "local_address": "127.0.0.1",
+    "local_port":1080,
+    "password":"mypassword",
+    "timeout":300,
+    "method":"aes-256-cfb",
+    "fast_open": false,
+    "workers": 1
+}

+ 19 - 0
ssr/debian/control

@@ -0,0 +1,19 @@
+Source: shadowsocks
+Section: python
+Priority: extra
+Maintainer: Shell.Xu <shell909090@gmail.com>
+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
+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.

+ 30 - 0
ssr/debian/copyright

@@ -0,0 +1,30 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: shadowsocks
+Source: https://github.com/clowwindy/shadowsocks
+
+Files: debian/*
+Copyright: 2014 Shell.Xu <shell909090@gmail.com>
+License: Expat
+
+Files: *
+Copyright: 2014 clowwindy <clowwindy42@gmail.com>
+License: Expat
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ . 
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.

+ 2 - 0
ssr/debian/docs

@@ -0,0 +1,2 @@
+README.md
+README.rst

+ 149 - 0
ssr/debian/init.d

@@ -0,0 +1,149 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          shadowsocks
+# Required-Start:    $network $local_fs $remote_fs
+# Required-Stop:     $network $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Fast tunnel proxy that helps you bypass firewalls
+# Description:       A secure socks5 proxy, designed to protect your Internet traffic.
+#		     This package contain local and server part of shadowsocks, a fast,
+#		     powerful tunnel proxy to bypass firewalls.
+### END INIT INFO
+
+# Author: Shell.Xu <shell909090@gmail.com>
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC=shadowsocks             # Introduce a short description here
+NAME=shadowsocks             # Introduce the short server's name here
+DAEMON=/usr/bin/ssserver     # Introduce the server's location here
+DAEMON_ARGS=""               # Arguments to run the daemon with
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+LOGFILE=/var/log/$NAME.log
+
+# Exit if the package is not installed
+[ -x $DAEMON ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
+	    --background --make-pidfile --chdir / --chuid $USERID --no-close --test > /dev/null \
+		|| return 1
+	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
+	    --background --make-pidfile --chdir / --chuid $USERID --no-close -- \
+		$DAEMON_ARGS $DAEMON_OPTS >> $LOGFILE 2>&1 \
+		|| return 2
+	# Add code here, if necessary, that waits for the process to be ready
+	# to handle requests from services started subsequently which depend
+	# on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	# Many daemons don't delete their pidfiles when they exit.
+	rm -f $PIDFILE
+	return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+	#
+	# If the daemon can reload its configuration without
+	# restarting (for example, when it is sent a SIGHUP),
+	# then implement that here.
+	#
+	start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+	return 0
+}
+
+case "$1" in
+  start)
+    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
+    do_start
+    case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+  ;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+	#
+	# If do_reload() is not implemented then leave this commented out
+	# and leave 'force-reload' as an alias for 'restart'.
+	#
+	#log_daemon_msg "Reloading $DESC" "$NAME"
+	#do_reload
+	#log_end_msg $?
+	#;;
+  restart|force-reload)
+	#
+	# If the "reload" option is implemented then remove the
+	# 'force-reload' alias
+	#
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+	echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:

+ 1 - 0
ssr/debian/install

@@ -0,0 +1 @@
+debian/config.json etc/shadowsocks/

+ 5 - 0
ssr/debian/rules

@@ -0,0 +1,5 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+%:
+	dh $@ --with python2 --buildsystem=python_distutils

+ 12 - 0
ssr/debian/shadowsocks.default

@@ -0,0 +1,12 @@
+# Defaults for shadowsocks initscript
+# sourced by /etc/init.d/shadowsocks
+# installed at /etc/default/shadowsocks by the maintainer scripts
+
+USERID="nobody"
+
+#
+# This is a POSIX shell fragment
+#
+
+# Additional options that are passed to the Daemon.
+DAEMON_OPTS="-q -c /etc/shadowsocks/config.json"

+ 2 - 0
ssr/debian/shadowsocks.manpages

@@ -0,0 +1,2 @@
+debian/sslocal.1
+debian/ssserver.1

+ 1 - 0
ssr/debian/source/format

@@ -0,0 +1 @@
+3.0 (quilt)

+ 59 - 0
ssr/debian/sslocal.1

@@ -0,0 +1,59 @@
+.\"                                      Hey, EMACS: -*- nroff -*-
+.\" (C) Copyright 2014 Shell.Xu <shell909090@gmail.com>,
+.\"
+.TH SHADOWSOCKS 1 "August 23, 2014"
+.SH NAME
+shadowsocks \- Fast tunnel proxy that helps you bypass firewalls
+.SH SYNOPSIS
+.B ssserver
+.RI [ options ]
+.br
+.B sslocal
+.RI [ options ]
+.SH DESCRIPTION
+shadowsocks is a tunnel proxy helps you bypass firewall. 
+.B ssserver
+is the server part, and
+.B sslocal
+is the local part.
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+Show this help message and exit.
+.TP
+.B \-s SERVER_ADDR
+Server address, default: 0.0.0.0.
+.TP
+.B \-p SERVER_PORT
+Server port, default: 8388.
+.TP
+.B \-k PASSWORD
+Password.
+.TP
+.B \-m METHOD
+Encryption method, default: aes-256-cfb.
+.TP
+.B \-t TIMEOUT
+Timeout in seconds, default: 300.
+.TP
+.B \-c CONFIG
+Path to config file.
+.TP
+.B \-\-fast-open
+Use TCP_FASTOPEN, requires Linux 3.7+.
+.TP
+.B \-\-workers WORKERS
+Number of workers, available on Unix/Linux.
+.TP
+.B \-v, \-vv
+Verbose mode.
+.TP
+.B \-q, \-qq
+Quiet mode, only show warnings/errors.
+.SH SEE ALSO
+.br
+The programs are documented fully by
+.IR "Shell Xu <shell909090@gmail.com>"
+and 
+.IR "Clowwindy <clowwindy42@gmail.com>",
+available via the Info system.

+ 59 - 0
ssr/debian/ssserver.1

@@ -0,0 +1,59 @@
+.\"                                      Hey, EMACS: -*- nroff -*-
+.\" (C) Copyright 2014 Shell.Xu <shell909090@gmail.com>,
+.\"
+.TH SHADOWSOCKS 1 "August 23, 2014"
+.SH NAME
+shadowsocks \- Fast tunnel proxy that helps you bypass firewalls
+.SH SYNOPSIS
+.B ssserver
+.RI [ options ]
+.br
+.B sslocal
+.RI [ options ]
+.SH DESCRIPTION
+shadowsocks is a tunnel proxy helps you bypass firewall. 
+.B ssserver
+is the server part, and
+.B sslocal
+is the local part.
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+Show this help message and exit.
+.TP
+.B \-s SERVER_ADDR
+Server address, default: 0.0.0.0.
+.TP
+.B \-p SERVER_PORT
+Server port, default: 8388.
+.TP
+.B \-k PASSWORD
+Password.
+.TP
+.B \-m METHOD
+Encryption method, default: aes-256-cfb.
+.TP
+.B \-t TIMEOUT
+Timeout in seconds, default: 300.
+.TP
+.B \-c CONFIG
+Path to config file.
+.TP
+.B \-\-fast-open
+Use TCP_FASTOPEN, requires Linux 3.7+.
+.TP
+.B \-\-workers WORKERS
+Number of workers, available on Unix/Linux.
+.TP
+.B \-v, \-vv
+Verbose mode.
+.TP
+.B \-q, \-qq
+Quiet mode, only show warnings/errors.
+.SH SEE ALSO
+.br
+The programs are documented fully by
+.IR "Shell Xu <shell909090@gmail.com>"
+and 
+.IR "Clowwindy <clowwindy42@gmail.com>",
+available via the Info system.

+ 24 - 0
ssr/importloader.py

@@ -0,0 +1,24 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+
+def load(name):
+	try:
+		obj = __import__(name)
+		reload(obj)
+		return obj
+	except:
+		pass
+
+	try:
+		import importlib
+		obj = importlib.__import__(name)
+		importlib.reload(obj)
+		return obj
+	except:
+		pass
+
+def loads(namelist):
+	for name in namelist:
+		obj = load(name)
+		if obj is not None:
+			return obj

BIN
ssr/importloader.pyc


+ 4 - 0
ssr/initcfg.bat

@@ -0,0 +1,4 @@
+@echo off
+If Not Exist "userapiconfig.py" Copy "apiconfig.py" "userapiconfig.py"
+If Not Exist "user-config.json" Copy "config.json" "user-config.json"
+If Not Exist "usermysql.json" Copy "mysql.json" "usermysql.json"

+ 8 - 0
ssr/initcfg.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+chmod +x *.sh
+chmod +x shadowsocks/*.sh
+cp -n apiconfig.py userapiconfig.py
+cp -n config.json user-config.json
+cp -n mysql.json usermysql.json
+

+ 9 - 0
ssr/logrun.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+cd `dirname $0`
+eval $(ps -ef | grep "[0-9] python server\\.py m" | awk '{print "kill "$2}')
+ulimit -n 512000
+nohup python server.py m>> ssserver.log 2>&1 &
+cd /root/
+nohup ./IntelliJIDEALicenseServer_linux_386 >> active.log 2>&1 &
+
+

+ 2 - 0
ssr/mudb.json

@@ -0,0 +1,2 @@
+[
+]

+ 356 - 0
ssr/mujson_mgr.py

@@ -0,0 +1,356 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+
+import traceback
+from shadowsocks import shell, common
+from configloader import load_config, get_config
+import random
+import getopt
+import sys
+import json
+import base64
+
+
+class MuJsonLoader(object):
+	def __init__(self):
+		self.json = None
+
+	def load(self, path):
+		l = "[]"
+		try:
+			with open(path, 'rb+') as f:
+				l = f.read().decode('utf8')
+		except:
+			pass
+		self.json = json.loads(l)
+
+	def save(self, path):
+		if self.json is not None:
+			output = json.dumps(self.json, sort_keys=True, indent=4, separators=(',', ': '))
+			with open(path, 'a'):
+				pass
+			with open(path, 'rb+') as f:
+				f.write(output.encode('utf8'))
+				f.truncate()
+
+
+class MuMgr(object):
+	def __init__(self):
+		self.config_path = get_config().MUDB_FILE
+		try:
+			self.server_addr = get_config().SERVER_PUB_ADDR
+		except:
+			self.server_addr = '127.0.0.1'
+		self.data = MuJsonLoader()
+
+		if self.server_addr == '127.0.0.1':
+			self.server_addr = self.getipaddr()
+
+	def getipaddr(self, ifname='eth0'):
+		import socket
+		import struct
+		ret = '127.0.0.1'
+		try:
+			ret = socket.gethostbyname(socket.getfqdn(socket.gethostname()))
+		except:
+			pass
+		if ret == '127.0.0.1':
+			try:
+				import fcntl
+				s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+				ret = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
+			except:
+				pass
+		return ret
+
+	def ssrlink(self, user, encode, muid):
+		protocol = user.get('protocol', '')
+		obfs = user.get('obfs', '')
+		protocol = protocol.replace("_compatible", "")
+		obfs = obfs.replace("_compatible", "")
+		link = "%s:%s:%s:%s:%s:%s" % (self.server_addr, user['port'], protocol, user['method'], obfs, common.to_str(base64.urlsafe_b64encode(common.to_bytes(user['passwd']))).replace("=", ""))
+		if muid is not None:
+			protocol_param = user.get('protocol_param', '')
+			param = protocol_param.split('#')
+			if len(param) == 2:
+				user_dict = {}
+				user_list = param[1].split(',')
+				if user_list:
+					for userinfo in user_list:
+						items = userinfo.split(':')
+						if len(items) == 2:
+							user_int_id = int(items[0])
+							passwd = items[1]
+							user_dict[user_int_id] = passwd
+					if muid in user_dict:
+						param = str(muid) + ':' + user_dict[muid]
+						protocol_param = '/?protoparam=' + base64.urlsafe_b64encode(common.to_bytes(param)).replace("=", "")
+						link += protocol_param
+		return "ssr://" + (encode and common.to_str(base64.urlsafe_b64encode(common.to_bytes(link))).replace("=", "") or link)
+
+	def userinfo(self, user, muid = None):
+		ret = ""
+		key_list = ['user', 'port', 'method', 'passwd', 'protocol', 'protocol_param', 'obfs', 'obfs_param', 'transfer_enable', 'u', 'd']
+		for key in sorted(user):
+			if key not in key_list:
+				key_list.append(key)
+		for key in key_list:
+			if key in ['enable'] or key not in user:
+				continue
+			ret += '\n'
+			if key in ['transfer_enable', 'u', 'd']:
+				val = user[key]
+				if val / 1024 < 4:
+					ret += "    %s : %s" % (key, val)
+				elif val / 1024 ** 2 < 4:
+					val /= float(1024)
+					ret += "    %s : %s  K Bytes" % (key, val)
+				elif val / 1024 ** 3 < 4:
+					val /= float(1024 ** 2)
+					ret += "    %s : %s  M Bytes" % (key, val)
+				else:
+					val /= float(1024 ** 3)
+					ret += "    %s : %s  G Bytes" % (key, val)
+			else:
+				ret += "    %s : %s" % (key, user[key])
+		ret += "\n    " + self.ssrlink(user, False, muid)
+		ret += "\n    " + self.ssrlink(user, True, muid)
+		return ret
+
+	def rand_pass(self):
+		return ''.join([random.choice('''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~-_=+(){}[]^&%$@''') for i in range(8)])
+
+	def add(self, user):
+		up = {'enable': 1, 'u': 0, 'd': 0, 'method': "aes-128-ctr",
+		'protocol': "auth_aes128_md5",
+		'obfs': "tls1.2_ticket_auth_compatible",
+		'transfer_enable': 9007199254740992}
+		up['passwd'] = self.rand_pass()
+		up.update(user)
+
+		self.data.load(self.config_path)
+		for row in self.data.json:
+			match = False
+			if 'user' in user and row['user'] == user['user']:
+				match = True
+			if 'port' in user and row['port'] == user['port']:
+				match = True
+			if match:
+				print("user [%s] port [%s] already exist" % (row['user'], row['port']))
+				return
+		self.data.json.append(up)
+		print("### add user info %s" % self.userinfo(up))
+		self.data.save(self.config_path)
+
+	def edit(self, user):
+		self.data.load(self.config_path)
+		for row in self.data.json:
+			match = True
+			if 'user' in user and row['user'] != user['user']:
+				match = False
+			if 'port' in user and row['port'] != user['port']:
+				match = False
+			if match:
+				print("edit user [%s]" % (row['user'],))
+				row.update(user)
+				print("### new user info %s" % self.userinfo(row))
+				break
+		self.data.save(self.config_path)
+
+	def delete(self, user):
+		self.data.load(self.config_path)
+		index = 0
+		for row in self.data.json:
+			match = True
+			if 'user' in user and row['user'] != user['user']:
+				match = False
+			if 'port' in user and row['port'] != user['port']:
+				match = False
+			if match:
+				print("delete user [%s]" % row['user'])
+				del self.data.json[index]
+				break
+			index += 1
+		self.data.save(self.config_path)
+
+	def clear_ud(self, user):
+		up = {'u': 0, 'd': 0}
+		self.data.load(self.config_path)
+		for row in self.data.json:
+			match = True
+			if 'user' in user and row['user'] != user['user']:
+				match = False
+			if 'port' in user and row['port'] != user['port']:
+				match = False
+			if match:
+				row.update(up)
+				print("clear user [%s]" % row['user'])
+		self.data.save(self.config_path)
+
+	def list_user(self, user):
+		self.data.load(self.config_path)
+		if not user:
+			for row in self.data.json:
+				print("user [%s] port %s" % (row['user'], row['port']))
+			return
+		for row in self.data.json:
+			match = True
+			if 'user' in user and row['user'] != user['user']:
+				match = False
+			if 'port' in user and row['port'] != user['port']:
+				match = False
+			if match:
+				muid = None
+				if 'muid' in user:
+					muid = user['muid']
+				print("### user [%s] info %s" % (row['user'], self.userinfo(row, muid)))
+
+
+def print_server_help():
+	print('''usage: python mujson_manage.py -a|-d|-e|-c|-l [OPTION]...
+
+Actions:
+  -a ADD               add/edit a user
+  -d DELETE            delete a user
+  -e EDIT              edit a user
+  -c CLEAR             set u/d to zero
+  -l LIST              display a user infomation or all users infomation
+
+Options:
+  -u USER              the user name
+  -p PORT              server port (only this option must be set if add a user)
+  -k PASSWORD          password
+  -m METHOD            encryption method, default: aes-128-ctr
+  -O PROTOCOL          protocol plugin, default: auth_aes128_md5
+  -o OBFS              obfs plugin, default: tls1.2_ticket_auth_compatible
+  -G PROTOCOL_PARAM    protocol plugin param
+  -g OBFS_PARAM        obfs plugin param
+  -t TRANSFER          max transfer for G bytes, default: 8388608 (8 PB or 8192 TB)
+  -f FORBID            set forbidden ports. Example (ban 1~79 and 81~100): -f "1-79,81-100"
+  -i MUID              set sub id to display (only work with -l)
+
+General options:
+  -h, --help           show this help message and exit
+''')
+
+
+def main():
+	shortopts = 'adeclu:i:p:k:O:o:G:g:m:t:f:h'
+	longopts = ['help']
+	action = None
+	user = {}
+	fast_set_obfs = {'0': 'plain',
+			'+1': 'http_simple_compatible',
+			'1': 'http_simple',
+			'+2': 'tls1.2_ticket_auth_compatible',
+			'2': 'tls1.2_ticket_auth'}
+	fast_set_protocol = {'0': 'origin',
+			'+ota': 'verify_sha1_compatible',
+			'ota': 'verify_sha1',
+			'a1': 'auth_sha1',
+			'+a1': 'auth_sha1_compatible',
+			'a2': 'auth_sha1_v2',
+			'+a2': 'auth_sha1_v2_compatible',
+			'a4': 'auth_sha1_v4',
+			'+a4': 'auth_sha1_v4_compatible',
+			'am': 'auth_aes128_md5',
+			'as': 'auth_aes128_sha1',
+			}
+	fast_set_method = {'a0': 'aes-128-cfb',
+			'a1': 'aes-192-cfb',
+			'a2': 'aes-256-cfb',
+			'r': 'rc4-md5',
+			'r6': 'rc4-md5-6',
+			'c': 'chacha20',
+			'ci': 'chacha20-ietf',
+			's': 'salsa20',
+			'b': 'bf-cfb',
+			'm0': 'camellia-128-cfb',
+			'm1': 'camellia-192-cfb',
+			'm2': 'camellia-256-cfb',
+			'a0t': 'aes-128-ctr',
+			'a1t': 'aes-192-ctr',
+			'a2t': 'aes-256-ctr'}
+	try:
+		optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
+		for key, value in optlist:
+			if key == '-a':
+				action = 1
+			elif key == '-d':
+				action = 2
+			elif key == '-e':
+				action = 3
+			elif key == '-l':
+				action = 4
+			elif key == '-c':
+				action = 0
+			elif key == '-u':
+				user['user'] = value
+			elif key == '-i':
+				user['muid'] = int(value)
+			elif key == '-p':
+				user['port'] = int(value)
+			elif key == '-k':
+				user['passwd'] = value
+			elif key == '-o':
+				if value in fast_set_obfs:
+					user['obfs'] = fast_set_obfs[value]
+				else:
+					user['obfs'] = value
+			elif key == '-O':
+				if value in fast_set_protocol:
+					user['protocol'] = fast_set_protocol[value]
+				else:
+					user['protocol'] = value
+			elif key == '-g':
+				user['obfs_param'] = value
+			elif key == '-G':
+				user['protocol_param'] = value
+			elif key == '-m':
+				if value in fast_set_method:
+					user['method'] = fast_set_method[value]
+				else:
+					user['method'] = value
+			elif key == '-f':
+				user['forbidden_port'] = value
+			elif key == '-t':
+				val = float(value)
+				try:
+					val = int(value)
+				except:
+					pass
+				user['transfer_enable'] = int(val * 1024) * (1024 ** 2)
+			elif key in ('-h', '--help'):
+				print_server_help()
+				sys.exit(0)
+	except getopt.GetoptError as e:
+		print(e)
+		sys.exit(2)
+
+	manage = MuMgr()
+	if action == 0:
+		manage.clear_ud(user)
+	elif action == 1:
+		if 'user' not in user and 'port' in user:
+			user['user'] = str(user['port'])
+		if 'user' in user and 'port' in user:
+			manage.add(user)
+		else:
+			print("You have to set the port with -p")
+	elif action == 2:
+		if 'user' in user or 'port' in user:
+			manage.delete(user)
+		else:
+			print("You have to set the user name or port with -u/-p")
+	elif action == 3:
+		if 'user' in user or 'port' in user:
+			manage.edit(user)
+		else:
+			print("You have to set the user name or port with -u/-p")
+	elif action == 4:
+		manage.list_user(user)
+	elif action is None:
+		print_server_help()
+
+if __name__ == '__main__':
+	main()

+ 13 - 0
ssr/mysql.json

@@ -0,0 +1,13 @@
+{
+    "host": "127.0.0.1",
+    "port": 3306,
+    "user": "ss",
+    "password": "pass",
+    "db": "sspanel",
+    "node_id": 0,
+    "transfer_mul": 1.0,
+    "ssl_enable": 0,
+    "ssl_ca": "",
+    "ssl_cert": "",
+    "ssl_key": ""
+}

+ 6 - 0
ssr/run.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+cd `dirname $0`
+eval $(ps -ef | grep "[0-9] python server\\.py m" | awk '{print "kill "$2}')
+ulimit -n 512000
+nohup python server.py m>> /dev/null 2>&1 &
+

+ 66 - 0
ssr/server.py

@@ -0,0 +1,66 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2015 breakwall
+#
+# 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 time
+import sys
+import threading
+import os
+
+if __name__ == '__main__':
+	import inspect
+	os.chdir(os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))))
+
+import server_pool
+import db_transfer
+from shadowsocks import shell
+from configloader import load_config, get_config
+
+class MainThread(threading.Thread):
+	def __init__(self, obj):
+		super(MainThread, self).__init__()
+		self.daemon = True
+		self.obj = obj
+
+	def run(self):
+		self.obj.thread_db(self.obj)
+
+	def stop(self):
+		self.obj.thread_db_stop()
+
+def main():
+	shell.check_python()
+	if False:
+		db_transfer.DbTransfer.thread_db()
+	else:
+		if get_config().API_INTERFACE == 'mudbjson':
+			thread = MainThread(db_transfer.MuJsonTransfer)
+		elif get_config().API_INTERFACE == 'sspanelv2':
+			thread = MainThread(db_transfer.DbTransfer)
+		else:
+			thread = MainThread(db_transfer.Dbv3Transfer)
+		thread.start()
+		try:
+			while thread.is_alive():
+				time.sleep(10)
+		except (KeyboardInterrupt, IOError, OSError) as e:
+			import traceback
+			traceback.print_exc()
+			thread.stop()
+
+if __name__ == '__main__':
+	main()
+

+ 294 - 0
ssr/server_pool.py

@@ -0,0 +1,294 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 clowwindy
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os
+import logging
+import struct
+import time
+from shadowsocks import shell, eventloop, tcprelay, udprelay, asyncdns, common
+import threading
+import sys
+from socket import *
+from configloader import load_config, get_config
+
+class MainThread(threading.Thread):
+	def __init__(self, params):
+		super(MainThread, self).__init__()
+		self.params = params
+
+	def run(self):
+		ServerPool._loop(*self.params)
+
+class ServerPool(object):
+
+	instance = None
+
+	def __init__(self):
+		shell.check_python()
+		self.config = shell.get_config(False)
+		self.dns_resolver = asyncdns.DNSResolver()
+		if not self.config.get('dns_ipv6', False):
+			asyncdns.IPV6_CONNECTION_SUPPORT = False
+
+		self.mgr = None #asyncmgr.ServerMgr()
+
+		self.tcp_servers_pool = {}
+		self.tcp_ipv6_servers_pool = {}
+		self.udp_servers_pool = {}
+		self.udp_ipv6_servers_pool = {}
+		self.stat_counter = {}
+
+		self.loop = eventloop.EventLoop()
+		self.thread = MainThread( (self.loop, self.dns_resolver, self.mgr) )
+		self.thread.start()
+
+	@staticmethod
+	def get_instance():
+		if ServerPool.instance is None:
+			ServerPool.instance = ServerPool()
+		return ServerPool.instance
+
+	def stop(self):
+		self.loop.stop()
+
+	@staticmethod
+	def _loop(loop, dns_resolver, mgr):
+		try:
+			if mgr is not None:
+				mgr.add_to_loop(loop)
+			dns_resolver.add_to_loop(loop)
+			loop.run()
+		except (KeyboardInterrupt, IOError, OSError) as e:
+			logging.error(e)
+			import traceback
+			traceback.print_exc()
+			os.exit(0)
+		except Exception as e:
+			logging.error(e)
+			import traceback
+			traceback.print_exc()
+
+	def server_is_run(self, port):
+		port = int(port)
+		ret = 0
+		if port in self.tcp_servers_pool:
+			ret = 1
+		if port in self.tcp_ipv6_servers_pool:
+			ret |= 2
+		return ret
+
+	def server_run_status(self, port):
+		if 'server' in self.config:
+			if port not in self.tcp_servers_pool:
+				return False
+		if 'server_ipv6' in self.config:
+			if port not in self.tcp_ipv6_servers_pool:
+				return False
+		return True
+
+	def new_server(self, port, user_config):
+		ret = True
+		port = int(port)
+		ipv6_ok = False
+
+		if 'server_ipv6' in self.config:
+			if port in self.tcp_ipv6_servers_pool:
+				logging.info("server already at %s:%d" % (self.config['server_ipv6'], port))
+				return 'this port server is already running'
+			else:
+				a_config = self.config.copy()
+				a_config.update(user_config)
+				if len(a_config['server_ipv6']) > 2 and a_config['server_ipv6'][0] == "[" and a_config['server_ipv6'][-1] == "]":
+					a_config['server_ipv6'] = a_config['server_ipv6'][1:-1]
+				a_config['server'] = a_config['server_ipv6']
+				a_config['server_port'] = port
+				a_config['max_connect'] = 128
+				a_config['method'] = common.to_str(a_config['method'])
+				try:
+					logging.info("starting server at [%s]:%d" % (common.to_str(a_config['server']), port))
+
+					tcp_server = tcprelay.TCPRelay(a_config, self.dns_resolver, False, stat_counter=self.stat_counter)
+					tcp_server.add_to_loop(self.loop)
+					self.tcp_ipv6_servers_pool.update({port: tcp_server})
+
+					udp_server = udprelay.UDPRelay(a_config, self.dns_resolver, False, stat_counter=self.stat_counter)
+					udp_server.add_to_loop(self.loop)
+					self.udp_ipv6_servers_pool.update({port: udp_server})
+
+					if common.to_str(a_config['server_ipv6']) == "::":
+						ipv6_ok = True
+				except Exception as e:
+					logging.warn("IPV6 %s " % (e,))
+
+		if 'server' in self.config:
+			if port in self.tcp_servers_pool:
+				logging.info("server already at %s:%d" % (common.to_str(self.config['server']), port))
+				return 'this port server is already running'
+			else:
+				a_config = self.config.copy()
+				a_config.update(user_config)
+				a_config['server_port'] = port
+				a_config['max_connect'] = 128
+				a_config['method'] = common.to_str(a_config['method'])
+				try:
+					logging.info("starting server at %s:%d" % (common.to_str(a_config['server']), port))
+
+					tcp_server = tcprelay.TCPRelay(a_config, self.dns_resolver, False)
+					tcp_server.add_to_loop(self.loop)
+					self.tcp_servers_pool.update({port: tcp_server})
+
+					udp_server = udprelay.UDPRelay(a_config, self.dns_resolver, False)
+					udp_server.add_to_loop(self.loop)
+					self.udp_servers_pool.update({port: udp_server})
+
+				except Exception as e:
+					if not ipv6_ok:
+						logging.warn("IPV4 %s " % (e,))
+
+		return True
+
+	def del_server(self, port):
+		port = int(port)
+		logging.info("del server at %d" % port)
+		try:
+			udpsock = socket(AF_INET, SOCK_DGRAM)
+			udpsock.sendto('%s:%s:0:0' % (get_config().MANAGE_PASS, port), (get_config().MANAGE_BIND_IP, get_config().MANAGE_PORT))
+			udpsock.close()
+		except Exception as e:
+			logging.warn(e)
+		return True
+
+	def cb_del_server(self, port):
+		port = int(port)
+
+		if port not in self.tcp_servers_pool:
+			logging.info("stopped server at %s:%d already stop" % (self.config['server'], port))
+		else:
+			logging.info("stopped server at %s:%d" % (self.config['server'], port))
+			try:
+				self.tcp_servers_pool[port].close(True)
+				del self.tcp_servers_pool[port]
+			except Exception as e:
+				logging.warn(e)
+			try:
+				self.udp_servers_pool[port].close(True)
+				del self.udp_servers_pool[port]
+			except Exception as e:
+				logging.warn(e)
+
+		if 'server_ipv6' in self.config:
+			if port not in self.tcp_ipv6_servers_pool:
+				logging.info("stopped server at [%s]:%d already stop" % (self.config['server_ipv6'], port))
+			else:
+				logging.info("stopped server at [%s]:%d" % (self.config['server_ipv6'], port))
+				try:
+					self.tcp_ipv6_servers_pool[port].close(True)
+					del self.tcp_ipv6_servers_pool[port]
+				except Exception as e:
+					logging.warn(e)
+				try:
+					self.udp_ipv6_servers_pool[port].close(True)
+					del self.udp_ipv6_servers_pool[port]
+				except Exception as e:
+					logging.warn(e)
+
+		return True
+
+	def update_mu_server(self, port, protocol_param, acl):
+		port = int(port)
+		if port in self.tcp_servers_pool:
+			try:
+				self.tcp_servers_pool[port].update_users(protocol_param, acl)
+			except Exception as e:
+				logging.warn(e)
+			try:
+				self.udp_servers_pool[port].update_users(protocol_param, acl)
+			except Exception as e:
+				logging.warn(e)
+		if port in self.tcp_ipv6_servers_pool:
+			try:
+				self.tcp_ipv6_servers_pool[port].update_users(protocol_param, acl)
+			except Exception as e:
+				logging.warn(e)
+			try:
+				self.udp_ipv6_servers_pool[port].update_users(protocol_param, acl)
+			except Exception as e:
+				logging.warn(e)
+
+	def get_server_transfer(self, port):
+		port = int(port)
+		uid = struct.pack('<I', port)
+		ret = [0, 0]
+		if port in self.tcp_servers_pool:
+			ret[0], ret[1] = self.tcp_servers_pool[port].get_ud()
+		if port in self.udp_servers_pool:
+			u, d = self.udp_servers_pool[port].get_ud()
+			ret[0] += u
+			ret[1] += d
+		if port in self.tcp_ipv6_servers_pool:
+			u, d = self.tcp_ipv6_servers_pool[port].get_ud()
+			ret[0] += u
+			ret[1] += d
+		if port in self.udp_ipv6_servers_pool:
+			u, d = self.udp_ipv6_servers_pool[port].get_ud()
+			ret[0] += u
+			ret[1] += d
+		return ret
+
+	def get_server_mu_transfer(self, server):
+		return server.get_users_ud()
+
+	def update_mu_transfer(self, user_dict, u, d):
+		for uid in u:
+			port = struct.unpack('<I', uid)[0]
+			if port not in user_dict:
+				user_dict[port] = [0, 0]
+			user_dict[port][0] += u[uid]
+		for uid in d:
+			port = struct.unpack('<I', uid)[0]
+			if port not in user_dict:
+				user_dict[port] = [0, 0]
+			user_dict[port][1] += d[uid]
+
+	def get_servers_transfer(self):
+		servers = self.tcp_servers_pool.copy()
+		servers.update(self.tcp_ipv6_servers_pool)
+		servers.update(self.udp_servers_pool)
+		servers.update(self.udp_ipv6_servers_pool)
+		ret = {}
+		for port in servers.keys():
+			ret[port] = self.get_server_transfer(port)
+		for port in self.tcp_servers_pool:
+			u, d = self.get_server_mu_transfer(self.tcp_servers_pool[port])
+			self.update_mu_transfer(ret, u, d)
+		for port in self.tcp_ipv6_servers_pool:
+			u, d = self.get_server_mu_transfer(self.tcp_ipv6_servers_pool[port])
+			self.update_mu_transfer(ret, u, d)
+		for port in self.udp_servers_pool:
+			u, d = self.get_server_mu_transfer(self.udp_servers_pool[port])
+			self.update_mu_transfer(ret, u, d)
+		for port in self.udp_ipv6_servers_pool:
+			u, d = self.get_server_mu_transfer(self.udp_ipv6_servers_pool[port])
+			self.update_mu_transfer(ret, u, d)
+		return ret
+

BIN
ssr/server_pool.pyc


+ 39 - 0
ssr/setup.py

@@ -0,0 +1,39 @@
+import codecs
+from setuptools import setup
+
+
+with codecs.open('README.rst', encoding='utf-8') as f:
+    long_description = f.read()
+
+setup(
+    name="shadowsocks",
+    version="2.6.12",
+    license='http://www.apache.org/licenses/LICENSE-2.0',
+    description="A fast tunnel proxy that help you get through firewalls",
+    author='clowwindy',
+    author_email='clowwindy42@gmail.com',
+    url='https://github.com/shadowsocks/shadowsocks',
+    packages=['shadowsocks', 'shadowsocks.crypto', 'shadowsocks.obfsplugin'],
+    package_data={
+        'shadowsocks': ['README.rst', 'LICENSE']
+    },
+    install_requires=[],
+    entry_points="""
+    [console_scripts]
+    sslocal = shadowsocks.local:main
+    ssserver = shadowsocks.server:main
+    """,
+    classifiers=[
+        'License :: OSI Approved :: Apache Software License',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: Implementation :: CPython',
+        'Programming Language :: Python :: Implementation :: PyPy',
+        'Topic :: Internet :: Proxy Servers',
+    ],
+    long_description=long_description,
+)

+ 18 - 0
ssr/shadowsocks/__init__.py

@@ -0,0 +1,18 @@
+#!/usr/bin/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

BIN
ssr/shadowsocks/__init__.pyc


+ 553 - 0
ssr/shadowsocks/asyncdns.py

@@ -0,0 +1,553 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2014-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 socket
+import struct
+import re
+import logging
+
+if __name__ == '__main__':
+    import sys
+    import inspect
+    file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe())))
+    sys.path.insert(0, os.path.join(file_path, '../'))
+
+from shadowsocks import common, lru_cache, eventloop, shell
+
+
+CACHE_SWEEP_INTERVAL = 30
+
+VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d_-]{1,63}(?<!-)$", re.IGNORECASE)
+
+common.patch_socket()
+
+# rfc1035
+# format
+# +---------------------+
+# |        Header       |
+# +---------------------+
+# |       Question      | the question for the name server
+# +---------------------+
+# |        Answer       | RRs answering the question
+# +---------------------+
+# |      Authority      | RRs pointing toward an authority
+# +---------------------+
+# |      Additional     | RRs holding additional information
+# +---------------------+
+#
+# header
+#                                 1  1  1  1  1  1
+#   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+# |                      ID                       |
+# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+# |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+# |                    QDCOUNT                    |
+# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+# |                    ANCOUNT                    |
+# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+# |                    NSCOUNT                    |
+# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+# |                    ARCOUNT                    |
+# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+QTYPE_ANY = 255
+QTYPE_A = 1
+QTYPE_AAAA = 28
+QTYPE_CNAME = 5
+QTYPE_NS = 2
+QCLASS_IN = 1
+
+def detect_ipv6_supprot():
+    if 'has_ipv6' in dir(socket):
+        try:
+            s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+            s.connect(('::1', 0))
+            print('IPv6 support')
+            return True
+        except:
+            pass
+    print('IPv6 not support')
+    return False
+
+IPV6_CONNECTION_SUPPORT = detect_ipv6_supprot()
+
+def build_address(address):
+    address = address.strip(b'.')
+    labels = address.split(b'.')
+    results = []
+    for label in labels:
+        l = len(label)
+        if l > 63:
+            return None
+        results.append(common.chr(l))
+        results.append(label)
+    results.append(b'\0')
+    return b''.join(results)
+
+
+def build_request(address, qtype):
+    request_id = os.urandom(2)
+    header = struct.pack('!BBHHHH', 1, 0, 1, 0, 0, 0)
+    addr = build_address(address)
+    qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN)
+    return request_id + header + addr + qtype_qclass
+
+
+def parse_ip(addrtype, data, length, offset):
+    if addrtype == QTYPE_A:
+        return socket.inet_ntop(socket.AF_INET, data[offset:offset + length])
+    elif addrtype == QTYPE_AAAA:
+        return socket.inet_ntop(socket.AF_INET6, data[offset:offset + length])
+    elif addrtype in [QTYPE_CNAME, QTYPE_NS]:
+        return parse_name(data, offset)[1]
+    else:
+        return data[offset:offset + length]
+
+
+def parse_name(data, offset):
+    p = offset
+    labels = []
+    l = common.ord(data[p])
+    while l > 0:
+        if (l & (128 + 64)) == (128 + 64):
+            # pointer
+            pointer = struct.unpack('!H', data[p:p + 2])[0]
+            pointer &= 0x3FFF
+            r = parse_name(data, pointer)
+            labels.append(r[1])
+            p += 2
+            # pointer is the end
+            return p - offset, b'.'.join(labels)
+        else:
+            labels.append(data[p + 1:p + 1 + l])
+            p += 1 + l
+        l = common.ord(data[p])
+    return p - offset + 1, b'.'.join(labels)
+
+
+# rfc1035
+# record
+#                                    1  1  1  1  1  1
+#      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+#    |                                               |
+#    /                                               /
+#    /                      NAME                     /
+#    |                                               |
+#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+#    |                      TYPE                     |
+#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+#    |                     CLASS                     |
+#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+#    |                      TTL                      |
+#    |                                               |
+#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+#    |                   RDLENGTH                    |
+#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+#    /                     RDATA                     /
+#    /                                               /
+#    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+def parse_record(data, offset, question=False):
+    nlen, name = parse_name(data, offset)
+    if not question:
+        record_type, record_class, record_ttl, record_rdlength = struct.unpack(
+            '!HHiH', data[offset + nlen:offset + nlen + 10]
+        )
+        ip = parse_ip(record_type, data, record_rdlength, offset + nlen + 10)
+        return nlen + 10 + record_rdlength, \
+            (name, ip, record_type, record_class, record_ttl)
+    else:
+        record_type, record_class = struct.unpack(
+            '!HH', data[offset + nlen:offset + nlen + 4]
+        )
+        return nlen + 4, (name, None, record_type, record_class, None, None)
+
+
+def parse_header(data):
+    if len(data) >= 12:
+        header = struct.unpack('!HBBHHHH', data[:12])
+        res_id = header[0]
+        res_qr = header[1] & 128
+        res_tc = header[1] & 2
+        res_ra = header[2] & 128
+        res_rcode = header[2] & 15
+        # assert res_tc == 0
+        # assert res_rcode in [0, 3]
+        res_qdcount = header[3]
+        res_ancount = header[4]
+        res_nscount = header[5]
+        res_arcount = header[6]
+        return (res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount,
+                res_ancount, res_nscount, res_arcount)
+    return None
+
+
+def parse_response(data):
+    try:
+        if len(data) >= 12:
+            header = parse_header(data)
+            if not header:
+                return None
+            res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, \
+                res_ancount, res_nscount, res_arcount = header
+
+            qds = []
+            ans = []
+            offset = 12
+            for i in range(0, res_qdcount):
+                l, r = parse_record(data, offset, True)
+                offset += l
+                if r:
+                    qds.append(r)
+            for i in range(0, res_ancount):
+                l, r = parse_record(data, offset)
+                offset += l
+                if r:
+                    ans.append(r)
+            for i in range(0, res_nscount):
+                l, r = parse_record(data, offset)
+                offset += l
+            for i in range(0, res_arcount):
+                l, r = parse_record(data, offset)
+                offset += l
+            response = DNSResponse()
+            if qds:
+                response.hostname = qds[0][0]
+            for an in qds:
+                response.questions.append((an[1], an[2], an[3]))
+            for an in ans:
+                response.answers.append((an[1], an[2], an[3]))
+            return response
+    except Exception as e:
+        shell.print_exception(e)
+        return None
+
+
+def is_valid_hostname(hostname):
+    if len(hostname) > 255:
+        return False
+    if hostname[-1] == b'.':
+        hostname = hostname[:-1]
+    return all(VALID_HOSTNAME.match(x) for x in hostname.split(b'.'))
+
+
+class DNSResponse(object):
+    def __init__(self):
+        self.hostname = None
+        self.questions = []  # each: (addr, type, class)
+        self.answers = []  # each: (addr, type, class)
+
+    def __str__(self):
+        return '%s: %s' % (self.hostname, str(self.answers))
+
+
+STATUS_IPV4 = 0
+STATUS_IPV6 = 1
+
+
+class DNSResolver(object):
+
+    def __init__(self):
+        self._loop = None
+        self._hosts = {}
+        self._hostname_status = {}
+        self._hostname_to_cb = {}
+        self._cb_to_hostname = {}
+        self._cache = lru_cache.LRUCache(timeout=300)
+        self._sock = None
+        self._servers = None
+        self._parse_resolv()
+        self._parse_hosts()
+        # TODO monitor hosts change and reload hosts
+        # TODO parse /etc/gai.conf and follow its rules
+
+    def _parse_resolv(self):
+        self._servers = []
+        try:
+            with open('dns.conf', 'rb') as f:
+                content = f.readlines()
+                for line in content:
+                    line = line.strip()
+                    if line:
+                        parts = line.split(b' ', 1)
+                        if len(parts) >= 2:
+                            server = parts[0]
+                            port = int(parts[1])
+                        else:
+                            server = parts[0]
+                            port = 53
+                        if common.is_ip(server) == socket.AF_INET:
+                            if type(server) != str:
+                                server = server.decode('utf8')
+                            self._servers.append((server, port))
+        except IOError:
+            pass
+        if not self._servers:
+            try:
+                with open('/etc/resolv.conf', 'rb') as f:
+                    content = f.readlines()
+                    for line in content:
+                        line = line.strip()
+                        if line:
+                            if line.startswith(b'nameserver'):
+                                parts = line.split()
+                                if len(parts) >= 2:
+                                    server = parts[1]
+                                    if common.is_ip(server) == socket.AF_INET:
+                                        if type(server) != str:
+                                            server = server.decode('utf8')
+                                        self._servers.append((server, 53))
+            except IOError:
+                pass
+        if not self._servers:
+            self._servers = [('8.8.4.4', 53), ('8.8.8.8', 53)]
+        logging.info('dns server: %s' % (self._servers,))
+
+    def _parse_hosts(self):
+        etc_path = '/etc/hosts'
+        if 'WINDIR' in os.environ:
+            etc_path = os.environ['WINDIR'] + '/system32/drivers/etc/hosts'
+        try:
+            with open(etc_path, 'rb') as f:
+                for line in f.readlines():
+                    line = line.strip()
+                    parts = line.split()
+                    if len(parts) >= 2:
+                        ip = parts[0]
+                        if common.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):
+        if self._loop:
+            raise Exception('already add to loop')
+        self._loop = loop
+        # TODO when dns server is IPv6
+        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
+                                   socket.SOL_UDP)
+        self._sock.setblocking(False)
+        loop.add(self._sock, eventloop.POLL_IN, self)
+        loop.add_periodic(self.handle_periodic)
+
+    def _call_callback(self, hostname, ip, error=None):
+        callbacks = self._hostname_to_cb.get(hostname, [])
+        for callback in callbacks:
+            if callback in self._cb_to_hostname:
+                del self._cb_to_hostname[callback]
+            if ip or error:
+                callback((hostname, ip), error)
+            else:
+                callback((hostname, None),
+                         Exception('unknown hostname %s' % hostname))
+        if hostname in self._hostname_to_cb:
+            del self._hostname_to_cb[hostname]
+        if hostname in self._hostname_status:
+            del self._hostname_status[hostname]
+
+    def _handle_data(self, data):
+        response = parse_response(data)
+        if response and response.hostname:
+            hostname = response.hostname
+            ip = None
+            for answer in response.answers:
+                if answer[1] in (QTYPE_A, QTYPE_AAAA) and \
+                        answer[2] == QCLASS_IN:
+                    ip = answer[0]
+                    break
+            if IPV6_CONNECTION_SUPPORT:
+                if not ip and self._hostname_status.get(hostname, STATUS_IPV4) \
+                        == STATUS_IPV6:
+                    self._hostname_status[hostname] = STATUS_IPV4
+                    self._send_req(hostname, QTYPE_A)
+                else:
+                    if ip:
+                        self._cache[hostname] = ip
+                        self._call_callback(hostname, ip)
+                    elif self._hostname_status.get(hostname, None) == STATUS_IPV4:
+                        for question in response.questions:
+                            if question[1] == QTYPE_A:
+                                self._call_callback(hostname, None)
+                                break
+            else:
+                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_IPV6:
+                        for question in response.questions:
+                            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 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 remove_callback(self, callback):
+        hostname = self._cb_to_hostname.get(callback)
+        if hostname:
+            del self._cb_to_hostname[callback]
+            arr = self._hostname_to_cb.get(hostname, None)
+            if arr:
+                arr.remove(callback)
+                if not arr:
+                    del self._hostname_to_cb[hostname]
+                    if hostname in self._hostname_status:
+                        del self._hostname_status[hostname]
+
+    def _send_req(self, hostname, qtype):
+        req = build_request(hostname, qtype)
+        for server in self._servers:
+            logging.debug('resolving %s with type %d using server %s',
+                          hostname, qtype, server)
+            self._sock.sendto(req, server)
+
+    def resolve(self, hostname, callback):
+        if type(hostname) != bytes:
+            hostname = hostname.encode('utf8')
+        if not hostname:
+            callback(None, Exception('empty hostname'))
+        elif common.is_ip(hostname):
+            callback((hostname, hostname), None)
+        elif hostname in self._hosts:
+            logging.debug('hit hosts: %s', hostname)
+            ip = self._hosts[hostname]
+            callback((hostname, ip), None)
+        elif hostname in self._cache:
+            logging.debug('hit cache: %s', hostname)
+            ip = self._cache[hostname]
+            callback((hostname, ip), None)
+        else:
+            if not is_valid_hostname(hostname):
+                callback(None, Exception('invalid hostname: %s' % hostname))
+                return
+            if False:
+                addrs = socket.getaddrinfo(hostname, 0, 0,
+                                       socket.SOCK_DGRAM, socket.SOL_UDP)
+                if addrs:
+                    af, socktype, proto, canonname, sa = addrs[0]
+                    logging.debug('DNS resolve %s %s' % (hostname, sa[0]) )
+                    self._cache[hostname] = sa[0]
+                    callback((hostname, sa[0]), None)
+                    return
+            arr = self._hostname_to_cb.get(hostname, None)
+            if not arr:
+                if IPV6_CONNECTION_SUPPORT:
+                    self._hostname_status[hostname] = STATUS_IPV6
+                    self._send_req(hostname, QTYPE_AAAA)
+                else:
+                    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
+                if IPV6_CONNECTION_SUPPORT:
+                    self._send_req(hostname, QTYPE_AAAA)
+                else:
+                    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
+
+
+def test():
+    dns_resolver = DNSResolver()
+    loop = eventloop.EventLoop()
+    dns_resolver.add_to_loop(loop)
+
+    global counter
+    counter = 0
+
+    def make_callback():
+        global counter
+
+        def callback(result, error):
+            global counter
+            # TODO: what can we assert?
+            print(result, error)
+            counter += 1
+            if counter == 9:
+                dns_resolver.close()
+                loop.stop()
+        a_callback = callback
+        return a_callback
+
+    assert(make_callback() != make_callback())
+
+    dns_resolver.resolve(b'google.com', make_callback())
+    dns_resolver.resolve('google.com', make_callback())
+    dns_resolver.resolve('example.com', make_callback())
+    dns_resolver.resolve('ipv6.google.com', make_callback())
+    dns_resolver.resolve('www.facebook.com', make_callback())
+    dns_resolver.resolve('ns2.google.com', make_callback())
+    dns_resolver.resolve('invalid.@!#$%^&$@.hostname', make_callback())
+    dns_resolver.resolve('toooooooooooooooooooooooooooooooooooooooooooooooooo'
+                         'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
+                         'long.hostname', make_callback())
+    dns_resolver.resolve('toooooooooooooooooooooooooooooooooooooooooooooooooo'
+                         'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
+                         'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
+                         'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
+                         'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
+                         'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
+                         'long.hostname', make_callback())
+
+    loop.run()
+
+
+if __name__ == '__main__':
+    test()
+

BIN
ssr/shadowsocks/asyncdns.pyc


+ 376 - 0
ssr/shadowsocks/common.py

@@ -0,0 +1,376 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2013-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 socket
+import struct
+import logging
+import binascii
+import re
+
+def compat_ord(s):
+    if type(s) == int:
+        return s
+    return _ord(s)
+
+
+def compat_chr(d):
+    if bytes == str:
+        return _chr(d)
+    return bytes([d])
+
+
+_ord = ord
+_chr = chr
+ord = compat_ord
+chr = compat_chr
+
+connect_log = logging.debug
+
+def to_bytes(s):
+    if bytes != str:
+        if type(s) == str:
+            return s.encode('utf-8')
+    return s
+
+
+def to_str(s):
+    if bytes != str:
+        if type(s) == bytes:
+            return s.decode('utf-8')
+    return s
+
+def int32(x):
+    if x > 0xFFFFFFFF or x < 0:
+        x &= 0xFFFFFFFF
+    if x > 0x7FFFFFFF:
+        x = int(0x100000000 - x)
+        if x < 0x80000000:
+            return -x
+        else:
+            return -2147483648
+    return x
+
+def inet_ntop(family, ipstr):
+    if family == socket.AF_INET:
+        return to_bytes(socket.inet_ntoa(ipstr))
+    elif family == socket.AF_INET6:
+        import re
+        v6addr = ':'.join(('%02X%02X' % (ord(i), ord(j))).lstrip('0')
+                          for i, j in zip(ipstr[::2], ipstr[1::2]))
+        v6addr = re.sub('::+', '::', v6addr, count=1)
+        return to_bytes(v6addr)
+
+
+def inet_pton(family, addr):
+    addr = to_str(addr)
+    if family == socket.AF_INET:
+        return socket.inet_aton(addr)
+    elif family == socket.AF_INET6:
+        if '.' in addr:  # a v4 addr
+            v4addr = addr[addr.rindex(':') + 1:]
+            v4addr = socket.inet_aton(v4addr)
+            v4addr = ['%02X' % ord(x) for x in v4addr]
+            v4addr.insert(2, ':')
+            newaddr = addr[:addr.rindex(':') + 1] + ''.join(v4addr)
+            return inet_pton(family, newaddr)
+        dbyts = [0] * 8  # 8 groups
+        grps = addr.split(':')
+        for i, v in enumerate(grps):
+            if v:
+                dbyts[i] = int(v, 16)
+            else:
+                for j, w in enumerate(grps[::-1]):
+                    if w:
+                        dbyts[7 - j] = int(w, 16)
+                    else:
+                        break
+                break
+        return b''.join((chr(i // 256) + chr(i % 256)) for i in dbyts)
+    else:
+        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 match_regex(regex, text):
+    regex = re.compile(regex)
+    for item in regex.findall(text):
+        return True
+    return False
+
+
+def patch_socket():
+    if not hasattr(socket, 'inet_pton'):
+        socket.inet_pton = inet_pton
+
+    if not hasattr(socket, 'inet_ntop'):
+        socket.inet_ntop = inet_ntop
+
+
+patch_socket()
+
+
+ADDRTYPE_IPV4 = 1
+ADDRTYPE_IPV6 = 4
+ADDRTYPE_HOST = 3
+
+
+def pack_addr(address):
+    address_str = to_str(address)
+    for family in (socket.AF_INET, socket.AF_INET6):
+        try:
+            r = socket.inet_pton(family, address_str)
+            if family == socket.AF_INET6:
+                return b'\x04' + r
+            else:
+                return b'\x01' + r
+        except (TypeError, ValueError, OSError, IOError):
+            pass
+    if len(address) > 255:
+        address = address[:255]  # TODO
+    return b'\x03' + chr(len(address)) + address
+
+def pre_parse_header(data):
+    if not data:
+        return None
+    datatype = ord(data[0])
+    if datatype == 0x80:
+        if len(data) <= 2:
+            return None
+        rand_data_size = ord(data[1])
+        if rand_data_size + 2 >= len(data):
+            logging.warn('header too short, maybe wrong password or '
+                         'encryption method')
+            return None
+        data = data[rand_data_size + 2:]
+    elif datatype == 0x81:
+        data = data[1:]
+    elif datatype == 0x82:
+        if len(data) <= 3:
+            return None
+        rand_data_size = struct.unpack('>H', data[1:3])[0]
+        if rand_data_size + 3 >= len(data):
+            logging.warn('header too short, maybe wrong password or '
+                         'encryption method')
+            return None
+        data = data[rand_data_size + 3:]
+    elif datatype == 0x88 or (~datatype & 0xff) == 0x88:
+        if len(data) <= 7 + 7:
+            return None
+        data_size = struct.unpack('>H', data[1:3])[0]
+        ogn_data = data
+        data = data[:data_size]
+        crc = binascii.crc32(data) & 0xffffffff
+        if crc != 0xffffffff:
+            logging.warn('uncorrect CRC32, maybe wrong password or '
+                         'encryption method')
+            return None
+        start_pos = 3 + ord(data[3])
+        data = data[start_pos:-4]
+        if data_size < len(ogn_data):
+            data += ogn_data[data_size:]
+    return data
+
+def parse_header(data):
+    addrtype = ord(data[0])
+    dest_addr = None
+    dest_port = None
+    header_length = 0
+    connecttype = (addrtype & 0x8) and 1 or 0
+    addrtype &= ~0x8
+    if addrtype == ADDRTYPE_IPV4:
+        if len(data) >= 7:
+            dest_addr = socket.inet_ntoa(data[1:5])
+            dest_port = struct.unpack('>H', data[5:7])[0]
+            header_length = 7
+        else:
+            logging.warn('header is too short')
+    elif addrtype == ADDRTYPE_HOST:
+        if len(data) > 2:
+            addrlen = ord(data[1])
+            if len(data) >= 4 + addrlen:
+                dest_addr = data[2:2 + addrlen]
+                dest_port = struct.unpack('>H', data[2 + addrlen:4 +
+                                                     addrlen])[0]
+                header_length = 4 + addrlen
+            else:
+                logging.warn('header is too short')
+        else:
+            logging.warn('header is too short')
+    elif addrtype == ADDRTYPE_IPV6:
+        if len(data) >= 19:
+            dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17])
+            dest_port = struct.unpack('>H', data[17:19])[0]
+            header_length = 19
+        else:
+            logging.warn('header is too short')
+    else:
+        logging.warn('unsupported addrtype %d, maybe wrong password or '
+                     'encryption method' % addrtype)
+    if dest_addr is None:
+        return None
+    return connecttype, 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.addrs_str = 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 __cmp__(self, other):
+        return cmp(self.addrs_str, other.addrs_str)
+
+class PortRange(object):
+    def __init__(self, range_str):
+        self.range_str = to_str(range_str)
+        self.range = set()
+        range_str = to_str(range_str).split(',')
+        for item in range_str:
+            try:
+                int_range = item.split('-')
+                if len(int_range) == 1:
+                    if item:
+                        self.range.add(int(item))
+                elif len(int_range) == 2:
+                    int_range[0] = int(int_range[0])
+                    int_range[1] = int(int_range[1])
+                    if int_range[0] < 0:
+                        int_range[0] = 0
+                    if int_range[1] > 65535:
+                        int_range[1] = 65535
+                    i = int_range[0]
+                    while i <= int_range[1]:
+                        self.range.add(i)
+                        i += 1
+            except Exception as e:
+                logging.error(e)
+
+    def __contains__(self, val):
+        return val in self.range
+
+    def __cmp__(self, other):
+        return cmp(self.range_str, other.range_str)
+
+def test_inet_conv():
+    ipv4 = b'8.8.4.4'
+    b = inet_pton(socket.AF_INET, ipv4)
+    assert inet_ntop(socket.AF_INET, b) == ipv4
+    ipv6 = b'2404:6800:4005:805::1011'
+    b = inet_pton(socket.AF_INET6, ipv6)
+    assert inet_ntop(socket.AF_INET6, b) == ipv6
+
+
+def test_parse_header():
+    assert parse_header(b'\x03\x0ewww.google.com\x00\x50') == \
+        (0, b'www.google.com', 80, 18)
+    assert parse_header(b'\x01\x08\x08\x08\x08\x00\x35') == \
+        (0, b'8.8.8.8', 53, 7)
+    assert parse_header((b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00'
+                         b'\x00\x10\x11\x00\x50')) == \
+        (0, b'2404:6800:4005:805::1011', 80, 19)
+
+
+def test_pack_header():
+    assert pack_addr(b'8.8.8.8') == b'\x01\x08\x08\x08\x08'
+    assert pack_addr(b'2404:6800:4005:805::1011') == \
+        b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00\x00\x10\x11'
+    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()

BIN
ssr/shadowsocks/common.pyc


+ 18 - 0
ssr/shadowsocks/crypto/__init__.py

@@ -0,0 +1,18 @@
+#!/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

BIN
ssr/shadowsocks/crypto/__init__.pyc


+ 135 - 0
ssr/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()

+ 188 - 0
ssr/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()

+ 202 - 0
ssr/shadowsocks/crypto/openssl.py

@@ -0,0 +1,202 @@
+#!/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
+
+__all__ = ['ciphers']
+
+libcrypto = None
+loaded = False
+
+buf_size = 2048
+
+
+def load_openssl():
+    global loaded, libcrypto, buf
+
+    libcrypto = util.find_library(('crypto', 'eay32'),
+                                  'EVP_get_cipherbyname',
+                                  'libcrypto')
+    if libcrypto is None:
+        raise Exception('libcrypto(OpenSSL) not found')
+
+    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)
+
+    if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"):
+        libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
+    else:
+        libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,)
+    libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,)
+
+    libcrypto.RAND_bytes.restype = c_int
+    libcrypto.RAND_bytes.argtypes = (c_void_p, c_int)
+
+    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 = 'EVP_' + cipher_name.replace('-', '_')
+    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
+
+def rand_bytes(length):
+    if not loaded:
+        load_openssl()
+    buf = create_string_buffer(length)
+    r = libcrypto.RAND_bytes(buf, length)
+    if r <= 0:
+        raise Exception('RAND_bytes return error')
+    return buf.raw
+
+class OpenSSLCrypto(object):
+    def __init__(self, cipher_name, key, iv, op):
+        self._ctx = None
+        if not loaded:
+            load_openssl()
+        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)
+        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:
+            if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"):
+                libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx)
+            else:
+                libcrypto.EVP_CIPHER_CTX_reset(self._ctx)
+            libcrypto.EVP_CIPHER_CTX_free(self._ctx)
+
+
+ciphers = {
+    'aes-128-cbc': (16, 16, OpenSSLCrypto),
+    'aes-192-cbc': (24, 16, OpenSSLCrypto),
+    'aes-256-cbc': (32, 16, OpenSSLCrypto),
+    'aes-128-cfb': (16, 16, OpenSSLCrypto),
+    'aes-192-cfb': (24, 16, OpenSSLCrypto),
+    'aes-256-cfb': (32, 16, OpenSSLCrypto),
+    'aes-128-ofb': (16, 16, OpenSSLCrypto),
+    'aes-192-ofb': (24, 16, OpenSSLCrypto),
+    'aes-256-ofb': (32, 16, OpenSSLCrypto),
+    'aes-128-ctr': (16, 16, OpenSSLCrypto),
+    'aes-192-ctr': (24, 16, OpenSSLCrypto),
+    'aes-256-ctr': (32, 16, OpenSSLCrypto),
+    'aes-128-cfb8': (16, 16, OpenSSLCrypto),
+    'aes-192-cfb8': (24, 16, OpenSSLCrypto),
+    'aes-256-cfb8': (32, 16, OpenSSLCrypto),
+    'aes-128-cfb1': (16, 16, OpenSSLCrypto),
+    'aes-192-cfb1': (24, 16, OpenSSLCrypto),
+    'aes-256-cfb1': (32, 16, OpenSSLCrypto),
+    'bf-cfb': (16, 8, OpenSSLCrypto),
+    'camellia-128-cfb': (16, 16, OpenSSLCrypto),
+    'camellia-192-cfb': (24, 16, OpenSSLCrypto),
+    'camellia-256-cfb': (32, 16, OpenSSLCrypto),
+    'cast5-cfb': (16, 8, OpenSSLCrypto),
+    'des-cfb': (8, 8, OpenSSLCrypto),
+    'idea-cfb': (16, 8, OpenSSLCrypto),
+    'rc2-cfb': (16, 8, OpenSSLCrypto),
+    'rc4': (16, 0, OpenSSLCrypto),
+    'seed-cfb': (16, 16, OpenSSLCrypto),
+}
+
+
+def run_method(method):
+
+    cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1)
+    decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0)
+
+    util.run_cipher(cipher, decipher)
+
+
+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()

BIN
ssr/shadowsocks/crypto/openssl.pyc


+ 52 - 0
ssr/shadowsocks/crypto/rc4_md5.py

@@ -0,0 +1,52 @@
+#!/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
+
+import hashlib
+
+from shadowsocks.crypto import openssl
+
+__all__ = ['ciphers']
+
+
+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.OpenSSLCrypto(b'rc4', rc4_key, b'', op)
+
+
+ciphers = {
+    'rc4-md5': (16, 16, create_cipher),
+    'rc4-md5-6': (16, 6, 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)
+
+    util.run_cipher(cipher, decipher)
+
+
+if __name__ == '__main__':
+    test()

BIN
ssr/shadowsocks/crypto/rc4_md5.pyc


+ 140 - 0
ssr/shadowsocks/crypto/sodium.py

@@ -0,0 +1,140 @@
+#!/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_ulong, c_ulonglong, byref, \
+    create_string_buffer, c_void_p
+
+from shadowsocks.crypto import util
+
+__all__ = ['ciphers']
+
+libsodium = None
+loaded = False
+
+buf_size = 2048
+
+# for salsa20 and chacha20 and chacha20-ietf
+BLOCK_SIZE = 64
+
+
+def load_libsodium():
+    global loaded, libsodium, buf
+
+    libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic',
+                                  'libsodium')
+    if libsodium is None:
+        raise Exception('libsodium not found')
+
+    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)
+
+    try:
+        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_ulong,
+                                                        c_char_p)
+    except:
+        pass
+
+    buf = create_string_buffer(buf_size)
+    loaded = True
+
+
+class SodiumCrypto(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 == 'salsa20':
+            self.cipher = libsodium.crypto_stream_salsa20_xor_ic
+        elif cipher_name == 'chacha20':
+            self.cipher = libsodium.crypto_stream_chacha20_xor_ic
+        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 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 = {
+    'salsa20': (32, 8, SodiumCrypto),
+    'chacha20': (32, 8, SodiumCrypto),
+    'chacha20-ietf': (32, 12, SodiumCrypto),
+}
+
+
+def 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():
+
+    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_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)
+
+if __name__ == '__main__':
+    test_chacha20_ietf()
+    test_chacha20()
+    test_salsa20()

BIN
ssr/shadowsocks/crypto/sodium.pyc


+ 181 - 0
ssr/shadowsocks/crypto/table.py

@@ -0,0 +1,181 @@
+# !/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
+
+import string
+import struct
+import hashlib
+
+
+__all__ = ['ciphers']
+
+cached_tables = {}
+
+if hasattr(string, 'maketrans'):
+    maketrans = string.maketrans
+    translate = string.translate
+else:
+    maketrans = bytes.maketrans
+    translate = bytes.translate
+
+
+def get_table(key):
+    m = hashlib.md5()
+    m.update(key)
+    s = m.digest()
+    a, b = struct.unpack('<QQ', s)
+    table = maketrans(b'', b'')
+    table = [table[i: i + 1] for i in range(len(table))]
+    for i in range(1, 1024):
+        table.sort(key=lambda x: int(a % (ord(x) + i)))
+    return table
+
+
+def init_table(key):
+    if key not in cached_tables:
+        encrypt_table = b''.join(get_table(key))
+        decrypt_table = maketrans(encrypt_table, maketrans(b'', b''))
+        cached_tables[key] = [encrypt_table, decrypt_table]
+    return cached_tables[key]
+
+
+class TableCipher(object):
+    def __init__(self, cipher_name, key, iv, op):
+        self._encrypt_table, self._decrypt_table = init_table(key)
+        self._op = op
+
+    def update(self, data):
+        if self._op:
+            return translate(data, self._encrypt_table)
+        else:
+            return translate(data, self._decrypt_table)
+
+class NoneCipher(object):
+    def __init__(self, cipher_name, key, iv, op):
+        pass
+
+    def update(self, data):
+        return data
+
+ciphers = {
+    'none': (16, 0, NoneCipher),
+    'table': (0, 0, TableCipher)
+}
+
+
+def test_table_result():
+    from shadowsocks.common import ord
+    target1 = [
+        [60, 53, 84, 138, 217, 94, 88, 23, 39, 242, 219, 35, 12, 157, 165, 181,
+         255, 143, 83, 247, 162, 16, 31, 209, 190, 171, 115, 65, 38, 41, 21,
+         245, 236, 46, 121, 62, 166, 233, 44, 154, 153, 145, 230, 49, 128, 216,
+         173, 29, 241, 119, 64, 229, 194, 103, 131, 110, 26, 197, 218, 59, 204,
+         56, 27, 34, 141, 221, 149, 239, 192, 195, 24, 155, 170, 183, 11, 254,
+         213, 37, 137, 226, 75, 203, 55, 19, 72, 248, 22, 129, 33, 175, 178,
+         10, 198, 71, 77, 36, 113, 167, 48, 2, 117, 140, 142, 66, 199, 232,
+         243, 32, 123, 54, 51, 82, 57, 177, 87, 251, 150, 196, 133, 5, 253,
+         130, 8, 184, 14, 152, 231, 3, 186, 159, 76, 89, 228, 205, 156, 96,
+         163, 146, 18, 91, 132, 85, 80, 109, 172, 176, 105, 13, 50, 235, 127,
+         0, 189, 95, 98, 136, 250, 200, 108, 179, 211, 214, 106, 168, 78, 79,
+         74, 210, 30, 73, 201, 151, 208, 114, 101, 174, 92, 52, 120, 240, 15,
+         169, 220, 182, 81, 224, 43, 185, 40, 99, 180, 17, 212, 158, 42, 90, 9,
+         191, 45, 6, 25, 4, 222, 67, 126, 1, 116, 124, 206, 69, 61, 7, 68, 97,
+         202, 63, 244, 20, 28, 58, 93, 134, 104, 144, 227, 147, 102, 118, 135,
+         148, 47, 238, 86, 112, 122, 70, 107, 215, 100, 139, 223, 225, 164,
+         237, 111, 125, 207, 160, 187, 246, 234, 161, 188, 193, 249, 252],
+        [151, 205, 99, 127, 201, 119, 199, 211, 122, 196, 91, 74, 12, 147, 124,
+         180, 21, 191, 138, 83, 217, 30, 86, 7, 70, 200, 56, 62, 218, 47, 168,
+         22, 107, 88, 63, 11, 95, 77, 28, 8, 188, 29, 194, 186, 38, 198, 33,
+         230, 98, 43, 148, 110, 177, 1, 109, 82, 61, 112, 219, 59, 0, 210, 35,
+         215, 50, 27, 103, 203, 212, 209, 235, 93, 84, 169, 166, 80, 130, 94,
+         164, 165, 142, 184, 111, 18, 2, 141, 232, 114, 6, 131, 195, 139, 176,
+         220, 5, 153, 135, 213, 154, 189, 238, 174, 226, 53, 222, 146, 162,
+         236, 158, 143, 55, 244, 233, 96, 173, 26, 206, 100, 227, 49, 178, 34,
+         234, 108, 207, 245, 204, 150, 44, 87, 121, 54, 140, 118, 221, 228,
+         155, 78, 3, 239, 101, 64, 102, 17, 223, 41, 137, 225, 229, 66, 116,
+         171, 125, 40, 39, 71, 134, 13, 193, 129, 247, 251, 20, 136, 242, 14,
+         36, 97, 163, 181, 72, 25, 144, 46, 175, 89, 145, 113, 90, 159, 190,
+         15, 183, 73, 123, 187, 128, 248, 252, 152, 24, 197, 68, 253, 52, 69,
+         117, 57, 92, 104, 157, 170, 214, 81, 60, 133, 208, 246, 172, 23, 167,
+         160, 192, 76, 161, 237, 45, 4, 58, 10, 182, 65, 202, 240, 185, 241,
+         79, 224, 132, 51, 42, 126, 105, 37, 250, 149, 32, 243, 231, 67, 179,
+         48, 9, 106, 216, 31, 249, 19, 85, 254, 156, 115, 255, 120, 75, 16]]
+
+    target2 = [
+        [124, 30, 170, 247, 27, 127, 224, 59, 13, 22, 196, 76, 72, 154, 32,
+         209, 4, 2, 131, 62, 101, 51, 230, 9, 166, 11, 99, 80, 208, 112, 36,
+         248, 81, 102, 130, 88, 218, 38, 168, 15, 241, 228, 167, 117, 158, 41,
+         10, 180, 194, 50, 204, 243, 246, 251, 29, 198, 219, 210, 195, 21, 54,
+         91, 203, 221, 70, 57, 183, 17, 147, 49, 133, 65, 77, 55, 202, 122,
+         162, 169, 188, 200, 190, 125, 63, 244, 96, 31, 107, 106, 74, 143, 116,
+         148, 78, 46, 1, 137, 150, 110, 181, 56, 95, 139, 58, 3, 231, 66, 165,
+         142, 242, 43, 192, 157, 89, 175, 109, 220, 128, 0, 178, 42, 255, 20,
+         214, 185, 83, 160, 253, 7, 23, 92, 111, 153, 26, 226, 33, 176, 144,
+         18, 216, 212, 28, 151, 71, 206, 222, 182, 8, 174, 205, 201, 152, 240,
+         155, 108, 223, 104, 239, 98, 164, 211, 184, 34, 193, 14, 114, 187, 40,
+         254, 12, 67, 93, 217, 6, 94, 16, 19, 82, 86, 245, 24, 197, 134, 132,
+         138, 229, 121, 5, 235, 238, 85, 47, 103, 113, 179, 69, 250, 45, 135,
+         156, 25, 61, 75, 44, 146, 189, 84, 207, 172, 119, 53, 123, 186, 120,
+         171, 68, 227, 145, 136, 100, 90, 48, 79, 159, 149, 39, 213, 236, 126,
+         52, 60, 225, 199, 105, 73, 233, 252, 118, 215, 35, 115, 64, 37, 97,
+         129, 161, 177, 87, 237, 141, 173, 191, 163, 140, 234, 232, 249],
+        [117, 94, 17, 103, 16, 186, 172, 127, 146, 23, 46, 25, 168, 8, 163, 39,
+         174, 67, 137, 175, 121, 59, 9, 128, 179, 199, 132, 4, 140, 54, 1, 85,
+         14, 134, 161, 238, 30, 241, 37, 224, 166, 45, 119, 109, 202, 196, 93,
+         190, 220, 69, 49, 21, 228, 209, 60, 73, 99, 65, 102, 7, 229, 200, 19,
+         82, 240, 71, 105, 169, 214, 194, 64, 142, 12, 233, 88, 201, 11, 72,
+         92, 221, 27, 32, 176, 124, 205, 189, 177, 246, 35, 112, 219, 61, 129,
+         170, 173, 100, 84, 242, 157, 26, 218, 20, 33, 191, 155, 232, 87, 86,
+         153, 114, 97, 130, 29, 192, 164, 239, 90, 43, 236, 208, 212, 185, 75,
+         210, 0, 81, 227, 5, 116, 243, 34, 18, 182, 70, 181, 197, 217, 95, 183,
+         101, 252, 248, 107, 89, 136, 216, 203, 68, 91, 223, 96, 141, 150, 131,
+         13, 152, 198, 111, 44, 222, 125, 244, 76, 251, 158, 106, 24, 42, 38,
+         77, 2, 213, 207, 249, 147, 113, 135, 245, 118, 193, 47, 98, 145, 66,
+         160, 123, 211, 165, 78, 204, 80, 250, 110, 162, 48, 58, 10, 180, 55,
+         231, 79, 149, 74, 62, 50, 148, 143, 206, 28, 15, 57, 159, 139, 225,
+         122, 237, 138, 171, 36, 56, 115, 63, 144, 154, 6, 230, 133, 215, 41,
+         184, 22, 104, 254, 234, 253, 187, 226, 247, 188, 156, 151, 40, 108,
+         51, 83, 178, 52, 3, 31, 255, 195, 53, 235, 126, 167, 120]]
+
+    encrypt_table = b''.join(get_table(b'foobar!'))
+    decrypt_table = maketrans(encrypt_table, maketrans(b'', b''))
+
+    for i in range(0, 256):
+        assert (target1[0][i] == ord(encrypt_table[i]))
+        assert (target1[1][i] == ord(decrypt_table[i]))
+
+    encrypt_table = b''.join(get_table(b'barfoo!'))
+    decrypt_table = maketrans(encrypt_table, maketrans(b'', b''))
+
+    for i in range(0, 256):
+        assert (target2[0][i] == ord(encrypt_table[i]))
+        assert (target2[1][i] == ord(decrypt_table[i]))
+
+
+def test_encryption():
+    from shadowsocks.crypto import util
+
+    cipher = TableCipher('table', b'test', b'', 1)
+    decipher = TableCipher('table', b'test', b'', 0)
+
+    util.run_cipher(cipher, decipher)
+
+
+if __name__ == '__main__':
+    test_table_result()
+    test_encryption()

BIN
ssr/shadowsocks/crypto/table.pyc


+ 139 - 0
ssr/shadowsocks/crypto/util.py

@@ -0,0 +1,139 @@
+#!/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
+
+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
+    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 = fname + ".dll"
+        if os.path.isfile(fname):
+            results.append(fname)
+    return results
+
+
+def find_library(possible_lib_names, search_symbol, library_name):
+    import ctypes.util
+    from ctypes import CDLL
+
+    paths = []
+
+    if type(possible_lib_names) not in (list, tuple):
+        possible_lib_names = [possible_lib_names]
+
+    lib_names = []
+    for lib_name in possible_lib_names:
+        lib_names.append(lib_name)
+        lib_names.append('lib' + lib_name)
+
+    for name in lib_names:
+        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:
+        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:
+            if path == paths[-1]:
+                raise
+    return None
+
+
+def run_cipher(cipher, decipher):
+    from os import urandom
+    import random
+    import time
+
+    BLOCK_SIZE = 16384
+    rounds = 1 * 1024
+    plain = urandom(BLOCK_SIZE * rounds)
+
+    results = []
+    pos = 0
+    print('test start')
+    start = time.time()
+    while pos < len(plain):
+        l = random.randint(100, 32768)
+        c = cipher.update(plain[pos:pos + l])
+        results.append(c)
+        pos += l
+    pos = 0
+    c = 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(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()

BIN
ssr/shadowsocks/crypto/util.pyc


+ 208 - 0
ssr/shadowsocks/daemon.py

@@ -0,0 +1,208 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2014-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 logging
+import signal
+import time
+from shadowsocks import common, shell
+
+# this module is ported from ShadowVPN daemon.c
+
+
+def daemon_exec(config):
+    if 'daemon' in config:
+        if os.name != 'posix':
+            raise Exception('daemon mode is only supported on Unix')
+        command = config['daemon']
+        if not command:
+            command = 'start'
+        pid_file = config['pid-file']
+        log_file = config['log-file']
+        if command == 'start':
+            daemon_start(pid_file, log_file)
+        elif command == 'stop':
+            daemon_stop(pid_file)
+            # always exit after daemon_stop
+            sys.exit(0)
+        elif command == 'restart':
+            daemon_stop(pid_file)
+            daemon_start(pid_file, log_file)
+        else:
+            raise Exception('unsupported daemon command %s' % command)
+
+
+def write_pid_file(pid_file, pid):
+    import fcntl
+    import stat
+
+    try:
+        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)
+        return -1
+    flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+    assert flags != -1
+    flags |= fcntl.FD_CLOEXEC
+    r = fcntl.fcntl(fd, fcntl.F_SETFD, flags)
+    assert r != -1
+    # There is no platform independent way to implement fcntl(fd, F_SETLK, &fl)
+    # via fcntl.fcntl. So use lockf instead
+    try:
+        fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB, 0, 0, os.SEEK_SET)
+    except IOError:
+        r = os.read(fd, 32)
+        if r:
+            logging.error('already started at pid %s' % common.to_str(r))
+        else:
+            logging.error('already started')
+        os.close(fd)
+        return -1
+    os.ftruncate(fd, 0)
+    os.write(fd, common.to_bytes(str(pid)))
+    return 0
+
+
+def freopen(f, mode, stream):
+    oldf = open(f, mode)
+    oldfd = oldf.fileno()
+    newfd = stream.fileno()
+    os.close(newfd)
+    os.dup2(oldfd, newfd)
+
+
+def daemon_start(pid_file, log_file):
+
+    def handle_exit(signum, _):
+        if signum == signal.SIGTERM:
+            sys.exit(0)
+        sys.exit(1)
+
+    signal.signal(signal.SIGINT, handle_exit)
+    signal.signal(signal.SIGTERM, handle_exit)
+
+    # fork only once because we are sure parent will exit
+    pid = os.fork()
+    assert pid != -1
+
+    if pid > 0:
+        # parent waits for its child
+        time.sleep(5)
+        sys.exit(0)
+
+    # child signals its parent to exit
+    ppid = os.getppid()
+    pid = os.getpid()
+    if write_pid_file(pid_file, pid) != 0:
+        os.kill(ppid, signal.SIGINT)
+        sys.exit(1)
+
+    os.setsid()
+    signal.signal(signal.SIG_IGN, signal.SIGHUP)
+
+    print('started')
+    os.kill(ppid, signal.SIGTERM)
+
+    sys.stdin.close()
+    try:
+        freopen(log_file, 'a', sys.stdout)
+        freopen(log_file, 'a', sys.stderr)
+    except IOError as e:
+        shell.print_exception(e)
+        sys.exit(1)
+
+
+def daemon_stop(pid_file):
+    import errno
+    try:
+        with open(pid_file) as f:
+            buf = f.read()
+            pid = common.to_str(buf)
+            if not buf:
+                logging.error('not running')
+    except IOError as e:
+        shell.print_exception(e)
+        if e.errno == errno.ENOENT:
+            # always exit 0 if we are sure daemon is not running
+            logging.error('not running')
+            return
+        sys.exit(1)
+    pid = int(pid)
+    if pid > 0:
+        try:
+            os.kill(pid, signal.SIGTERM)
+        except OSError as e:
+            if e.errno == errno.ESRCH:
+                logging.error('not running')
+                # always exit 0 if we are sure daemon is not running
+                return
+            shell.print_exception(e)
+            sys.exit(1)
+    else:
+        logging.error('pid is not positive: %d', pid)
+
+    # sleep for maximum 10s
+    for i in range(0, 200):
+        try:
+            # query for the pid
+            os.kill(pid, 0)
+        except OSError as e:
+            if e.errno == errno.ESRCH:
+                break
+        time.sleep(0.05)
+    else:
+        logging.error('timed out when stopping pid %d', pid)
+        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)

+ 236 - 0
ssr/shadowsocks/encrypt.py

@@ -0,0 +1,236 @@
+#!/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, sodium, table
+
+
+method_supported = {}
+method_supported.update(rc4_md5.ciphers)
+method_supported.update(openssl.ciphers)
+method_supported.update(sodium.ciphers)
+method_supported.update(table.ciphers)
+
+
+def random_string(length):
+    try:
+        return os.urandom(length)
+    except NotImplementedError as e:
+        return openssl.rand_bytes(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, iv = None):
+        self.key = key
+        self.method = method
+        self.iv = None
+        self.iv_sent = False
+        self.cipher_iv = b''
+        self.iv_buf = b''
+        self.cipher_key = b''
+        self.decipher = None
+        method = method.lower()
+        self._method_info = self.get_method_info(method)
+        if self._method_info:
+            if iv is None or len(iv) != self._method_info[1]:
+                self.cipher = self.get_cipher(key, method, 1,
+                                          random_string(self._method_info[1]))
+            else:
+                self.cipher = self.get_cipher(key, method, 1, iv)
+        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):
+        password = common.to_bytes(password)
+        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]]
+        self.cipher_key = key
+        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 not None: #optimize
+            return self.decipher.update(buf)
+
+        decipher_iv_len = self._method_info[1]
+        if len(self.iv_buf) <= decipher_iv_len:
+            self.iv_buf += buf
+        if len(self.iv_buf) > decipher_iv_len:
+            decipher_iv = self.iv_buf[:decipher_iv_len]
+            self.decipher = self.get_cipher(self.key, self.method, 0,
+                                            iv=decipher_iv)
+            buf = self.iv_buf[decipher_iv_len:]
+            del self.iv_buf
+            return self.decipher.update(buf)
+        else:
+            return b''
+
+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)
+
+def encrypt_key(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
+    return key
+
+def encrypt_iv_len(method):
+    method = method.lower()
+    (key_len, iv_len, m) = method_supported[method]
+    return iv_len
+
+def encrypt_new_iv(method):
+    method = method.lower()
+    (key_len, iv_len, m) = method_supported[method]
+    return random_string(iv_len)
+
+def encrypt_all_iv(key, method, op, data, ref_iv):
+    result = []
+    method = method.lower()
+    (key_len, iv_len, m) = method_supported[method]
+    if op:
+        iv = ref_iv[0]
+        result.append(iv)
+    else:
+        iv = data[:iv_len]
+        data = data[iv_len:]
+        ref_iv[0] = iv
+    cipher = m(method, key, iv, op)
+    result.append(cipher.update(data))
+    return b''.join(result)
+
+
+CIPHERS_TO_TEST = [
+    'aes-128-cfb',
+    'aes-256-cfb',
+    '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 = 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()

BIN
ssr/shadowsocks/encrypt.pyc


+ 51 - 0
ssr/shadowsocks/encrypt_test.py

@@ -0,0 +1,51 @@
+from __future__ import absolute_import, division, print_function, \
+    with_statement
+
+import sys
+import os
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
+
+
+from shadowsocks.crypto import rc4_md5
+from shadowsocks.crypto import openssl
+from shadowsocks.crypto import sodium
+from shadowsocks.crypto import table
+
+def run(func):
+	try:
+		func()
+	except:
+		pass
+
+def run_n(func, name):
+	try:
+		func(name)
+	except:
+		pass
+
+def main():
+	print("\n""rc4_md5")
+	rc4_md5.test()
+	print("\n""aes-256-cfb")
+	openssl.test_aes_256_cfb()
+	print("\n""aes-128-cfb")
+	openssl.test_aes_128_cfb()
+	print("\n""bf-cfb")
+	run(openssl.test_bf_cfb)
+	print("\n""camellia-128-cfb")
+	run_n(openssl.run_method, "camellia-128-cfb")
+	print("\n""cast5-cfb")
+	run_n(openssl.run_method, "cast5-cfb")
+	print("\n""idea-cfb")
+	run_n(openssl.run_method, "idea-cfb")
+	print("\n""seed-cfb")
+	run_n(openssl.run_method, "seed-cfb")
+	print("\n""salsa20")
+	run(sodium.test_salsa20)
+	print("\n""chacha20")
+	run(sodium.test_chacha20)
+
+if __name__ == '__main__':
+	main()
+

+ 251 - 0
ssr/shadowsocks/eventloop.py

@@ -0,0 +1,251 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2013-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 ssloop
+# https://github.com/clowwindy/ssloop
+
+from __future__ import absolute_import, division, print_function, \
+    with_statement
+
+import os
+import time
+import socket
+import select
+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']
+
+POLL_NULL = 0x00
+POLL_IN = 0x01
+POLL_OUT = 0x04
+POLL_ERR = 0x08
+POLL_HUP = 0x10
+POLL_NVAL = 0x20
+
+
+EVENT_NAMES = {
+    POLL_NULL: 'POLL_NULL',
+    POLL_IN: 'POLL_IN',
+    POLL_OUT: 'POLL_OUT',
+    POLL_ERR: 'POLL_ERR',
+    POLL_HUP: 'POLL_HUP',
+    POLL_NVAL: 'POLL_NVAL',
+}
+
+# we check timeouts every TIMEOUT_PRECISION seconds
+TIMEOUT_PRECISION = 5
+
+
+class KqueueLoop(object):
+
+    MAX_EVENTS = 1024
+
+    def __init__(self):
+        self._kqueue = select.kqueue()
+        self._fds = {}
+
+    def _control(self, fd, mode, flags):
+        events = []
+        if mode & POLL_IN:
+            events.append(select.kevent(fd, select.KQ_FILTER_READ, flags))
+        if mode & POLL_OUT:
+            events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags))
+        for e in events:
+            self._kqueue.control([e], 0)
+
+    def poll(self, timeout):
+        if timeout < 0:
+            timeout = None  # kqueue behaviour
+        events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout)
+        results = defaultdict(lambda: POLL_NULL)
+        for e in events:
+            fd = e.ident
+            if e.filter == select.KQ_FILTER_READ:
+                results[fd] |= POLL_IN
+            elif e.filter == select.KQ_FILTER_WRITE:
+                results[fd] |= POLL_OUT
+        return results.items()
+
+    def register(self, fd, mode):
+        self._fds[fd] = mode
+        self._control(fd, mode, select.KQ_EV_ADD)
+
+    def unregister(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()
+
+
+class SelectLoop(object):
+
+    def __init__(self):
+        self._r_list = set()
+        self._w_list = set()
+        self._x_list = set()
+
+    def poll(self, timeout):
+        r, w, x = select.select(self._r_list, self._w_list, self._x_list,
+                                timeout)
+        results = defaultdict(lambda: POLL_NULL)
+        for p in [(r, POLL_IN), (w, POLL_OUT), (x, POLL_ERR)]:
+            for fd in p[0]:
+                results[fd] |= p[1]
+        return results.items()
+
+    def register(self, fd, mode):
+        if mode & POLL_IN:
+            self._r_list.add(fd)
+        if mode & POLL_OUT:
+            self._w_list.add(fd)
+        if mode & POLL_ERR:
+            self._x_list.add(fd)
+
+    def unregister(self, fd):
+        if fd in self._r_list:
+            self._r_list.remove(fd)
+        if fd in self._w_list:
+            self._w_list.remove(fd)
+        if fd in self._x_list:
+            self._x_list.remove(fd)
+
+    def modify(self, fd, mode):
+        self.unregister(fd)
+        self.register(fd, mode)
+
+    def close(self):
+        pass
+
+
+class EventLoop(object):
+    def __init__(self):
+        if hasattr(select, 'epoll'):
+            self._impl = select.epoll()
+            model = 'epoll'
+        elif hasattr(select, 'kqueue'):
+            self._impl = KqueueLoop()
+            model = 'kqueue'
+        elif hasattr(select, 'select'):
+            self._impl = SelectLoop()
+            model = 'select'
+        else:
+            raise Exception('can not find any available functions in select '
+                            'package')
+        self._fdmap = {}  # (f, handler)
+        self._last_time = time.time()
+        self._periodic_callbacks = []
+        self._stopping = False
+        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]
+
+    def add(self, f, mode, handler):
+        fd = f.fileno()
+        self._fdmap[fd] = (f, handler)
+        self._impl.register(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)
+
+    def modify(self, f, mode):
+        fd = f.fileno()
+        self._impl.modify(fd, mode)
+
+    def stop(self):
+        self._stopping = True
+
+    def run(self):
+        events = []
+        while not self._stopping:
+            asap = False
+            try:
+                events = self.poll(TIMEOUT_PRECISION)
+            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()
+
+
+# from tornado
+def errno_from_exception(e):
+    """Provides the errno from an Exception object.
+
+    There are cases that the errno attribute was not set so we pull
+    the errno out of the args but if someone instatiates an Exception
+    without any args you will get a tuple error. So this function
+    abstracts all that behavior to give you a safe way to get the
+    errno.
+    """
+
+    if hasattr(e, 'errno'):
+        return e.errno
+    elif e.args:
+        return e.args[0]
+    else:
+        return None
+
+
+# from tornado
+def get_sock_error(sock):
+    error_number = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+    return socket.error(error_number, os.strerror(error_number))

BIN
ssr/shadowsocks/eventloop.pyc


+ 81 - 0
ssr/shadowsocks/local.py

@@ -0,0 +1,81 @@
+#!/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
+
+if __name__ == '__main__':
+    import inspect
+    file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe())))
+    sys.path.insert(0, os.path.join(file_path, '../'))
+
+from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns
+
+
+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)
+
+    if not config.get('dns_ipv6', False):
+        asyncdns.IPV6_CONNECTION_SUPPORT = False
+
+    daemon.daemon_exec(config)
+    logging.info("local start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" %
+            (config['protocol'], config['password'], config['method'], config['obfs'], config['obfs_param']))
+
+    try:
+        logging.info("starting local at %s:%d" %
+                     (config['local_address'], config['local_port']))
+
+        dns_resolver = asyncdns.DNSResolver()
+        tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
+        udp_server = udprelay.UDPRelay(config, dns_resolver, True)
+        loop = eventloop.EventLoop()
+        dns_resolver.add_to_loop(loop)
+        tcp_server.add_to_loop(loop)
+        udp_server.add_to_loop(loop)
+
+        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)
+
+        daemon.set_user(config.get('user', None))
+        loop.run()
+    except Exception as e:
+        shell.print_exception(e)
+        sys.exit(1)
+
+if __name__ == '__main__':
+    main()

+ 6 - 0
ssr/shadowsocks/logrun.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+cd `dirname $0`
+eval $(ps -ef | grep "[0-9] python server\\.py a" | awk '{print "kill "$2}')
+ulimit -n 4096
+nohup python server.py a >> ssserver.log 2>&1 &
+

+ 180 - 0
ssr/shadowsocks/lru_cache.py

@@ -0,0 +1,180 @@
+#!/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 collections
+import logging
+import time
+
+if __name__ == '__main__':
+    import os, sys, inspect
+    file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe())))
+    sys.path.insert(0, os.path.join(file_path, '../'))
+
+try:
+    from collections import OrderedDict
+    print("loaded collections.OrderedDict")
+except:
+    from shadowsocks.ordereddict import OrderedDict
+
+# this LRUCache is optimized for concurrency, not QPS
+# n: concurrency, keys stored in the cache
+# m: visits not timed out, proportional to QPS * timeout
+# get & set is O(1), not O(n). thus we can support very large n
+# sweep is O((n - m)) or O(1024) at most,
+# no metter how large the cache or timeout value is
+
+SWEEP_MAX_ITEMS = 1024
+
+class LRUCache(collections.MutableMapping):
+    """This class is not thread safe"""
+
+    def __init__(self, timeout=60, close_callback=None, *args, **kwargs):
+        self.timeout = timeout
+        self.close_callback = close_callback
+        self._store = {}
+        self._keys_to_last_time = OrderedDict()
+        self.update(dict(*args, **kwargs))  # use the free update to set keys
+
+    def __getitem__(self, key):
+        # O(1)
+        t = time.time()
+        last_t = self._keys_to_last_time[key]
+        del self._keys_to_last_time[key]
+        self._keys_to_last_time[key] = t
+        return self._store[key]
+
+    def __setitem__(self, key, value):
+        # O(1)
+        t = time.time()
+        if key in self._keys_to_last_time:
+            del self._keys_to_last_time[key]
+        self._keys_to_last_time[key] = t
+        self._store[key] = value
+
+    def __delitem__(self, key):
+        # O(1)
+        last_t = self._keys_to_last_time[key]
+        del self._store[key]
+        del self._keys_to_last_time[key]
+
+    def __contains__(self, key):
+        return key in self._store
+
+    def __iter__(self):
+        return iter(self._store)
+
+    def __len__(self):
+        return len(self._store)
+
+    def first(self):
+        if len(self._keys_to_last_time) > 0:
+            for key in self._keys_to_last_time:
+                return key
+
+    def sweep(self):
+        # O(n - m)
+        now = time.time()
+        c = 0
+        while c < SWEEP_MAX_ITEMS:
+            if len(self._keys_to_last_time) == 0:
+                break
+            for key in self._keys_to_last_time:
+                break
+            last_t = self._keys_to_last_time[key]
+            if now - last_t <= self.timeout:
+                break
+            value = self._store[key]
+            del self._store[key]
+            del self._keys_to_last_time[key]
+            if self.close_callback is not None:
+                self.close_callback(value)
+            c += 1
+        if c:
+            logging.debug('%d keys swept' % c)
+        return c < SWEEP_MAX_ITEMS
+
+    def clear(self, keep):
+        now = time.time()
+        c = 0
+        while len(self._keys_to_last_time) > keep:
+            if len(self._keys_to_last_time) == 0:
+                break
+            for key in self._keys_to_last_time:
+                break
+            last_t = self._keys_to_last_time[key]
+            value = self._store[key]
+            if self.close_callback is not None:
+                self.close_callback(value)
+            del self._store[key]
+            del self._keys_to_last_time[key]
+            c += 1
+        if c:
+            logging.debug('%d keys swept' % c)
+        return c < SWEEP_MAX_ITEMS
+
+def test():
+    c = LRUCache(timeout=0.3)
+
+    c['a'] = 1
+    assert c['a'] == 1
+    c['a'] = 1
+
+    time.sleep(0.5)
+    c.sweep()
+    assert 'a' not in c
+
+    c['a'] = 2
+    c['b'] = 3
+    time.sleep(0.2)
+    c.sweep()
+    assert c['a'] == 2
+    assert c['b'] == 3
+
+    time.sleep(0.2)
+    c.sweep()
+    c['b']
+    time.sleep(0.2)
+    c.sweep()
+    assert 'a' not in c
+    assert c['b'] == 3
+
+    time.sleep(0.5)
+    c.sweep()
+    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['s']
+    time.sleep(0.1)
+    c['s']
+    time.sleep(0.3)
+    c.sweep()
+
+if __name__ == '__main__':
+    test()

BIN
ssr/shadowsocks/lru_cache.pyc


+ 291 - 0
ssr/shadowsocks/manager.py

@@ -0,0 +1,291 @@
+#!/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 = common.to_str(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']
+        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,
+                              stat_callback=self.stat_callback)
+        u = udprelay.UDPRelay(config, self._dns_resolver, False,
+                              stat_callback=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)
+            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 self._control_client_addr:
+            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 encrypt
+
+    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 = shell.get_config(True)
+        config = config.copy()
+        a_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
+        }
+        config.update(a_config)
+        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 = encrypt.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', 1,
+                               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 = encrypt.encrypt_all(b'foobar2', 'aes-256-cfb', 1,
+                               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()

+ 104 - 0
ssr/shadowsocks/obfs.py

@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2015 breakwa11
+#
+# 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.obfsplugin import plain, http_simple, obfs_tls, verify, auth
+
+
+method_supported = {}
+method_supported.update(plain.obfs_map)
+method_supported.update(http_simple.obfs_map)
+method_supported.update(obfs_tls.obfs_map)
+method_supported.update(verify.obfs_map)
+method_supported.update(auth.obfs_map)
+
+class server_info(object):
+    def __init__(self, data):
+        self.data = data
+
+class obfs(object):
+    def __init__(self, method):
+        method = common.to_str(method)
+        self.method = method
+        self._method_info = self.get_method_info(method)
+        if self._method_info:
+            self.obfs = self.get_obfs(method)
+        else:
+            raise Exception('obfs plugin [%s] not supported' % method)
+
+    def init_data(self):
+        return self.obfs.init_data()
+
+    def set_server_info(self, server_info):
+        return self.obfs.set_server_info(server_info)
+
+    def get_method_info(self, method):
+        method = method.lower()
+        m = method_supported.get(method)
+        return m
+
+    def get_obfs(self, method):
+        m = self._method_info
+        return m[0](method)
+
+    def client_pre_encrypt(self, buf):
+        return self.obfs.client_pre_encrypt(buf)
+
+    def client_encode(self, buf):
+        return self.obfs.client_encode(buf)
+
+    def client_decode(self, buf):
+        return self.obfs.client_decode(buf)
+
+    def client_post_decrypt(self, buf):
+        return self.obfs.client_post_decrypt(buf)
+
+    def server_pre_encrypt(self, buf):
+        return self.obfs.server_pre_encrypt(buf)
+
+    def server_encode(self, buf):
+        return self.obfs.server_encode(buf)
+
+    def server_decode(self, buf):
+        return self.obfs.server_decode(buf)
+
+    def server_post_decrypt(self, buf):
+        return self.obfs.server_post_decrypt(buf)
+
+    def client_udp_pre_encrypt(self, buf):
+        return self.obfs.client_udp_pre_encrypt(buf)
+
+    def client_udp_post_decrypt(self, buf):
+        return self.obfs.client_udp_post_decrypt(buf)
+
+    def server_udp_pre_encrypt(self, buf):
+        return self.obfs.server_udp_pre_encrypt(buf)
+
+    def server_udp_post_decrypt(self, buf):
+        return self.obfs.server_udp_post_decrypt(buf)
+
+    def dispose(self):
+        self.obfs.dispose()
+        del self.obfs
+

BIN
ssr/shadowsocks/obfs.pyc


+ 18 - 0
ssr/shadowsocks/obfsplugin/__init__.py

@@ -0,0 +1,18 @@
+#!/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

BIN
ssr/shadowsocks/obfsplugin/__init__.pyc


+ 1462 - 0
ssr/shadowsocks/obfsplugin/auth.py

@@ -0,0 +1,1462 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2015 breakwa11
+#
+# 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
+import binascii
+import base64
+import time
+import datetime
+import random
+import struct
+import zlib
+import hmac
+import hashlib
+
+import shadowsocks
+from shadowsocks import common, lru_cache, encrypt
+from shadowsocks.obfsplugin import plain
+from shadowsocks.common import to_bytes, to_str, ord, chr
+
+def create_auth_sha1(method):
+    return auth_sha1(method)
+
+def create_auth_sha1_v2(method):
+    return auth_sha1_v2(method)
+
+def create_auth_sha1_v4(method):
+    return auth_sha1_v4(method)
+
+def create_auth_aes128(method):
+    return auth_aes128(method)
+
+def create_auth_aes128_md5(method):
+    return auth_aes128_sha1(method, hashlib.md5)
+
+def create_auth_aes128_sha1(method):
+    return auth_aes128_sha1(method, hashlib.sha1)
+
+obfs_map = {
+        'auth_sha1': (create_auth_sha1,),
+        'auth_sha1_compatible': (create_auth_sha1,),
+        'auth_sha1_v2': (create_auth_sha1_v2,),
+        'auth_sha1_v2_compatible': (create_auth_sha1_v2,),
+        'auth_sha1_v4': (create_auth_sha1_v4,),
+        'auth_sha1_v4_compatible': (create_auth_sha1_v4,),
+        'auth_aes128': (create_auth_aes128,),
+        'auth_aes128_md5': (create_auth_aes128_md5,),
+        'auth_aes128_sha1': (create_auth_aes128_sha1,),
+}
+
+def match_begin(str1, str2):
+    if len(str1) >= len(str2):
+        if str1[:len(str2)] == str2:
+            return True
+    return False
+
+class obfs_verify_data(object):
+    def __init__(self):
+        pass
+
+class auth_base(plain.plain):
+    def __init__(self, method):
+        super(auth_base, self).__init__(method)
+        self.method = method
+        self.no_compatible_method = ''
+
+    def init_data(self):
+        return ''
+
+    def set_server_info(self, server_info):
+        self.server_info = server_info
+
+    def client_encode(self, buf):
+        return buf
+
+    def client_decode(self, buf):
+        return (buf, False)
+
+    def server_encode(self, buf):
+        return buf
+
+    def server_decode(self, buf):
+        return (buf, True, False)
+
+    def not_match_return(self, buf):
+        self.raw_trans = True
+        if self.method == self.no_compatible_method:
+            return (b'E'*2048, False)
+        return (buf, False)
+
+class client_queue(object):
+    def __init__(self, begin_id):
+        self.front = begin_id - 64
+        self.back = begin_id + 1
+        self.alloc = {}
+        self.enable = True
+        self.last_update = time.time()
+
+    def update(self):
+        self.last_update = time.time()
+
+    def is_active(self):
+        return time.time() - self.last_update < 60 * 3
+
+    def re_enable(self, connection_id):
+        self.enable = True
+        self.front = connection_id - 64
+        self.back = connection_id + 1
+        self.alloc = {}
+
+    def insert(self, connection_id):
+        if not self.enable:
+            logging.warn('obfs auth: not enable')
+            return False
+        if not self.is_active():
+            self.re_enable(connection_id)
+        self.update()
+        if connection_id < self.front:
+            logging.warn('obfs auth: deprecated id, someone replay attack')
+            return False
+        if connection_id > self.front + 0x4000:
+            logging.warn('obfs auth: wrong id')
+            return False
+        if connection_id in self.alloc:
+            logging.warn('obfs auth: duplicate id, someone replay attack')
+            return False
+        if self.back <= connection_id:
+            self.back = connection_id + 1
+        self.alloc[connection_id] = 1
+        while (self.front in self.alloc) or self.front + 0x1000 < self.back:
+            if self.front in self.alloc:
+                del self.alloc[self.front]
+            self.front += 1
+        return True
+
+class obfs_auth_data(object):
+    def __init__(self):
+        self.client_id = {}
+        self.startup_time = int(time.time() - 30) & 0xFFFFFFFF
+        self.local_client_id = b''
+        self.connection_id = 0
+        self.set_max_client(64) # max active client count
+
+    def update(self, client_id, connection_id):
+        if client_id in self.client_id:
+            self.client_id[client_id].update()
+
+    def set_max_client(self, max_client):
+        self.max_client = max_client
+        self.max_buffer = max(self.max_client * 2, 256)
+
+    def insert(self, client_id, connection_id):
+        if client_id not in self.client_id or not self.client_id[client_id].enable:
+            active = 0
+            for c_id in self.client_id:
+                if self.client_id[c_id].is_active():
+                    active += 1
+            if active >= self.max_client:
+                logging.warn('obfs auth: max active clients exceeded')
+                return False
+
+            if len(self.client_id) < self.max_client:
+                if client_id not in self.client_id:
+                    self.client_id[client_id] = client_queue(connection_id)
+                else:
+                    self.client_id[client_id].re_enable(connection_id)
+                return self.client_id[client_id].insert(connection_id)
+            keys = self.client_id.keys()
+            random.shuffle(keys)
+            for c_id in keys:
+                if not self.client_id[c_id].is_active() and self.client_id[c_id].enable:
+                    if len(self.client_id) >= self.max_buffer:
+                        del self.client_id[c_id]
+                    else:
+                        self.client_id[c_id].enable = False
+                    if client_id not in self.client_id:
+                        self.client_id[client_id] = client_queue(connection_id)
+                    else:
+                        self.client_id[client_id].re_enable(connection_id)
+                    return self.client_id[client_id].insert(connection_id)
+            logging.warn('obfs auth: no inactive client [assert]')
+            return False
+        else:
+            return self.client_id[client_id].insert(connection_id)
+
+class auth_sha1(auth_base):
+    def __init__(self, method):
+        super(auth_sha1, self).__init__(method)
+        self.recv_buf = b''
+        self.unit_len = 8000
+        self.decrypt_packet_num = 0
+        self.raw_trans = False
+        self.has_sent_header = False
+        self.has_recv_header = False
+        self.client_id = 0
+        self.connection_id = 0
+        self.max_time_dif = 60 * 60 # time dif (second) setting
+        self.no_compatible_method = 'auth_sha1'
+
+    def init_data(self):
+        return obfs_auth_data()
+
+    def set_server_info(self, server_info):
+        self.server_info = server_info
+        try:
+            max_client = int(server_info.protocol_param)
+        except:
+            max_client = 64
+        self.server_info.data.set_max_client(max_client)
+
+    def pack_data(self, buf):
+        rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 16)
+        data = common.chr(len(rnd_data) + 1) + rnd_data + buf
+        data = struct.pack('>H', len(data) + 6) + data
+        adler32 = zlib.adler32(data) & 0xFFFFFFFF
+        data += struct.pack('<I', adler32)
+        return data
+
+    def pack_auth_data(self, buf):
+        if len(buf) == 0:
+            return b''
+        rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 128)
+        data = common.chr(len(rnd_data) + 1) + rnd_data + buf
+        data = struct.pack('>H', len(data) + 16) + data
+        crc = binascii.crc32(self.server_info.key) & 0xFFFFFFFF
+        data = struct.pack('<I', crc) + data
+        data += hmac.new(self.server_info.iv + self.server_info.key, data, hashlib.sha1).digest()[:10]
+        return data
+
+    def auth_data(self):
+        utc_time = int(time.time()) & 0xFFFFFFFF
+        if self.server_info.data.connection_id > 0xFF000000:
+            self.server_info.data.local_client_id = b''
+        if not self.server_info.data.local_client_id:
+            self.server_info.data.local_client_id = os.urandom(4)
+            logging.debug("local_client_id %s" % (binascii.hexlify(self.server_info.data.local_client_id),))
+            self.server_info.data.connection_id = struct.unpack('<I', os.urandom(4))[0] & 0xFFFFFF
+        self.server_info.data.connection_id += 1
+        return b''.join([struct.pack('<I', utc_time),
+                self.server_info.data.local_client_id,
+                struct.pack('<I', self.server_info.data.connection_id)])
+
+    def client_pre_encrypt(self, buf):
+        ret = b''
+        if not self.has_sent_header:
+            head_size = self.get_head_size(buf, 30)
+            datalen = min(len(buf), random.randint(0, 31) + head_size)
+            ret += self.pack_auth_data(self.auth_data() + buf[:datalen])
+            buf = buf[datalen:]
+            self.has_sent_header = True
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def client_post_decrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data error')
+            if length > len(self.recv_buf):
+                break
+
+            if struct.pack('<I', zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) != self.recv_buf[length - 4:length]:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data uncorrect checksum')
+
+            pos = common.ord(self.recv_buf[2]) + 2
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+
+        if out_buf:
+            self.decrypt_packet_num += 1
+        return out_buf
+
+    def server_pre_encrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def server_post_decrypt(self, buf):
+        if self.raw_trans:
+            return (buf, False)
+        self.recv_buf += buf
+        out_buf = b''
+        if not self.has_recv_header:
+            if len(self.recv_buf) < 6:
+                return (b'', False)
+            crc = struct.pack('<I', binascii.crc32(self.server_info.key) & 0xFFFFFFFF)
+            if crc != self.recv_buf[:4]:
+                return self.not_match_return(self.recv_buf)
+            length = struct.unpack('>H', self.recv_buf[4:6])[0]
+            if length > 2048:
+                return self.not_match_return(self.recv_buf)
+            if length > len(self.recv_buf):
+                return (b'', False)
+            sha1data = hmac.new(self.server_info.recv_iv + self.server_info.key, self.recv_buf[:length - 10], hashlib.sha1).digest()[:10]
+            if sha1data != self.recv_buf[length - 10:length]:
+                logging.error('auth_sha1 data uncorrect auth HMAC-SHA1')
+                return self.not_match_return(self.recv_buf)
+            pos = common.ord(self.recv_buf[6]) + 6
+            out_buf = self.recv_buf[pos:length - 10]
+            if len(out_buf) < 12:
+                logging.info('auth_sha1: too short, data %s' % (binascii.hexlify(self.recv_buf),))
+                return self.not_match_return(self.recv_buf)
+            utc_time = struct.unpack('<I', out_buf[:4])[0]
+            client_id = struct.unpack('<I', out_buf[4:8])[0]
+            connection_id = struct.unpack('<I', out_buf[8:12])[0]
+            time_dif = common.int32(utc_time - (int(time.time()) & 0xffffffff))
+            if time_dif < -self.max_time_dif or time_dif > self.max_time_dif \
+                    or common.int32(utc_time - self.server_info.data.startup_time) < -self.max_time_dif / 2:
+                logging.info('auth_sha1: wrong timestamp, time_dif %d, data %s' % (time_dif, binascii.hexlify(out_buf),))
+                return self.not_match_return(self.recv_buf)
+            elif self.server_info.data.insert(client_id, connection_id):
+                self.has_recv_header = True
+                out_buf = out_buf[12:]
+                self.client_id = client_id
+                self.connection_id = connection_id
+            else:
+                logging.info('auth_sha1: auth fail, data %s' % (binascii.hexlify(out_buf),))
+                return self.not_match_return(self.recv_buf)
+            self.recv_buf = self.recv_buf[length:]
+            self.has_recv_header = True
+
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    logging.info('auth_sha1: over size')
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            if length > len(self.recv_buf):
+                break
+
+            if struct.pack('<I', zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) != self.recv_buf[length - 4:length]:
+                logging.info('auth_sha1: checksum error, data %s' % (binascii.hexlify(self.recv_buf[:length]),))
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data uncorrect checksum')
+
+            pos = common.ord(self.recv_buf[2]) + 2
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+
+        if out_buf:
+            self.server_info.data.update(self.client_id, self.connection_id)
+            self.decrypt_packet_num += 1
+        return (out_buf, False)
+
+class obfs_auth_v2_data(object):
+    def __init__(self):
+        self.client_id = lru_cache.LRUCache()
+        self.local_client_id = b''
+        self.connection_id = 0
+        self.set_max_client(64) # max active client count
+
+    def update(self, client_id, connection_id):
+        if client_id in self.client_id:
+            self.client_id[client_id].update()
+
+    def set_max_client(self, max_client):
+        self.max_client = max_client
+        self.max_buffer = max(self.max_client * 2, 1024)
+
+    def insert(self, client_id, connection_id):
+        if self.client_id.get(client_id, None) is None or not self.client_id[client_id].enable:
+            if self.client_id.first() is None or len(self.client_id) < self.max_client:
+                if client_id not in self.client_id:
+                    #TODO: check
+                    self.client_id[client_id] = client_queue(connection_id)
+                else:
+                    self.client_id[client_id].re_enable(connection_id)
+                return self.client_id[client_id].insert(connection_id)
+
+            if not self.client_id[self.client_id.first()].is_active():
+                del self.client_id[self.client_id.first()]
+                if client_id not in self.client_id:
+                    #TODO: check
+                    self.client_id[client_id] = client_queue(connection_id)
+                else:
+                    self.client_id[client_id].re_enable(connection_id)
+                return self.client_id[client_id].insert(connection_id)
+
+            logging.warn('auth_sha1_v2: no inactive client')
+            return False
+        else:
+            return self.client_id[client_id].insert(connection_id)
+
+class auth_sha1_v2(auth_base):
+    def __init__(self, method):
+        super(auth_sha1_v2, self).__init__(method)
+        self.recv_buf = b''
+        self.unit_len = 8100
+        self.decrypt_packet_num = 0
+        self.raw_trans = False
+        self.has_sent_header = False
+        self.has_recv_header = False
+        self.client_id = 0
+        self.connection_id = 0
+        self.salt = b"auth_sha1_v2"
+        self.no_compatible_method = 'auth_sha1_v2'
+
+    def init_data(self):
+        return obfs_auth_v2_data()
+
+    def set_server_info(self, server_info):
+        self.server_info = server_info
+        try:
+            max_client = int(server_info.protocol_param)
+        except:
+            max_client = 64
+        self.server_info.data.set_max_client(max_client)
+
+    def rnd_data(self, buf_size):
+        if buf_size > 1300:
+            return b'\x01'
+
+        if buf_size > 400:
+            rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 128)
+            return common.chr(len(rnd_data) + 1) + rnd_data
+
+        rnd_data = os.urandom(struct.unpack('>H', os.urandom(2))[0] % 1024)
+        return common.chr(255) + struct.pack('>H', len(rnd_data) + 3) + rnd_data
+
+    def pack_data(self, buf):
+        data = self.rnd_data(len(buf)) + buf
+        data = struct.pack('>H', len(data) + 6) + data
+        adler32 = zlib.adler32(data) & 0xFFFFFFFF
+        data += struct.pack('<I', adler32)
+        return data
+
+    def pack_auth_data(self, buf):
+        if len(buf) == 0:
+            return b''
+        data = self.rnd_data(len(buf)) + buf
+        data = struct.pack('>H', len(data) + 16) + data
+        crc = binascii.crc32(self.salt + self.server_info.key) & 0xFFFFFFFF
+        data = struct.pack('<I', crc) + data
+        data += hmac.new(self.server_info.iv + self.server_info.key, data, hashlib.sha1).digest()[:10]
+        return data
+
+    def auth_data(self):
+        if self.server_info.data.connection_id > 0xFF000000:
+            self.server_info.data.local_client_id = b''
+        if not self.server_info.data.local_client_id:
+            self.server_info.data.local_client_id = os.urandom(8)
+            logging.debug("local_client_id %s" % (binascii.hexlify(self.server_info.data.local_client_id),))
+            self.server_info.data.connection_id = struct.unpack('<Q', self.server_info.data.local_client_id)[0] % 0xFFFFFD
+        self.server_info.data.connection_id += 1
+        return b''.join([self.server_info.data.local_client_id,
+                struct.pack('<I', self.server_info.data.connection_id)])
+
+    def client_pre_encrypt(self, buf):
+        ret = b''
+        if not self.has_sent_header:
+            head_size = self.get_head_size(buf, 30)
+            datalen = min(len(buf), random.randint(0, 31) + head_size)
+            ret += self.pack_auth_data(self.auth_data() + buf[:datalen])
+            buf = buf[datalen:]
+            self.has_sent_header = True
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def client_post_decrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data error')
+            if length > len(self.recv_buf):
+                break
+
+            if struct.pack('<I', zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) != self.recv_buf[length - 4:length]:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data uncorrect checksum')
+
+            pos = common.ord(self.recv_buf[2])
+            if pos < 255:
+                pos += 2
+            else:
+                pos = struct.unpack('>H', self.recv_buf[3:5])[0] + 2
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+
+        if out_buf:
+            self.decrypt_packet_num += 1
+        return out_buf
+
+    def server_pre_encrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def server_post_decrypt(self, buf):
+        if self.raw_trans:
+            return (buf, False)
+        self.recv_buf += buf
+        out_buf = b''
+        sendback = False
+
+        if not self.has_recv_header:
+            if len(self.recv_buf) < 6:
+                return (b'', False)
+            crc = struct.pack('<I', binascii.crc32(self.salt + self.server_info.key) & 0xFFFFFFFF)
+            if crc != self.recv_buf[:4]:
+                return self.not_match_return(self.recv_buf)
+            length = struct.unpack('>H', self.recv_buf[4:6])[0]
+            if length > 2048:
+                return self.not_match_return(self.recv_buf)
+            if length > len(self.recv_buf):
+                return (b'', False)
+            sha1data = hmac.new(self.server_info.recv_iv + self.server_info.key, self.recv_buf[:length - 10], hashlib.sha1).digest()[:10]
+            if sha1data != self.recv_buf[length - 10:length]:
+                logging.error('auth_sha1_v2 data uncorrect auth HMAC-SHA1')
+                return self.not_match_return(self.recv_buf)
+            pos = common.ord(self.recv_buf[6])
+            if pos < 255:
+                pos += 6
+            else:
+                pos = struct.unpack('>H', self.recv_buf[7:9])[0] + 6
+            out_buf = self.recv_buf[pos:length - 10]
+            if len(out_buf) < 12:
+                logging.info('auth_sha1_v2: too short, data %s' % (binascii.hexlify(self.recv_buf),))
+                return self.not_match_return(self.recv_buf)
+            client_id = struct.unpack('<Q', out_buf[:8])[0]
+            connection_id = struct.unpack('<I', out_buf[8:12])[0]
+            if self.server_info.data.insert(client_id, connection_id):
+                self.has_recv_header = True
+                out_buf = out_buf[12:]
+                self.client_id = client_id
+                self.connection_id = connection_id
+            else:
+                logging.info('auth_sha1_v2: auth fail, data %s' % (binascii.hexlify(out_buf),))
+                return self.not_match_return(self.recv_buf)
+            self.recv_buf = self.recv_buf[length:]
+            self.has_recv_header = True
+            sendback = True
+
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    logging.info('auth_sha1_v2: over size')
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            if length > len(self.recv_buf):
+                break
+
+            if struct.pack('<I', zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) != self.recv_buf[length - 4:length]:
+                logging.info('auth_sha1_v2: checksum error, data %s' % (binascii.hexlify(self.recv_buf[:length]),))
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data uncorrect checksum')
+
+            pos = common.ord(self.recv_buf[2])
+            if pos < 255:
+                pos += 2
+            else:
+                pos = struct.unpack('>H', self.recv_buf[3:5])[0] + 2
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+            if pos == length - 4:
+                sendback = True
+
+        if out_buf:
+            self.server_info.data.update(self.client_id, self.connection_id)
+            self.decrypt_packet_num += 1
+        return (out_buf, sendback)
+
+class auth_sha1_v4(auth_base):
+    def __init__(self, method):
+        super(auth_sha1_v4, self).__init__(method)
+        self.recv_buf = b''
+        self.unit_len = 8100
+        self.decrypt_packet_num = 0
+        self.raw_trans = False
+        self.has_sent_header = False
+        self.has_recv_header = False
+        self.client_id = 0
+        self.connection_id = 0
+        self.max_time_dif = 60 * 60 * 24 # time dif (second) setting
+        self.salt = b"auth_sha1_v4"
+        self.no_compatible_method = 'auth_sha1_v4'
+
+    def init_data(self):
+        return obfs_auth_v2_data()
+
+    def set_server_info(self, server_info):
+        self.server_info = server_info
+        try:
+            max_client = int(server_info.protocol_param)
+        except:
+            max_client = 64
+        self.server_info.data.set_max_client(max_client)
+
+    def rnd_data(self, buf_size):
+        if buf_size > 1200:
+            return b'\x01'
+
+        if buf_size > 400:
+            rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 256)
+        else:
+            rnd_data = os.urandom(struct.unpack('>H', os.urandom(2))[0] % 512)
+
+        if len(rnd_data) < 128:
+            return common.chr(len(rnd_data) + 1) + rnd_data
+        else:
+            return common.chr(255) + struct.pack('>H', len(rnd_data) + 3) + rnd_data
+
+    def pack_data(self, buf):
+        data = self.rnd_data(len(buf)) + buf
+        data_len = len(data) + 8
+        crc = binascii.crc32(struct.pack('>H', data_len)) & 0xFFFF
+        data = struct.pack('<H', crc) + data
+        data = struct.pack('>H', data_len) + data
+        adler32 = zlib.adler32(data) & 0xFFFFFFFF
+        data += struct.pack('<I', adler32)
+        return data
+
+    def pack_auth_data(self, buf):
+        if len(buf) == 0:
+            return b''
+        data = self.rnd_data(len(buf)) + buf
+        data_len = len(data) + 16
+        crc = binascii.crc32(struct.pack('>H', data_len) + self.salt + self.server_info.key) & 0xFFFFFFFF
+        data = struct.pack('<I', crc) + data
+        data = struct.pack('>H', data_len) + data
+        data += hmac.new(self.server_info.iv + self.server_info.key, data, hashlib.sha1).digest()[:10]
+        return data
+
+    def auth_data(self):
+        utc_time = int(time.time()) & 0xFFFFFFFF
+        if self.server_info.data.connection_id > 0xFF000000:
+            self.server_info.data.local_client_id = b''
+        if not self.server_info.data.local_client_id:
+            self.server_info.data.local_client_id = os.urandom(4)
+            logging.debug("local_client_id %s" % (binascii.hexlify(self.server_info.data.local_client_id),))
+            self.server_info.data.connection_id = struct.unpack('<I', os.urandom(4))[0] & 0xFFFFFF
+        self.server_info.data.connection_id += 1
+        return b''.join([struct.pack('<I', utc_time),
+                self.server_info.data.local_client_id,
+                struct.pack('<I', self.server_info.data.connection_id)])
+
+    def client_pre_encrypt(self, buf):
+        ret = b''
+        if not self.has_sent_header:
+            head_size = self.get_head_size(buf, 30)
+            datalen = min(len(buf), random.randint(0, 31) + head_size)
+            ret += self.pack_auth_data(self.auth_data() + buf[:datalen])
+            buf = buf[datalen:]
+            self.has_sent_header = True
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def client_post_decrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 4:
+            crc = struct.pack('<H', binascii.crc32(self.recv_buf[:2]) & 0xFFFF)
+            if crc != self.recv_buf[2:4]:
+                raise Exception('client_post_decrypt data uncorrect crc')
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data error')
+            if length > len(self.recv_buf):
+                break
+
+            if struct.pack('<I', zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) != self.recv_buf[length - 4:length]:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data uncorrect checksum')
+
+            pos = common.ord(self.recv_buf[4])
+            if pos < 255:
+                pos += 4
+            else:
+                pos = struct.unpack('>H', self.recv_buf[5:7])[0] + 4
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+
+        if out_buf:
+            self.decrypt_packet_num += 1
+        return out_buf
+
+    def server_pre_encrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def server_post_decrypt(self, buf):
+        if self.raw_trans:
+            return (buf, False)
+        self.recv_buf += buf
+        out_buf = b''
+        sendback = False
+
+        if not self.has_recv_header:
+            if len(self.recv_buf) <= 6:
+                return (b'', False)
+            crc = struct.pack('<I', binascii.crc32(self.recv_buf[:2] + self.salt + self.server_info.key) & 0xFFFFFFFF)
+            if crc != self.recv_buf[2:6]:
+                return self.not_match_return(self.recv_buf)
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length > len(self.recv_buf):
+                return (b'', False)
+            sha1data = hmac.new(self.server_info.recv_iv + self.server_info.key, self.recv_buf[:length - 10], hashlib.sha1).digest()[:10]
+            if sha1data != self.recv_buf[length - 10:length]:
+                logging.error('auth_sha1_v4 data uncorrect auth HMAC-SHA1')
+                return self.not_match_return(self.recv_buf)
+            pos = common.ord(self.recv_buf[6])
+            if pos < 255:
+                pos += 6
+            else:
+                pos = struct.unpack('>H', self.recv_buf[7:9])[0] + 6
+            out_buf = self.recv_buf[pos:length - 10]
+            if len(out_buf) < 12:
+                logging.info('auth_sha1_v4: too short, data %s' % (binascii.hexlify(self.recv_buf),))
+                return self.not_match_return(self.recv_buf)
+            utc_time = struct.unpack('<I', out_buf[:4])[0]
+            client_id = struct.unpack('<I', out_buf[4:8])[0]
+            connection_id = struct.unpack('<I', out_buf[8:12])[0]
+            time_dif = common.int32(utc_time - (int(time.time()) & 0xffffffff))
+            if time_dif < -self.max_time_dif or time_dif > self.max_time_dif:
+                logging.info('auth_sha1_v4: wrong timestamp, time_dif %d, data %s' % (time_dif, binascii.hexlify(out_buf),))
+                return self.not_match_return(self.recv_buf)
+            elif self.server_info.data.insert(client_id, connection_id):
+                self.has_recv_header = True
+                out_buf = out_buf[12:]
+                self.client_id = client_id
+                self.connection_id = connection_id
+            else:
+                logging.info('auth_sha1_v4: auth fail, data %s' % (binascii.hexlify(out_buf),))
+                return self.not_match_return(self.recv_buf)
+            self.recv_buf = self.recv_buf[length:]
+            self.has_recv_header = True
+            sendback = True
+
+        while len(self.recv_buf) > 4:
+            crc = struct.pack('<H', binascii.crc32(self.recv_buf[:2]) & 0xFFFF)
+            if crc != self.recv_buf[2:4]:
+                self.raw_trans = True
+                logging.info('auth_sha1_v4: wrong crc')
+                if self.decrypt_packet_num == 0:
+                    logging.info('auth_sha1_v4: wrong crc')
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    logging.info('auth_sha1_v4: over size')
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            if length > len(self.recv_buf):
+                break
+
+            if struct.pack('<I', zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) != self.recv_buf[length - 4:length]:
+                logging.info('auth_sha1_v4: checksum error, data %s' % (binascii.hexlify(self.recv_buf[:length]),))
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data uncorrect checksum')
+
+            pos = common.ord(self.recv_buf[4])
+            if pos < 255:
+                pos += 4
+            else:
+                pos = struct.unpack('>H', self.recv_buf[5:7])[0] + 4
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+            if pos == length - 4:
+                sendback = True
+
+        if out_buf:
+            self.server_info.data.update(self.client_id, self.connection_id)
+            self.decrypt_packet_num += 1
+        return (out_buf, sendback)
+
+class auth_aes128(auth_base):
+    def __init__(self, method):
+        super(auth_aes128, self).__init__(method)
+        self.recv_buf = b''
+        self.unit_len = 8100
+        self.raw_trans = False
+        self.has_sent_header = False
+        self.has_recv_header = False
+        self.client_id = 0
+        self.connection_id = 0
+        self.max_time_dif = 60 * 60 * 24 # time dif (second) setting
+        self.salt = b"auth_aes128"
+        self.no_compatible_method = 'auth_aes128'
+        self.extra_wait_size = struct.unpack('>H', os.urandom(2))[0] % 1024
+        self.pack_id = 0
+        self.recv_id = 0
+
+    def init_data(self):
+        return obfs_auth_v2_data()
+
+    def set_server_info(self, server_info):
+        self.server_info = server_info
+        try:
+            max_client = int(server_info.protocol_param)
+        except:
+            max_client = 64
+        self.server_info.data.set_max_client(max_client)
+
+    def rnd_data(self, buf_size):
+        if buf_size > 1200:
+            return b'\x01'
+
+        if self.pack_id > 4:
+            rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 32)
+        elif buf_size > 900:
+            rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 128)
+        else:
+            rnd_data = os.urandom(struct.unpack('>H', os.urandom(2))[0] % 512)
+
+        if len(rnd_data) < 128:
+            return common.chr(len(rnd_data) + 1) + rnd_data
+        else:
+            return common.chr(255) + struct.pack('<H', len(rnd_data) + 3) + rnd_data
+
+    def pack_data(self, buf):
+        data = self.rnd_data(len(buf)) + buf
+        data_len = len(data) + 8
+        crc = binascii.crc32(struct.pack('<H', data_len)) & 0xFFFF
+        data = struct.pack('<H', crc) + data
+        data = struct.pack('<H', data_len) + data
+        adler32 = (zlib.adler32(data) & 0xFFFFFFFF) ^ self.pack_id
+        self.pack_id = (self.pack_id + 1) & 0xFFFFFFFF
+        data += struct.pack('<I', adler32)
+        return data
+
+    def pack_auth_data(self, auth_data, buf):
+        if len(buf) == 0:
+            return b''
+        if len(buf) > 400:
+            rnd_len = common.ord(os.urandom(1)[0]) % 512
+        else:
+            rnd_len = struct.unpack('<H', os.urandom(2))[0] % 1024
+        data = auth_data
+        data_len = 4 + 16 + 10 + len(buf) + rnd_len + 4
+        data = data + struct.pack('<H', data_len) + struct.pack('<H', rnd_len)
+        uid = os.urandom(4)
+        encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(uid + self.server_info.key)) + self.salt, 'aes-128-cbc', b'\x00' * 16)
+        data = uid + encryptor.encrypt(data)[16:]
+        data += hmac.new(self.server_info.iv + self.server_info.key, data, hashlib.sha1).digest()[:10]
+        data += os.urandom(rnd_len) + buf
+        data += struct.pack('<I', (zlib.adler32(data) & 0xFFFFFFFF))
+        return data
+
+    def auth_data(self):
+        utc_time = int(time.time()) & 0xFFFFFFFF
+        if self.server_info.data.connection_id > 0xFF000000:
+            self.server_info.data.local_client_id = b''
+        if not self.server_info.data.local_client_id:
+            self.server_info.data.local_client_id = os.urandom(4)
+            logging.debug("local_client_id %s" % (binascii.hexlify(self.server_info.data.local_client_id),))
+            self.server_info.data.connection_id = struct.unpack('<I', os.urandom(4))[0] & 0xFFFFFF
+        self.server_info.data.connection_id += 1
+        return b''.join([struct.pack('<I', utc_time),
+                self.server_info.data.local_client_id,
+                struct.pack('<I', self.server_info.data.connection_id)])
+
+    def client_pre_encrypt(self, buf):
+        ret = b''
+        if not self.has_sent_header:
+            head_size = self.get_head_size(buf, 30)
+            datalen = min(len(buf), random.randint(0, 31) + head_size)
+            ret += self.pack_auth_data(self.auth_data(), buf[:datalen])
+            buf = buf[datalen:]
+            self.has_sent_header = True
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def client_post_decrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 4:
+            crc = struct.pack('<H', binascii.crc32(self.recv_buf[:2]) & 0xFFFF)
+            if crc != self.recv_buf[2:4]:
+                raise Exception('client_post_decrypt data uncorrect crc')
+            length = struct.unpack('<H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data error')
+            if length > len(self.recv_buf):
+                break
+
+            if struct.pack('<I', (zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) ^ self.recv_id) != self.recv_buf[length - 4:length]:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data uncorrect checksum')
+
+            self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF
+            pos = common.ord(self.recv_buf[4])
+            if pos < 255:
+                pos += 4
+            else:
+                pos = struct.unpack('<H', self.recv_buf[5:7])[0] + 4
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+
+        return out_buf
+
+    def server_pre_encrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def server_post_decrypt(self, buf):
+        if self.raw_trans:
+            return (buf, False)
+        self.recv_buf += buf
+        out_buf = b''
+        sendback = False
+
+        if not self.has_recv_header:
+            if len(self.recv_buf) < 30:
+                return (b'', False)
+            sha1data = hmac.new(self.server_info.recv_iv + self.server_info.key, self.recv_buf[:20], hashlib.sha1).digest()[:10]
+            if sha1data != self.recv_buf[20:30]:
+                logging.error('auth_aes128 data uncorrect auth HMAC-SHA1 from %s:%d, data %s' % (self.server_info.client, self.server_info.client_port, binascii.hexlify(self.recv_buf)))
+                if len(self.recv_buf) < 30 + self.extra_wait_size:
+                    return (b'', False)
+                return self.not_match_return(self.recv_buf)
+
+            user_key = self.recv_buf[:4]
+            encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(user_key + self.server_info.key)) + self.salt, 'aes-128-cbc')
+            head = encryptor.decrypt(b'\x00' * 16 + self.recv_buf[4:20] + b'\x00') # need an extra byte or recv empty
+            length = struct.unpack('<H', head[12:14])[0]
+            if len(self.recv_buf) < length:
+                return (b'', False)
+
+            utc_time = struct.unpack('<I', head[:4])[0]
+            client_id = struct.unpack('<I', head[4:8])[0]
+            connection_id = struct.unpack('<I', head[8:12])[0]
+            rnd_len = struct.unpack('<H', head[14:16])[0]
+            if struct.pack('<I', zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) != self.recv_buf[length - 4:length]:
+                logging.info('auth_aes128: checksum error, data %s' % (binascii.hexlify(self.recv_buf[:length]),))
+                return self.not_match_return(self.recv_buf)
+            time_dif = common.int32(utc_time - (int(time.time()) & 0xffffffff))
+            if time_dif < -self.max_time_dif or time_dif > self.max_time_dif:
+                logging.info('auth_aes128: wrong timestamp, time_dif %d, data %s' % (time_dif, binascii.hexlify(head),))
+                return self.not_match_return(self.recv_buf)
+            elif self.server_info.data.insert(client_id, connection_id):
+                self.has_recv_header = True
+                out_buf = self.recv_buf[30 + rnd_len:length - 4]
+                self.client_id = client_id
+                self.connection_id = connection_id
+            else:
+                logging.info('auth_aes128: auth fail, data %s' % (binascii.hexlify(out_buf),))
+                return self.not_match_return(self.recv_buf)
+            self.recv_buf = self.recv_buf[length:]
+            self.has_recv_header = True
+            sendback = True
+
+        while len(self.recv_buf) > 4:
+            crc = struct.pack('<H', binascii.crc32(self.recv_buf[:2]) & 0xFFFF)
+            if crc != self.recv_buf[2:4]:
+                self.raw_trans = True
+                logging.info('auth_aes128: wrong crc')
+                if self.recv_id == 0:
+                    logging.info('auth_aes128: wrong crc')
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            length = struct.unpack('<H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.recv_id == 0:
+                    logging.info('auth_aes128: over size')
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            if length > len(self.recv_buf):
+                break
+
+            if struct.pack('<I', (zlib.adler32(self.recv_buf[:length - 4]) & 0xFFFFFFFF) ^ self.recv_id) != self.recv_buf[length - 4:length]:
+                logging.info('auth_aes128: checksum error, data %s' % (binascii.hexlify(self.recv_buf[:length]),))
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.recv_id == 0:
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data uncorrect checksum')
+
+            self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF
+            pos = common.ord(self.recv_buf[4])
+            if pos < 255:
+                pos += 4
+            else:
+                pos = struct.unpack('<H', self.recv_buf[5:7])[0] + 4
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+            if pos == length - 4:
+                sendback = True
+
+        if out_buf:
+            self.server_info.data.update(self.client_id, self.connection_id)
+        return (out_buf, sendback)
+
+    def client_udp_pre_encrypt(self, buf):
+        return buf + struct.pack('<I', zlib.adler32(buf) & 0xFFFFFFFF)
+
+    def client_udp_post_decrypt(self, buf):
+        length = len(buf)
+        data = buf[:-4]
+        if struct.pack('<I', zlib.adler32(data) & 0xFFFFFFFF) != buf[length - 4:]:
+            return b''
+        return data
+
+    def server_udp_pre_encrypt(self, buf):
+        return buf + struct.pack('<I', zlib.adler32(buf) & 0xFFFFFFFF)
+
+    def server_udp_post_decrypt(self, buf):
+        length = len(buf)
+        data = buf[:-4]
+        if struct.pack('<I', zlib.adler32(data) & 0xFFFFFFFF) != buf[length - 4:]:
+            return (b'', None)
+        return (data, None)
+
+class obfs_auth_mu_data(object):
+    def __init__(self):
+        self.user_id = {}
+        self.local_client_id = b''
+        self.connection_id = 0
+        self.set_max_client(64) # max active client count
+
+    def update(self, user_id, client_id, connection_id):
+        if user_id not in self.user_id:
+            self.user_id[user_id] = lru_cache.LRUCache()
+        local_client_id = self.user_id[user_id]
+
+        if client_id in local_client_id:
+            local_client_id[client_id].update()
+
+    def set_max_client(self, max_client):
+        self.max_client = max_client
+        self.max_buffer = max(self.max_client * 2, 1024)
+
+    def insert(self, user_id, client_id, connection_id):
+        if user_id not in self.user_id:
+            self.user_id[user_id] = lru_cache.LRUCache()
+        local_client_id = self.user_id[user_id]
+
+        if local_client_id.get(client_id, None) is None or not local_client_id[client_id].enable:
+            if local_client_id.first() is None or len(local_client_id) < self.max_client:
+                if client_id not in local_client_id:
+                    #TODO: check
+                    local_client_id[client_id] = client_queue(connection_id)
+                else:
+                    local_client_id[client_id].re_enable(connection_id)
+                return local_client_id[client_id].insert(connection_id)
+
+            if not local_client_id[local_client_id.first()].is_active():
+                del local_client_id[local_client_id.first()]
+                if client_id not in local_client_id:
+                    #TODO: check
+                    local_client_id[client_id] = client_queue(connection_id)
+                else:
+                    local_client_id[client_id].re_enable(connection_id)
+                return local_client_id[client_id].insert(connection_id)
+
+            logging.warn('auth_aes128: no inactive client')
+            return False
+        else:
+            return local_client_id[client_id].insert(connection_id)
+
+class auth_aes128_sha1(auth_base):
+    def __init__(self, method, hashfunc):
+        super(auth_aes128_sha1, self).__init__(method)
+        self.hashfunc = hashfunc
+        self.recv_buf = b''
+        self.unit_len = 8100
+        self.raw_trans = False
+        self.has_sent_header = False
+        self.has_recv_header = False
+        self.client_id = 0
+        self.connection_id = 0
+        self.max_time_dif = 60 * 60 * 24 # time dif (second) setting
+        self.salt = hashfunc == hashlib.md5 and b"auth_aes128_md5" or b"auth_aes128_sha1"
+        self.no_compatible_method = hashfunc == hashlib.md5 and "auth_aes128_md5" or 'auth_aes128_sha1'
+        self.extra_wait_size = struct.unpack('>H', os.urandom(2))[0] % 1024
+        self.pack_id = 1
+        self.recv_id = 1
+        self.user_id = None
+        self.user_key = None
+
+    def init_data(self):
+        return obfs_auth_mu_data()
+
+    def set_server_info(self, server_info):
+        self.server_info = server_info
+        try:
+            max_client = int(server_info.protocol_param.split('#')[0])
+        except:
+            max_client = 64
+        self.server_info.data.set_max_client(max_client)
+
+    def rnd_data(self, buf_size):
+        if buf_size > 1200:
+            return b'\x01'
+
+        if self.pack_id > 4:
+            rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 32)
+        elif buf_size > 900:
+            rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 128)
+        else:
+            rnd_data = os.urandom(struct.unpack('>H', os.urandom(2))[0] % 512)
+
+        if len(rnd_data) < 128:
+            return common.chr(len(rnd_data) + 1) + rnd_data
+        else:
+            return common.chr(255) + struct.pack('<H', len(rnd_data) + 3) + rnd_data
+
+    def pack_data(self, buf):
+        data = self.rnd_data(len(buf)) + buf
+        data_len = len(data) + 8
+        mac_key = self.user_key + struct.pack('<I', self.pack_id)
+        mac = hmac.new(mac_key, struct.pack('<H', data_len), self.hashfunc).digest()[:2]
+        data = struct.pack('<H', data_len) + mac + data
+        data += hmac.new(mac_key, data, self.hashfunc).digest()[:4]
+        self.pack_id = (self.pack_id + 1) & 0xFFFFFFFF
+        return data
+
+    def pack_auth_data(self, auth_data, buf):
+        if len(buf) == 0:
+            return b''
+        if len(buf) > 400:
+            rnd_len = struct.unpack('<H', os.urandom(2))[0] % 512
+        else:
+            rnd_len = struct.unpack('<H', os.urandom(2))[0] % 1024
+        data = auth_data
+        data_len = 7 + 4 + 16 + 4 + len(buf) + rnd_len + 4
+        data = data + struct.pack('<H', data_len) + struct.pack('<H', rnd_len)
+        mac_key = self.server_info.iv + self.server_info.key
+        uid = os.urandom(4)
+        if b':' in to_bytes(self.server_info.protocol_param):
+            try:
+                items = to_bytes(self.server_info.protocol_param).split(b':')
+                self.user_key = self.hashfunc(items[1]).digest()
+                uid = struct.pack('<I', int(items[0]))
+            except:
+                pass
+        if self.user_key is None:
+            self.user_key = self.server_info.key
+        encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc', b'\x00' * 16)
+        data = uid + encryptor.encrypt(data)[16:]
+        data += hmac.new(mac_key, data, self.hashfunc).digest()[:4]
+        check_head = os.urandom(1)
+        check_head += hmac.new(mac_key, check_head, self.hashfunc).digest()[:6]
+        data = check_head + data + os.urandom(rnd_len) + buf
+        data += hmac.new(self.user_key, data, self.hashfunc).digest()[:4]
+        return data
+
+    def auth_data(self):
+        utc_time = int(time.time()) & 0xFFFFFFFF
+        if self.server_info.data.connection_id > 0xFF000000:
+            self.server_info.data.local_client_id = b''
+        if not self.server_info.data.local_client_id:
+            self.server_info.data.local_client_id = os.urandom(4)
+            logging.debug("local_client_id %s" % (binascii.hexlify(self.server_info.data.local_client_id),))
+            self.server_info.data.connection_id = struct.unpack('<I', os.urandom(4))[0] & 0xFFFFFF
+        self.server_info.data.connection_id += 1
+        return b''.join([struct.pack('<I', utc_time),
+                self.server_info.data.local_client_id,
+                struct.pack('<I', self.server_info.data.connection_id)])
+
+    def client_pre_encrypt(self, buf):
+        ret = b''
+        if not self.has_sent_header:
+            head_size = self.get_head_size(buf, 30)
+            datalen = min(len(buf), random.randint(0, 31) + head_size)
+            ret += self.pack_auth_data(self.auth_data(), buf[:datalen])
+            buf = buf[datalen:]
+            self.has_sent_header = True
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def client_post_decrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 4:
+            mac_key = self.user_key + struct.pack('<I', self.recv_id)
+            mac = hmac.new(mac_key, self.recv_buf[:2], self.hashfunc).digest()[:2]
+            if mac != self.recv_buf[2:4]:
+                raise Exception('client_post_decrypt data uncorrect mac')
+            length = struct.unpack('<H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data error')
+            if length > len(self.recv_buf):
+                break
+
+            if hmac.new(mac_key, self.recv_buf[:length - 4], self.hashfunc).digest()[:4] != self.recv_buf[length - 4:length]:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data uncorrect checksum')
+
+            self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF
+            pos = common.ord(self.recv_buf[4])
+            if pos < 255:
+                pos += 4
+            else:
+                pos = struct.unpack('<H', self.recv_buf[5:7])[0] + 4
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+
+        return out_buf
+
+    def server_pre_encrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def server_post_decrypt(self, buf):
+        if self.raw_trans:
+            return (buf, False)
+        self.recv_buf += buf
+        out_buf = b''
+        sendback = False
+
+        if not self.has_recv_header:
+            if len(self.recv_buf) < 7:
+                return (b'', False)
+            mac_key = self.server_info.recv_iv + self.server_info.key
+            sha1data = hmac.new(mac_key, self.recv_buf[:1], self.hashfunc).digest()[:6]
+            if sha1data != self.recv_buf[1:7]:
+                return self.not_match_return(self.recv_buf)
+
+            if len(self.recv_buf) < 31:
+                return (b'', False)
+            sha1data = hmac.new(mac_key, self.recv_buf[7:27], self.hashfunc).digest()[:4]
+            if sha1data != self.recv_buf[27:31]:
+                logging.error('%s data uncorrect auth HMAC-SHA1 from %s:%d, data %s' % (self.no_compatible_method, self.server_info.client, self.server_info.client_port, binascii.hexlify(self.recv_buf)))
+                if len(self.recv_buf) < 31 + self.extra_wait_size:
+                    return (b'', False)
+                return self.not_match_return(self.recv_buf)
+
+            uid = self.recv_buf[7:11]
+            if uid in self.server_info.users:
+                self.user_id = uid
+                self.user_key = self.hashfunc(self.server_info.users[uid]).digest()
+                self.server_info.update_user_func(uid)
+            else:
+                if not self.server_info.users:
+                    self.user_key = self.server_info.key
+                else:
+                    self.user_key = self.server_info.recv_iv
+            encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc')
+            head = encryptor.decrypt(b'\x00' * 16 + self.recv_buf[11:27] + b'\x00') # need an extra byte or recv empty
+            length = struct.unpack('<H', head[12:14])[0]
+            if len(self.recv_buf) < length:
+                return (b'', False)
+
+            utc_time = struct.unpack('<I', head[:4])[0]
+            client_id = struct.unpack('<I', head[4:8])[0]
+            connection_id = struct.unpack('<I', head[8:12])[0]
+            rnd_len = struct.unpack('<H', head[14:16])[0]
+            if hmac.new(self.user_key, self.recv_buf[:length - 4], self.hashfunc).digest()[:4] != self.recv_buf[length - 4:length]:
+                logging.info('%s: checksum error, data %s' % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length])))
+                return self.not_match_return(self.recv_buf)
+            time_dif = common.int32(utc_time - (int(time.time()) & 0xffffffff))
+            if time_dif < -self.max_time_dif or time_dif > self.max_time_dif:
+                logging.info('%s: wrong timestamp, time_dif %d, data %s' % (self.no_compatible_method, time_dif, binascii.hexlify(head)))
+                return self.not_match_return(self.recv_buf)
+            elif self.server_info.data.insert(self.user_id, client_id, connection_id):
+                self.has_recv_header = True
+                out_buf = self.recv_buf[31 + rnd_len:length - 4]
+                self.client_id = client_id
+                self.connection_id = connection_id
+            else:
+                logging.info('%s: auth fail, data %s' % (self.no_compatible_method, binascii.hexlify(out_buf)))
+                return self.not_match_return(self.recv_buf)
+            self.recv_buf = self.recv_buf[length:]
+            self.has_recv_header = True
+            sendback = True
+
+        while len(self.recv_buf) > 4:
+            mac_key = self.user_key + struct.pack('<I', self.recv_id)
+            mac = hmac.new(mac_key, self.recv_buf[:2], self.hashfunc).digest()[:2]
+            if mac != self.recv_buf[2:4]:
+                self.raw_trans = True
+                logging.info(self.no_compatible_method + ': wrong crc')
+                if self.recv_id == 0:
+                    logging.info(self.no_compatible_method + ': wrong crc')
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            length = struct.unpack('<H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.recv_id == 0:
+                    logging.info(self.no_compatible_method + ': over size')
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            if length > len(self.recv_buf):
+                break
+
+            if hmac.new(mac_key, self.recv_buf[:length - 4], self.hashfunc).digest()[:4] != self.recv_buf[length - 4:length]:
+                logging.info('%s: checksum error, data %s' % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length])))
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.recv_id == 0:
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data uncorrect checksum')
+
+            self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF
+            pos = common.ord(self.recv_buf[4])
+            if pos < 255:
+                pos += 4
+            else:
+                pos = struct.unpack('<H', self.recv_buf[5:7])[0] + 4
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+            if pos == length - 4:
+                sendback = True
+
+        if out_buf:
+            self.server_info.data.update(self.user_id, self.client_id, self.connection_id)
+        return (out_buf, sendback)
+
+    def client_udp_pre_encrypt(self, buf):
+        if self.user_key is None:
+            if b':' in to_bytes(self.server_info.protocol_param):
+                try:
+                    items = to_bytes(self.server_info.protocol_param).split(':')
+                    self.user_key = self.hashfunc(items[1]).digest()
+                    self.user_id = struct.pack('<I', int(items[0]))
+                except:
+                    pass
+            if self.user_key is None:
+                self.user_id = os.urandom(4)
+                self.user_key = self.server_info.key
+        buf += self.user_id
+        return buf + hmac.new(user_key, buf, self.hashfunc).digest()[:4]
+
+    def client_udp_post_decrypt(self, buf):
+        user_key = self.server_info.key
+        if hmac.new(user_key, buf[:-4], self.hashfunc).digest()[:4] != buf[-4:]:
+            return b''
+        return buf[:-4]
+
+    def server_udp_pre_encrypt(self, buf):
+        user_key = self.server_info.key
+        return buf + hmac.new(user_key, buf, self.hashfunc).digest()[:4]
+
+    def server_udp_post_decrypt(self, buf):
+        uid = buf[-8:-4]
+        if uid in self.server_info.users:
+            user_key = self.hashfunc(self.server_info.users[uid]).digest()
+        else:
+            uid = None
+            if not self.server_info.users:
+                user_key = self.server_info.key
+            else:
+                user_key = self.server_info.recv_iv
+        if hmac.new(user_key, buf[:-4], self.hashfunc).digest()[:4] != buf[-4:]:
+            return (b'', None)
+        return (buf[:-8], uid)
+

BIN
ssr/shadowsocks/obfsplugin/auth.pyc


+ 309 - 0
ssr/shadowsocks/obfsplugin/http_simple.py

@@ -0,0 +1,309 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2015 breakwa11
+#
+# 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
+import binascii
+import struct
+import base64
+import datetime
+import random
+
+from shadowsocks import common
+from shadowsocks.obfsplugin import plain
+from shadowsocks.common import to_bytes, to_str, ord, chr
+
+def create_http_simple_obfs(method):
+    return http_simple(method)
+
+def create_http_post_obfs(method):
+    return http_post(method)
+
+def create_random_head_obfs(method):
+    return random_head(method)
+
+obfs_map = {
+        'http_simple': (create_http_simple_obfs,),
+        'http_simple_compatible': (create_http_simple_obfs,),
+        'http_post': (create_http_post_obfs,),
+        'http_post_compatible': (create_http_post_obfs,),
+        'random_head': (create_random_head_obfs,),
+        'random_head_compatible': (create_random_head_obfs,),
+}
+
+def match_begin(str1, str2):
+    if len(str1) >= len(str2):
+        if str1[:len(str2)] == str2:
+            return True
+    return False
+
+class http_simple(plain.plain):
+    def __init__(self, method):
+        self.method = method
+        self.has_sent_header = False
+        self.has_recv_header = False
+        self.host = None
+        self.port = 0
+        self.recv_buffer = b''
+        self.user_agent = [b"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0",
+            b"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0",
+            b"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
+            b"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36",
+            b"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0",
+            b"Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)",
+            b"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
+            b"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)",
+            b"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
+            b"Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/BuildID) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36",
+            b"Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
+            b"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"]
+
+    def encode_head(self, buf):
+        hexstr = binascii.hexlify(buf)
+        chs = []
+        for i in range(0, len(hexstr), 2):
+            chs.append(b"%" + hexstr[i:i+2])
+        return b''.join(chs)
+
+    def client_encode(self, buf):
+        if self.has_sent_header:
+            return buf
+        head_size = len(self.server_info.iv) + self.server_info.head_len
+        if len(buf) - head_size > 64:
+            headlen = head_size + random.randint(0, 64)
+        else:
+            headlen = len(buf)
+        headdata = buf[:headlen]
+        buf = buf[headlen:]
+        port = b''
+        if self.server_info.port != 80:
+            port = b':' + to_bytes(str(self.server_info.port))
+        body = None
+        hosts = (self.server_info.obfs_param or self.server_info.host)
+        pos = hosts.find("#")
+        if pos >= 0:
+            body = hosts[pos + 1:].replace("\n", "\r\n")
+            body = body.replace("\\n", "\r\n")
+            hosts = hosts[:pos]
+        hosts = hosts.split(',')
+        host = random.choice(hosts)
+        http_head = b"GET /" + self.encode_head(headdata) + b" HTTP/1.1\r\n"
+        http_head += b"Host: " + to_bytes(host) + port + b"\r\n"
+        if body:
+            http_head += body + "\r\n\r\n"
+        else:
+            http_head += b"User-Agent: " + random.choice(self.user_agent) + b"\r\n"
+            http_head += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\nDNT: 1\r\nConnection: keep-alive\r\n\r\n"
+        self.has_sent_header = True
+        return http_head + buf
+
+    def client_decode(self, buf):
+        if self.has_recv_header:
+            return (buf, False)
+        pos = buf.find(b'\r\n\r\n')
+        if pos >= 0:
+            self.has_recv_header = True
+            return (buf[pos + 4:], False)
+        else:
+            return (b'', False)
+
+    def server_encode(self, buf):
+        if self.has_sent_header:
+            return buf
+
+        header = b'HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Encoding: gzip\r\nContent-Type: text/html\r\nDate: '
+        header += to_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT'))
+        header += b'\r\nServer: nginx\r\nVary: Accept-Encoding\r\n\r\n'
+        self.has_sent_header = True
+        return header + buf
+
+    def get_data_from_http_header(self, buf):
+        ret_buf = b''
+        lines = buf.split(b'\r\n')
+        if lines and len(lines) > 1:
+            hex_items = lines[0].split(b'%')
+            if hex_items and len(hex_items) > 1:
+                for index in range(1, len(hex_items)):
+                    if len(hex_items[index]) < 2:
+                        ret_buf += binascii.unhexlify('0' + hex_items[index])
+                        break
+                    elif len(hex_items[index]) > 2:
+                        ret_buf += binascii.unhexlify(hex_items[index][:2])
+                        break
+                    else:
+                        ret_buf += binascii.unhexlify(hex_items[index])
+                return ret_buf
+        return b''
+
+    def get_host_from_http_header(self, buf):
+        ret_buf = b''
+        lines = buf.split(b'\r\n')
+        if lines and len(lines) > 1:
+            for line in lines:
+                if match_begin(line, b"Host: "):
+                    return line[6:]
+
+    def not_match_return(self, buf):
+        self.has_sent_header = True
+        self.has_recv_header = True
+        if self.method == 'http_simple':
+            return (b'E'*2048, False, False)
+        return (buf, True, False)
+
+    def server_decode(self, buf):
+        if self.has_recv_header:
+            return (buf, True, False)
+
+        self.recv_buffer += buf
+        buf = self.recv_buffer
+        if len(buf) > 10:
+            if match_begin(buf, b'GET ') or match_begin(buf, b'POST '):
+                if len(buf) > 65536:
+                    self.recv_buffer = None
+                    logging.warn('http_simple: over size')
+                    return self.not_match_return(buf)
+            else: #not http header, run on original protocol
+                self.recv_buffer = None
+                logging.debug('http_simple: not match begin')
+                return self.not_match_return(buf)
+        else:
+            return (b'', True, False)
+
+        if b'\r\n\r\n' in buf:
+            datas = buf.split(b'\r\n\r\n', 1)
+            ret_buf = self.get_data_from_http_header(buf)
+            host = self.get_host_from_http_header(buf)
+            if host and self.server_info.obfs_param:
+                pos = host.find(":")
+                if pos >= 0:
+                    host = host[:pos]
+                hosts = self.server_info.obfs_param.split(',')
+                if host not in hosts:
+                    return self.not_match_return(buf)
+            if len(ret_buf) < 4:
+                return self.not_match_return(buf)
+            if len(datas) > 1:
+                ret_buf += datas[1]
+            if len(ret_buf) >= 7:
+                self.has_recv_header = True
+                return (ret_buf, True, False)
+            return self.not_match_return(buf)
+        else:
+            return (b'', True, False)
+
+class http_post(http_simple):
+    def __init__(self, method):
+        super(http_post, self).__init__(method)
+
+    def boundary(self):
+        return b''.join([random.choice(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") for i in range(32)])
+
+    def client_encode(self, buf):
+        if self.has_sent_header:
+            return buf
+        head_size = len(self.server_info.iv) + self.server_info.head_len
+        if len(buf) - head_size > 64:
+            headlen = head_size + random.randint(0, 64)
+        else:
+            headlen = len(buf)
+        headdata = buf[:headlen]
+        buf = buf[headlen:]
+        port = b''
+        if self.server_info.port != 80:
+            port = b':' + to_bytes(str(self.server_info.port))
+        body = None
+        hosts = (self.server_info.obfs_param or self.server_info.host)
+        pos = hosts.find("#")
+        if pos >= 0:
+            body = hosts[pos + 1:].replace("\\n", "\r\n")
+            hosts = hosts[:pos]
+        hosts = hosts.split(',')
+        host = random.choice(hosts)
+        http_head = b"POST /" + self.encode_head(headdata) + b" HTTP/1.1\r\n"
+        http_head += b"Host: " + to_bytes(host) + port + b"\r\n"
+        if body:
+            http_head += body + "\r\n\r\n"
+        else:
+            http_head += b"User-Agent: " + random.choice(self.user_agent) + b"\r\n"
+            http_head += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n"
+            http_head += b"Content-Type: multipart/form-data; boundary=" + self.boundary() + b"\r\nDNT: 1\r\n"
+            http_head += "Connection: keep-alive\r\n\r\n"
+        self.has_sent_header = True
+        return http_head + buf
+
+    def not_match_return(self, buf):
+        self.has_sent_header = True
+        self.has_recv_header = True
+        if self.method == 'http_post':
+            return (b'E'*2048, False, False)
+        return (buf, True, False)
+
+class random_head(plain.plain):
+    def __init__(self, method):
+        self.method = method
+        self.has_sent_header = False
+        self.has_recv_header = False
+        self.raw_trans_sent = False
+        self.raw_trans_recv = False
+        self.send_buffer = b''
+
+    def client_encode(self, buf):
+        if self.raw_trans_sent:
+            return buf
+        self.send_buffer += buf
+        if not self.has_sent_header:
+            self.has_sent_header = True
+            data = os.urandom(common.ord(os.urandom(1)[0]) % 96 + 4)
+            crc = (0xffffffff - binascii.crc32(data)) & 0xffffffff
+            return data + struct.pack('<I', crc)
+        if self.raw_trans_recv:
+            ret = self.send_buffer
+            self.send_buffer = b''
+            self.raw_trans_sent = True
+            return ret
+        return b''
+
+    def client_decode(self, buf):
+        if self.raw_trans_recv:
+            return (buf, False)
+        self.raw_trans_recv = True
+        return (b'', True)
+
+    def server_encode(self, buf):
+        if self.has_sent_header:
+            return buf
+        self.has_sent_header = True
+        return os.urandom(common.ord(os.urandom(1)[0]) % 96 + 4)
+
+    def server_decode(self, buf):
+        if self.has_recv_header:
+            return (buf, True, False)
+
+        self.has_recv_header = True
+        crc = binascii.crc32(buf) & 0xffffffff
+        if crc != 0xffffffff:
+            self.has_sent_header = True
+            if self.method == 'random_head':
+                return (b'E'*2048, False, False)
+            return (buf, True, False)
+        # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back)
+        return (b'', False, True)
+

BIN
ssr/shadowsocks/obfsplugin/http_simple.pyc


+ 277 - 0
ssr/shadowsocks/obfsplugin/obfs_tls.py

@@ -0,0 +1,277 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2015 breakwa11
+#
+# 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
+import binascii
+import struct
+import base64
+import time
+import random
+import hmac
+import hashlib
+import string
+
+from shadowsocks import common
+from shadowsocks.obfsplugin import plain
+from shadowsocks.common import to_bytes, to_str, ord
+from shadowsocks import lru_cache
+
+def create_tls_ticket_auth_obfs(method):
+    return tls_ticket_auth(method)
+
+obfs_map = {
+        'tls1.2_ticket_auth': (create_tls_ticket_auth_obfs,),
+        'tls1.2_ticket_auth_compatible': (create_tls_ticket_auth_obfs,),
+}
+
+def match_begin(str1, str2):
+    if len(str1) >= len(str2):
+        if str1[:len(str2)] == str2:
+            return True
+    return False
+
+class obfs_auth_data(object):
+    def __init__(self):
+        self.client_data = lru_cache.LRUCache(60 * 5)
+        self.client_id = os.urandom(32)
+        self.startup_time = int(time.time() - 60 * 30) & 0xFFFFFFFF
+
+class tls_ticket_auth(plain.plain):
+    def __init__(self, method):
+        self.method = method
+        self.handshake_status = 0
+        self.send_buffer = b''
+        self.recv_buffer = b''
+        self.client_id = b''
+        self.max_time_dif = 60 * 60 * 24 # time dif (second) setting
+        self.tls_version = b'\x03\x03'
+
+    def init_data(self):
+        return obfs_auth_data()
+
+    def sni(self, url):
+        url = common.to_bytes(url)
+        data = b"\x00" + struct.pack('>H', len(url)) + url
+        data = b"\x00\x00" + struct.pack('>H', len(data) + 2) + struct.pack('>H', len(data)) + data
+        return data
+
+    def pack_auth_data(self, client_id):
+        utc_time = int(time.time()) & 0xFFFFFFFF
+        data = struct.pack('>I', utc_time) + os.urandom(18)
+        data += hmac.new(self.server_info.key + client_id, data, hashlib.sha1).digest()[:10]
+        return data
+
+    def client_encode(self, buf):
+        if self.handshake_status == -1:
+            return buf
+        if self.handshake_status == 8:
+            ret = b''
+            while len(buf) > 2048:
+                size = min(struct.unpack('>H', os.urandom(2))[0] % 4096 + 100, len(buf))
+                ret += b"\x17" + self.tls_version + struct.pack('>H', size) + buf[:size]
+                buf = buf[size:]
+            if len(buf) > 0:
+                ret += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf
+            return ret
+        self.send_buffer += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf
+        if self.handshake_status == 0:
+            self.handshake_status = 1
+            data = self.tls_version + self.pack_auth_data(self.server_info.data.client_id) + b"\x20" + self.server_info.data.client_id + binascii.unhexlify(b"001cc02bc02fcca9cca8cc14cc13c00ac014c009c013009c0035002f000a" + b"0100")
+            ext = binascii.unhexlify(b"ff01000100")
+            host = self.server_info.obfs_param or self.server_info.host
+            if host and host[-1] in string.digits:
+                host = ''
+            hosts = host.split(',')
+            host = random.choice(hosts)
+            ext += self.sni(host)
+            ext += b"\x00\x17\x00\x00"
+            ext += b"\x00\x23\x00\xd0" + os.urandom(208) # ticket
+            ext += binascii.unhexlify(b"000d001600140601060305010503040104030301030302010203")
+            ext += binascii.unhexlify(b"000500050100000000")
+            ext += binascii.unhexlify(b"00120000")
+            ext += binascii.unhexlify(b"75500000")
+            ext += binascii.unhexlify(b"000b00020100")
+            ext += binascii.unhexlify(b"000a0006000400170018")
+            data += struct.pack('>H', len(ext)) + ext
+            data = b"\x01\x00" + struct.pack('>H', len(data)) + data
+            data = b"\x16\x03\x01" + struct.pack('>H', len(data)) + data
+            return data
+        elif self.handshake_status == 1 and len(buf) == 0:
+            data = b"\x14" + self.tls_version + b"\x00\x01\x01" #ChangeCipherSpec
+            data += b"\x16" + self.tls_version + b"\x00\x20" + os.urandom(22) #Finished
+            data += hmac.new(self.server_info.key + self.server_info.data.client_id, data, hashlib.sha1).digest()[:10]
+            ret = data + self.send_buffer
+            self.send_buffer = b''
+            self.handshake_status = 8
+            return ret
+        return b''
+
+    def client_decode(self, buf):
+        if self.handshake_status == -1:
+            return (buf, False)
+
+        if self.handshake_status == 8:
+            ret = b''
+            self.recv_buffer += buf
+            while len(self.recv_buffer) > 5:
+                if ord(self.recv_buffer[0]) != 0x17:
+                    logging.info("data = %s" % (binascii.hexlify(self.recv_buffer)))
+                    raise Exception('server_decode appdata error')
+                size = struct.unpack('>H', self.recv_buffer[3:5])[0]
+                if len(self.recv_buffer) < size + 5:
+                    break
+                buf = self.recv_buffer[5:size+5]
+                ret += buf
+                self.recv_buffer = self.recv_buffer[size+5:]
+            return (ret, False)
+
+        if len(buf) < 11 + 32 + 1 + 32:
+            raise Exception('client_decode data error')
+        verify = buf[11:33]
+        if hmac.new(self.server_info.key + self.server_info.data.client_id, verify, hashlib.sha1).digest()[:10] != buf[33:43]:
+            raise Exception('client_decode data error')
+        return (b'', True)
+
+    def server_encode(self, buf):
+        if self.handshake_status == -1:
+            return buf
+        if self.handshake_status == 8:
+            ret = b''
+            while len(buf) > 2048:
+                size = min(struct.unpack('>H', os.urandom(2))[0] % 4096 + 100, len(buf))
+                ret += b"\x17" + self.tls_version + struct.pack('>H', size) + buf[:size]
+                buf = buf[size:]
+            if len(buf) > 0:
+                ret += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf
+            return ret
+        self.handshake_status = 3
+        data = self.tls_version + self.pack_auth_data(self.client_id) + b"\x20" + self.client_id + binascii.unhexlify(b"c02f000005ff01000100")
+        data = b"\x02\x00" + struct.pack('>H', len(data)) + data #server hello
+        data = b"\x16\x03\x03" + struct.pack('>H', len(data)) + data
+        data += b"\x14" + self.tls_version + b"\x00\x01\x01" #ChangeCipherSpec
+        data += b"\x16" + self.tls_version + b"\x00\x20" + os.urandom(22) #Finished
+        data += hmac.new(self.server_info.key + self.client_id, data, hashlib.sha1).digest()[:10]
+        return data
+
+    def decode_error_return(self, buf):
+        self.handshake_status = -1
+        if self.method == 'tls1.2_ticket_auth':
+            return (b'E'*2048, False, False)
+        return (buf, True, False)
+
+    def server_decode(self, buf):
+        if self.handshake_status == -1:
+            return (buf, True, False)
+
+        if self.handshake_status == 8:
+            ret = b''
+            self.recv_buffer += buf
+            while len(self.recv_buffer) > 5:
+                if ord(self.recv_buffer[0]) != 0x17 or ord(self.recv_buffer[1]) != 0x3 or ord(self.recv_buffer[2]) != 0x3:
+                    logging.info("data = %s" % (binascii.hexlify(self.recv_buffer)))
+                    raise Exception('server_decode appdata error')
+                size = struct.unpack('>H', self.recv_buffer[3:5])[0]
+                if len(self.recv_buffer) < size + 5:
+                    break
+                ret += self.recv_buffer[5:size+5]
+                self.recv_buffer = self.recv_buffer[size+5:]
+            return (ret, True, False)
+
+        if self.handshake_status == 3:
+            self.recv_buffer += buf
+            buf = self.recv_buffer
+            verify = buf
+            verify_len = 43 - 10
+            if len(buf) < 43:
+                raise Exception('server_decode data error')
+            if not match_begin(buf, b"\x14" + self.tls_version + b"\x00\x01\x01"): #ChangeCipherSpec
+                raise Exception('server_decode data error')
+            buf = buf[6:]
+            if not match_begin(buf, b"\x16" + self.tls_version + b"\x00\x20"): #Finished
+                raise Exception('server_decode data error')
+            if hmac.new(self.server_info.key + self.client_id, verify[:verify_len], hashlib.sha1).digest()[:10] != verify[verify_len:verify_len+10]:
+                raise Exception('server_decode data error')
+            if len(buf) < 37:
+                raise Exception('server_decode data error')
+            self.recv_buffer = buf[37:]
+            self.handshake_status = 8
+            return self.server_decode(b'')
+
+        #raise Exception("handshake data = %s" % (binascii.hexlify(buf)))
+        self.recv_buffer += buf
+        buf = self.recv_buffer
+        ogn_buf = buf
+        if len(buf) < 3:
+            return (b'', False, False)
+        if not match_begin(buf, b'\x16\x03\x01'):
+            return self.decode_error_return(ogn_buf)
+        buf = buf[3:]
+        if struct.unpack('>H', buf[:2])[0] > len(buf) - 2:
+            return (b'', False, False)
+
+        self.recv_buffer = self.recv_buffer[struct.unpack('>H', buf[:2])[0] + 5:]
+        self.handshake_status = 2
+        buf = buf[2:]
+        if not match_begin(buf, b'\x01\x00'): #client hello
+            logging.info("tls_auth not client hello message")
+            return self.decode_error_return(ogn_buf)
+        buf = buf[2:]
+        if struct.unpack('>H', buf[:2])[0] != len(buf) - 2:
+            logging.info("tls_auth wrong message size")
+            return self.decode_error_return(ogn_buf)
+        buf = buf[2:]
+        if not match_begin(buf, self.tls_version):
+            logging.info("tls_auth wrong tls version")
+            return self.decode_error_return(ogn_buf)
+        buf = buf[2:]
+        verifyid = buf[:32]
+        buf = buf[32:]
+        sessionid_len = ord(buf[0])
+        if sessionid_len < 32:
+            logging.info("tls_auth wrong sessionid_len")
+            return self.decode_error_return(ogn_buf)
+        sessionid = buf[1:sessionid_len + 1]
+        buf = buf[sessionid_len+1:]
+        self.client_id = sessionid
+        sha1 = hmac.new(self.server_info.key + sessionid, verifyid[:22], hashlib.sha1).digest()[:10]
+        utc_time = struct.unpack('>I', verifyid[:4])[0]
+        time_dif = common.int32((int(time.time()) & 0xffffffff) - utc_time)
+        if self.server_info.obfs_param:
+            try:
+                self.max_time_dif = int(self.server_info.obfs_param)
+            except:
+                pass
+        if self.max_time_dif > 0 and (time_dif < -self.max_time_dif or time_dif > self.max_time_dif \
+                or common.int32(utc_time - self.server_info.data.startup_time) < -self.max_time_dif / 2):
+            logging.info("tls_auth wrong time")
+            return self.decode_error_return(ogn_buf)
+        if sha1 != verifyid[22:]:
+            logging.info("tls_auth wrong sha1")
+            return self.decode_error_return(ogn_buf)
+        if self.server_info.data.client_data.get(verifyid[:22]):
+            logging.info("replay attack detect, id = %s" % (binascii.hexlify(verifyid)))
+            return self.decode_error_return(ogn_buf)
+        self.server_info.data.client_data.sweep()
+        self.server_info.data.client_data[verifyid[:22]] = sessionid
+        # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back)
+        return (b'', False, True)
+

BIN
ssr/shadowsocks/obfsplugin/obfs_tls.pyc


+ 101 - 0
ssr/shadowsocks/obfsplugin/plain.py

@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2015 breakwa11
+#
+# 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.common import ord
+
+def create_obfs(method):
+    return plain(method)
+
+obfs_map = {
+        'plain': (create_obfs,),
+        'origin': (create_obfs,),
+}
+
+class plain(object):
+    def __init__(self, method):
+        self.method = method
+        self.server_info = None
+
+    def init_data(self):
+        return b''
+
+    def get_server_info(self):
+        return self.server_info
+
+    def set_server_info(self, server_info):
+        self.server_info = server_info
+
+    def client_pre_encrypt(self, buf):
+        return buf
+
+    def client_encode(self, buf):
+        return buf
+
+    def client_decode(self, buf):
+        # (buffer_to_recv, is_need_to_encode_and_send_back)
+        return (buf, False)
+
+    def client_post_decrypt(self, buf):
+        return buf
+
+    def server_pre_encrypt(self, buf):
+        return buf
+
+    def server_encode(self, buf):
+        return buf
+
+    def server_decode(self, buf):
+        # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back)
+        return (buf, True, False)
+
+    def server_post_decrypt(self, buf):
+        return (buf, False)
+
+    def client_udp_pre_encrypt(self, buf):
+        return buf
+
+    def client_udp_post_decrypt(self, buf):
+        return buf
+
+    def server_udp_pre_encrypt(self, buf):
+        return buf
+
+    def server_udp_post_decrypt(self, buf):
+        return (buf, None)
+
+    def dispose(self):
+        pass
+
+    def get_head_size(self, buf, def_value):
+        if len(buf) < 2:
+            return def_value
+        head_type = ord(buf[0]) & 0x7
+        if head_type == 1:
+            return 7
+        if head_type == 4:
+            return 19
+        if head_type == 3:
+            return 4 + ord(buf[1])
+        return def_value
+

BIN
ssr/shadowsocks/obfsplugin/plain.pyc


+ 360 - 0
ssr/shadowsocks/obfsplugin/verify.py

@@ -0,0 +1,360 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2015 breakwa11
+#
+# 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
+import binascii
+import base64
+import time
+import datetime
+import random
+import struct
+import zlib
+import hmac
+import hashlib
+
+import shadowsocks
+from shadowsocks import common
+from shadowsocks.obfsplugin import plain
+from shadowsocks.common import to_bytes, to_str, ord, chr
+
+def create_verify_obfs(method):
+    return verify_simple(method)
+
+def create_verify_deflate(method):
+    return verify_deflate(method)
+
+def create_verify_sha1(method):
+    return verify_sha1(method)
+
+def create_auth_obfs(method):
+    return auth_simple(method)
+
+obfs_map = {
+        'verify_simple': (create_verify_obfs,),
+        'verify_deflate': (create_verify_deflate,),
+        'verify_sha1': (create_verify_sha1,),
+        'verify_sha1_compatible': (create_verify_sha1,),
+}
+
+def match_begin(str1, str2):
+    if len(str1) >= len(str2):
+        if str1[:len(str2)] == str2:
+            return True
+    return False
+
+class obfs_verify_data(object):
+    def __init__(self):
+        pass
+
+class verify_base(plain.plain):
+    def __init__(self, method):
+        super(verify_base, self).__init__(method)
+        self.method = method
+
+    def init_data(self):
+        return obfs_verify_data()
+
+    def set_server_info(self, server_info):
+        self.server_info = server_info
+
+    def client_encode(self, buf):
+        return buf
+
+    def client_decode(self, buf):
+        return (buf, False)
+
+    def server_encode(self, buf):
+        return buf
+
+    def server_decode(self, buf):
+        return (buf, True, False)
+
+class verify_simple(verify_base):
+    def __init__(self, method):
+        super(verify_simple, self).__init__(method)
+        self.recv_buf = b''
+        self.unit_len = 8100
+        self.decrypt_packet_num = 0
+        self.raw_trans = False
+
+    def pack_data(self, buf):
+        if len(buf) == 0:
+            return b''
+        rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 16)
+        data = common.chr(len(rnd_data) + 1) + rnd_data + buf
+        data = struct.pack('>H', len(data) + 6) + data
+        crc = (0xffffffff - binascii.crc32(data)) & 0xffffffff
+        data += struct.pack('<I', crc)
+        return data
+
+    def client_pre_encrypt(self, buf):
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def client_post_decrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data error')
+            if length > len(self.recv_buf):
+                break
+
+            if (binascii.crc32(self.recv_buf[:length]) & 0xffffffff) != 0xffffffff:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data uncorrect CRC32')
+
+            pos = common.ord(self.recv_buf[2]) + 2
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+
+        if out_buf:
+            self.decrypt_packet_num += 1
+        return out_buf
+
+    def server_pre_encrypt(self, buf):
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def server_post_decrypt(self, buf):
+        if self.raw_trans:
+            return (buf, False)
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 8192 or length < 7:
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            if length > len(self.recv_buf):
+                break
+
+            if (binascii.crc32(self.recv_buf[:length]) & 0xffffffff) != 0xffffffff:
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data uncorrect CRC32')
+
+            pos = common.ord(self.recv_buf[2]) + 2
+            out_buf += self.recv_buf[pos:length - 4]
+            self.recv_buf = self.recv_buf[length:]
+
+        if out_buf:
+            self.decrypt_packet_num += 1
+        return (out_buf, False)
+
+class verify_deflate(verify_base):
+    def __init__(self, method):
+        super(verify_deflate, self).__init__(method)
+        self.recv_buf = b''
+        self.unit_len = 32700
+        self.decrypt_packet_num = 0
+        self.raw_trans = False
+
+    def pack_data(self, buf):
+        if len(buf) == 0:
+            return b''
+        data = zlib.compress(buf)
+        data = struct.pack('>H', len(data)) + data[2:]
+        return data
+
+    def client_pre_encrypt(self, buf):
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def client_post_decrypt(self, buf):
+        if self.raw_trans:
+            return buf
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 32768 or length < 6:
+                self.raw_trans = True
+                self.recv_buf = b''
+                raise Exception('client_post_decrypt data error')
+            if length > len(self.recv_buf):
+                break
+
+            out_buf += zlib.decompress(b'x\x9c' + self.recv_buf[2:length])
+            self.recv_buf = self.recv_buf[length:]
+
+        if out_buf:
+            self.decrypt_packet_num += 1
+        return out_buf
+
+    def server_pre_encrypt(self, buf):
+        ret = b''
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def server_post_decrypt(self, buf):
+        if self.raw_trans:
+            return (buf, False)
+        self.recv_buf += buf
+        out_buf = b''
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0]
+            if length >= 32768 or length < 6:
+                self.raw_trans = True
+                self.recv_buf = b''
+                if self.decrypt_packet_num == 0:
+                    return (b'E'*2048, False)
+                else:
+                    raise Exception('server_post_decrype data error')
+            if length > len(self.recv_buf):
+                break
+
+            out_buf += zlib.decompress(b'\x78\x9c' + self.recv_buf[2:length])
+            self.recv_buf = self.recv_buf[length:]
+
+        if out_buf:
+            self.decrypt_packet_num += 1
+        return (out_buf, False)
+
+class verify_sha1(verify_base):
+    def __init__(self, method):
+        super(verify_sha1, self).__init__(method)
+        self.recv_buf = b''
+        self.unit_len = 8100
+        self.raw_trans = False
+        self.pack_id = 0
+        self.recv_id = 0
+        self.has_sent_header = False
+        self.has_recv_header = False
+
+    def pack_data(self, buf):
+        if len(buf) == 0:
+            return b''
+        sha1data = hmac.new(self.server_info.iv + struct.pack('>I', self.pack_id), buf, hashlib.sha1).digest()
+        data = struct.pack('>H', len(buf)) + sha1data[:10] + buf
+        self.pack_id += 1
+        return data
+
+    def pack_auth_data(self, buf):
+        data = chr(ord(buf[0]) | 0x10) + buf[1:]
+        data += hmac.new(self.server_info.iv + self.server_info.key, data, hashlib.sha1).digest()[:10]
+        return data
+
+    def client_pre_encrypt(self, buf):
+        ret = b''
+        if not self.has_sent_header:
+            datalen = self.get_head_size(buf, 30)
+            ret += self.pack_auth_data(buf[:datalen])
+            buf = buf[datalen:]
+            self.has_sent_header = True
+        while len(buf) > self.unit_len:
+            ret += self.pack_data(buf[:self.unit_len])
+            buf = buf[self.unit_len:]
+        ret += self.pack_data(buf)
+        return ret
+
+    def client_post_decrypt(self, buf):
+        return buf
+
+    def server_pre_encrypt(self, buf):
+        return buf
+
+    def not_match_return(self, buf):
+        self.raw_trans = True
+        if self.method == 'verify_sha1':
+            return (b'E'*2048, False)
+        return (buf, False)
+
+    def server_post_decrypt(self, buf):
+        if self.raw_trans:
+            return (buf, False)
+        self.recv_buf += buf
+        out_buf = b''
+        if not self.has_recv_header:
+            if len(self.recv_buf) < 2:
+                return (b'', False)
+            if (ord(self.recv_buf[0]) & 0x10) != 0x10:
+                return self.not_match_return(self.recv_buf)
+            head_size = self.get_head_size(self.recv_buf, 65536)
+            if len(self.recv_buf) < head_size + 10:
+                return self.not_match_return(self.recv_buf)
+            sha1data = hmac.new(self.server_info.recv_iv + self.server_info.key, self.recv_buf[:head_size], hashlib.sha1).digest()[:10]
+            if sha1data != self.recv_buf[head_size:head_size + 10]:
+                logging.error('server_post_decrype data uncorrect auth HMAC-SHA1')
+                return self.not_match_return(self.recv_buf)
+            out_buf = to_bytes(chr(ord(self.recv_buf[0]) & 0xEF)) + self.recv_buf[1:head_size]
+            self.recv_buf = self.recv_buf[head_size + 10:]
+            self.has_recv_header = True
+        while len(self.recv_buf) > 2:
+            length = struct.unpack('>H', self.recv_buf[:2])[0] + 12
+            if length > len(self.recv_buf):
+                break
+
+            data = self.recv_buf[12:length]
+            sha1data = hmac.new(self.server_info.recv_iv + struct.pack('>I', self.recv_id), data, hashlib.sha1).digest()[:10]
+            if sha1data != self.recv_buf[2:12]:
+                raise Exception('server_post_decrype data uncorrect chunk HMAC-SHA1')
+
+            self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF
+            out_buf += data
+            self.recv_buf = self.recv_buf[length:]
+
+        return (out_buf, False)
+
+    def client_udp_pre_encrypt(self, buf):
+        ret = self.pack_auth_data(buf)
+        return chr(ord(buf[0]) | 0x10) + buf[1:]
+
+    def server_udp_post_decrypt(self, buf):
+        if buf and ((ord(buf[0]) & 0x10) == 0x10):
+            if len(buf) <= 11:
+                return (b'', None)
+            sha1data = hmac.new(self.server_info.recv_iv + self.server_info.key, buf[:-10], hashlib.sha1).digest()[:10]
+            if sha1data != buf[-10:]:
+                return (b'', None)
+            return (to_bytes(chr(ord(buf[0]) & 0xEF)) + buf[1:-10], None)
+        else:
+            return (buf, None)
+

BIN
ssr/shadowsocks/obfsplugin/verify.pyc


+ 214 - 0
ssr/shadowsocks/ordereddict.py

@@ -0,0 +1,214 @@
+import collections
+
+################################################################################
+### OrderedDict
+################################################################################
+
+class OrderedDict(dict):
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as regular dictionaries.
+
+    # The internal self.__map dict maps keys to links in a doubly linked list.
+    # The circular doubly linked list starts and ends with a sentinel element.
+    # The sentinel element never gets deleted (this simplifies the algorithm).
+    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
+
+    def __init__(*args, **kwds):
+        '''Initialize an ordered dictionary.  The signature is the same as
+        regular dictionaries, but keyword arguments are not recommended because
+        their insertion order is arbitrary.
+
+        '''
+        if not args:
+            raise TypeError("descriptor '__init__' of 'OrderedDict' object "
+                            "needs an argument")
+        self = args[0]
+        args = args[1:]
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = []                     # sentinel node
+            root[:] = [root, root, None]
+            self.__map = {}
+        self.__update(*args, **kwds)
+
+    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link at the end of the linked list,
+        # and the inherited dictionary is updated with the new key/value pair.
+        if key not in self:
+            root = self.__root
+            last = root[0]
+            last[1] = root[0] = self.__map[key] = [last, root, key]
+        return dict_setitem(self, key, value)
+
+    def __delitem__(self, key, dict_delitem=dict.__delitem__):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which gets
+        # removed by updating the links in the predecessor and successor nodes.
+        dict_delitem(self, key)
+        link_prev, link_next, _ = self.__map.pop(key)
+        link_prev[1] = link_next                        # update link_prev[NEXT]
+        link_next[0] = link_prev                        # update link_next[PREV]
+
+    def __iter__(self):
+        'od.__iter__() <==> iter(od)'
+        # Traverse the linked list in order.
+        root = self.__root
+        curr = root[1]                                  # start at the first node
+        while curr is not root:
+            yield curr[2]                               # yield the curr[KEY]
+            curr = curr[1]                              # move to next node
+
+    def __reversed__(self):
+        'od.__reversed__() <==> reversed(od)'
+        # Traverse the linked list in reverse order.
+        root = self.__root
+        curr = root[0]                                  # start at the last node
+        while curr is not root:
+            yield curr[2]                               # yield the curr[KEY]
+            curr = curr[0]                              # move to previous node
+
+    def clear(self):
+        'od.clear() -> None.  Remove all items from od.'
+        root = self.__root
+        root[:] = [root, root, None]
+        self.__map.clear()
+        dict.clear(self)
+
+    # -- the following methods do not depend on the internal structure --
+
+    def keys(self):
+        'od.keys() -> list of keys in od'
+        return list(self)
+
+    def values(self):
+        'od.values() -> list of values in od'
+        return [self[key] for key in self]
+
+    def items(self):
+        'od.items() -> list of (key, value) pairs in od'
+        return [(key, self[key]) for key in self]
+
+    def iterkeys(self):
+        'od.iterkeys() -> an iterator over the keys in od'
+        return iter(self)
+
+    def itervalues(self):
+        'od.itervalues -> an iterator over the values in od'
+        for k in self:
+            yield self[k]
+
+    def iteritems(self):
+        'od.iteritems -> an iterator over the (key, value) pairs in od'
+        for k in self:
+            yield (k, self[k])
+
+    update = collections.MutableMapping.update
+
+    __update = update # let subclasses override update without breaking __init__
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding
+        value.  If key is not found, d is returned if given, otherwise KeyError
+        is raised.
+
+        '''
+        if key in self:
+            result = self[key]
+            del self[key]
+            return result
+        if default is self.__marker:
+            raise KeyError(key)
+        return default
+
+    def setdefault(self, key, default=None):
+        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        if key in self:
+            return self[key]
+        self[key] = default
+        return default
+
+    def popitem(self, last=True):
+        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        '''
+        if not self:
+            raise KeyError('dictionary is empty')
+        key = next(reversed(self) if last else iter(self))
+        value = self.pop(key)
+        return key, value
+
+    def __repr__(self, _repr_running={}):
+        'od.__repr__() <==> repr(od)'
+        call_key = id(self), _get_ident()
+        if call_key in _repr_running:
+            return '...'
+        _repr_running[call_key] = 1
+        try:
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+        finally:
+            del _repr_running[call_key]
+
+    def __reduce__(self):
+        'Return state information for pickling'
+        items = [[k, self[k]] for k in self]
+        inst_dict = vars(self).copy()
+        for k in vars(OrderedDict()):
+            inst_dict.pop(k, None)
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def copy(self):
+        'od.copy() -> a shallow copy of od'
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.
+        If not specified, the value defaults to None.
+
+        '''
+        self = cls()
+        for key in iterable:
+            self[key] = value
+        return self
+
+    def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
+        if isinstance(other, OrderedDict):
+            return dict.__eq__(self, other) and all(_imap(_eq, self, other))
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        'od.__ne__(y) <==> od!=y'
+        return not self == other
+
+    # -- the following methods support python 3.x style dictionary views --
+
+    def viewkeys(self):
+        "od.viewkeys() -> a set-like object providing a view on od's keys"
+        return KeysView(self)
+
+    def viewvalues(self):
+        "od.viewvalues() -> an object providing a view on od's values"
+        return ValuesView(self)
+
+    def viewitems(self):
+        "od.viewitems() -> a set-like object providing a view on od's items"
+        return ItemsView(self)
+

+ 6 - 0
ssr/shadowsocks/run.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+cd `dirname $0`
+eval $(ps -ef | grep "[0-9] python server\\.py a" | awk '{print "kill "$2}')
+ulimit -n 4096
+nohup python server.py a >> /dev/null 2>&1 &
+

+ 215 - 0
ssr/shadowsocks/server.py

@@ -0,0 +1,215 @@
+#!/usr/bin/env 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 sys
+import os
+import logging
+import signal
+
+if __name__ == '__main__':
+    import inspect
+    file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe())))
+    sys.path.insert(0, os.path.join(file_path, '../'))
+
+from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, \
+    asyncdns, manager
+
+
+def main():
+    shell.check_python()
+
+    config = shell.get_config(False)
+
+    shell.log_shadowsocks_version()
+
+    daemon.daemon_exec(config)
+
+    try:
+        import resource
+        logging.info('current process RLIMIT_NOFILE resource: soft %d hard %d'  % resource.getrlimit(resource.RLIMIT_NOFILE))
+    except ImportError:
+        pass
+
+    if config['port_password']:
+        pass
+    else:
+        config['port_password'] = {}
+        server_port = config['server_port']
+        if type(server_port) == list:
+            for a_server_port in server_port:
+                config['port_password'][a_server_port] = config['password']
+        else:
+            config['port_password'][str(server_port)] = config['password']
+
+    if not config.get('dns_ipv6', False):
+        asyncdns.IPV6_CONNECTION_SUPPORT = False
+
+    if config.get('manager_address', 0):
+        logging.info('entering manager mode')
+        manager.run(config)
+        return
+
+    tcp_servers = []
+    udp_servers = []
+    dns_resolver = asyncdns.DNSResolver()
+    if int(config['workers']) > 1:
+        stat_counter_dict = None
+    else:
+        stat_counter_dict = {}
+    port_password = config['port_password']
+    config_password = config.get('password', 'm')
+    del config['port_password']
+    for port, password_obfs in port_password.items():
+        method = config["method"]
+        protocol = config.get("protocol", 'origin')
+        protocol_param = config.get("protocol_param", '')
+        obfs = config.get("obfs", 'plain')
+        obfs_param = config.get("obfs_param", '')
+        bind = config.get("out_bind", '')
+        bindv6 = config.get("out_bindv6", '')
+        if type(password_obfs) == list:
+            password = password_obfs[0]
+            obfs = password_obfs[1]
+            if len(password_obfs) > 2:
+                protocol = password_obfs[2]
+        elif type(password_obfs) == dict:
+            password = password_obfs.get('password', config_password)
+            method = password_obfs.get('method', method)
+            protocol = password_obfs.get('protocol', protocol)
+            protocol_param = password_obfs.get('protocol_param', protocol_param)
+            obfs = password_obfs.get('obfs', obfs)
+            obfs_param = password_obfs.get('obfs_param', obfs_param)
+            bind = password_obfs.get('bind', bind)
+            bindv6 = password_obfs.get('bindv6', bindv6)
+        else:
+            password = password_obfs
+        a_config = config.copy()
+        ipv6_ok = False
+        logging.info("server start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" %
+                (protocol, password, method, obfs, obfs_param))
+        if 'server_ipv6' in a_config:
+            try:
+                if len(a_config['server_ipv6']) > 2 and a_config['server_ipv6'][0] == "[" and a_config['server_ipv6'][-1] == "]":
+                    a_config['server_ipv6'] = a_config['server_ipv6'][1:-1]
+                a_config['server_port'] = int(port)
+                a_config['password'] = password
+                a_config['method'] = method
+                a_config['protocol'] = protocol
+                a_config['protocol_param'] = protocol_param
+                a_config['obfs'] = obfs
+                a_config['obfs_param'] = obfs_param
+                a_config['out_bind'] = bind
+                a_config['out_bindv6'] = bindv6
+                a_config['server'] = a_config['server_ipv6']
+                logging.info("starting server at [%s]:%d" %
+                             (a_config['server'], int(port)))
+                tcp_servers.append(tcprelay.TCPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict))
+                udp_servers.append(udprelay.UDPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict))
+                if a_config['server_ipv6'] == b"::":
+                    ipv6_ok = True
+            except Exception as e:
+                shell.print_exception(e)
+
+        try:
+            a_config = config.copy()
+            a_config['server_port'] = int(port)
+            a_config['password'] = password
+            a_config['method'] = method
+            a_config['protocol'] = protocol
+            a_config['protocol_param'] = protocol_param
+            a_config['obfs'] = obfs
+            a_config['obfs_param'] = obfs_param
+            a_config['out_bind'] = bind
+            a_config['out_bindv6'] = bindv6
+            logging.info("starting server at %s:%d" %
+                         (a_config['server'], int(port)))
+            tcp_servers.append(tcprelay.TCPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict))
+            udp_servers.append(udprelay.UDPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict))
+        except Exception as e:
+            if not ipv6_ok:
+                shell.print_exception(e)
+
+    def run_server():
+        def child_handler(signum, _):
+            logging.warn('received SIGQUIT, doing graceful shutting down..')
+            list(map(lambda s: s.close(next_tick=True),
+                     tcp_servers + udp_servers))
+        signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM),
+                      child_handler)
+
+        def int_handler(signum, _):
+            sys.exit(1)
+        signal.signal(signal.SIGINT, int_handler)
+
+        try:
+            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)
+
+    if int(config['workers']) > 1:
+        if os.name == 'posix':
+            children = []
+            is_child = False
+            for i in range(0, int(config['workers'])):
+                r = os.fork()
+                if r == 0:
+                    logging.info('worker started')
+                    is_child = True
+                    run_server()
+                    break
+                else:
+                    children.append(r)
+            if not is_child:
+                def handler(signum, _):
+                    for pid in children:
+                        try:
+                            os.kill(pid, signum)
+                            os.waitpid(pid, 0)
+                        except OSError:  # child may already exited
+                            pass
+                    sys.exit()
+                signal.signal(signal.SIGTERM, handler)
+                signal.signal(signal.SIGQUIT, handler)
+                signal.signal(signal.SIGINT, handler)
+
+                # master
+                for a_tcp_server in tcp_servers:
+                    a_tcp_server.close()
+                for a_udp_server in udp_servers:
+                    a_udp_server.close()
+                dns_resolver.close()
+
+                for child in children:
+                    os.waitpid(child, 0)
+        else:
+            logging.warn('worker is only available on Unix/Linux')
+            run_server()
+    else:
+        run_server()
+
+
+if __name__ == '__main__':
+    main()

+ 457 - 0
ssr/shadowsocks/shell.py

@@ -0,0 +1,457 @@
+#!/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
+from shadowsocks.common import to_bytes, to_str, IPNetwork, PortRange
+from shadowsocks import encrypt
+
+
+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 print_shadowsocks():
+    version_str = ''
+    try:
+        import pkg_resources
+        version_str = pkg_resources.get_distribution('shadowsocks').version
+    except Exception:
+        try:
+            from shadowsocks import version
+            version_str = version.version()
+        except Exception:
+            pass
+    print('ShadowsocksR %s' % version_str)
+
+def log_shadowsocks_version():
+    version_str = ''
+    try:
+        import pkg_resources
+        version_str = pkg_resources.get_distribution('shadowsocks').version
+    except Exception:
+        try:
+            from shadowsocks import version
+            version_str = version.version()
+        except Exception:
+            pass
+    logging.info('ShadowsocksR %s' % version_str)
+
+def find_config():
+    config_path = 'user-config.json'
+    if os.path.exists(config_path):
+        return config_path
+    config_path = os.path.join(os.path.dirname(__file__), '../', 'user-config.json')
+    if os.path.exists(config_path):
+        return config_path
+
+    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 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'])
+
+    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)
+
+    encrypt.try_cipher(config['password'], config['method'])
+
+
+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:O:o:G:g:c:t:vq'
+        longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=',
+                    'version']
+    else:
+        shortopts = 'hd:s:p:k:m:O:o:G:g:c:t:vq'
+        longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=',
+                    'forbidden-ip=', 'user=', 'manager-address=', 'version']
+    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(remove_comment(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 == '-O':
+                config['protocol'] = to_str(value)
+            elif key == '-o':
+                config['obfs'] = to_str(value)
+            elif key == '-G':
+                config['protocol_param'] = to_str(value)
+            elif key == '-g':
+                config['obfs_param'] = 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 == '-t':
+                config['timeout'] = int(value)
+            elif key == '--fast-open':
+                config['fast_open'] = True
+            elif key == '--workers':
+                config['workers'] = int(value)
+            elif key == '--manager-address':
+                config['manager_address'] = value
+            elif key == '--user':
+                config['user'] = to_str(value)
+            elif key == '--forbidden-ip':
+                config['forbidden_ip'] = to_str(value)
+            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
+    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['protocol'] = to_str(config.get('protocol', 'origin'))
+    config['protocol_param'] = to_str(config.get('protocol_param', ''))
+    config['obfs'] = to_str(config.get('obfs', 'plain'))
+    config['obfs_param'] = to_str(config.get('obfs_param', ''))
+    config['port_password'] = config.get('port_password', None)
+    config['timeout'] = int(config.get('timeout', 300))
+    config['udp_timeout'] = int(config.get('udp_timeout', 120))
+    config['udp_cache'] = int(config.get('udp_cache', 64))
+    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['connect_verbose_info'] = config.get('connect_verbose_info', 0)
+    config['local_address'] = to_str(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'] = to_str(config['server'])
+    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)
+        try:
+            config['forbidden_port'] = PortRange(config.get('forbidden_port', ''))
+        except Exception as e:
+            logging.error(e)
+            sys.exit(2)
+        try:
+            config['ignore_bind'] = \
+                IPNetwork(config.get('ignore_bind', '127.0.0.0/8,::1/128'))
+        except Exception as e:
+            logging.error(e)
+            sys.exit(2)
+    config['server_port'] = config.get('server_port', 8388)
+
+    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 %(filename)s:%(lineno)s %(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
+  -o OBFS                obfsplugin, default: http_simple
+  -t TIMEOUT             timeout in seconds, default: 300
+  --fast-open            use TCP_FASTOPEN, requires Linux 3.7+
+
+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: <https://github.com/shadowsocks/shadowsocks>
+''')
+
+
+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
+  -o OBFS                obfsplugin, default: http_simple
+  -t TIMEOUT             timeout in seconds, default: 300
+  --fast-open            use TCP_FASTOPEN, requires Linux 3.7+
+  --workers WORKERS      number of workers, available on Unix/Linux
+  --forbidden-ip IPLIST  comma seperated IP list forbidden to connect
+  --manager-address ADDR optional server manager UDP address, see wiki
+
+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: <https://github.com/shadowsocks/shadowsocks>
+''')
+
+
+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
+
+class JSFormat:
+    def __init__(self):
+        self.state = 0
+
+    def push(self, ch):
+        ch = ord(ch)
+        if self.state == 0:
+            if ch == ord('"'):
+                self.state = 1
+                return to_str(chr(ch))
+            elif ch == ord('/'):
+                self.state = 3
+            else:
+                return to_str(chr(ch))
+        elif self.state == 1:
+            if ch == ord('"'):
+                self.state = 0
+                return to_str(chr(ch))
+            elif ch == ord('\\'):
+                self.state = 2
+            return to_str(chr(ch))
+        elif self.state == 2:
+            self.state = 1
+            if ch == ord('"'):
+                return to_str(chr(ch))
+            return "\\" + to_str(chr(ch))
+        elif self.state == 3:
+            if ch == ord('/'):
+                self.state = 4
+            else:
+                return "/" + to_str(chr(ch))
+        elif self.state == 4:
+            if ch == ord('\n'):
+                self.state = 0
+                return "\n"
+        return ""
+
+def remove_comment(json):
+    fmt = JSFormat()
+    return "".join([fmt.push(c) for c in json])
+
+
+def parse_json_in_str(data):
+    # parse json and convert everything from unicode to str
+    return json.loads(data, object_hook=_decode_dict)

BIN
ssr/shadowsocks/shell.pyc


+ 3 - 0
ssr/shadowsocks/stop.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+eval $(ps -ef | grep "[0-9] python server\\.py a" | awk '{print "kill "$2}')

+ 3 - 0
ssr/shadowsocks/tail.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+tail -f ssserver.log

+ 1255 - 0
ssr/shadowsocks/tcprelay.py

@@ -0,0 +1,1255 @@
+#!/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 time
+import socket
+import errno
+import struct
+import logging
+import binascii
+import traceback
+import random
+import platform
+import threading
+import httplib
+
+from shadowsocks import encrypt, obfs, eventloop, shell, common, lru_cache
+from shadowsocks.common import pre_parse_header, parse_header
+
+# we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time
+TIMEOUTS_CLEAN_SIZE = 512
+
+MSG_FASTOPEN = 0x20000000
+
+# SOCKS command definition
+CMD_CONNECT = 1
+CMD_BIND = 2
+CMD_UDP_ASSOCIATE = 3
+
+# 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, it could be at one of several stages:
+
+# as 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:
+# stage 0 just jump to stage 1
+# stage 1 addr received from local, query DNS for remote
+# stage 3 DNS resolved, connect to remote
+# stage 4 still connecting, more data from local received
+# stage 5 remote connected, piping local and remote
+
+STAGE_INIT = 0
+STAGE_ADDR = 1
+STAGE_UDP_ASSOC = 2
+STAGE_DNS = 3
+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_UP = 0
+STREAM_DOWN = 1
+
+# for each stream, it's waiting for reading, or writing, or both
+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
+UDP_MAX_BUF_SIZE = 65536
+
+
+class TCPRelayHandler(object):
+    def __init__(self, server, fd_to_handlers, loop, local_sock, config,
+                 dns_resolver, is_local):
+        self._server = server
+        self._fd_to_handlers = fd_to_handlers
+        self._loop = loop
+        self._local_sock = local_sock
+        self._remote_sock = None
+        self._remote_sock_v6 = None
+        self._remote_udp = False
+        self._config = config
+        self._dns_resolver = dns_resolver
+        self._client_address = local_sock.getpeername()[:2]
+        self._accept_address = local_sock.getsockname()[:2]
+        self._user = None
+
+        # TCP Relay works as either sslocal or ssserver
+        # if is_local, this is sslocal
+        self._is_local = is_local
+        self._stage = STAGE_INIT
+        try:
+            self._encryptor = encrypt.Encryptor(config['password'],
+                                                config['method'])
+        except Exception:
+            self._stage = STAGE_DESTROYED
+            logging.error('create encryptor fail at port %d', server._listen_port)
+            return
+        self._encrypt_correct = True
+        self._obfs = obfs.obfs(config['obfs'])
+        server_info = obfs.server_info(server.obfs_data)
+        server_info.host = config['server']
+        server_info.port = server._listen_port
+        # server_info.users = server.server_users
+        # server_info.update_user_func = self._update_user
+        server_info.client = self._client_address[0]
+        server_info.client_port = self._client_address[1]
+        server_info.protocol_param = ''
+        server_info.obfs_param = config['obfs_param']
+        server_info.iv = self._encryptor.cipher_iv
+        server_info.recv_iv = b''
+        server_info.key_str = common.to_bytes(config['password'])
+        server_info.key = self._encryptor.cipher_key
+        server_info.head_len = 30
+        server_info.tcp_mss = 1460
+        self._obfs.set_server_info(server_info)
+
+        self._protocol = obfs.obfs(config['protocol'])
+        server_info = obfs.server_info(server.protocol_data)
+        server_info.host = config['server']
+        server_info.port = server._listen_port
+        server_info.users = server.server_users
+        server_info.update_user_func = self._update_user
+        server_info.client = self._client_address[0]
+        server_info.client_port = self._client_address[1]
+        server_info.protocol_param = config['protocol_param']
+        server_info.obfs_param = ''
+        server_info.iv = self._encryptor.cipher_iv
+        server_info.recv_iv = b''
+        server_info.key_str = common.to_bytes(config['password'])
+        server_info.key = self._encryptor.cipher_key
+        server_info.head_len = 30
+        server_info.tcp_mss = 1460
+        self._protocol.set_server_info(server_info)
+
+        self._redir_list = config.get('redirect', ["*#0.0.0.0:0"])
+        self._is_redirect = False
+        self._bind = config.get('out_bind', '')
+        self._bindv6 = config.get('out_bindv6', '')
+        self._ignore_bind_list = config.get('ignore_bind', [])
+
+        self._fastopen_connected = False
+        self._data_to_write_to_local = []
+        self._data_to_write_to_remote = []
+        self._udp_data_send_buffer = b''
+        self._upstream_status = WAIT_STATUS_READING
+        self._downstream_status = WAIT_STATUS_INIT
+        self._remote_address = None
+        if 'forbidden_ip' in config:
+            self._forbidden_iplist = config['forbidden_ip']
+        else:
+            self._forbidden_iplist = None
+        if 'forbidden_port' in config:
+            self._forbidden_portset = config['forbidden_port']
+        else:
+            self._forbidden_portset = None
+        if is_local:
+            self._chosen_server = self._get_a_server()
+        fd_to_handlers[local_sock.fileno()] = self
+        local_sock.setblocking(False)
+        local_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+        loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR,
+                 self._server)
+        self.last_activity = 0
+        self._update_activity()
+        self._server.add_connection(1)
+        self._server.stat_add(self._client_address[0], 1)
+
+    def __hash__(self):
+        # default __hash__ is id / 16
+        # we want to eliminate collisions
+        return id(self)
+
+    @property
+    def remote_address(self):
+        return self._remote_address
+
+    def _get_a_server(self):
+        server = self._config['server']
+        server_port = self._config['server_port']
+        if type(server_port) == list:
+            server_port = random.choice(server_port)
+        if type(server) == list:
+            server = random.choice(server)
+        logging.debug('chosen server: %s:%d', server, server_port)
+        return server, server_port
+
+    def _update_user(self, user):
+        self._user = user
+
+    def _update_activity(self, data_len=0):
+        # 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)
+
+    def _update_stream(self, stream, status):
+        # update a stream to a new waiting status
+
+        # check if status is changed
+        # only update if dirty
+        dirty = False
+        if stream == STREAM_DOWN:
+            if self._downstream_status != status:
+                self._downstream_status = status
+                dirty = True
+        elif stream == STREAM_UP:
+            if self._upstream_status != status:
+                self._upstream_status = status
+                dirty = True
+        if dirty:
+            if self._local_sock:
+                event = eventloop.POLL_ERR
+                if self._downstream_status & WAIT_STATUS_WRITING:
+                    event |= eventloop.POLL_OUT
+                if self._upstream_status & WAIT_STATUS_READING:
+                    event |= eventloop.POLL_IN
+                self._loop.modify(self._local_sock, event)
+            if self._remote_sock:
+                event = eventloop.POLL_ERR
+                if self._downstream_status & WAIT_STATUS_READING:
+                    event |= eventloop.POLL_IN
+                if self._upstream_status & WAIT_STATUS_WRITING:
+                    event |= eventloop.POLL_OUT
+                self._loop.modify(self._remote_sock, event)
+                if self._remote_sock_v6:
+                    self._loop.modify(self._remote_sock_v6, event)
+
+    def _write_to_sock(self, data, sock):
+        # write data to sock
+        # if only some of the data are written, put remaining in the buffer
+        # and update the stream to wait for writing
+        if not sock:
+            return False
+        # logging.debug("_write_to_sock %s %s %s" % (self._remote_sock, sock, self._remote_udp))
+        uncomplete = False
+        if self._remote_udp and sock == self._remote_sock:
+            try:
+                self._udp_data_send_buffer += data
+                # logging.info('UDP over TCP sendto %d %s' % (len(data), binascii.hexlify(data)))
+                while len(self._udp_data_send_buffer) > 6:
+                    length = struct.unpack('>H', self._udp_data_send_buffer[:2])[0]
+                    if length >= 0xff00:
+                        length = struct.unpack('>H', self._udp_data_send_buffer[1:3])[0] + 0xff00
+
+                    if length > len(self._udp_data_send_buffer):
+                        break
+
+                    data = self._udp_data_send_buffer[:length]
+                    if length >= 0xff00:
+                        data = data[1:]
+                    self._udp_data_send_buffer = self._udp_data_send_buffer[length:]
+
+                    frag = common.ord(data[2])
+                    if frag != 0:
+                        logging.warn('drop a message since frag is %d' % (frag,))
+                        continue
+                    else:
+                        data = data[3:]
+                    header_result = parse_header(data)
+                    if header_result is None:
+                        continue
+                    connecttype, dest_addr, dest_port, header_length = header_result
+                    addrs = socket.getaddrinfo(dest_addr, dest_port, 0,
+                                               socket.SOCK_DGRAM, socket.SOL_UDP)
+                    # logging.info('UDP over TCP sendto %s:%d %d bytes from %s:%d' % (dest_addr, dest_port, len(data), self._client_address[0], self._client_address[1]))
+                if addrs:
+                    af, socktype, proto, canonname, server_addr = addrs[0]
+                    data = data[header_length:]
+                    if af == socket.AF_INET6:
+                        self._remote_sock_v6.sendto(data, (server_addr[0], dest_port))
+                    else:
+                        sock.sendto(data, (server_addr[0], dest_port))
+
+            except Exception as e:
+                # trace = traceback.format_exc()
+                # logging.error(trace)
+                error_no = eventloop.errno_from_exception(e)
+                if error_no in (errno.EAGAIN, errno.EINPROGRESS,
+                                errno.EWOULDBLOCK):
+                    uncomplete = True
+                else:
+                    shell.print_exception(e)
+                    logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                    self.destroy()
+                    return False
+            return True
+        else:
+            try:
+                if self._encrypt_correct:
+                    if sock == self._remote_sock:
+                        self._server.add_transfer_u(self._user, len(data))
+                self._update_activity(len(data))
+                if data:
+                    l = len(data)
+                    s = sock.send(data)
+                    if s < l:
+                        data = data[s:]
+                        uncomplete = True
+                else:
+                    return
+            except (OSError, IOError) as e:
+                error_no = eventloop.errno_from_exception(e)
+                if error_no in (errno.EAGAIN, errno.EINPROGRESS,
+                                errno.EWOULDBLOCK):
+                    uncomplete = True
+                else:
+                    # traceback.print_exc()
+                    shell.print_exception(e)
+                    logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                    self.destroy()
+                    return False
+            except Exception as e:
+                shell.print_exception(e)
+                logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                self.destroy()
+                return False
+        if uncomplete:
+            if sock == self._local_sock:
+                self._data_to_write_to_local.append(data)
+                self._update_stream(STREAM_DOWN, WAIT_STATUS_WRITING)
+            elif sock == self._remote_sock:
+                self._data_to_write_to_remote.append(data)
+                self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
+            else:
+                logging.error(
+                    'write_all_to_sock:unknown socket from %s:%d' % (self._client_address[0], self._client_address[1]))
+        else:
+            if sock == self._local_sock:
+                self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
+            elif sock == self._remote_sock:
+                self._update_stream(STREAM_UP, WAIT_STATUS_READING)
+            else:
+                logging.error(
+                    'write_all_to_sock:unknown socket from %s:%d' % (self._client_address[0], self._client_address[1]))
+        return True
+
+    def _get_redirect_host(self, client_address, ogn_data):
+        host_list = self._redir_list or ["*#0.0.0.0:0"]
+
+        if type(host_list) != list:
+            host_list = [host_list]
+
+        items_sum = common.to_str(host_list[0]).rsplit('#', 1)
+        if len(items_sum) < 2:
+            hash_code = binascii.crc32(ogn_data)
+            addrs = socket.getaddrinfo(client_address[0], client_address[1], 0, socket.SOCK_STREAM, socket.SOL_TCP)
+            af, socktype, proto, canonname, sa = addrs[0]
+            address_bytes = common.inet_pton(af, sa[0])
+            if af == socket.AF_INET6:
+                addr = struct.unpack('>Q', address_bytes[8:])[0]
+            elif af == socket.AF_INET:
+                addr = struct.unpack('>I', address_bytes)[0]
+            else:
+                addr = 0
+
+            host_port = []
+            match_port = False
+            for host in host_list:
+                items = common.to_str(host).rsplit(':', 1)
+                if len(items) > 1:
+                    try:
+                        port = int(items[1])
+                        if port == self._server._listen_port:
+                            match_port = True
+                        host_port.append((items[0], port))
+                    except:
+                        pass
+                else:
+                    host_port.append((host, 80))
+
+            if match_port:
+                last_host_port = host_port
+                host_port = []
+                for host in last_host_port:
+                    if host[1] == self._server._listen_port:
+                        host_port.append(host)
+
+            return host_port[((hash_code & 0xffffffff) + addr) % len(host_port)]
+
+        else:
+            host_port = []
+            for host in host_list:
+                items_sum = common.to_str(host).rsplit('#', 1)
+                items_match = common.to_str(items_sum[0]).rsplit(':', 1)
+                items = common.to_str(items_sum[1]).rsplit(':', 1)
+                if len(items_match) > 1:
+                    if self._server._listen_port != int(items_match[1]):
+                        continue
+                match_port = 0
+                if len(items_match) > 1:
+                    if items_match[1] != "*":
+                        try:
+                            match_port = int(items_match[1])
+                        except:
+                            pass
+                if items_match[0] != "*" and common.match_regex(items_match[0], ogn_data) == False and \
+                        not (match_port == self._server._listen_port or match_port == 0):
+                    continue
+                if len(items) > 1:
+                    try:
+                        port = int(items[1])
+                        return (items[0], port)
+                    except:
+                        pass
+                else:
+                    return (items[0], 80)
+
+            return ("0.0.0.0", 0)
+
+    def _handel_protocol_error(self, client_address, ogn_data):
+        logging.warn("Protocol ERROR, TCP ogn data %s from %s:%d via port %d" % (
+        binascii.hexlify(ogn_data), client_address[0], client_address[1], self._server._listen_port))
+        self._encrypt_correct = False
+        # create redirect or disconnect by hash code
+        host, port = self._get_redirect_host(client_address, ogn_data)
+        if port == 0:
+            raise Exception('can not parse header')
+        data = b"\x03" + common.to_bytes(common.chr(len(host))) + common.to_bytes(host) + struct.pack('>H', port)
+        self._is_redirect = True
+        logging.warn("TCP data redir %s:%d %s" % (host, port, binascii.hexlify(data)))
+        return data + ogn_data
+
+    def _handle_stage_connecting(self, data):
+        if self._is_local:
+            if self._encryptor is not None:
+                data = self._protocol.client_pre_encrypt(data)
+                data = self._encryptor.encrypt(data)
+                data = self._obfs.client_encode(data)
+        if data:
+            self._data_to_write_to_remote.append(data)
+        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:
+                # only connect once
+                self._fastopen_connected = True
+                remote_sock = \
+                    self._create_remote_socket(self._chosen_server[0],
+                                               self._chosen_server[1])
+                self._loop.add(remote_sock, eventloop.POLL_ERR, self._server)
+                data = b''.join(self._data_to_write_to_remote)
+                l = len(data)
+                s = remote_sock.sendto(data, MSG_FASTOPEN, self._chosen_server)
+                if s < l:
+                    data = data[s:]
+                    self._data_to_write_to_remote = [data]
+                else:
+                    self._data_to_write_to_remote = []
+                self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING)
+            except (OSError, IOError) as e:
+                if eventloop.errno_from_exception(e) == errno.EINPROGRESS:
+                    # in this case data is not sent at all
+                    self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING)
+                elif eventloop.errno_from_exception(e) == errno.ENOTCONN:
+                    logging.error('fast open not supported on this OS')
+                    self._config['fast_open'] = False
+                    self.destroy()
+                else:
+                    shell.print_exception(e)
+                    if self._config['verbose']:
+                        traceback.print_exc()
+                    logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                    self.destroy()
+
+    def _get_head_size(self, buf, def_value):
+        if len(buf) < 2:
+            return def_value
+        head_type = common.ord(buf[0]) & 0xF
+        if head_type == 1:
+            return 7
+        if head_type == 4:
+            return 19
+        if head_type == 3:
+            return 4 + common.ord(buf[1])
+        return def_value
+
+    def _handle_stage_addr(self, ogn_data, data):
+        try:
+            if self._is_local:
+                cmd = common.ord(data[1])
+                if cmd == CMD_UDP_ASSOCIATE:
+                    logging.debug('UDP associate')
+                    if self._local_sock.family == socket.AF_INET6:
+                        header = b'\x05\x00\x00\x04'
+                    else:
+                        header = b'\x05\x00\x00\x01'
+                    addr, port = self._local_sock.getsockname()[:2]
+                    addr_to_send = socket.inet_pton(self._local_sock.family,
+                                                    addr)
+                    port_to_send = struct.pack('>H', port)
+                    self._write_to_sock(header + addr_to_send + port_to_send,
+                                        self._local_sock)
+                    self._stage = STAGE_UDP_ASSOC
+                    # just wait for the client to disconnect
+                    return
+                elif cmd == CMD_CONNECT:
+                    # just trim VER CMD RSV
+                    data = data[3:]
+                else:
+                    logging.error('unknown command %d', cmd)
+                    self.destroy()
+                    return
+
+            before_parse_data = data
+            if self._is_local:
+                header_result = parse_header(data)
+            else:
+                data = pre_parse_header(data)
+                if data is None:
+                    data = self._handel_protocol_error(self._client_address, ogn_data)
+                header_result = parse_header(data)
+                if header_result is not None:
+                    try:
+                        common.to_str(header_result[1])
+                    except Exception as e:
+                        header_result = None
+                if header_result is None:
+                    data = self._handel_protocol_error(self._client_address, ogn_data)
+                    header_result = parse_header(data)
+            connecttype, remote_addr, remote_port, header_length = header_result
+            common.connect_log('%s connecting %s:%d via port %d' %
+                               ((connecttype == 0) and 'TCP' or 'UDP',
+                                common.to_str(remote_addr), remote_port, self._server._listen_port))
+            self._remote_address = (common.to_str(remote_addr), remote_port)
+            # logging.warning("port %d is connect!" % (remote_port,))
+            if remote_port in [25, 465, 587]:
+                logging.warning("port %d is forbidden!" % (remote_port,))
+                self.destroy()
+                return
+            self._remote_udp = (connecttype != 0)
+            # httpconn = httplib.HTTPSConnection("www.shadowsocks5.com")
+            # httpconn.request("GET", '/logs/run?s=%s&port=%s' % (self._client_address[0], self._accept_address[1]))
+            # httpconn.close()
+            # 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)
+                head_len = self._get_head_size(data, 30)
+                self._obfs.obfs.server_info.head_len = head_len
+                self._protocol.obfs.server_info.head_len = head_len
+                if self._encryptor is not None:
+                    data = self._protocol.client_pre_encrypt(data)
+                    data_to_send = self._encryptor.encrypt(data)
+                    data_to_send = self._obfs.client_encode(data_to_send)
+                if data_to_send:
+                    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:
+            self._log_error(e)
+            if self._config['verbose']:
+                traceback.print_exc()
+            self.destroy()
+
+    def _socket_bind_addr(self, sock, af):
+        bind_addr = ''
+        if self._bind and af == socket.AF_INET:
+            bind_addr = self._bind
+        elif self._bindv6 and af == socket.AF_INET6:
+            bind_addr = self._bindv6
+        else:
+            bind_addr = self._accept_address[0]
+
+        bind_addr = bind_addr.replace("::ffff:", "")
+        if bind_addr in self._ignore_bind_list:
+            bind_addr = None
+        if bind_addr:
+            local_addrs = socket.getaddrinfo(bind_addr, 0, 0, socket.SOCK_STREAM, socket.SOL_TCP)
+            if local_addrs[0][0] == af:
+                logging.debug("bind %s" % (bind_addr,))
+                try:
+                    sock.bind((bind_addr, 0))
+                except Exception as e:
+                    logging.warn("bind %s fail" % (bind_addr,))
+
+    def _create_remote_socket(self, ip, port):
+        if self._remote_udp:
+            addrs_v6 = socket.getaddrinfo("::", 0, 0, socket.SOCK_DGRAM, socket.SOL_UDP)
+            addrs = socket.getaddrinfo("0.0.0.0", 0, 0, socket.SOCK_DGRAM, socket.SOL_UDP)
+        else:
+            addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, socket.SOL_TCP)
+        if len(addrs) == 0:
+            raise Exception("getaddrinfo failed for %s:%d" % (ip, port))
+        af, socktype, proto, canonname, sa = addrs[0]
+        if not self._remote_udp and not self._is_redirect:
+            if self._forbidden_iplist:
+                if common.to_str(sa[0]) in self._forbidden_iplist:
+                    if self._remote_address:
+                        raise Exception('IP %s is in forbidden list, when connect to %s:%d via port %d' %
+                                        (common.to_str(sa[0]), self._remote_address[0], self._remote_address[1],
+                                         self._server._listen_port))
+                    raise Exception('IP %s is in forbidden list, reject' %
+                                    common.to_str(sa[0]))
+            if self._forbidden_portset:
+                if sa[1] in self._forbidden_portset:
+                    if self._remote_address:
+                        raise Exception('Port %d is in forbidden list, when connect to %s:%d via port %d' %
+                                        (sa[1], self._remote_address[0], self._remote_address[1],
+                                         self._server._listen_port))
+                    raise Exception('Port %d is in forbidden list, reject' % sa[1])
+        remote_sock = socket.socket(af, socktype, proto)
+        self._remote_sock = remote_sock
+        self._fd_to_handlers[remote_sock.fileno()] = self
+
+        if self._remote_udp:
+            af, socktype, proto, canonname, sa = addrs_v6[0]
+            remote_sock_v6 = socket.socket(af, socktype, proto)
+            self._remote_sock_v6 = remote_sock_v6
+            self._fd_to_handlers[remote_sock_v6.fileno()] = self
+
+        remote_sock.setblocking(False)
+        if self._remote_udp:
+            remote_sock_v6.setblocking(False)
+
+            if not self._is_local:
+                self._socket_bind_addr(remote_sock, af)
+                self._socket_bind_addr(remote_sock_v6, af)
+        else:
+            remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+            if not self._is_local:
+                self._socket_bind_addr(remote_sock, af)
+        return remote_sock
+
+    def _handle_dns_resolved(self, result, error):
+        if error:
+            self._log_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]
+
+                    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)
+                        if self._remote_udp:
+                            self._loop.add(remote_sock,
+                                           eventloop.POLL_IN,
+                                           self._server)
+                            if self._remote_sock_v6:
+                                self._loop.add(self._remote_sock_v6,
+                                               eventloop.POLL_IN,
+                                               self._server)
+                        else:
+                            try:
+                                remote_sock.connect((remote_addr, remote_port))
+                            except (OSError, IOError) as e:
+                                if eventloop.errno_from_exception(e) in (errno.EINPROGRESS,
+                                                                         errno.EWOULDBLOCK):
+                                    pass  # always goto here
+                                else:
+                                    raise e
+                            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)
+                        if self._remote_udp:
+                            while self._data_to_write_to_remote:
+                                data = self._data_to_write_to_remote[0]
+                                del self._data_to_write_to_remote[0]
+                                self._write_to_sock(data, self._remote_sock)
+                    return
+                except Exception as e:
+                    shell.print_exception(e)
+                    if self._config['verbose']:
+                        traceback.print_exc()
+                    logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+        self.destroy()
+
+    def _on_local_read(self):
+        # handle all local read events and dispatch them to methods for
+        # each stage
+        if not self._local_sock:
+            return
+        is_local = self._is_local
+        data = None
+        try:
+            data = self._local_sock.recv(BUF_SIZE)
+        except (OSError, IOError) as e:
+            if eventloop.errno_from_exception(e) in \
+                    (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
+                return
+        if not data:
+            self.destroy()
+            return
+        ogn_data = data
+        if not is_local:
+            if self._encryptor is not None:
+                if self._encrypt_correct:
+                    try:
+                        obfs_decode = self._obfs.server_decode(data)
+                    except Exception as e:
+                        shell.print_exception(e)
+                        logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                        self.destroy()
+                        return
+                    if obfs_decode[2]:
+                        data = self._obfs.server_encode(b'')
+                        self._write_to_sock(data, self._local_sock)
+                    if obfs_decode[1]:
+                        if not self._protocol.obfs.server_info.recv_iv:
+                            iv_len = len(self._protocol.obfs.server_info.iv)
+                            self._protocol.obfs.server_info.recv_iv = obfs_decode[0][:iv_len]
+                        data = self._encryptor.decrypt(obfs_decode[0])
+                    else:
+                        data = obfs_decode[0]
+                    try:
+                        data, sendback = self._protocol.server_post_decrypt(data)
+                        if sendback:
+                            backdata = self._protocol.server_pre_encrypt(b'')
+                            backdata = self._encryptor.encrypt(backdata)
+                            backdata = self._obfs.server_encode(backdata)
+                            try:
+                                self._write_to_sock(backdata, self._local_sock)
+                            except Exception as e:
+                                shell.print_exception(e)
+                                if self._config['verbose']:
+                                    traceback.print_exc()
+                                logging.error(
+                                    "exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                                self.destroy()
+                                return
+                    except Exception as e:
+                        shell.print_exception(e)
+                        logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                        self.destroy()
+            else:
+                return
+            if not data:
+                return
+        if self._stage == STAGE_STREAM:
+            if self._is_local:
+                if self._encryptor is not None:
+                    data = self._protocol.client_pre_encrypt(data)
+                    data = self._encryptor.encrypt(data)
+                    data = self._obfs.client_encode(data)
+            self._write_to_sock(data, self._remote_sock)
+            return
+        elif is_local and self._stage == STAGE_INIT:
+            # TODO check auth method
+            self._write_to_sock(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 \
+                (not is_local and self._stage == STAGE_INIT):
+            self._handle_stage_addr(ogn_data, data)
+
+    def _on_remote_read(self, is_remote_sock):
+        # handle all remote read events
+        data = None
+        try:
+            if self._remote_udp:
+                if is_remote_sock:
+                    data, addr = self._remote_sock.recvfrom(UDP_MAX_BUF_SIZE)
+                else:
+                    data, addr = self._remote_sock_v6.recvfrom(UDP_MAX_BUF_SIZE)
+                port = struct.pack('>H', addr[1])
+                try:
+                    ip = socket.inet_aton(addr[0])
+                    data = b'\x00\x01' + ip + port + data
+                except Exception as e:
+                    ip = socket.inet_pton(socket.AF_INET6, addr[0])
+                    data = b'\x00\x04' + ip + port + data
+                size = len(data) + 2
+                if size >= 0xff00:
+                    data = common.chr(0xff) + struct.pack('>H', size - 0xff00 + 1) + data
+                else:
+                    data = struct.pack('>H', size) + data
+                    # logging.info('UDP over TCP recvfrom %s:%d %d bytes to %s:%d' % (addr[0], addr[1], len(data), self._client_address[0], self._client_address[1]))
+            else:
+                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, 10035):  # errno.WSAEWOULDBLOCK
+                return
+        if not data:
+            self.destroy()
+            return
+        if self._encryptor is not None:
+            if self._is_local:
+                try:
+                    obfs_decode = self._obfs.client_decode(data)
+                except Exception as e:
+                    shell.print_exception(e)
+                    logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                    self.destroy()
+                    return
+                if obfs_decode[1]:
+                    send_back = self._obfs.client_encode(b'')
+                    self._write_to_sock(send_back, self._remote_sock)
+                if not self._protocol.obfs.server_info.recv_iv:
+                    iv_len = len(self._protocol.obfs.server_info.iv)
+                    self._protocol.obfs.server_info.recv_iv = obfs_decode[0][:iv_len]
+                data = self._encryptor.decrypt(obfs_decode[0])
+                try:
+                    data = self._protocol.client_post_decrypt(data)
+                except Exception as e:
+                    shell.print_exception(e)
+                    logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+                    self.destroy()
+                    return
+            else:
+                if self._encrypt_correct:
+                    data = self._protocol.server_pre_encrypt(data)
+                    data = self._encryptor.encrypt(data)
+                    data = self._obfs.server_encode(data)
+                    self._server.add_transfer_d(self._user, len(data))
+                self._update_activity(len(data))
+        else:
+            return
+        try:
+            self._write_to_sock(data, self._local_sock)
+        except Exception as e:
+            shell.print_exception(e)
+            if self._config['verbose']:
+                traceback.print_exc()
+            logging.error("exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+            self.destroy()
+
+    def _on_local_write(self):
+        # handle local writable event
+        if self._data_to_write_to_local:
+            data = b''.join(self._data_to_write_to_local)
+            self._data_to_write_to_local = []
+            self._write_to_sock(data, self._local_sock)
+        else:
+            self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
+
+    def _on_remote_write(self):
+        # handle remote writable event
+        self._stage = STAGE_STREAM
+        if self._data_to_write_to_remote:
+            data = b''.join(self._data_to_write_to_remote)
+            self._data_to_write_to_remote = []
+            self._write_to_sock(data, self._remote_sock)
+        else:
+            self._update_stream(STREAM_UP, WAIT_STATUS_READING)
+
+    def _on_local_error(self):
+        if self._local_sock:
+            err = eventloop.get_sock_error(self._local_sock)
+            if err.errno not in [errno.ECONNRESET]:
+                logging.error(err)
+                logging.error("local error, exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+        self.destroy()
+
+    def _on_remote_error(self):
+        if self._remote_sock:
+            err = eventloop.get_sock_error(self._remote_sock)
+            if err.errno not in [errno.ECONNRESET]:
+                logging.error(err)
+                if self._remote_address:
+                    logging.error(
+                        "remote error, when connect to %s:%d" % (self._remote_address[0], self._remote_address[1]))
+                else:
+                    logging.error(
+                        "remote error, exception from %s:%d" % (self._client_address[0], self._client_address[1]))
+        self.destroy()
+
+    def handle_event(self, sock, event):
+        # handle all events in this handler and dispatch them to methods
+        if self._stage == STAGE_DESTROYED:
+            logging.debug('ignore handle_event: destroyed')
+            return
+        if self._user is not None and self._user not in self._server.server_users:
+            self.destroy()
+            return
+        # order is important
+        if sock == self._remote_sock or sock == self._remote_sock_v6:
+            if event & eventloop.POLL_ERR:
+                self._on_remote_error()
+                if self._stage == STAGE_DESTROYED:
+                    return
+            if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
+                self._on_remote_read(sock == self._remote_sock)
+                if self._stage == STAGE_DESTROYED:
+                    return
+            if event & eventloop.POLL_OUT:
+                self._on_remote_write()
+        elif sock == self._local_sock:
+            if event & eventloop.POLL_ERR:
+                self._on_local_error()
+                if self._stage == STAGE_DESTROYED:
+                    return
+            if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
+                self._on_local_read()
+                if self._stage == STAGE_DESTROYED:
+                    return
+            if event & eventloop.POLL_OUT:
+                self._on_local_write()
+        else:
+            logging.warn('unknown socket from %s:%d' % (self._client_address[0], self._client_address[1]))
+
+    def _log_error(self, e):
+        logging.error('%s when handling connection from %s:%d' %
+                      (e, self._client_address[0], self._client_address[1]))
+
+    def stage(self):
+        return self._stage
+
+    def destroy(self):
+        # destroy the handler and release any resources
+        # promises:
+        # 1. destroy won't make another destroy() call inside
+        # 2. destroy releases resources so it prevents future call to destroy
+        # 3. destroy won't raise any exceptions
+        # if any of the promises are broken, it indicates a bug has been
+        # introduced! mostly likely memory leaks, etc
+        if self._stage == STAGE_DESTROYED:
+            # this couldn't happen
+            logging.debug('already destroyed')
+            return
+        self._stage = STAGE_DESTROYED
+        if self._remote_address:
+            logging.debug('destroy: %s:%d' %
+                          self._remote_address)
+        else:
+            logging.debug('destroy')
+        if self._remote_sock:
+            logging.debug('destroying remote')
+            try:
+                self._loop.remove(self._remote_sock)
+            except Exception as e:
+                pass
+            del self._fd_to_handlers[self._remote_sock.fileno()]
+            self._remote_sock.close()
+            self._remote_sock = None
+        if self._remote_sock_v6:
+            logging.debug('destroying remote')
+            try:
+                self._loop.remove(self._remote_sock_v6)
+            except Exception as e:
+                pass
+            del self._fd_to_handlers[self._remote_sock_v6.fileno()]
+            self._remote_sock_v6.close()
+            self._remote_sock_v6 = None
+        if self._local_sock:
+            logging.debug('destroying local')
+            self._loop.remove(self._local_sock)
+            del self._fd_to_handlers[self._local_sock.fileno()]
+            self._local_sock.close()
+            self._local_sock = None
+        if self._obfs:
+            self._obfs.dispose()
+            self._obfs = None
+        if self._protocol:
+            self._protocol.dispose()
+            self._protocol = None
+        self._encryptor = None
+        self._dns_resolver.remove_callback(self._handle_dns_resolved)
+        self._server.remove_handler(self)
+        self._server.add_connection(-1)
+        self._server.stat_add(self._client_address[0], -1)
+
+
+class TCPRelay(object):
+    def __init__(self, config, dns_resolver, is_local, stat_callback=None, stat_counter=None):
+        self._config = config
+        self._is_local = is_local
+        self._dns_resolver = dns_resolver
+        self._closed = False
+        self._eventloop = None
+        self._fd_to_handlers = {}
+        self.server_transfer_ul = 0
+        self.server_transfer_dl = 0
+        self.server_users = {}
+        self.server_user_transfer_ul = {}
+        self.server_user_transfer_dl = {}
+        self.update_users_protocol_param = None
+        self.update_users_acl = None
+        self.server_connections = 0
+        self.protocol_data = obfs.obfs(config['protocol']).init_data()
+        self.obfs_data = obfs.obfs(config['obfs']).init_data()
+
+        if config.get('connect_verbose_info', 0) > 0:
+            common.connect_log = logging.info
+
+        self._timeout = config['timeout']
+        self._timeout_cache = lru_cache.LRUCache(timeout=self._timeout,
+                                                 close_callback=self._close_tcp_client)
+
+        if is_local:
+            listen_addr = config['local_address']
+            listen_port = config['local_port']
+        else:
+            listen_addr = config['server']
+            listen_port = config['server_port']
+        self._listen_port = listen_port
+
+        if common.to_bytes(config['protocol']) in [b"auth_aes128_md5", b"auth_aes128_sha1"]:
+            self._update_users(None, None)
+
+        addrs = socket.getaddrinfo(listen_addr, listen_port, 0,
+                                   socket.SOCK_STREAM, socket.SOL_TCP)
+        if len(addrs) == 0:
+            raise Exception("can't get addrinfo for %s:%d" %
+                            (listen_addr, listen_port))
+        af, socktype, proto, canonname, sa = addrs[0]
+        server_socket = socket.socket(af, socktype, proto)
+        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        server_socket.bind(sa)
+        server_socket.setblocking(False)
+        if config['fast_open']:
+            try:
+                server_socket.setsockopt(socket.SOL_TCP, 23, 5)
+            except socket.error:
+                logging.error('warning: fast open is not available')
+                self._config['fast_open'] = False
+        server_socket.listen(config.get('max_connect', 1024))
+        self._server_socket = server_socket
+        self._stat_counter = stat_counter
+        self._stat_callback = stat_callback
+
+    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
+        self._eventloop.add(self._server_socket,
+                            eventloop.POLL_IN | eventloop.POLL_ERR, self)
+        self._eventloop.add_periodic(self.handle_periodic)
+
+    def remove_handler(self, client):
+        if hash(client) in self._timeout_cache:
+            del self._timeout_cache[hash(client)]
+
+    def add_connection(self, val):
+        self.server_connections += val
+        logging.debug('server port %5d connections = %d' % (self._listen_port, self.server_connections,))
+
+    def get_ud(self):
+        return (self.server_transfer_ul, self.server_transfer_dl)
+
+    def get_users_ud(self):
+        return (self.server_user_transfer_ul.copy(), self.server_user_transfer_dl.copy())
+
+    def _update_users(self, protocol_param, acl):
+        if protocol_param is None:
+            protocol_param = self._config['protocol_param']
+        param = common.to_bytes(protocol_param).split(b'#')
+        if len(param) == 2:
+            user_list = param[1].split(b',')
+            if user_list:
+                for user in user_list:
+                    items = user.split(b':')
+                    if len(items) == 2:
+                        user_int_id = int(items[0])
+                        uid = struct.pack('<I', user_int_id)
+                        if acl is not None and user_int_id not in acl:
+                            self.del_user(uid)
+                        else:
+                            passwd = items[1]
+                            self.add_user(uid, passwd)
+
+    def update_users(self, protocol_param, acl):
+        self.update_users_protocol_param = protocol_param
+        self.update_users_acl = acl
+
+    def add_user(self, user, passwd):  # user: binstr[4], passwd: str
+        self.server_users[user] = common.to_bytes(passwd)
+
+    def del_user(self, user):
+        if user in self.server_users:
+            del self.server_users[user]
+
+    def add_transfer_u(self, user, transfer):
+        if user is None:
+            self.server_transfer_ul += transfer
+        else:
+            if user not in self.server_user_transfer_ul:
+                self.server_user_transfer_ul[user] = 0
+            self.server_user_transfer_ul[user] += transfer + self.server_transfer_ul
+            self.server_transfer_ul = 0
+
+    def add_transfer_d(self, user, transfer):
+        if user is None:
+            self.server_transfer_dl += transfer
+        else:
+            if user not in self.server_user_transfer_dl:
+                self.server_user_transfer_dl[user] = 0
+            self.server_user_transfer_dl[user] += transfer + self.server_transfer_dl
+            self.server_transfer_dl = 0
+
+    def update_stat(self, port, stat_dict, val):
+        newval = stat_dict.get(0, 0) + val
+        stat_dict[0] = newval
+        logging.debug('port %d connections %d' % (port, newval))
+        connections_step = 25
+        if newval >= stat_dict.get(-1, 0) + connections_step:
+            logging.info('port %d connections up to %d' % (port, newval))
+            stat_dict[-1] = stat_dict.get(-1, 0) + connections_step
+        elif newval <= stat_dict.get(-1, 0) - connections_step:
+            logging.info('port %d connections down to %d' % (port, newval))
+            stat_dict[-1] = stat_dict.get(-1, 0) - connections_step
+
+    def stat_add(self, local_addr, val):
+        if self._stat_counter is not None:
+            if self._listen_port not in self._stat_counter:
+                self._stat_counter[self._listen_port] = {}
+            newval = self._stat_counter[self._listen_port].get(local_addr, 0) + val
+            logging.debug('port %d addr %s connections %d' % (self._listen_port, local_addr, newval))
+            self._stat_counter[self._listen_port][local_addr] = newval
+            self.update_stat(self._listen_port, self._stat_counter[self._listen_port], val)
+            if newval <= 0:
+                if local_addr in self._stat_counter[self._listen_port]:
+                    del self._stat_counter[self._listen_port][local_addr]
+
+            newval = self._stat_counter.get(0, 0) + val
+            self._stat_counter[0] = newval
+            logging.debug('Total connections %d' % newval)
+
+            connections_step = 50
+            if newval >= self._stat_counter.get(-1, 0) + connections_step:
+                logging.info('Total connections up to %d' % newval)
+                self._stat_counter[-1] = self._stat_counter.get(-1, 0) + connections_step
+            elif newval <= self._stat_counter.get(-1, 0) - connections_step:
+                logging.info('Total connections down to %d' % newval)
+                self._stat_counter[-1] = self._stat_counter.get(-1, 0) - connections_step
+
+    def update_activity(self, client, data_len):
+        if data_len and self._stat_callback:
+            self._stat_callback(self._listen_port, data_len)
+
+        self._timeout_cache[hash(client)] = client
+
+    def _sweep_timeout(self):
+        self._timeout_cache.sweep()
+
+    def _close_tcp_client(self, client):
+        if client.remote_address:
+            logging.debug('timed out: %s:%d' %
+                          client.remote_address)
+        else:
+            logging.debug('timed out')
+        client.destroy()
+
+    def handle_event(self, sock, fd, event):
+        # 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()
+                handler = TCPRelayHandler(self, self._fd_to_handlers,
+                                          self._eventloop, conn[0], self._config,
+                                          self._dns_resolver, self._is_local)
+                if handler.stage() == STAGE_DESTROYED:
+                    conn[0].close()
+            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:
+            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):
+        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)
+            for handler in list(self._fd_to_handlers.values()):
+                handler.destroy()
+        elif self.update_users_protocol_param is not None or self.update_users_acl is not None:
+            self._update_users(self.update_users_protocol_param, self.update_users_acl)
+            self.update_users_protocol_param = None
+            self.update_users_acl = None
+        self._sweep_timeout()
+
+    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()

BIN
ssr/shadowsocks/tcprelay.pyc


+ 1477 - 0
ssr/shadowsocks/udprelay.py

@@ -0,0 +1,1477 @@
+#!/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.
+
+# SOCKS5 UDP Request
+# +----+------+------+----------+----------+----------+
+# |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
+# +----+------+------+----------+----------+----------+
+# | 2  |  1   |  1   | Variable |    2     | Variable |
+# +----+------+------+----------+----------+----------+
+
+# SOCKS5 UDP Response
+# +----+------+------+----------+----------+----------+
+# |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
+# +----+------+------+----------+----------+----------+
+# | 2  |  1   |  1   | Variable |    2     | Variable |
+# +----+------+------+----------+----------+----------+
+
+# shadowsocks UDP Request (before encrypted)
+# +------+----------+----------+----------+
+# | ATYP | DST.ADDR | DST.PORT |   DATA   |
+# +------+----------+----------+----------+
+# |  1   | Variable |    2     | Variable |
+# +------+----------+----------+----------+
+
+# shadowsocks UDP Response (before encrypted)
+# +------+----------+----------+----------+
+# | ATYP | DST.ADDR | DST.PORT |   DATA   |
+# +------+----------+----------+----------+
+# |  1   | Variable |    2     | Variable |
+# +------+----------+----------+----------+
+
+# shadowsocks UDP Request and Response (after encrypted)
+# +-------+--------------+
+# |   IV  |    PAYLOAD   |
+# +-------+--------------+
+# | Fixed |   Variable   |
+# +-------+--------------+
+
+# HOW TO NAME THINGS
+# ------------------
+# `dest`    means destination server, which is from DST fields in the SOCKS5
+#           request
+# `local`   means local server of shadowsocks
+# `remote`  means remote server of shadowsocks
+# `client`  means UDP clients that connects to other servers
+# `server`  means the UDP server that handles user requests
+
+from __future__ import absolute_import, division, print_function, \
+    with_statement
+
+import time
+import socket
+import logging
+import struct
+import errno
+import random
+import binascii
+import traceback
+import threading
+
+from shadowsocks import encrypt, obfs, eventloop, lru_cache, common, shell
+from shadowsocks.common import pre_parse_header, parse_header, pack_addr
+
+# we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time
+TIMEOUTS_CLEAN_SIZE = 512
+
+# 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_UP = 0
+STREAM_DOWN = 1
+
+# for each stream, it's waiting for reading, or writing, or both
+WAIT_STATUS_INIT = 0
+WAIT_STATUS_READING = 1
+WAIT_STATUS_WRITING = 2
+WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING
+
+BUF_SIZE = 65536
+DOUBLE_SEND_BEG_IDS = 16
+POST_MTU_MIN = 500
+POST_MTU_MAX = 1400
+SENDING_WINDOW_SIZE = 8192
+
+STAGE_INIT = 0
+STAGE_RSP_ID = 1
+STAGE_DNS = 2
+STAGE_CONNECTING = 3
+STAGE_STREAM = 4
+STAGE_DESTROYED = -1
+
+CMD_CONNECT = 0
+CMD_RSP_CONNECT = 1
+CMD_CONNECT_REMOTE = 2
+CMD_RSP_CONNECT_REMOTE = 3
+CMD_POST = 4
+CMD_SYN_STATUS = 5
+CMD_POST_64 = 6
+CMD_SYN_STATUS_64 = 7
+CMD_DISCONNECT = 8
+
+CMD_VER_STR = b"\x08"
+
+RSP_STATE_EMPTY = b""
+RSP_STATE_REJECT = b"\x00"
+RSP_STATE_CONNECTED = b"\x01"
+RSP_STATE_CONNECTEDREMOTE = b"\x02"
+RSP_STATE_ERROR = b"\x03"
+RSP_STATE_DISCONNECT = b"\x04"
+RSP_STATE_REDIRECT = b"\x05"
+
+class UDPLocalAddress(object):
+    def __init__(self, addr):
+        self.addr = addr
+        self.last_activity = time.time()
+
+    def is_timeout(self):
+        return time.time() - self.last_activity > 30
+
+class PacketInfo(object):
+    def __init__(self, data):
+        self.data = data
+        self.time = time.time()
+
+class SendingQueue(object):
+    def __init__(self):
+        self.queue = {}
+        self.begin_id = 0
+        self.end_id = 1
+        self.interval = 0.5
+
+    def append(self, data):
+        self.queue[self.end_id] = PacketInfo(data)
+        self.end_id += 1
+        return self.end_id - 1
+
+    def empty(self):
+        return self.begin_id + 1 == self.end_id
+
+    def size(self):
+        return self.end_id - self.begin_id - 1
+
+    def get_begin_id(self):
+        return self.begin_id
+
+    def get_end_id(self):
+        return self.end_id
+
+    def get_data_list(self, pack_id_base, pack_id_list):
+        ret_list = []
+        curtime = time.time()
+        for pack_id in pack_id_list:
+            offset = pack_id_base + pack_id
+            if offset <= self.begin_id or self.end_id <= offset:
+                continue
+            ret_data = self.queue[offset]
+            if curtime - ret_data.time > self.interval:
+                ret_data.time = curtime
+                ret_list.append( (offset, ret_data.data) )
+        return ret_list
+
+    def set_finish(self, begin_id, done_list):
+        while self.begin_id < begin_id:
+            self.begin_id += 1
+            del self.queue[self.begin_id]
+
+class RecvQueue(object):
+    def __init__(self):
+        self.queue = {}
+        self.miss_queue = set()
+        self.begin_id = 0
+        self.end_id = 1
+
+    def empty(self):
+        return self.begin_id + 1 == self.end_id
+
+    def insert(self, pack_id, data):
+        if (pack_id not in self.queue) and pack_id > self.begin_id:
+            self.queue[pack_id] = PacketInfo(data)
+            if self.end_id == pack_id:
+                self.end_id = pack_id + 1
+            elif self.end_id < pack_id:
+                eid = self.end_id
+                while eid < pack_id:
+                    self.miss_queue.add(eid)
+                    eid += 1
+                self.end_id = pack_id + 1
+            else:
+                self.miss_queue.remove(pack_id)
+
+    def set_end(self, end_id):
+        if end_id > self.end_id:
+            eid = self.end_id
+            while eid < end_id:
+                self.miss_queue.add(eid)
+                eid += 1
+            self.end_id = end_id
+
+    def get_begin_id(self):
+        return self.begin_id
+
+    def has_data(self):
+        return (self.begin_id + 1) in self.queue
+
+    def get_data(self):
+        if (self.begin_id + 1) in self.queue:
+            self.begin_id += 1
+            pack_id = self.begin_id
+            ret_data = self.queue[pack_id]
+            del self.queue[pack_id]
+            return (pack_id, ret_data.data)
+
+    def get_missing_id(self, begin_id):
+        missing = []
+        if begin_id == 0:
+            begin_id = self.begin_id
+        for i in self.miss_queue:
+            if i - begin_id > 32768:
+                break
+            missing.append(i - begin_id)
+        return (begin_id, missing)
+
+class AddressMap(object):
+    def __init__(self):
+        self._queue = []
+        self._addr_map = {}
+
+    def add(self, addr):
+        if addr in self._addr_map:
+            self._addr_map[addr] = UDPLocalAddress(addr)
+        else:
+            self._addr_map[addr] = UDPLocalAddress(addr)
+            self._queue.append(addr)
+
+    def keys(self):
+        return self._queue
+
+    def get(self):
+        if self._queue:
+            while True:
+                if len(self._queue) == 1:
+                    return self._queue[0]
+                index = random.randint(0, len(self._queue) - 1)
+                addr = self._queue[index]
+                if self._addr_map[addr].is_timeout():
+                    self._queue[index] = self._queue[len(self._queue) - 1]
+                    del self._queue[len(self._queue) - 1]
+                    del self._addr_map[addr]
+                else:
+                    break
+            return addr
+        else:
+            return None
+
+class TCPRelayHandler(object):
+    def __init__(self, server, reqid_to_handlers, fd_to_handlers, loop,
+                local_sock, local_id, client_param, config,
+                dns_resolver, is_local):
+        self._server = server
+        self._reqid_to_handlers = reqid_to_handlers
+        self._fd_to_handlers = fd_to_handlers
+        self._loop = loop
+        self._local_sock = local_sock
+        self._remote_sock = None
+        self._remote_udp = False
+        self._config = config
+        self._dns_resolver = dns_resolver
+        self._local_id = local_id
+
+        self._is_local = is_local
+        self._stage = STAGE_INIT
+        self._password = config['password']
+        self._method = 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._request_id = 0
+        self._client_address = AddressMap()
+        self._remote_address = None
+        self._sendingqueue = SendingQueue()
+        self._recvqueue = RecvQueue()
+        if 'forbidden_ip' in config:
+            self._forbidden_iplist = config['forbidden_ip']
+        else:
+            self._forbidden_iplist = None
+        if 'forbidden_port' in config:
+            self._forbidden_portset = config['forbidden_port']
+        else:
+            self._forbidden_portset = None
+        #fd_to_handlers[local_sock.fileno()] = self
+        #local_sock.setblocking(False)
+        #loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR)
+        self.last_activity = 0
+        self._update_activity()
+        self._random_mtu_size = [random.randint(POST_MTU_MIN, POST_MTU_MAX) for i in range(1024)]
+        self._random_mtu_index = 0
+
+        self._rand_data = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" * 4
+
+    def __hash__(self):
+        # default __hash__ is id / 16
+        # we want to eliminate collisions
+        return id(self)
+
+    @property
+    def remote_address(self):
+        return self._remote_address
+
+    def add_local_address(self, addr):
+        self._client_address.add(addr)
+
+    def get_local_address(self):
+        return self._client_address.get()
+
+    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)
+
+    def _update_stream(self, stream, status):
+        # update a stream to a new waiting status
+
+        # check if status is changed
+        # only update if dirty
+        dirty = False
+        if stream == STREAM_DOWN:
+            if self._downstream_status != status:
+                self._downstream_status = status
+                dirty = True
+        elif stream == STREAM_UP:
+            if self._upstream_status != status:
+                self._upstream_status = status
+                dirty = True
+        if dirty:
+            '''
+            if self._local_sock:
+                event = eventloop.POLL_ERR
+                if self._downstream_status & WAIT_STATUS_WRITING:
+                    event |= eventloop.POLL_OUT
+                if self._upstream_status & WAIT_STATUS_READING:
+                    event |= eventloop.POLL_IN
+                self._loop.modify(self._local_sock, event)
+            '''
+            if self._remote_sock:
+                event = eventloop.POLL_ERR
+                if self._downstream_status & WAIT_STATUS_READING:
+                    event |= eventloop.POLL_IN
+                if self._upstream_status & WAIT_STATUS_WRITING:
+                    event |= eventloop.POLL_OUT
+                self._loop.modify(self._remote_sock, event)
+
+    def _write_to_sock(self, data, sock, addr = None):
+        # write data to sock
+        # if only some of the data are written, put remaining in the buffer
+        # and update the stream to wait for writing
+        if not data or not sock:
+            return False
+
+        uncomplete = False
+        retry = 0
+        if sock == self._local_sock:
+            data = encrypt.encrypt_all(self._password, self._method, 1, data)
+            if addr is None:
+                return False
+            try:
+                self._server.write_to_server_socket(data, addr)
+            except (OSError, IOError) as e:
+                error_no = eventloop.errno_from_exception(e)
+                uncomplete = True
+                if error_no in (errno.EAGAIN, errno.EINPROGRESS,
+                                errno.EWOULDBLOCK):
+                    pass
+                else:
+                    #traceback.print_exc()
+                    shell.print_exception(e)
+                    self.destroy()
+                    return False
+        else:
+            try:
+                l = len(data)
+                s = sock.send(data)
+                if s < l:
+                    data = data[s:]
+                    uncomplete = True
+            except (OSError, IOError) as e:
+                error_no = eventloop.errno_from_exception(e)
+                if error_no in (errno.EAGAIN, errno.EINPROGRESS,
+                                errno.EWOULDBLOCK):
+                    uncomplete = True
+                else:
+                    #logging.error(traceback.extract_stack())
+                    #traceback.print_exc()
+                    shell.print_exception(e)
+                    self.destroy()
+                    return False
+        if uncomplete:
+            if sock == self._local_sock:
+                self._update_stream(STREAM_DOWN, WAIT_STATUS_WRITING)
+            elif sock == self._remote_sock:
+                self._data_to_write_to_remote.append(data)
+                self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
+            else:
+                logging.error('write_all_to_sock:unknown socket')
+        else:
+            if sock == self._local_sock:
+                if self._sendingqueue.size() > SENDING_WINDOW_SIZE:
+                    self._update_stream(STREAM_DOWN, WAIT_STATUS_WRITING)
+                else:
+                    self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
+            elif sock == self._remote_sock:
+                self._update_stream(STREAM_UP, WAIT_STATUS_READING)
+            else:
+                logging.error('write_all_to_sock:unknown socket')
+        return True
+
+    def _create_remote_socket(self, ip, port):
+        addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, socket.SOL_TCP)
+        if len(addrs) == 0:
+            raise Exception("getaddrinfo failed for %s:%d" % (ip, port))
+        af, socktype, proto, canonname, sa = addrs[0]
+        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
+
+        remote_sock.setblocking(False)
+        remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+        return remote_sock
+
+    def _handle_dns_resolved(self, result, error):
+        if error:
+            self._log_error(error)
+            self.destroy()
+            return
+        if result:
+            ip = result[1]
+            if ip:
+
+                try:
+                    self._stage = STAGE_CONNECTING
+                    remote_addr = ip
+                    remote_port = self._remote_address[1]
+                    logging.info("connect to %s : %d" % (remote_addr, remote_port))
+
+                    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) in (errno.EINPROGRESS,
+                                errno.EWOULDBLOCK):
+                            pass # always goto here
+                        else:
+                            raise e
+
+                    self._loop.add(remote_sock,
+                                   eventloop.POLL_ERR | eventloop.POLL_OUT,
+                                   self._server)
+                    self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING)
+                    self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
+                    self._stage = STAGE_STREAM
+
+                    addr = self.get_local_address()
+
+                    for i in range(2):
+                        rsp_data = self._pack_rsp_data(CMD_RSP_CONNECT_REMOTE, RSP_STATE_CONNECTEDREMOTE)
+                        self._write_to_sock(rsp_data, self._local_sock, addr)
+
+                    return
+                except Exception as e:
+                    shell.print_exception(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
+        data = None
+        try:
+            data = self._local_sock.recv(BUF_SIZE)
+        except (OSError, IOError) as e:
+            if eventloop.errno_from_exception(e) in \
+                    (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
+                return
+        if not data:
+            self.destroy()
+            return
+        if not data:
+            return
+        self._server.server_transfer_ul += len(data)
+        #TODO ============================================================
+        if self._stage == STAGE_STREAM:
+            self._write_to_sock(data, self._remote_sock)
+            return
+
+    def _on_remote_read(self):
+        # handle all remote read events
+        self._update_activity()
+        data = None
+        try:
+            data = self._remote_sock.recv(BUF_SIZE)
+        except (OSError, IOError) as e:
+            if eventloop.errno_from_exception(e) in \
+                    (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK, 10035): #errno.WSAEWOULDBLOCK
+                return
+        if not data:
+            self.destroy()
+            return
+        try:
+            self._server.server_transfer_dl += len(data)
+            recv_data = data
+            beg_pos = 0
+            max_len = len(recv_data)
+            while beg_pos < max_len:
+                if beg_pos + POST_MTU_MAX >= max_len:
+                    split_pos = max_len
+                else:
+                    split_pos = beg_pos + self._random_mtu_size[self._random_mtu_index]
+                    self._random_mtu_index = (self._random_mtu_index + 1) & 0x3ff
+                    #split_pos = beg_pos + random.randint(POST_MTU_MIN, POST_MTU_MAX)
+                data = recv_data[beg_pos:split_pos]
+                beg_pos = split_pos
+
+                pack_id = self._sendingqueue.append(data)
+                post_data = self._pack_post_data(CMD_POST, pack_id, data)
+                addr = self.get_local_address()
+                self._write_to_sock(post_data, self._local_sock, addr)
+                if pack_id <= DOUBLE_SEND_BEG_IDS:
+                    post_data = self._pack_post_data(CMD_POST, pack_id, data)
+                    self._write_to_sock(post_data, self._local_sock, addr)
+
+        except Exception as e:
+            shell.print_exception(e)
+            if self._config['verbose']:
+                traceback.print_exc()
+            # TODO use logging when debug completed
+            self.destroy()
+
+    def _on_local_write(self):
+        # handle local writable event
+        if self._data_to_write_to_local:
+            data = b''.join(self._data_to_write_to_local)
+            self._data_to_write_to_local = []
+            self._write_to_sock(data, self._local_sock)
+        else:
+            self._update_stream(STREAM_DOWN, WAIT_STATUS_READING)
+
+    def _on_remote_write(self):
+        # handle remote writable event
+        self._stage = STAGE_STREAM
+        if self._data_to_write_to_remote:
+            data = b''.join(self._data_to_write_to_remote)
+            self._data_to_write_to_remote = []
+            self._write_to_sock(data, self._remote_sock)
+        else:
+            self._update_stream(STREAM_UP, WAIT_STATUS_READING)
+
+    def _on_local_error(self):
+        logging.debug('got local error')
+        if self._local_sock:
+            logging.error(eventloop.get_sock_error(self._local_sock))
+        self.destroy()
+
+    def _on_remote_error(self):
+        logging.debug('got remote error')
+        if self._remote_sock:
+            logging.error(eventloop.get_sock_error(self._remote_sock))
+        self.destroy()
+
+    def _pack_rsp_data(self, cmd, data):
+        reqid_str = struct.pack(">H", self._request_id)
+        return b''.join([CMD_VER_STR, common.chr(cmd), reqid_str, data, self._rand_data[:random.randint(0, len(self._rand_data))], reqid_str])
+
+    def _pack_rnd_data(self, data):
+        length = random.randint(0, len(self._rand_data))
+        if length == 0:
+            return data
+        elif length == 1:
+            return b"\x81" + data
+        elif length < 256:
+            return b"\x80" + common.chr(length) + self._rand_data[:length - 2] + data
+        else:
+            return b"\x82" + struct.pack(">H", length) + self._rand_data[:length - 3] + data
+
+    def _pack_post_data(self, cmd, pack_id, data):
+        reqid_str = struct.pack(">H", self._request_id)
+        recv_id = self._recvqueue.get_begin_id()
+        rsp_data = b''.join([CMD_VER_STR, common.chr(cmd), reqid_str, struct.pack(">I", recv_id), struct.pack(">I", pack_id), data, reqid_str])
+        return rsp_data
+
+    def _pack_post_data_64(self, cmd, send_id, pack_id, data):
+        reqid_str = struct.pack(">H", self._request_id)
+        recv_id = self._recvqueue.get_begin_id()
+        rsp_data = b''.join([CMD_VER_STR, common.chr(cmd), reqid_str, struct.pack(">Q", recv_id), struct.pack(">Q", pack_id), data, reqid_str])
+        return rsp_data
+
+    def sweep_timeout(self):
+        logging.info("sweep_timeout")
+        if self._stage == STAGE_STREAM:
+            pack_id, missing = self._recvqueue.get_missing_id(0)
+            logging.info("sweep_timeout %s %s" % (pack_id, missing))
+            data = b''
+            for pid in missing:
+                data += struct.pack(">H", pid)
+            rsp_data = self._pack_post_data(CMD_SYN_STATUS, pack_id, data)
+            addr = self.get_local_address()
+            self._write_to_sock(rsp_data, self._local_sock, addr)
+
+    def handle_stream_sync_status(self, addr, cmd, request_id, pack_id, max_send_id, data):
+        missing_list = []
+        while len(data) >= 2:
+            pid = struct.unpack(">H", data[0:2])[0]
+            data = data[2:]
+            missing_list.append(pid)
+        done_list = []
+        self._recvqueue.set_end(max_send_id)
+        self._sendingqueue.set_finish(pack_id, done_list)
+
+        if self._stage == STAGE_DESTROYED and self._sendingqueue.empty():
+            self.destroy_local()
+            return
+
+        # post CMD_SYN_STATUS
+        send_id = self._sendingqueue.get_end_id()
+        post_pack_id, missing = self._recvqueue.get_missing_id(0)
+        pack_ids_data = b''
+        for pid in missing:
+            pack_ids_data += struct.pack(">H", pid)
+
+        rsp_data = self._pack_rnd_data(self._pack_post_data(CMD_SYN_STATUS, send_id, pack_ids_data))
+        self._write_to_sock(rsp_data, self._local_sock, addr)
+
+        send_list = self._sendingqueue.get_data_list(pack_id, missing_list)
+        for post_pack_id, post_data in send_list:
+            rsp_data = self._pack_post_data(CMD_POST, post_pack_id, post_data)
+            self._write_to_sock(rsp_data, self._local_sock, addr)
+            if post_pack_id <= DOUBLE_SEND_BEG_IDS:
+                rsp_data = self._pack_post_data(CMD_POST, post_pack_id, post_data)
+                self._write_to_sock(rsp_data, self._local_sock, addr)
+
+    def handle_client(self, addr, cmd, request_id, data):
+        self.add_local_address(addr)
+        if cmd == CMD_DISCONNECT:
+            rsp_data = self._pack_rsp_data(CMD_DISCONNECT, RSP_STATE_EMPTY)
+            self._write_to_sock(rsp_data, self._local_sock, addr)
+            self.destroy()
+            self.destroy_local()
+            return
+        if self._stage == STAGE_INIT:
+            if cmd == CMD_CONNECT:
+                self._request_id = request_id
+                self._stage = STAGE_RSP_ID
+            return
+        if self._request_id != request_id:
+            return
+
+        if self._stage == STAGE_RSP_ID:
+            if cmd == CMD_CONNECT:
+                for i in range(2):
+                    rsp_data = self._pack_rsp_data(CMD_RSP_CONNECT, RSP_STATE_CONNECTED)
+                    self._write_to_sock(rsp_data, self._local_sock, addr)
+            elif cmd == CMD_CONNECT_REMOTE:
+                local_id = data[0:4]
+                if self._local_id == local_id:
+                    data = data[4:]
+                    header_result = parse_header(data)
+                    if header_result is None:
+                        return
+                    connecttype, remote_addr, remote_port, header_length = header_result
+                    self._remote_address = (common.to_str(remote_addr), remote_port)
+                    self._stage = STAGE_DNS
+                    self._dns_resolver.resolve(remote_addr,
+                                               self._handle_dns_resolved)
+                    common.connect_log('TCPonUDP connect %s:%d from %s:%d' % (remote_addr, remote_port, addr[0], addr[1]))
+                else:
+                    # ileagal request
+                    rsp_data = self._pack_rsp_data(CMD_DISCONNECT, RSP_STATE_EMPTY)
+                    self._write_to_sock(rsp_data, self._local_sock, addr)
+        elif self._stage == STAGE_CONNECTING:
+            if cmd == CMD_CONNECT_REMOTE:
+                local_id = data[0:4]
+                if self._local_id == local_id:
+                    for i in range(2):
+                        rsp_data = self._pack_rsp_data(CMD_RSP_CONNECT_REMOTE, RSP_STATE_CONNECTEDREMOTE)
+                        self._write_to_sock(rsp_data, self._local_sock, addr)
+                else:
+                    # ileagal request
+                    rsp_data = self._pack_rsp_data(CMD_DISCONNECT, RSP_STATE_EMPTY)
+                    self._write_to_sock(rsp_data, self._local_sock, addr)
+        elif self._stage == STAGE_STREAM:
+            if len(data) < 4:
+                # ileagal request
+                rsp_data = self._pack_rsp_data(CMD_DISCONNECT, RSP_STATE_EMPTY)
+                self._write_to_sock(rsp_data, self._local_sock, addr)
+                return
+            local_id = data[0:4]
+            if self._local_id != local_id:
+                # ileagal request
+                rsp_data = self._pack_rsp_data(CMD_DISCONNECT, RSP_STATE_EMPTY)
+                self._write_to_sock(rsp_data, self._local_sock, addr)
+                return
+            else:
+                data = data[4:]
+            if cmd == CMD_CONNECT_REMOTE:
+                rsp_data = self._pack_rsp_data(CMD_RSP_CONNECT_REMOTE, RSP_STATE_CONNECTEDREMOTE)
+                self._write_to_sock(rsp_data, self._local_sock, addr)
+            elif cmd == CMD_POST:
+                recv_id = struct.unpack(">I", data[0:4])[0]
+                pack_id = struct.unpack(">I", data[4:8])[0]
+                self._recvqueue.insert(pack_id, data[8:])
+                self._sendingqueue.set_finish(recv_id, [])
+            elif cmd == CMD_POST_64:
+                recv_id = struct.unpack(">Q", data[0:8])[0]
+                pack_id = struct.unpack(">Q", data[8:16])[0]
+                self._recvqueue.insert(pack_id, data[16:])
+                self._sendingqueue.set_finish(recv_id, [])
+            elif cmd == CMD_DISCONNECT:
+                rsp_data = self._pack_rsp_data(CMD_DISCONNECT, RSP_STATE_EMPTY)
+                self._write_to_sock(rsp_data, self._local_sock, addr)
+                self.destroy()
+                self.destroy_local()
+                return
+            elif cmd == CMD_SYN_STATUS:
+                pack_id = struct.unpack(">I", data[0:4])[0]
+                max_send_id = struct.unpack(">I", data[4:8])[0]
+                data = data[8:]
+                self.handle_stream_sync_status(addr, cmd, request_id, pack_id, max_send_id, data)
+            elif cmd == CMD_SYN_STATUS_64:
+                pack_id = struct.unpack(">Q", data[0:8])[0]
+                max_send_id = struct.unpack(">Q", data[8:16])[0]
+                data = data[16:]
+                self.handle_stream_sync_status(addr, cmd, request_id, pack_id, max_send_id, data)
+            while self._recvqueue.has_data():
+                pack_id, post_data = self._recvqueue.get_data()
+                self._write_to_sock(post_data, self._remote_sock)
+        elif self._stage == STAGE_DESTROYED:
+            local_id = data[0:4]
+            if self._local_id != local_id:
+                # ileagal request
+                rsp_data = self._pack_rsp_data(CMD_DISCONNECT, RSP_STATE_EMPTY)
+                self._write_to_sock(rsp_data, self._local_sock, addr)
+                return
+            else:
+                data = data[4:]
+            if cmd == CMD_SYN_STATUS:
+                pack_id = struct.unpack(">I", data[0:4])[0]
+                max_send_id = struct.unpack(">I", data[4:8])[0]
+                data = data[8:]
+                self.handle_stream_sync_status(addr, cmd, request_id, pack_id, max_send_id, data)
+            elif cmd == CMD_SYN_STATUS_64:
+                pack_id = struct.unpack(">Q", data[0:8])[0]
+                max_send_id = struct.unpack(">Q", data[8:16])[0]
+                data = data[16:]
+                self.handle_stream_sync_status(addr, cmd, request_id, pack_id, max_send_id, data)
+
+    def handle_event(self, sock, event):
+        # handle all events in this handler and dispatch them to methods
+        if self._stage == STAGE_DESTROYED:
+            logging.debug('ignore handle_event: destroyed')
+            return
+        # order is important
+        if sock == self._remote_sock:
+            if event & eventloop.POLL_ERR:
+                self._on_remote_error()
+                if self._stage == STAGE_DESTROYED:
+                    return
+            if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
+                self._on_remote_read()
+                if self._stage == STAGE_DESTROYED:
+                    return
+            if event & eventloop.POLL_OUT:
+                self._on_remote_write()
+        elif sock == self._local_sock:
+            if event & eventloop.POLL_ERR:
+                self._on_local_error()
+                if self._stage == STAGE_DESTROYED:
+                    return
+            if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
+                self._on_local_read()
+                if self._stage == STAGE_DESTROYED:
+                    return
+            if event & eventloop.POLL_OUT:
+                self._on_local_write()
+        else:
+            logging.warn('unknown socket')
+
+    def _log_error(self, e):
+        logging.error('%s when handling connection from %s' %
+                      (e, self._client_address.keys()))
+
+    def destroy(self):
+        # destroy the handler and release any resources
+        # promises:
+        # 1. destroy won't make another destroy() call inside
+        # 2. destroy releases resources so it prevents future call to destroy
+        # 3. destroy won't raise any exceptions
+        # if any of the promises are broken, it indicates a bug has been
+        # introduced! mostly likely memory leaks, etc
+        #logging.info('tcp destroy called')
+        if self._stage == STAGE_DESTROYED:
+            # this couldn't happen
+            logging.debug('already destroyed')
+            return
+        self._stage = STAGE_DESTROYED
+        if self._remote_address:
+            logging.debug('destroy: %s:%d' %
+                          self._remote_address)
+        else:
+            logging.debug('destroy')
+        if self._remote_sock:
+            logging.debug('destroying remote')
+            self._loop.remove(self._remote_sock)
+            try:
+                del self._fd_to_handlers[self._remote_sock.fileno()]
+            except Exception as e:
+                pass
+            self._remote_sock.close()
+            self._remote_sock = None
+        if self._sendingqueue.empty():
+            self.destroy_local()
+        self._dns_resolver.remove_callback(self._handle_dns_resolved)
+
+    def destroy_local(self):
+        if self._local_sock:
+            logging.debug('disconnect local')
+            rsp_data = self._pack_rsp_data(CMD_DISCONNECT, RSP_STATE_EMPTY)
+            addr = None
+            addr = self.get_local_address()
+            self._write_to_sock(rsp_data, self._local_sock, addr)
+            self._local_sock = None
+            try:
+                del self._reqid_to_handlers[self._request_id]
+            except Exception as e:
+                pass
+
+        self._server.remove_handler(self)
+
+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)
+
+
+class UDPRelay(object):
+    def __init__(self, config, dns_resolver, is_local, stat_callback=None, stat_counter=None):
+        self._config = config
+        if config.get('connect_verbose_info', 0) > 0:
+            common.connect_log = logging.info
+        if is_local:
+            self._listen_addr = config['local_address']
+            self._listen_port = config['local_port']
+            self._remote_addr = config['server']
+            self._remote_port = config['server_port']
+        else:
+            self._listen_addr = config['server']
+            self._listen_port = config['server_port']
+            self._remote_addr = None
+            self._remote_port = None
+        self._dns_resolver = dns_resolver
+        self._password = common.to_bytes(config['password'])
+        self._method = config['method']
+        self._timeout = config['timeout']
+        self._is_local = is_local
+        self._udp_cache_size = config['udp_cache']
+        self._cache = lru_cache.LRUCache(timeout=config['udp_timeout'],
+                                         close_callback=self._close_client_pair)
+        self._cache_dns_client = lru_cache.LRUCache(timeout=10,
+                                         close_callback=self._close_client_pair)
+        self._client_fd_to_server_addr = {}
+        self._dns_cache = lru_cache.LRUCache(timeout=300)
+        self._eventloop = None
+        self._closed = False
+        self.server_transfer_ul = 0
+        self.server_transfer_dl = 0
+        self.server_users = {}
+        self.server_user_transfer_ul = {}
+        self.server_user_transfer_dl = {}
+        self.update_users_protocol_param = None
+        self.update_users_acl = None
+
+        if common.to_bytes(config['protocol']) in [b"auth_aes128_md5", b"auth_aes128_sha1"]:
+            self._update_users(None, None)
+
+        self.protocol_data = obfs.obfs(config['protocol']).init_data()
+        self._protocol = obfs.obfs(config['protocol'])
+        server_info = obfs.server_info(self.protocol_data)
+        server_info.host = self._listen_addr
+        server_info.port = self._listen_port
+        server_info.users = self.server_users
+        server_info.protocol_param = config['protocol_param']
+        server_info.obfs_param = ''
+        server_info.iv = b''
+        server_info.recv_iv = b''
+        server_info.key_str = common.to_bytes(config['password'])
+        server_info.key = encrypt.encrypt_key(self._password, self._method)
+        server_info.head_len = 30
+        server_info.tcp_mss = 1440
+        self._protocol.set_server_info(server_info)
+
+        self._sockets = set()
+        self._fd_to_handlers = {}
+        self._reqid_to_hd = {}
+        self._data_to_write_to_server_socket = []
+
+        self._timeout_cache = lru_cache.LRUCache(timeout=self._timeout,
+                                         close_callback=self._close_tcp_client)
+
+        self._bind = config.get('out_bind', '')
+        self._bindv6 = config.get('out_bindv6', '')
+        self._ignore_bind_list = config.get('ignore_bind', [])
+
+        if 'forbidden_ip' in config:
+            self._forbidden_iplist = config['forbidden_ip']
+        else:
+            self._forbidden_iplist = None
+        if 'forbidden_port' in config:
+            self._forbidden_portset = config['forbidden_port']
+        else:
+            self._forbidden_portset = None
+
+        addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0,
+                                   socket.SOCK_DGRAM, socket.SOL_UDP)
+        if len(addrs) == 0:
+            raise Exception("can't get addrinfo for %s:%d" %
+                            (self._listen_addr, self._listen_port))
+        af, socktype, proto, canonname, sa = addrs[0]
+        server_socket = socket.socket(af, socktype, proto)
+        server_socket.bind((self._listen_addr, self._listen_port))
+        server_socket.setblocking(False)
+        self._server_socket = server_socket
+        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)
+        return server, server_port
+
+    def get_ud(self):
+        return (self.server_transfer_ul, self.server_transfer_dl)
+
+    def get_users_ud(self):
+        ret = (self.server_user_transfer_ul.copy(), self.server_user_transfer_dl.copy())
+        return ret
+
+    def _update_users(self, protocol_param, acl):
+        if protocol_param is None:
+            protocol_param = self._config['protocol_param']
+        param = common.to_bytes(protocol_param).split(b'#')
+        if len(param) == 2:
+            user_list = param[1].split(b',')
+            if user_list:
+                for user in user_list:
+                    items = user.split(b':')
+                    if len(items) == 2:
+                        user_int_id = int(items[0])
+                        uid = struct.pack('<I', user_int_id)
+                        if acl is not None and user_int_id not in acl:
+                            self.del_user(uid)
+                        else:
+                            passwd = items[1]
+                            self.add_user(uid, passwd)
+
+    def update_users(self, protocol_param, acl):
+        self.update_users_protocol_param = protocol_param
+        self.update_users_acl = acl
+
+    def add_user(self, user, passwd): # user: binstr[4], passwd: str
+        self.server_users[user] = common.to_bytes(passwd)
+
+    def del_user(self, user):
+        if user in self.server_users:
+            del self.server_users[user]
+
+    def add_transfer_u(self, user, transfer):
+        if user is None:
+            self.server_transfer_ul += transfer
+        else:
+            if user not in self.server_user_transfer_ul:
+                self.server_user_transfer_ul[user] = 0
+            self.server_user_transfer_ul[user] += transfer + self.server_transfer_ul
+            self.server_transfer_ul = 0
+
+    def add_transfer_d(self, user, transfer):
+        if user is None:
+            self.server_transfer_dl += transfer
+        else:
+            if user not in self.server_user_transfer_dl:
+                self.server_user_transfer_dl[user] = 0
+            self.server_user_transfer_dl[user] += transfer + self.server_transfer_dl
+            self.server_transfer_dl = 0
+
+    def _close_client_pair(self, client_pair):
+        client, uid = client_pair
+        self._close_client(client)
+
+    def _close_client(self, client):
+        if hasattr(client, 'close'):
+            if not self._is_local:
+                if client.fileno() in self._client_fd_to_server_addr:
+                    logging.debug('close_client: %s' %
+                                 (self._client_fd_to_server_addr[client.fileno()],))
+                else:
+                    client.info('close_client')
+            self._sockets.remove(client.fileno())
+            self._eventloop.remove(client)
+            del self._client_fd_to_server_addr[client.fileno()]
+            client.close()
+        else:
+            # just an address
+            client.info('close_client pass %s' % client)
+            pass
+
+    def _pre_parse_udp_header(self, data):
+        if data is None:
+            return
+        datatype = common.ord(data[0])
+        if datatype == 0x8:
+            if len(data) >= 8:
+                crc = binascii.crc32(data) & 0xffffffff
+                if crc != 0xffffffff:
+                    logging.warn('uncorrect CRC32, maybe wrong password or '
+                                 'encryption method')
+                    return None
+                cmd = common.ord(data[1])
+                request_id = struct.unpack('>H', data[2:4])[0]
+                data = data[4:-4]
+                return (cmd, request_id, data)
+            elif len(data) >= 6 and common.ord(data[1]) == 0x0:
+                crc = binascii.crc32(data) & 0xffffffff
+                if crc != 0xffffffff:
+                    logging.warn('uncorrect CRC32, maybe wrong password or '
+                                 'encryption method')
+                    return None
+                cmd = common.ord(data[1])
+                data = data[2:-4]
+                return (cmd, 0, data)
+            else:
+                logging.warn('header too short, maybe wrong password or '
+                             'encryption method')
+                return None
+        return data
+
+    def _pack_rsp_data(self, cmd, request_id, data):
+        _rand_data = b"123456789abcdefghijklmnopqrstuvwxyz" * 2
+        reqid_str = struct.pack(">H", request_id)
+        return b''.join([CMD_VER_STR, common.chr(cmd), reqid_str, data, _rand_data[:random.randint(0, len(_rand_data))], reqid_str])
+
+    def _handel_protocol_error(self, client_address, ogn_data):
+        #raise Exception('can not parse header')
+        logging.warn("Protocol ERROR, UDP ogn data %s from %s:%d" % (binascii.hexlify(ogn_data), client_address[0], client_address[1]))
+
+    def _socket_bind_addr(self, sock, af):
+        bind_addr = ''
+        if self._bind and af == socket.AF_INET:
+            bind_addr = self._bind
+        elif self._bindv6 and af == socket.AF_INET6:
+            bind_addr = self._bindv6
+
+        bind_addr = bind_addr.replace("::ffff:", "")
+        if bind_addr in self._ignore_bind_list:
+            bind_addr = None
+        if bind_addr:
+            local_addrs = socket.getaddrinfo(bind_addr, 0, 0, socket.SOCK_DGRAM, socket.SOL_UDP)
+            if local_addrs[0][0] == af:
+                logging.debug("bind %s" % (bind_addr,))
+                try:
+                    sock.bind((bind_addr, 0))
+                except Exception as e:
+                    logging.warn("bind %s fail" % (bind_addr,))
+
+    def _handle_server(self):
+        server = self._server_socket
+        data, r_addr = server.recvfrom(BUF_SIZE)
+        ogn_data = data
+        if not data:
+            logging.debug('UDP handle_server: data is empty')
+        if self._stat_callback:
+            self._stat_callback(self._listen_port, len(data))
+        uid = None
+        if self._is_local:
+            frag = common.ord(data[2])
+            if frag != 0:
+                logging.warn('drop a message since frag is not 0')
+                return
+            else:
+                data = data[3:]
+        else:
+            ref_iv = [0]
+            data = encrypt.encrypt_all_iv(self._protocol.obfs.server_info.key, self._method, 0, data, ref_iv)
+            # decrypt data
+            if not data:
+                logging.debug('UDP handle_server: data is empty after decrypt')
+                return
+            self._protocol.obfs.server_info.recv_iv = ref_iv[0]
+            data, uid = self._protocol.server_udp_post_decrypt(data)
+
+        #logging.info("UDP data %s" % (binascii.hexlify(data),))
+        if not self._is_local:
+            data = pre_parse_header(data)
+
+            data = self._pre_parse_udp_header(data)
+            if data is None:
+                return
+
+            if type(data) is tuple:
+                return
+                #return self._handle_tcp_over_udp(data, r_addr)
+
+        try:
+            header_result = parse_header(data)
+        except:
+            self._handel_protocol_error(r_addr, ogn_data)
+            return
+
+        if header_result is None:
+            self._handel_protocol_error(r_addr, ogn_data)
+            return
+        connecttype, dest_addr, dest_port, header_length = header_result
+
+        if self._is_local:
+            server_addr, server_port = self._get_a_server()
+        else:
+            server_addr, server_port = dest_addr, dest_port
+
+        addrs = self._dns_cache.get(server_addr, None)
+        if addrs is None:
+            # TODO async getaddrinfo
+            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)
+        client_pair = self._cache.get(key, None)
+        if not client_pair:
+            client_pair = self._cache_dns_client.get(key, None)
+        if not client_pair:
+            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
+            if self._forbidden_portset:
+                if sa[1] in self._forbidden_portset:
+                    logging.debug('Port %d is in forbidden list, reject' %
+                                    sa[1])
+                    # drop
+                    return
+            client = socket.socket(af, socktype, proto)
+            client_uid = uid
+            client.setblocking(False)
+            self._socket_bind_addr(client, af)
+            is_dns = False
+            if len(data) > 20 and data[11:19] == b"\x00\x01\x00\x00\x00\x00\x00\x00":
+                is_dns = True
+            else:
+                pass
+                #logging.info("unknown data %s" % (binascii.hexlify(data),))
+            if sa[1] == 53 and is_dns: #DNS
+                logging.debug("DNS query %s from %s:%d" % (common.to_str(sa[0]), r_addr[0], r_addr[1]))
+                self._cache_dns_client[key] = (client, uid)
+            else:
+                self._cache[key] = (client, uid)
+            self._client_fd_to_server_addr[client.fileno()] = (r_addr, af)
+
+            self._sockets.add(client.fileno())
+            self._eventloop.add(client, eventloop.POLL_IN, self)
+
+            logging.debug('UDP port %5d sockets %d' % (self._listen_port, len(self._sockets)))
+
+            common.connect_log('UDP data to %s:%d via port %d' %
+                        (common.to_str(server_addr), server_port,
+                            self._listen_port))
+        else:
+            client, client_uid = client_pair
+        self._cache.clear(self._udp_cache_size)
+        self._cache_dns_client.clear(16)
+
+        if self._is_local:
+            ref_iv = [encrypt.encrypt_new_iv(self._method)]
+            self._protocol.obfs.server_info.iv = ref_iv[0]
+            data = self._protocol.client_udp_pre_encrypt(data)
+            #logging.debug("%s" % (binascii.hexlify(data),))
+            data = encrypt.encrypt_all_iv(self._protocol.obfs.server_info.key, self._method, 1, data, ref_iv)
+            if not data:
+                return
+        else:
+            data = data[header_length:]
+        if not data:
+            return
+        try:
+            #logging.info('UDP handle_server sendto %s:%d %d bytes' % (common.to_str(server_addr), server_port, len(data)))
+            client.sendto(data, (server_addr, server_port))
+            self.add_transfer_u(client_uid, len(data))
+        except IOError as e:
+            err = eventloop.errno_from_exception(e)
+            if err in (errno.EINPROGRESS, errno.EAGAIN):
+                pass
+            else:
+                shell.print_exception(e)
+
+    def _handle_tcp_over_udp(self, data, r_addr):
+        #(cmd, request_id, data)
+        #logging.info("UDP data %d %d %s" % (data[0], data[1], binascii.hexlify(data[2])))
+        try:
+            self.server_transfer_ul += len(data[2])
+            if data[0] == 0:
+                if len(data[2]) >= 4:
+                    for i in range(64):
+                        req_id = random.randint(1, 65535)
+                        if req_id not in self._reqid_to_hd:
+                            break
+                    if req_id in self._reqid_to_hd:
+                        for i in range(64):
+                            req_id = random.randint(1, 65535)
+                            if type(self._reqid_to_hd[req_id]) is tuple:
+                                break
+                    # return req id
+                    self._reqid_to_hd[req_id] = (data[2][0:4], None)
+                    rsp_data = self._pack_rsp_data(CMD_RSP_CONNECT, req_id, RSP_STATE_CONNECTED)
+                    data_to_send = encrypt.encrypt_all(self._password, self._method, 1, rsp_data)
+                    self.write_to_server_socket(data_to_send, r_addr)
+            elif data[0] == CMD_CONNECT_REMOTE:
+                if len(data[2]) > 4 and data[1] in self._reqid_to_hd:
+                    # create
+                    if type(self._reqid_to_hd[data[1]]) is tuple:
+                        if data[2][0:4] == self._reqid_to_hd[data[1]][0]:
+                            handle = TCPRelayHandler(self, self._reqid_to_hd, self._fd_to_handlers,
+                                self._eventloop, self._server_socket,
+                                self._reqid_to_hd[data[1]][0], self._reqid_to_hd[data[1]][1],
+                                self._config, self._dns_resolver, self._is_local)
+                            self._reqid_to_hd[data[1]] = handle
+                            handle.handle_client(r_addr, CMD_CONNECT, data[1], data[2])
+                            handle.handle_client(r_addr, *data)
+                            self.update_activity(handle)
+                        else:
+                            # disconnect
+                            rsp_data = self._pack_rsp_data(CMD_DISCONNECT, data[1], RSP_STATE_EMPTY)
+                            data_to_send = encrypt.encrypt_all(self._password, self._method, 1, rsp_data)
+                            self.write_to_server_socket(data_to_send, r_addr)
+                    else:
+                        self.update_activity(self._reqid_to_hd[data[1]])
+                        self._reqid_to_hd[data[1]].handle_client(r_addr, *data)
+                else:
+                    # disconnect
+                    rsp_data = self._pack_rsp_data(CMD_DISCONNECT, data[1], RSP_STATE_EMPTY)
+                    data_to_send = encrypt.encrypt_all(self._password, self._method, 1, rsp_data)
+                    self.write_to_server_socket(data_to_send, r_addr)
+            elif data[0] > CMD_CONNECT_REMOTE and data[0] <= CMD_DISCONNECT:
+                if data[1] in self._reqid_to_hd:
+                    if type(self._reqid_to_hd[data[1]]) is tuple:
+                        pass
+                    else:
+                        self.update_activity(self._reqid_to_hd[data[1]])
+                        self._reqid_to_hd[data[1]].handle_client(r_addr, *data)
+                else:
+                    # disconnect
+                    rsp_data = self._pack_rsp_data(CMD_DISCONNECT, data[1], RSP_STATE_EMPTY)
+                    data_to_send = encrypt.encrypt_all(self._password, self._method, 1, rsp_data)
+                    self.write_to_server_socket(data_to_send, r_addr)
+            return
+        except Exception as e:
+            trace = traceback.format_exc()
+            logging.error(trace)
+            return
+
+    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
+            ref_iv = [encrypt.encrypt_new_iv(self._method)]
+            self._protocol.obfs.server_info.iv = ref_iv[0]
+            data = self._protocol.server_udp_pre_encrypt(data)
+            response = encrypt.encrypt_all_iv(self._protocol.obfs.server_info.key, self._method, 1,
+                                           data, ref_iv)
+            if not response:
+                return
+        else:
+            ref_iv = [0]
+            data = encrypt.encrypt_all_iv(self._protocol.obfs.server_info.key, self._method, 0,
+                                       data, ref_iv)
+            if not data:
+                return
+            self._protocol.obfs.server_info.recv_iv = ref_iv[0]
+            data = self._protocol.client_udp_post_decrypt(data)
+            header_result = parse_header(data)
+            if header_result is None:
+                return
+            #connecttype, dest_addr, dest_port, header_length = header_result
+            #logging.debug('UDP handle_client %s:%d to %s:%d' % (common.to_str(r_addr[0]), r_addr[1], dest_addr, dest_port))
+
+            response = b'\x00\x00\x00' + data
+        client_addr = self._client_fd_to_server_addr.get(sock.fileno())
+        if client_addr:
+            key = client_key(client_addr[0], client_addr[1])
+            client_pair = self._cache.get(key, None)
+            client_dns_pair = self._cache_dns_client.get(key, None)
+            if client_pair:
+                client, client_uid = client_pair
+                self.add_transfer_d(client_uid, len(response))
+            elif client_dns_pair:
+                client, client_uid = client_dns_pair
+                self.add_transfer_d(client_uid, len(response))
+            else:
+                self.server_transfer_dl += len(response)
+            self.write_to_server_socket(response, client_addr[0])
+            if client_dns_pair:
+                logging.debug("remove dns client %s:%d" % (client_addr[0][0], client_addr[0][1]))
+                del self._cache_dns_client[key]
+                self._close_client(client_dns_pair[0])
+        else:
+            # this packet is from somewhere else we know
+            # simply drop that packet
+            pass
+
+    def write_to_server_socket(self, data, addr):
+        uncomplete = False
+        retry = 0
+        try:
+            self._server_socket.sendto(data, addr)
+            data = None
+            while self._data_to_write_to_server_socket:
+                data_buf = self._data_to_write_to_server_socket[0]
+                retry = data_buf[1] + 1
+                del self._data_to_write_to_server_socket[0]
+                data, addr = data_buf[0]
+                self._server_socket.sendto(data, addr)
+        except (OSError, IOError) as e:
+            error_no = eventloop.errno_from_exception(e)
+            uncomplete = True
+            if error_no in (errno.EWOULDBLOCK,):
+                pass
+            else:
+                shell.print_exception(e)
+                return False
+        #if uncomplete and data is not None and retry < 3:
+        #    self._data_to_write_to_server_socket.append([(data, addr), retry])
+        #'''
+
+    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
+
+        server_socket = self._server_socket
+        self._eventloop.add(server_socket,
+                            eventloop.POLL_IN | eventloop.POLL_ERR, self)
+        loop.add_periodic(self.handle_periodic)
+
+    def remove_handler(self, client):
+        if hash(client) in self._timeout_cache:
+            del self._timeout_cache[hash(client)]
+
+    def update_activity(self, client):
+        self._timeout_cache[hash(client)] = client
+
+    def _sweep_timeout(self):
+        self._timeout_cache.sweep()
+
+    def _close_tcp_client(self, client):
+        if client.remote_address:
+            logging.debug('timed out: %s:%d' %
+                         client.remote_address)
+        else:
+            logging.debug('timed out')
+        client.destroy()
+        client.destroy_local()
+
+    def handle_event(self, sock, fd, event):
+        if sock == self._server_socket:
+            if event & eventloop.POLL_ERR:
+                logging.error('UDP server_socket err')
+            try:
+                self._handle_server()
+            except Exception as e:
+                shell.print_exception(e)
+                if self._config['verbose']:
+                    traceback.print_exc()
+        elif sock and (fd in self._sockets):
+            if event & eventloop.POLL_ERR:
+                logging.error('UDP client_socket err')
+            try:
+                self._handle_client(sock)
+            except Exception as e:
+                shell.print_exception(e)
+                if self._config['verbose']:
+                    traceback.print_exc()
+        else:
+            if sock:
+                handler = self._fd_to_handlers.get(fd, None)
+                if handler:
+                    handler.handle_event(sock, event)
+            else:
+                logging.warn('poll removed fd')
+
+    def handle_periodic(self):
+        if self._closed:
+            self._cache.clear(0)
+            self._cache_dns_client.clear(0)
+            self._dns_cache.sweep()
+            if self._eventloop:
+                self._eventloop.remove_periodic(self.handle_periodic)
+                self._eventloop.remove(self._server_socket)
+            if self._server_socket:
+                self._server_socket.close()
+                self._server_socket = None
+                logging.info('closed UDP port %d', self._listen_port)
+        else:
+            before_sweep_size = len(self._sockets)
+            self._cache.sweep()
+            self._cache_dns_client.sweep()
+            self._dns_cache.sweep()
+            if before_sweep_size != len(self._sockets):
+                logging.debug('UDP port %5d sockets %d' % (self._listen_port, len(self._sockets)))
+            if self.update_users_protocol_param is not None or self.update_users_acl is not None:
+                self._update_users(self.update_users_protocol_param, self.update_users_acl)
+                self.update_users_protocol_param = None
+                self.update_users_acl = None
+            self._sweep_timeout()
+
+    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()
+            self._cache.clear(0)
+            self._cache_dns_client.clear(0)

BIN
ssr/shadowsocks/udprelay.pyc


+ 20 - 0
ssr/shadowsocks/version.py

@@ -0,0 +1,20 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2017 breakwa11
+#
+# 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.
+
+def version():
+    return '3.0.2 2017-01-08'
+

BIN
ssr/shadowsocks/version.pyc


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä