├── .project ├── .pydevproject ├── .settings └── org.eclipse.core.resources.prefs ├── CHANGES ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── setup.py └── shadowsocks ├── Config.py ├── __init__.py ├── asyncdns.py ├── asyncmgr.py ├── common.py ├── config.json ├── db_transfer.py ├── encrypt.py ├── encrypt_rc4_md5.py ├── encrypt_salsa20.py ├── eventloop.py ├── local.py ├── lru_cache.py ├── server.py ├── server_pool.py ├── shadowsocks.sql ├── tcprelay.py ├── udpmanagetest.py ├── udprelay.py └── utils.py /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | GoOut 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | Default 4 | python 2.7 5 | 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//shadowsocks/asyncmgr.py=utf-8 3 | encoding//shadowsocks/db_transfer.py=UTF-8 4 | encoding//shadowsocks/local.py=utf-8 5 | encoding//shadowsocks/server_pool.py=utf-8 6 | encoding//shadowsocks/utils.py=utf-8 7 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 2.2.0 2014-09-09 2 | - Add RC4-MD5 encryption 3 | 4 | 2.1.0 2014-08-10 5 | - Use only IPv4 DNS server 6 | - Does not ship config.json 7 | - Better error message 8 | 9 | 2.0.12 2014-07-26 10 | - Support -q quiet mode 11 | - Exit 0 when showing help with -h 12 | 13 | 2.0.11 2014-07-12 14 | - Prefers IP addresses over hostnames, more friendly with socksify and openvpn 15 | 16 | 2.0.10 2014-07-11 17 | - Fix UDP on local 18 | 19 | 2.0.9 2014-07-06 20 | - Fix EWOULDBLOCK on Windows 21 | - Fix Unicode config problem on some platforms 22 | 23 | 2.0.8 2014-06-23 24 | - Use multiple DNS to query hostnames 25 | 26 | 2.0.7 2014-06-21 27 | - Fix fastopen on local 28 | - Fallback when fastopen is not available 29 | - Add verbose logging mode -vv 30 | - Verify if hostname is valid 31 | 32 | 2.0.6 2014-06-19 33 | - Fix CPU 100% on POLL_HUP 34 | - More friendly logging 35 | 36 | 2.0.5 2014-06-18 37 | - Support a simple config format for multiple ports 38 | 39 | 2.0.4 2014-06-12 40 | - Fix worker master 41 | 42 | 2.0.3 2014-06-11 43 | - Fix table encryption with UDP 44 | 45 | 2.0.2 2014-06-11 46 | - Add asynchronous DNS in TCP relay 47 | 48 | 2.0.1 2014-06-05 49 | - Better logging 50 | - Maybe fix bad file descriptor 51 | 52 | 2.0 2014-06-05 53 | - Use a new event model 54 | - Remove gevent 55 | - Refuse to use default password 56 | - Fix a problem when using multiple passwords with table encryption 57 | 58 | 1.4.5 2014-05-24 59 | - Add timeout in TCP server 60 | - Close sockets in master process 61 | 62 | 1.4.4 2014-05-17 63 | - Support multiple workers 64 | 65 | 1.4.3 2014-05-13 66 | - Fix Windows 67 | 68 | 1.4.2 2014-05-10 69 | - Add salsa20-ctr cipher 70 | 71 | 1.4.1 2014-05-03 72 | - Fix error log 73 | - Fix EINPROGESS with some version of gevent 74 | 75 | 1.4.0 2014-05-02 76 | - Adds UDP relay 77 | - TCP fast open support on Linux 3.7+ 78 | 79 | 1.3.7 2014-04-10 80 | - Fix a typo in help 81 | 82 | 1.3.6 2014-04-10 83 | - Fix a typo in help 84 | 85 | 1.3.5 2014-04-07 86 | - Add help 87 | - Change default local binding address into 127.0.0.1 88 | 89 | 1.3.4 2014-02-17 90 | - Fix a bug when no config file exists 91 | - Client now support multiple server ports and multiple server/port pairs 92 | - Better error message with bad config.json format and wrong password 93 | 94 | 1.3.3 2013-07-09 95 | - Fix default key length of rc2 96 | 97 | 1.3.2 2013-07-04 98 | - Server will listen at server IP specified in config 99 | - Check config file and show some warning messages 100 | 101 | 1.3.1 2013-06-29 102 | - Fix -c arg 103 | 104 | 1.3.0 2013-06-22 105 | - Move to pypi 106 | 107 | 1.2.3 2013-06-14 108 | - add bind address 109 | 110 | 1.2.2 2013-05-31 111 | - local can listen at ::0 with -6 arg; bump 1.2.2 112 | 113 | 1.2.1 2013-05-23 114 | - Fix an OpenSSL crash 115 | 116 | 1.2 2013-05-22 117 | - Use random iv, we finally have strong encryption 118 | 119 | 1.1.1 2013-05-21 120 | - Add encryption, AES, blowfish, etc. 121 | 122 | 1.1 2013-05-16 123 | - Support IPv6 addresses (type 4) 124 | - Drop Python 2.5 support 125 | 126 | 1.0 2013-04-03 127 | - Fix -6 IPv6 128 | 129 | 0.9.4 2013-03-04 130 | - Support Python 2.5 131 | 132 | 0.9.3 2013-01-14 133 | - Fix conn termination null data 134 | 135 | 0.9.2 2013-01-05 136 | - Change default timeout 137 | 138 | 0.9.1 2013-01-05 139 | - Add Travis-CI test 140 | 141 | 0.9 2012-12-30 142 | - Replace send with sendall, fix FreeBSD 143 | 144 | 0.6 2012-12-06 145 | - Support args 146 | 147 | 0.5 2012-11-08 148 | - Fix encryption with negative md5sum 149 | 150 | 0.4 2012-11-02 151 | - Move config into a JSON file 152 | - Auto-detect config path 153 | 154 | 0.3 2012-06-06 155 | - Move socks5 negotiation to local 156 | 157 | 0.2 2012-05-11 158 | - Add -6 arg for IPv6 159 | - Fix socket.error 160 | 161 | 0.1 2012-04-20 162 | - Initial version 163 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | 在你提交问题前,请先[自行诊断]一下。提交时附上诊断过程中的问题和下列结果, 5 | 否则如果我们无法重现你的问题,也就不能帮助你。 6 | 7 | Before you submit issues, please read [Troubleshooting] and take a few minutes 8 | to read this guide. 9 | 10 | 问题反馈 11 | ------- 12 | 13 | 请提交下面的信息: 14 | 15 | 1. 你是如何搭建环境的?(操作系统,Shadowsocks 版本) 16 | 2. 有无错误提示?错误是发生在哪里,客户端还是服务器? 17 | 3. 浏览器里的现象是什么?一直转菊花,还是有提示错误? 18 | 4. 发生错误时,客户端最后一页完整的日志。 19 | 5. 发生错误时,服务器端最后一页完整的日志。 20 | 6. 其它你认为可能和问题有关的信息。 21 | 22 | 如果你不清楚其中某条的含义, 可以直接跳过那一条。 23 | 24 | Issues 25 | ------ 26 | 27 | Please include the following information in your submission: 28 | 29 | 1. How did you set up your environment? (OS, version of Shadowsocks) 30 | 2. Did you see any error? Where did you see this error, was it on local or on server? 31 | 3. What happened in your browser? Just no response, or any error message? 32 | 4. 10 lines of log on the local side of shadowsocks when the error happened. 33 | 5. 10 lines of log on the server side of shadowsocks when the error happened. 34 | 6. Any other useful information. 35 | 36 | Skip any of them if you don't know its meaning. 37 | 38 | [Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting 39 | [自行诊断]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Shadowsocks 2 | 3 | Copyright (c) 2014 clowwindy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include *.py 2 | include README.rst 3 | include LICENSE 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GoOut-shadowsocks-manyuser 是shadowsocks-manyuser 的一个分支 2 | 3 | 主要更新: 4 | 1.支持多服务器支持判断标识 5 | 2.配合GoOut.wang前端支持 6 | 3.添加流量套餐与时间套餐双套餐判断 7 | 4.优化数据库读取方式 8 | 9 | 支持库 10 | 1.Python 11 | 2.python-cymysql 12 | 3.python-m2crypto 13 | 14 | 配置文件 15 | 1.~/shadowsocks/Config.py 16 | 17 | #Config 18 | MYSQL_HOST = '数据库服务器地址' 19 | MYSQL_PORT = 3306 20 | MYSQL_USER = '用户名' 21 | MYSQL_PASS = '密码' 22 | MYSQL_DB = '数据库' 23 | ServerSign = '唯一标识符' 24 | 25 | MANAGE_PASS = '管理密码' 26 | #管理IP 27 | MANAGE_BIND_IP = '127.0.0.1' 28 | #管理端口 29 | MANAGE_PORT = 22222 30 | 31 | 数据库需要配合前端使用,后台不提供数据库 32 | 33 | 2.~/shadowsocks/config.json 34 | 35 | { 36 | "server":"0.0.0.0", 37 | "server_ipv6": "[::]", 38 | "server_port":8388, 39 | "local_address": "127.0.0.1", 40 | "local_port":1080, 41 | "password":"m", 42 | "timeout":300, 43 | "method":"aes-256-cfb" 44 | } 45 | 46 | 该配置文件只需要修改method 加密方式即可,其他参数基本没用但不可删除 -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | shadowsocks 2 | =========== 3 | 4 | |PyPI version| |Build Status| 5 | 6 | A fast tunnel proxy that help you get through firewalls. 7 | 8 | `中文说明 `__ 9 | 10 | Install 11 | ------- 12 | 13 | You'll have a client on your local machine, and install a server on a 14 | remote server. 15 | 16 | Client 17 | ~~~~~~ 18 | 19 | - `Windows `__ 20 | / `OS 21 | X `__ 22 | - `Android `__ 23 | / 24 | `iOS `__ 25 | - `OpenWRT `__ 26 | 27 | Server 28 | ~~~~~~ 29 | 30 | Debian / Ubuntu: 31 | ^^^^^^^^^^^^^^^^ 32 | 33 | :: 34 | 35 | apt-get install python-pip python-m2crypto 36 | pip install shadowsocks 37 | 38 | CentOS: 39 | ^^^^^^^ 40 | 41 | :: 42 | 43 | yum install m2crypto python-setuptools 44 | easy_install pip 45 | pip install shadowsocks 46 | 47 | Configuration 48 | ------------- 49 | 50 | On your server create a config file ``/etc/shadowsocks.json``. Example: 51 | 52 | :: 53 | 54 | { 55 | "server":"my_server_ip", 56 | "server_port":8388, 57 | "local_address": "127.0.0.1", 58 | "local_port":1080, 59 | "password":"mypassword", 60 | "timeout":300, 61 | "method":"aes-256-cfb", 62 | "fast_open": false, 63 | "workers": 1 64 | } 65 | 66 | Explanation of the fields: 67 | 68 | +------------------+-----------------------------------------------------------------------------------------------------+ 69 | | Name | Explanation | 70 | +==================+=====================================================================================================+ 71 | | server | the address your server listens | 72 | +------------------+-----------------------------------------------------------------------------------------------------+ 73 | | server\_port | server port | 74 | +------------------+-----------------------------------------------------------------------------------------------------+ 75 | | local\_address | the address your local listens | 76 | +------------------+-----------------------------------------------------------------------------------------------------+ 77 | | local\_port | local port | 78 | +------------------+-----------------------------------------------------------------------------------------------------+ 79 | | password | password used for encryption | 80 | +------------------+-----------------------------------------------------------------------------------------------------+ 81 | | timeout | in seconds | 82 | +------------------+-----------------------------------------------------------------------------------------------------+ 83 | | method | encryption method, "aes-256-cfb" is recommended | 84 | +------------------+-----------------------------------------------------------------------------------------------------+ 85 | | fast\_open | use `TCP\_FASTOPEN `__, true / false | 86 | +------------------+-----------------------------------------------------------------------------------------------------+ 87 | | workers | number of workers, available on Unix/Linux | 88 | +------------------+-----------------------------------------------------------------------------------------------------+ 89 | 90 | Run ``ssserver -c /etc/shadowsocks.json`` on your server. To run it in 91 | the background, use 92 | `Supervisor `__. 93 | 94 | On your client machine, use the same configuration as your server, and 95 | start your client. 96 | 97 | If you use Chrome, it's recommended to use 98 | `SwitchySharp `__. 99 | Change the proxy settings to 100 | 101 | :: 102 | 103 | protocol: socks5 104 | hostname: 127.0.0.1 105 | port: your local_port 106 | 107 | If you can't install 108 | `SwitchySharp `__, 109 | you can launch Chrome with the following arguments to force Chrome to 110 | use the proxy: 111 | 112 | :: 113 | 114 | Chrome.exe --proxy-server="socks5://127.0.0.1:1080" --host-resolver-rules="MAP * 0.0.0.0 , EXCLUDE localhost" 115 | 116 | If you can't even download Chrome, find a friend to download a `Chrome 117 | Standalone `__ 118 | installer for you. 119 | 120 | Command line args 121 | ----------------- 122 | 123 | You can use args to override settings from ``config.json``. 124 | 125 | :: 126 | 127 | sslocal -s server_name -p server_port -l local_port -k password -m bf-cfb 128 | ssserver -p server_port -k password -m bf-cfb --workers 2 129 | ssserver -c /etc/shadowsocks/config.json 130 | 131 | List all available args with ``-h``. 132 | 133 | Wiki 134 | ---- 135 | 136 | https://github.com/clowwindy/shadowsocks/wiki 137 | 138 | License 139 | ------- 140 | 141 | MIT 142 | 143 | Bugs and Issues 144 | --------------- 145 | 146 | - `Troubleshooting `__ 147 | - `Issue 148 | Tracker `__ 149 | - `Mailing list `__ 150 | 151 | .. |PyPI version| image:: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat 152 | :target: https://pypi.python.org/pypi/shadowsocks 153 | .. |Build Status| image:: https://img.shields.io/travis/clowwindy/shadowsocks/master.svg?style=flat 154 | :target: https://travis-ci.org/clowwindy/shadowsocks 155 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | with open('README.rst') as f: 5 | long_description = f.read() 6 | 7 | setup( 8 | name="shadowsocks", 9 | version="2.2.0", 10 | license='MIT', 11 | description="A fast tunnel proxy that help you get through firewalls", 12 | author='clowwindy', 13 | author_email='clowwindy42@gmail.com', 14 | url='https://github.com/clowwindy/shadowsocks', 15 | packages=['shadowsocks'], 16 | package_data={ 17 | 'shadowsocks': ['README.rst', 'LICENSE'] 18 | }, 19 | install_requires=[], 20 | entry_points=""" 21 | [console_scripts] 22 | sslocal = shadowsocks.local:main 23 | ssserver = shadowsocks.server:main 24 | """, 25 | classifiers=[ 26 | 'License :: OSI Approved :: MIT License', 27 | 'Programming Language :: Python :: 2', 28 | 'Programming Language :: Python :: 2.6', 29 | 'Programming Language :: Python :: 2.7', 30 | 'Topic :: Internet :: Proxy Servers', 31 | ], 32 | long_description=long_description, 33 | ) 34 | -------------------------------------------------------------------------------- /shadowsocks/Config.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stclair2201/GoOut-Shadowsocks-Manyuser/cb5cbc8fb7d359a8820a89944b7e67ae0dfa5c83/shadowsocks/Config.py -------------------------------------------------------------------------------- /shadowsocks/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | -------------------------------------------------------------------------------- /shadowsocks/asyncdns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import time 25 | import os 26 | import socket 27 | import struct 28 | import re 29 | import logging 30 | import common 31 | import lru_cache 32 | import eventloop 33 | 34 | 35 | CACHE_SWEEP_INTERVAL = 30 36 | 37 | VALID_HOSTNAME = re.compile("(?!-)[A-Z\d-]{1,63}(? 63: 87 | return None 88 | results.append(chr(l)) 89 | results.append(label) 90 | results.append('\0') 91 | return ''.join(results) 92 | 93 | 94 | def build_request(address, qtype, request_id): 95 | header = struct.pack('!HBBHHHH', request_id, 1, 0, 1, 0, 0, 0) 96 | addr = build_address(address) 97 | qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN) 98 | return header + addr + qtype_qclass 99 | 100 | 101 | def parse_ip(addrtype, data, length, offset): 102 | if addrtype == QTYPE_A: 103 | return socket.inet_ntop(socket.AF_INET, data[offset:offset + length]) 104 | elif addrtype == QTYPE_AAAA: 105 | return socket.inet_ntop(socket.AF_INET6, data[offset:offset + length]) 106 | elif addrtype in [QTYPE_CNAME, QTYPE_NS]: 107 | return parse_name(data, offset)[1] 108 | else: 109 | return data[offset:offset + length] 110 | 111 | 112 | def parse_name(data, offset): 113 | p = offset 114 | labels = [] 115 | l = ord(data[p]) 116 | while l > 0: 117 | if (l & (128 + 64)) == (128 + 64): 118 | # pointer 119 | pointer = struct.unpack('!H', data[p:p + 2])[0] 120 | pointer &= 0x3FFF 121 | r = parse_name(data, pointer) 122 | labels.append(r[1]) 123 | p += 2 124 | # pointer is the end 125 | return p - offset, '.'.join(labels) 126 | else: 127 | labels.append(data[p + 1:p + 1 + l]) 128 | p += 1 + l 129 | l = ord(data[p]) 130 | return p - offset + 1, '.'.join(labels) 131 | 132 | 133 | # rfc1035 134 | # record 135 | # 1 1 1 1 1 1 136 | # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 137 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 138 | # | | 139 | # / / 140 | # / NAME / 141 | # | | 142 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 143 | # | TYPE | 144 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 145 | # | CLASS | 146 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 147 | # | TTL | 148 | # | | 149 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 150 | # | RDLENGTH | 151 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| 152 | # / RDATA / 153 | # / / 154 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 155 | def parse_record(data, offset, question=False): 156 | nlen, name = parse_name(data, offset) 157 | if not question: 158 | record_type, record_class, record_ttl, record_rdlength = struct.unpack( 159 | '!HHiH', data[offset + nlen:offset + nlen + 10] 160 | ) 161 | ip = parse_ip(record_type, data, record_rdlength, offset + nlen + 10) 162 | return nlen + 10 + record_rdlength, \ 163 | (name, ip, record_type, record_class, record_ttl) 164 | else: 165 | record_type, record_class = struct.unpack( 166 | '!HH', data[offset + nlen:offset + nlen + 4] 167 | ) 168 | return nlen + 4, (name, None, record_type, record_class, None, None) 169 | 170 | 171 | def parse_header(data): 172 | if len(data) >= 12: 173 | header = struct.unpack('!HBBHHHH', data[:12]) 174 | res_id = header[0] 175 | res_qr = header[1] & 128 176 | res_tc = header[1] & 2 177 | res_ra = header[2] & 128 178 | res_rcode = header[2] & 15 179 | # assert res_tc == 0 180 | # assert res_rcode in [0, 3] 181 | res_qdcount = header[3] 182 | res_ancount = header[4] 183 | res_nscount = header[5] 184 | res_arcount = header[6] 185 | return (res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, 186 | res_ancount, res_nscount, res_arcount) 187 | return None 188 | 189 | 190 | def parse_response(data): 191 | try: 192 | if len(data) >= 12: 193 | header = parse_header(data) 194 | if not header: 195 | return None 196 | res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, \ 197 | res_ancount, res_nscount, res_arcount = header 198 | 199 | qds = [] 200 | ans = [] 201 | offset = 12 202 | for i in xrange(0, res_qdcount): 203 | l, r = parse_record(data, offset, True) 204 | offset += l 205 | if r: 206 | qds.append(r) 207 | for i in xrange(0, res_ancount): 208 | l, r = parse_record(data, offset) 209 | offset += l 210 | if r: 211 | ans.append(r) 212 | for i in xrange(0, res_nscount): 213 | l, r = parse_record(data, offset) 214 | offset += l 215 | for i in xrange(0, res_arcount): 216 | l, r = parse_record(data, offset) 217 | offset += l 218 | response = DNSResponse() 219 | if qds: 220 | response.hostname = qds[0][0] 221 | for an in ans: 222 | response.answers.append((an[1], an[2], an[3])) 223 | return response 224 | except Exception as e: 225 | import traceback 226 | traceback.print_exc() 227 | logging.error(e) 228 | return None 229 | 230 | 231 | def is_ip(address): 232 | for family in (socket.AF_INET, socket.AF_INET6): 233 | try: 234 | socket.inet_pton(family, address) 235 | return family 236 | except (TypeError, ValueError, OSError, IOError): 237 | pass 238 | return False 239 | 240 | 241 | def is_valid_hostname(hostname): 242 | if len(hostname) > 255: 243 | return False 244 | if hostname[-1] == ".": 245 | hostname = hostname[:-1] 246 | return all(VALID_HOSTNAME.match(x) for x in hostname.split(".")) 247 | 248 | 249 | class DNSResponse(object): 250 | def __init__(self): 251 | self.hostname = None 252 | self.answers = [] # each: (addr, type, class) 253 | 254 | def __str__(self): 255 | return '%s: %s' % (self.hostname, str(self.answers)) 256 | 257 | 258 | STATUS_IPV4 = 0 259 | STATUS_IPV6 = 1 260 | 261 | 262 | class DNSResolver(object): 263 | 264 | def __init__(self): 265 | self._loop = None 266 | self._request_id = 1 267 | self._hosts = {} 268 | self._hostname_status = {} 269 | self._hostname_to_cb = {} 270 | self._cb_to_hostname = {} 271 | self._cache = lru_cache.LRUCache(timeout=300) 272 | self._last_time = time.time() 273 | self._sock = None 274 | self._servers = None 275 | self._parse_resolv() 276 | self._parse_hosts() 277 | # TODO monitor hosts change and reload hosts 278 | # TODO parse /etc/gai.conf and follow its rules 279 | 280 | def _parse_resolv(self): 281 | self._servers = [] 282 | try: 283 | with open('/etc/resolv.conf', 'rb') as f: 284 | content = f.readlines() 285 | for line in content: 286 | line = line.strip() 287 | if line: 288 | if line.startswith('nameserver'): 289 | parts = line.split() 290 | if len(parts) >= 2: 291 | server = parts[1] 292 | if is_ip(server) == socket.AF_INET: 293 | self._servers.append(server) 294 | except IOError: 295 | pass 296 | if not self._servers: 297 | self._servers = ['8.8.4.4', '8.8.8.8'] 298 | 299 | def _parse_hosts(self): 300 | etc_path = '/etc/hosts' 301 | if os.environ.__contains__('WINDIR'): 302 | etc_path = os.environ['WINDIR'] + '/system32/drivers/etc/hosts' 303 | try: 304 | with open(etc_path, 'rb') as f: 305 | for line in f.readlines(): 306 | line = line.strip() 307 | parts = line.split() 308 | if len(parts) >= 2: 309 | ip = parts[0] 310 | if is_ip(ip): 311 | for i in xrange(1, len(parts)): 312 | hostname = parts[i] 313 | if hostname: 314 | self._hosts[hostname] = ip 315 | except IOError: 316 | self._hosts['localhost'] = '127.0.0.1' 317 | 318 | def add_to_loop(self, loop): 319 | if self._loop: 320 | raise Exception('already add to loop') 321 | self._loop = loop 322 | # TODO when dns server is IPv6 323 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 324 | socket.SOL_UDP) 325 | self._sock.setblocking(False) 326 | loop.add(self._sock, eventloop.POLL_IN) 327 | loop.add_handler(self.handle_events) 328 | 329 | def _call_callback(self, hostname, ip, error=None): 330 | callbacks = self._hostname_to_cb.get(hostname, []) 331 | for callback in callbacks: 332 | if self._cb_to_hostname.__contains__(callback): 333 | del self._cb_to_hostname[callback] 334 | if ip or error: 335 | callback((hostname, ip), error) 336 | else: 337 | callback((hostname, None), 338 | Exception('unknown hostname %s' % hostname)) 339 | if self._hostname_to_cb.__contains__(hostname): 340 | del self._hostname_to_cb[hostname] 341 | if self._hostname_status.__contains__(hostname): 342 | del self._hostname_status[hostname] 343 | 344 | def _handle_data(self, data): 345 | response = parse_response(data) 346 | if response and response.hostname: 347 | hostname = response.hostname 348 | ip = None 349 | for answer in response.answers: 350 | if answer[1] in (QTYPE_A, QTYPE_AAAA) and \ 351 | answer[2] == QCLASS_IN: 352 | ip = answer[0] 353 | break 354 | if not ip and self._hostname_status.get(hostname, STATUS_IPV6) \ 355 | == STATUS_IPV4: 356 | self._hostname_status[hostname] = STATUS_IPV6 357 | self._send_req(hostname, QTYPE_AAAA) 358 | else: 359 | if ip: 360 | self._cache[hostname] = ip 361 | self._call_callback(hostname, ip) 362 | 363 | def handle_events(self, events): 364 | for sock, fd, event in events: 365 | if sock != self._sock: 366 | continue 367 | if event & eventloop.POLL_ERR: 368 | logging.error('dns socket err') 369 | self._loop.remove(self._sock) 370 | self._sock.close() 371 | # TODO when dns server is IPv6 372 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 373 | socket.SOL_UDP) 374 | self._sock.setblocking(False) 375 | self._loop.add(self._sock, eventloop.POLL_IN) 376 | else: 377 | data, addr = sock.recvfrom(1024) 378 | if addr[0] not in self._servers: 379 | logging.warn('received a packet other than our dns') 380 | break 381 | self._handle_data(data) 382 | break 383 | now = time.time() 384 | if now - self._last_time > CACHE_SWEEP_INTERVAL: 385 | self._cache.sweep() 386 | self._last_time = now 387 | 388 | def remove_callback(self, callback): 389 | hostname = self._cb_to_hostname.get(callback) 390 | if hostname: 391 | del self._cb_to_hostname[callback] 392 | arr = self._hostname_to_cb.get(hostname, None) 393 | if arr: 394 | arr.remove(callback) 395 | if not arr: 396 | del self._hostname_to_cb[hostname] 397 | if self._hostname_status.__contains__(hostname): 398 | del self._hostname_status[hostname] 399 | 400 | def _send_req(self, hostname, qtype): 401 | self._request_id += 1 402 | if self._request_id > 32768: 403 | self._request_id = 1 404 | req = build_request(hostname, qtype, self._request_id) 405 | for server in self._servers: 406 | logging.debug('resolving %s with type %d using server %s', 407 | hostname, qtype, server) 408 | self._sock.sendto(req, (server, 53)) 409 | 410 | def resolve(self, hostname, callback): 411 | if not hostname: 412 | callback(None, Exception('empty hostname')) 413 | elif is_ip(hostname): 414 | callback((hostname, hostname), None) 415 | elif self._hosts.__contains__(hostname): 416 | logging.debug('hit hosts: %s', hostname) 417 | ip = self._hosts[hostname] 418 | callback((hostname, ip), None) 419 | elif self._cache.__contains__(hostname): 420 | logging.debug('hit cache: %s', hostname) 421 | ip = self._cache[hostname] 422 | callback((hostname, ip), None) 423 | else: 424 | if not is_valid_hostname(hostname): 425 | callback(None, Exception('invalid hostname: %s' % hostname)) 426 | return 427 | arr = self._hostname_to_cb.get(hostname, None) 428 | if not arr: 429 | self._hostname_status[hostname] = STATUS_IPV4 430 | self._send_req(hostname, QTYPE_A) 431 | self._hostname_to_cb[hostname] = [callback] 432 | self._cb_to_hostname[callback] = hostname 433 | else: 434 | arr.append(callback) 435 | # TODO send again only if waited too long 436 | self._send_req(hostname, QTYPE_A) 437 | 438 | def close(self): 439 | if self._sock: 440 | self._sock.close() 441 | self._sock = None 442 | 443 | 444 | def test(): 445 | logging.getLogger('').handlers = [] 446 | logging.basicConfig(level=logging.DEBUG, 447 | format='%(asctime)s %(levelname)-8s %(message)s', 448 | datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') 449 | 450 | def _callback(address, error): 451 | print error, address 452 | 453 | loop = eventloop.EventLoop() 454 | resolver = DNSResolver() 455 | resolver.add_to_loop(loop) 456 | 457 | for hostname in ['www.google.com', 458 | '8.8.8.8', 459 | 'localhost', 460 | 'activate.adobe.com', 461 | 'www.twitter.com', 462 | 'ipv6.google.com', 463 | 'ipv6.l.google.com', 464 | 'www.gmail.com', 465 | 'r4---sn-3qqp-ioql.googlevideo.com', 466 | 'www.baidu.com', 467 | 'www.a.shifen.com', 468 | 'm.baidu.jp', 469 | 'www.youku.com', 470 | 'www.twitter.com', 471 | 'ipv6.google.com']: 472 | resolver.resolve(hostname, _callback) 473 | 474 | loop.run() 475 | 476 | 477 | if __name__ == '__main__': 478 | test() 479 | -------------------------------------------------------------------------------- /shadowsocks/asyncmgr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import time 25 | import os 26 | import socket 27 | import struct 28 | import re 29 | import logging 30 | import common 31 | import lru_cache 32 | import eventloop 33 | import server_pool 34 | import Config 35 | 36 | class ServerMgr(object): 37 | 38 | def __init__(self): 39 | self._loop = None 40 | self._sock = None 41 | 42 | def add_to_loop(self, loop): 43 | if self._loop: 44 | raise Exception('already add to loop') 45 | self._loop = loop 46 | # TODO when dns server is IPv6 47 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 48 | socket.SOL_UDP) 49 | self._sock.bind((Config.MANAGE_BIND_IP, Config.MANAGE_PORT)) 50 | self._sock.setblocking(False) 51 | loop.add(self._sock, eventloop.POLL_IN) 52 | loop.add_handler(self.handle_events) 53 | 54 | def _handle_data(self, sock): 55 | data, addr = sock.recvfrom(128) 56 | #manage pwd:port:passwd:action 57 | args = data.split(':') 58 | if len(args) < 4: 59 | return 60 | if args[0] == Config.MANAGE_PASS: 61 | if args[3] == '0': 62 | server_pool.ServerPool.get_instance().cb_del_server(args[1]) 63 | elif args[3] == '1': 64 | server_pool.ServerPool.get_instance().cb_new_server(args[1], args[2]) 65 | 66 | def handle_events(self, events): 67 | for sock, fd, event in events: 68 | if sock != self._sock: 69 | continue 70 | if event & eventloop.POLL_ERR: 71 | logging.error('mgr socket err') 72 | self._loop.remove(self._sock) 73 | self._sock.close() 74 | # TODO when dns server is IPv6 75 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 76 | socket.SOL_UDP) 77 | self._sock.setblocking(False) 78 | self._loop.add(self._sock, eventloop.POLL_IN) 79 | else: 80 | self._handle_data(sock) 81 | break 82 | 83 | def close(self): 84 | if self._sock: 85 | self._sock.close() 86 | self._sock = None 87 | 88 | 89 | def test(): 90 | pass 91 | 92 | if __name__ == '__main__': 93 | test() 94 | -------------------------------------------------------------------------------- /shadowsocks/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import socket 25 | import struct 26 | import logging 27 | 28 | 29 | def inet_ntop(family, ipstr): 30 | if family == socket.AF_INET: 31 | return socket.inet_ntoa(ipstr) 32 | elif family == socket.AF_INET6: 33 | v6addr = ':'.join(('%02X%02X' % (ord(i), ord(j))) 34 | for i, j in zip(ipstr[::2], ipstr[1::2])) 35 | return v6addr 36 | 37 | 38 | def inet_pton(family, addr): 39 | if family == socket.AF_INET: 40 | return socket.inet_aton(addr) 41 | elif family == socket.AF_INET6: 42 | if '.' in addr: # a v4 addr 43 | v4addr = addr[addr.rindex(':') + 1:] 44 | v4addr = socket.inet_aton(v4addr) 45 | v4addr = map(lambda x: ('%02X' % ord(x)), v4addr) 46 | v4addr.insert(2, ':') 47 | newaddr = addr[:addr.rindex(':') + 1] + ''.join(v4addr) 48 | return inet_pton(family, newaddr) 49 | dbyts = [0] * 8 # 8 groups 50 | grps = addr.split(':') 51 | for i, v in enumerate(grps): 52 | if v: 53 | dbyts[i] = int(v, 16) 54 | else: 55 | for j, w in enumerate(grps[::-1]): 56 | if w: 57 | dbyts[7 - j] = int(w, 16) 58 | else: 59 | break 60 | break 61 | return ''.join((chr(i // 256) + chr(i % 256)) for i in dbyts) 62 | else: 63 | raise RuntimeError("What family?") 64 | 65 | 66 | def patch_socket(): 67 | if not hasattr(socket, 'inet_pton'): 68 | socket.inet_pton = inet_pton 69 | 70 | if not hasattr(socket, 'inet_ntop'): 71 | socket.inet_ntop = inet_ntop 72 | 73 | 74 | patch_socket() 75 | 76 | 77 | ADDRTYPE_IPV4 = 1 78 | ADDRTYPE_IPV6 = 4 79 | ADDRTYPE_HOST = 3 80 | 81 | 82 | def pack_addr(address): 83 | for family in (socket.AF_INET, socket.AF_INET6): 84 | try: 85 | r = socket.inet_pton(family, address) 86 | if family == socket.AF_INET6: 87 | return '\x04' + r 88 | else: 89 | return '\x01' + r 90 | except (TypeError, ValueError, OSError, IOError): 91 | pass 92 | if len(address) > 255: 93 | address = address[:255] # TODO 94 | return '\x03' + chr(len(address)) + address 95 | 96 | 97 | def parse_header(data): 98 | addrtype = ord(data[0]) 99 | dest_addr = None 100 | dest_port = None 101 | header_length = 0 102 | if addrtype == ADDRTYPE_IPV4: 103 | if len(data) >= 7: 104 | dest_addr = socket.inet_ntoa(data[1:5]) 105 | dest_port = struct.unpack('>H', data[5:7])[0] 106 | header_length = 7 107 | else: 108 | logging.warn('header is too short') 109 | elif addrtype == ADDRTYPE_HOST: 110 | if len(data) > 2: 111 | addrlen = ord(data[1]) 112 | if len(data) >= 2 + addrlen: 113 | dest_addr = data[2:2 + addrlen] 114 | dest_port = struct.unpack('>H', data[2 + addrlen:4 + 115 | addrlen])[0] 116 | header_length = 4 + addrlen 117 | else: 118 | logging.warn('header is too short') 119 | else: 120 | logging.warn('header is too short') 121 | elif addrtype == ADDRTYPE_IPV6: 122 | if len(data) >= 19: 123 | dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17]) 124 | dest_port = struct.unpack('>H', data[17:19])[0] 125 | header_length = 19 126 | else: 127 | logging.warn('header is too short') 128 | else: 129 | logging.warn('unsupported addrtype %d, maybe wrong password' % 130 | addrtype) 131 | if dest_addr is None: 132 | return None 133 | return addrtype, dest_addr, dest_port, header_length 134 | -------------------------------------------------------------------------------- /shadowsocks/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"0.0.0.0", 3 | "server_ipv6": "[::]", 4 | "server_port":8388, 5 | "local_address": "127.0.0.1", 6 | "local_port":1080, 7 | "password":"m", 8 | "timeout":300, 9 | "method":"aes-256-cfb" 10 | } 11 | -------------------------------------------------------------------------------- /shadowsocks/db_transfer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import logging 5 | import cymysql 6 | import time 7 | import sys 8 | from server_pool import ServerPool 9 | import Config 10 | 11 | class DbTransfer(object): 12 | 13 | instance = None 14 | 15 | def __init__(self): 16 | self.last_get_transfer = {} 17 | 18 | @staticmethod 19 | def get_instance(): 20 | if DbTransfer.instance is None: 21 | DbTransfer.instance = DbTransfer() 22 | return DbTransfer.instance 23 | 24 | def push_db_all_user(self): 25 | #更新用户流量到数据库 26 | last_transfer = self.last_get_transfer 27 | curr_transfer = ServerPool.get_instance().get_servers_transfer() 28 | #上次和本次的增量 29 | dt_transfer = {} 30 | for id in curr_transfer.keys(): 31 | if id in last_transfer: 32 | if last_transfer[id][0] == curr_transfer[id][0] and last_transfer[id][1] == curr_transfer[id][1]: 33 | continue 34 | elif curr_transfer[id][0] == 0 and curr_transfer[id][1] == 0: 35 | continue 36 | elif last_transfer[id][0] <= curr_transfer[id][0] and \ 37 | last_transfer[id][1] <= curr_transfer[id][1]: 38 | dt_transfer[id] = [curr_transfer[id][0] - last_transfer[id][0], 39 | curr_transfer[id][1] - last_transfer[id][1]] 40 | else: 41 | dt_transfer[id] = [curr_transfer[id][0], curr_transfer[id][1]] 42 | else: 43 | if curr_transfer[id][0] == 0 and curr_transfer[id][1] == 0: 44 | continue 45 | dt_transfer[id] = [curr_transfer[id][0], curr_transfer[id][1]] 46 | 47 | self.last_get_transfer = curr_transfer 48 | query_head = 'UPDATE go_ss' 49 | query_sub_when = '' 50 | query_sub_when2 = '' 51 | query_sub_in = None 52 | last_time = time.time() 53 | for id in dt_transfer.keys(): 54 | query_sub_when += ' WHEN %s THEN u+%s' % (id, dt_transfer[id][0]) 55 | query_sub_when2 += ' WHEN %s THEN d+%s' % (id, dt_transfer[id][1]) 56 | if query_sub_in is not None: 57 | query_sub_in += ',%s' % id 58 | else: 59 | query_sub_in = '%s' % id 60 | if query_sub_when == '': 61 | return 62 | query_sql = query_head + ' SET u = CASE port' + query_sub_when + \ 63 | ' END, d = CASE port' + query_sub_when2 + \ 64 | ' END, t = ' + str(int(last_time)) + \ 65 | ' WHERE port IN (%s)' % query_sub_in + \ 66 | ' AND sign IN("%s")' % Config.ServerSign 67 | #print query_sql 68 | 69 | conn = cymysql.connect(host=Config.MYSQL_HOST, port=Config.MYSQL_PORT, user=Config.MYSQL_USER, 70 | passwd=Config.MYSQL_PASS, db=Config.MYSQL_DB, charset='utf8') 71 | cur = conn.cursor() 72 | cur.execute(query_sql) 73 | cur.close() 74 | conn.commit() 75 | conn.close() 76 | 77 | @staticmethod 78 | def changeStatus(status,port): 79 | query_sql = "UPDATE go_ss SET enable='{0}' where sign = '{1}' and port= '{2}'".format(status,Config.ServerSign,port) 80 | conn = cymysql.connect(host=Config.MYSQL_HOST, port=Config.MYSQL_PORT, user=Config.MYSQL_USER, 81 | passwd=Config.MYSQL_PASS, db=Config.MYSQL_DB, charset='utf8') 82 | cur = conn.cursor() 83 | cur.execute(query_sql) 84 | cur.close() 85 | conn.commit() 86 | conn.close() 87 | 88 | @staticmethod 89 | def pull_db_all_user(): 90 | #数据库所有用户信息 91 | conn = cymysql.connect(host=Config.MYSQL_HOST, port=Config.MYSQL_PORT, user=Config.MYSQL_USER, 92 | passwd=Config.MYSQL_PASS, db=Config.MYSQL_DB, charset='utf8') 93 | cur = conn.cursor() 94 | cur.execute("SELECT port, u, d, transfer_enable, sspassword, switch, enable,type,overdue FROM go_ss where sign ='{0}' and switch=1 and enable=1".format(Config.ServerSign)) 95 | rows = [] 96 | for r in cur.fetchall(): 97 | rows.append(list(r)) 98 | cur.close() 99 | conn.close() 100 | return rows 101 | 102 | @staticmethod 103 | def del_server_out_of_bound_safe(rows): 104 | #停止超流量的服务 105 | #启动没超流量的服务 106 | #修改下面的逻辑要小心包含跨线程访问 107 | for row in rows: 108 | ##[port=0, u=1, d=2, transfer_enable=3, sspassword=4, switch=5, enable=6,type=7,overdue=8] 109 | ##端口,使用流量1,使用流量2,总流量3,端口密码4,是否打开5,是否可用6,套餐性质A.B7,过期时间8 110 | if ServerPool.get_instance().server_is_run(row[0]) is True: 111 | if row[5] == 0 or row[6] == 0: 112 | #stop disable or switch off user 113 | logging.info('db stop server at port [%s] reason: disable' % (row[0])) 114 | ServerPool.get_instance().del_server(row[0]) 115 | elif row[7] == 'A' and row[1] + row[2] >= row[3]: 116 | #stop out bandwidth user 117 | logging.info('db stop server at port [%s] reason: out bandwidth' % (row[0])) 118 | ServerPool.get_instance().del_server(row[0]) 119 | DbTransfer.get_instance().changeStatus(-1,row[0]) 120 | elif row[7] == 'B' and time.time() >= row[8]: 121 | #stop overdue user 122 | logging.info('db stop server at port [%s] reason: be overdue' % (row[0])) 123 | ServerPool.get_instance().del_server(row[0]) 124 | DbTransfer.get_instance().changeStatus(-2,row[0]) 125 | if ServerPool.get_instance().tcp_servers_pool[row[0]]._config['password'] != row[4]: 126 | #password changed 127 | logging.info('db stop server at port [%s] reason: password changed' % (row[0])) 128 | ServerPool.get_instance().del_server(row[0]) 129 | else: 130 | if row[5] == 1 and row[6] == 1 and row[7] == 'A' and row[1] + row[2] < row[3]: 131 | logging.info('db start server at port [%s] pass [%s]' % (row[0], row[4])) 132 | ServerPool.get_instance().new_server(row[0], row[4]) 133 | elif row[5] == 1 and row[6] == 1 and row[7] == 'B' and time.time() < row[8]: 134 | logging.info('db start server at port [%s] pass [%s]' % (row[0], row[4])) 135 | ServerPool.get_instance().new_server(row[0], row[4]) 136 | 137 | @staticmethod 138 | def thread_db(): 139 | import socket 140 | import time 141 | timeout = 60 142 | socket.setdefaulttimeout(timeout) 143 | while True: 144 | #logging.warn('db loop') 145 | try: 146 | DbTransfer.get_instance().push_db_all_user() 147 | rows = DbTransfer.get_instance().pull_db_all_user() 148 | DbTransfer.del_server_out_of_bound_safe(rows) 149 | except Exception as e: 150 | logging.warn('db thread except:%s' % e) 151 | finally: 152 | time.sleep(15) 153 | 154 | 155 | #SQLData.pull_db_all_user() 156 | #print DbTransfer.get_instance().test() 157 | -------------------------------------------------------------------------------- /shadowsocks/encrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2014 clowwindy 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import os 24 | import sys 25 | import hashlib 26 | import string 27 | import struct 28 | import logging 29 | import encrypt_salsa20 30 | import encrypt_rc4_md5 31 | 32 | 33 | def random_string(length): 34 | try: 35 | import M2Crypto.Rand 36 | return M2Crypto.Rand.rand_bytes(length) 37 | except ImportError: 38 | # TODO really strong enough on Linux? 39 | return os.urandom(length) 40 | 41 | 42 | cached_tables = {} 43 | cached_keys = {} 44 | 45 | 46 | def get_table(key): 47 | m = hashlib.md5() 48 | m.update(key) 49 | s = m.digest() 50 | (a, b) = struct.unpack(' 0: 95 | data = m[i - 1] + password 96 | md5.update(data) 97 | m.append(md5.digest()) 98 | i += 1 99 | ms = ''.join(m) 100 | key = ms[:key_len] 101 | iv = ms[key_len:key_len + iv_len] 102 | cached_keys[password] = (key, iv) 103 | return (key, iv) 104 | 105 | 106 | method_supported = { 107 | 'aes-128-cfb': (16, 16), 108 | 'aes-192-cfb': (24, 16), 109 | 'aes-256-cfb': (32, 16), 110 | 'bf-cfb': (16, 8), 111 | 'camellia-128-cfb': (16, 16), 112 | 'camellia-192-cfb': (24, 16), 113 | 'camellia-256-cfb': (32, 16), 114 | 'cast5-cfb': (16, 8), 115 | 'des-cfb': (8, 8), 116 | 'idea-cfb': (16, 8), 117 | 'rc2-cfb': (16, 8), 118 | 'rc4': (16, 0), 119 | 'rc4-md5': (16, 16), 120 | 'seed-cfb': (16, 16), 121 | 'salsa20-ctr': (32, 8), 122 | } 123 | 124 | 125 | class Encryptor(object): 126 | def __init__(self, key, method=None): 127 | if method == 'table': 128 | method = None 129 | self.key = key 130 | self.method = method 131 | self.iv = None 132 | self.iv_sent = False 133 | self.cipher_iv = '' 134 | self.decipher = None 135 | if method: 136 | self.cipher = self.get_cipher(key, method, 1, iv=random_string(32)) 137 | else: 138 | self.encrypt_table, self.decrypt_table = init_table(key) 139 | self.cipher = None 140 | 141 | def get_cipher_len(self, method): 142 | method = method.lower() 143 | m = method_supported.get(method, None) 144 | return m 145 | 146 | def iv_len(self): 147 | return len(self.cipher_iv) 148 | 149 | def get_cipher(self, password, method, op, iv=None): 150 | password = password.encode('utf-8') 151 | method = method.lower() 152 | m = self.get_cipher_len(method) 153 | if m: 154 | key, iv_ = EVP_BytesToKey(password, m[0], m[1]) 155 | if iv is None: 156 | iv = iv_ 157 | iv = iv[:m[1]] 158 | if op == 1: 159 | # this iv is for cipher not decipher 160 | self.cipher_iv = iv[:m[1]] 161 | if method == 'salsa20-ctr': 162 | return encrypt_salsa20.Salsa20Cipher(method, key, iv, op) 163 | elif method == 'rc4-md5': 164 | return encrypt_rc4_md5.create_cipher(method, key, iv, op) 165 | else: 166 | import M2Crypto.EVP 167 | return M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv, 168 | op, key_as_bytes=0, d='md5', 169 | salt=None, i=1, padding=1) 170 | 171 | logging.error('method %s not supported' % method) 172 | sys.exit(1) 173 | 174 | def encrypt(self, buf): 175 | if len(buf) == 0: 176 | return buf 177 | if not self.method: 178 | return string.translate(buf, self.encrypt_table) 179 | else: 180 | if self.iv_sent: 181 | return self.cipher.update(buf) 182 | else: 183 | self.iv_sent = True 184 | return self.cipher_iv + self.cipher.update(buf) 185 | 186 | def decrypt(self, buf): 187 | if len(buf) == 0: 188 | return buf 189 | if not self.method: 190 | return string.translate(buf, self.decrypt_table) 191 | else: 192 | if self.decipher is None: 193 | decipher_iv_len = self.get_cipher_len(self.method)[1] 194 | decipher_iv = buf[:decipher_iv_len] 195 | self.decipher = self.get_cipher(self.key, self.method, 0, 196 | iv=decipher_iv) 197 | buf = buf[decipher_iv_len:] 198 | if len(buf) == 0: 199 | return buf 200 | return self.decipher.update(buf) 201 | 202 | 203 | def encrypt_all(password, method, op, data): 204 | if method is not None and method.lower() == 'table': 205 | method = None 206 | if not method: 207 | [encrypt_table, decrypt_table] = init_table(password) 208 | if op: 209 | return string.translate(data, encrypt_table) 210 | else: 211 | return string.translate(data, decrypt_table) 212 | else: 213 | import M2Crypto.EVP 214 | result = [] 215 | method = method.lower() 216 | (key_len, iv_len) = method_supported[method] 217 | (key, _) = EVP_BytesToKey(password, key_len, iv_len) 218 | if op: 219 | iv = random_string(iv_len) 220 | result.append(iv) 221 | else: 222 | iv = data[:iv_len] 223 | data = data[iv_len:] 224 | if method == 'salsa20-ctr': 225 | cipher = encrypt_salsa20.Salsa20Cipher(method, key, iv, op) 226 | elif method == 'rc4-md5': 227 | cipher = encrypt_rc4_md5.create_cipher(method, key, iv, op) 228 | else: 229 | cipher = M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv, 230 | op, key_as_bytes=0, d='md5', 231 | salt=None, i=1, padding=1) 232 | result.append(cipher.update(data)) 233 | return ''.join(result) 234 | -------------------------------------------------------------------------------- /shadowsocks/encrypt_rc4_md5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2014 clowwindy 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import hashlib 24 | 25 | 26 | def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, 27 | i=1, padding=1): 28 | md5 = hashlib.md5() 29 | md5.update(key) 30 | md5.update(iv) 31 | rc4_key = md5.digest() 32 | 33 | import M2Crypto.EVP 34 | return M2Crypto.EVP.Cipher('rc4', rc4_key, '', op, key_as_bytes=0, 35 | d='md5', salt=None, i=1, padding=1) 36 | -------------------------------------------------------------------------------- /shadowsocks/encrypt_salsa20.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2014 clowwindy 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import time 24 | import struct 25 | import logging 26 | import sys 27 | 28 | slow_xor = False 29 | imported = False 30 | 31 | BLOCK_SIZE = 16384 32 | 33 | 34 | def run_imports(): 35 | global imported, slow_xor, salsa20, numpy 36 | if not imported: 37 | imported = True 38 | try: 39 | import numpy 40 | except ImportError: 41 | logging.error('can not import numpy, using SLOW XOR') 42 | logging.error('please install numpy if you use salsa20') 43 | slow_xor = True 44 | try: 45 | import salsa20 46 | except ImportError: 47 | logging.error('you have to install salsa20 before you use salsa20') 48 | sys.exit(1) 49 | 50 | 51 | def numpy_xor(a, b): 52 | if slow_xor: 53 | return py_xor_str(a, b) 54 | dtype = numpy.byte 55 | if len(a) % 4 == 0: 56 | dtype = numpy.uint32 57 | elif len(a) % 2 == 0: 58 | dtype = numpy.uint16 59 | 60 | ab = numpy.frombuffer(a, dtype=dtype) 61 | bb = numpy.frombuffer(b, dtype=dtype) 62 | c = numpy.bitwise_xor(ab, bb) 63 | r = c.tostring() 64 | return r 65 | 66 | 67 | def py_xor_str(a, b): 68 | c = [] 69 | for i in xrange(0, len(a)): 70 | c.append(chr(ord(a[i]) ^ ord(b[i]))) 71 | return ''.join(c) 72 | 73 | 74 | class Salsa20Cipher(object): 75 | """a salsa20 CTR implemetation, provides m2crypto like cipher API""" 76 | 77 | def __init__(self, alg, key, iv, op, key_as_bytes=0, d=None, salt=None, 78 | i=1, padding=1): 79 | run_imports() 80 | if alg != 'salsa20-ctr': 81 | raise Exception('unknown algorithm') 82 | self._key = key 83 | self._nonce = struct.unpack('= BLOCK_SIZE: 108 | self._next_stream() 109 | self._pos = 0 110 | if not data: 111 | break 112 | return ''.join(results) 113 | 114 | 115 | def test(): 116 | from os import urandom 117 | import random 118 | 119 | rounds = 1 * 1024 120 | plain = urandom(BLOCK_SIZE * rounds) 121 | import M2Crypto.EVP 122 | # cipher = M2Crypto.EVP.Cipher('aes_128_cfb', 'k' * 32, 'i' * 16, 1, 123 | # key_as_bytes=0, d='md5', salt=None, i=1, 124 | # padding=1) 125 | # decipher = M2Crypto.EVP.Cipher('aes_128_cfb', 'k' * 32, 'i' * 16, 0, 126 | # key_as_bytes=0, d='md5', salt=None, i=1, 127 | # padding=1) 128 | 129 | cipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1) 130 | decipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1) 131 | results = [] 132 | pos = 0 133 | print 'salsa20 test start' 134 | start = time.time() 135 | while pos < len(plain): 136 | l = random.randint(100, 32768) 137 | c = cipher.update(plain[pos:pos + l]) 138 | results.append(c) 139 | pos += l 140 | pos = 0 141 | c = ''.join(results) 142 | results = [] 143 | while pos < len(plain): 144 | l = random.randint(100, 32768) 145 | results.append(decipher.update(c[pos:pos + l])) 146 | pos += l 147 | end = time.time() 148 | print 'speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start)) 149 | assert ''.join(results) == plain 150 | 151 | 152 | if __name__ == '__main__': 153 | test() 154 | -------------------------------------------------------------------------------- /shadowsocks/eventloop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | # from ssloop 25 | # https://github.com/clowwindy/ssloop 26 | 27 | 28 | import os 29 | import socket 30 | import select 31 | import errno 32 | import logging 33 | from collections import defaultdict 34 | 35 | 36 | __all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR', 37 | 'POLL_HUP', 'POLL_NVAL', 'EVENT_NAMES'] 38 | 39 | POLL_NULL = 0x00 40 | POLL_IN = 0x01 41 | POLL_OUT = 0x04 42 | POLL_ERR = 0x08 43 | POLL_HUP = 0x10 44 | POLL_NVAL = 0x20 45 | 46 | 47 | EVENT_NAMES = { 48 | POLL_NULL: 'POLL_NULL', 49 | POLL_IN: 'POLL_IN', 50 | POLL_OUT: 'POLL_OUT', 51 | POLL_ERR: 'POLL_ERR', 52 | POLL_HUP: 'POLL_HUP', 53 | POLL_NVAL: 'POLL_NVAL', 54 | } 55 | 56 | 57 | class EpollLoop(object): 58 | 59 | def __init__(self): 60 | self._epoll = select.epoll() 61 | 62 | def poll(self, timeout): 63 | return self._epoll.poll(timeout) 64 | 65 | def add_fd(self, fd, mode): 66 | self._epoll.register(fd, mode) 67 | 68 | def remove_fd(self, fd): 69 | self._epoll.unregister(fd) 70 | 71 | def modify_fd(self, fd, mode): 72 | self._epoll.modify(fd, mode) 73 | 74 | 75 | class KqueueLoop(object): 76 | 77 | MAX_EVENTS = 1024 78 | 79 | def __init__(self): 80 | self._kqueue = select.kqueue() 81 | self._fds = {} 82 | 83 | def _control(self, fd, mode, flags): 84 | events = [] 85 | if mode & POLL_IN: 86 | events.append(select.kevent(fd, select.KQ_FILTER_READ, flags)) 87 | if mode & POLL_OUT: 88 | events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags)) 89 | for e in events: 90 | self._kqueue.control([e], 0) 91 | 92 | def poll(self, timeout): 93 | if timeout < 0: 94 | timeout = None # kqueue behaviour 95 | events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout) 96 | results = defaultdict(lambda: POLL_NULL) 97 | for e in events: 98 | fd = e.ident 99 | if e.filter == select.KQ_FILTER_READ: 100 | results[fd] |= POLL_IN 101 | elif e.filter == select.KQ_FILTER_WRITE: 102 | results[fd] |= POLL_OUT 103 | return results.iteritems() 104 | 105 | def add_fd(self, fd, mode): 106 | self._fds[fd] = mode 107 | self._control(fd, mode, select.KQ_EV_ADD) 108 | 109 | def remove_fd(self, fd): 110 | self._control(fd, self._fds[fd], select.KQ_EV_DELETE) 111 | del self._fds[fd] 112 | 113 | def modify_fd(self, fd, mode): 114 | self.remove_fd(fd) 115 | self.add_fd(fd, mode) 116 | 117 | 118 | class SelectLoop(object): 119 | 120 | def __init__(self): 121 | self._r_list = set() 122 | self._w_list = set() 123 | self._x_list = set() 124 | 125 | def poll(self, timeout): 126 | r, w, x = select.select(self._r_list, self._w_list, self._x_list, 127 | timeout) 128 | results = defaultdict(lambda: POLL_NULL) 129 | for p in [(r, POLL_IN), (w, POLL_OUT), (x, POLL_ERR)]: 130 | for fd in p[0]: 131 | results[fd] |= p[1] 132 | return results.items() 133 | 134 | def add_fd(self, fd, mode): 135 | if mode & POLL_IN: 136 | self._r_list.add(fd) 137 | if mode & POLL_OUT: 138 | self._w_list.add(fd) 139 | if mode & POLL_ERR: 140 | self._x_list.add(fd) 141 | 142 | def remove_fd(self, fd): 143 | if fd in self._r_list: 144 | self._r_list.remove(fd) 145 | if fd in self._w_list: 146 | self._w_list.remove(fd) 147 | if fd in self._x_list: 148 | self._x_list.remove(fd) 149 | 150 | def modify_fd(self, fd, mode): 151 | self.remove_fd(fd) 152 | self.add_fd(fd, mode) 153 | 154 | 155 | class EventLoop(object): 156 | def __init__(self): 157 | if hasattr(select, 'epoll'): 158 | self._impl = EpollLoop() 159 | model = 'epoll' 160 | elif hasattr(select, 'kqueue'): 161 | self._impl = KqueueLoop() 162 | model = 'kqueue' 163 | elif hasattr(select, 'select'): 164 | self._impl = SelectLoop() 165 | model = 'select' 166 | else: 167 | raise Exception('can not find any available functions in select ' 168 | 'package') 169 | self._fd_to_f = {} 170 | self._handlers = [] 171 | self.stopping = False 172 | logging.debug('using event model: %s', model) 173 | 174 | def poll(self, timeout=None): 175 | events = self._impl.poll(timeout) 176 | return [(self._fd_to_f[fd], fd, event) for fd, event in events] 177 | 178 | def add(self, f, mode): 179 | fd = f.fileno() 180 | self._fd_to_f[fd] = f 181 | self._impl.add_fd(fd, mode) 182 | 183 | def remove(self, f): 184 | fd = f.fileno() 185 | self._fd_to_f[fd] = None 186 | self._impl.remove_fd(fd) 187 | 188 | def modify(self, f, mode): 189 | fd = f.fileno() 190 | self._impl.modify_fd(fd, mode) 191 | 192 | def add_handler(self, handler): 193 | self._handlers.append(handler) 194 | 195 | def remove_handler(self, handler): 196 | self._handlers.remove(handler) 197 | 198 | def run(self): 199 | while not self.stopping: 200 | try: 201 | events = self.poll(1) 202 | except (OSError, IOError) as e: 203 | if errno_from_exception(e) == errno.EPIPE: 204 | # Happens when the client closes the connection 205 | logging.error('poll:%s', e) 206 | continue 207 | else: 208 | logging.error('poll:%s', e) 209 | import traceback 210 | traceback.print_exc() 211 | continue 212 | for handler in self._handlers: 213 | # TODO when there are a lot of handlers 214 | try: 215 | handler(events) 216 | except (OSError, IOError) as e: 217 | logging.error(e) 218 | import traceback 219 | traceback.print_exc() 220 | 221 | 222 | # from tornado 223 | def errno_from_exception(e): 224 | """Provides the errno from an Exception object. 225 | 226 | There are cases that the errno attribute was not set so we pull 227 | the errno out of the args but if someone instatiates an Exception 228 | without any args you will get a tuple error. So this function 229 | abstracts all that behavior to give you a safe way to get the 230 | errno. 231 | """ 232 | 233 | if hasattr(e, 'errno'): 234 | return e.errno 235 | elif e.args: 236 | return e.args[0] 237 | else: 238 | return None 239 | 240 | 241 | # from tornado 242 | def get_sock_error(sock): 243 | error_number = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) 244 | return socket.error(error_number, os.strerror(error_number)) 245 | -------------------------------------------------------------------------------- /shadowsocks/local.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import sys 25 | import os 26 | import logging 27 | import utils 28 | import encrypt 29 | import eventloop 30 | import tcprelay 31 | import udprelay 32 | import asyncdns 33 | 34 | 35 | def main(): 36 | utils.check_python() 37 | 38 | # fix py2exe 39 | if hasattr(sys, "frozen") and sys.frozen in \ 40 | ("windows_exe", "console_exe"): 41 | p = os.path.dirname(os.path.abspath(sys.executable)) 42 | os.chdir(p) 43 | 44 | config = utils.get_config(True) 45 | 46 | utils.print_shadowsocks() 47 | 48 | encrypt.init_table(config['password'], config['method']) 49 | 50 | try: 51 | logging.info("starting local at %s:%d" % 52 | (config['local_address'], config['local_port'])) 53 | 54 | dns_resolver = asyncdns.DNSResolver() 55 | tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) 56 | udp_server = udprelay.UDPRelay(config, dns_resolver, True) 57 | loop = eventloop.EventLoop() 58 | dns_resolver.add_to_loop(loop) 59 | tcp_server.add_to_loop(loop) 60 | udp_server.add_to_loop(loop) 61 | loop.run() 62 | except (KeyboardInterrupt, IOError, OSError) as e: 63 | logging.error(e) 64 | import traceback 65 | traceback.print_exc() 66 | os._exit(0) 67 | 68 | if __name__ == '__main__': 69 | main() 70 | -------------------------------------------------------------------------------- /shadowsocks/lru_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import collections 5 | import logging 6 | import heapq 7 | import time 8 | 9 | 10 | class LRUCache(collections.MutableMapping): 11 | """This class is not thread safe""" 12 | 13 | def __init__(self, timeout=60, close_callback=None, *args, **kwargs): 14 | self.timeout = timeout 15 | self.close_callback = close_callback 16 | self._store = {} 17 | self._time_to_keys = collections.defaultdict(list) 18 | self._last_visits = [] 19 | self.update(dict(*args, **kwargs)) # use the free update to set keys 20 | 21 | def __getitem__(self, key): 22 | # O(logm) 23 | t = time.time() 24 | self._time_to_keys[t].append(key) 25 | heapq.heappush(self._last_visits, t) 26 | return self._store[key] 27 | 28 | def __setitem__(self, key, value): 29 | # O(logm) 30 | t = time.time() 31 | self._store[key] = value 32 | self._time_to_keys[t].append(key) 33 | heapq.heappush(self._last_visits, t) 34 | 35 | def __delitem__(self, key): 36 | # O(1) 37 | del self._store[key] 38 | 39 | def __iter__(self): 40 | return iter(self._store) 41 | 42 | def __len__(self): 43 | return len(self._store) 44 | 45 | def sweep(self): 46 | # O(m) 47 | now = time.time() 48 | c = 0 49 | while len(self._last_visits) > 0: 50 | least = self._last_visits[0] 51 | if now - least <= self.timeout: 52 | break 53 | if self.close_callback is not None: 54 | for key in self._time_to_keys[least]: 55 | if self._store.__contains__(key): 56 | value = self._store[key] 57 | self.close_callback(value) 58 | for key in self._time_to_keys[least]: 59 | heapq.heappop(self._last_visits) 60 | if self._store.__contains__(key): 61 | del self._store[key] 62 | c += 1 63 | del self._time_to_keys[least] 64 | if c: 65 | logging.debug('%d keys swept' % c) -------------------------------------------------------------------------------- /shadowsocks/server.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import thread 4 | import server_pool 5 | import db_transfer 6 | 7 | #def test(): 8 | # thread.start_new_thread(DbTransfer.thread_db, ()) 9 | # Api.web_server() 10 | 11 | if __name__ == '__main__': 12 | thread.start_new_thread(db_transfer.DbTransfer.thread_db, ()) 13 | """ 14 | time.sleep(2) 15 | server_pool.ServerPool.get_instance().new_server(3333, '2333') 16 | while True: 17 | server_pool.ServerPool.get_instance().new_server(2333, '2333') 18 | server_pool.ServerPool.get_instance().del_server(2333) 19 | time.sleep(0.01) 20 | """ 21 | while True: 22 | time.sleep(99999) 23 | -------------------------------------------------------------------------------- /shadowsocks/server_pool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import os 25 | import logging 26 | import utils 27 | import time 28 | import eventloop 29 | import tcprelay 30 | import udprelay 31 | import asyncdns 32 | import thread 33 | import threading 34 | import sys 35 | import asyncmgr 36 | import Config 37 | from socket import * 38 | 39 | class ServerPool(object): 40 | 41 | instance = None 42 | 43 | def __init__(self): 44 | utils.check_python() 45 | self.config = utils.get_config(False) 46 | utils.print_shadowsocks() 47 | self.dns_resolver = asyncdns.DNSResolver() 48 | self.mgr = asyncmgr.ServerMgr() 49 | self.tcp_servers_pool = {} 50 | #self.udp_servers_pool = {} 51 | 52 | self.loop = eventloop.EventLoop() 53 | thread.start_new_thread(ServerPool._loop, (self.loop, self.dns_resolver, self.mgr)) 54 | 55 | @staticmethod 56 | def get_instance(): 57 | if ServerPool.instance is None: 58 | ServerPool.instance = ServerPool() 59 | return ServerPool.instance 60 | 61 | @staticmethod 62 | def _loop(loop, dns_resolver, mgr): 63 | try: 64 | mgr.add_to_loop(loop) 65 | dns_resolver.add_to_loop(loop) 66 | loop.run() 67 | except (KeyboardInterrupt, IOError, OSError) as e: 68 | logging.error(e) 69 | import traceback 70 | traceback.print_exc() 71 | os.exit(0) 72 | 73 | def server_is_run(self, port): 74 | port = int(port) 75 | if port in self.tcp_servers_pool: 76 | return True 77 | return False 78 | 79 | def new_server(self, port, password): 80 | port = int(port) 81 | logging.info("start server at %d" % port) 82 | try: 83 | udpsock = socket(AF_INET, SOCK_DGRAM) 84 | udpsock.sendto('%s:%s:%s:1' % (Config.MANAGE_PASS, port, password), (Config.MANAGE_BIND_IP, Config.MANAGE_PORT)) 85 | udpsock.close() 86 | except Exception, e: 87 | logging.warn(e) 88 | return True 89 | 90 | def cb_new_server(self, port, password): 91 | ret = True 92 | port = int(port) 93 | 94 | if 'server' in self.config: 95 | if port in self.tcp_servers_pool: 96 | logging.info("server already at %s:%d" % (self.config['server'], port)) 97 | return 'this port server is already running' 98 | else: 99 | a_config = self.config.copy() 100 | a_config['server_port'] = port 101 | a_config['password'] = password 102 | try: 103 | logging.info("starting server at %s:%d" % (a_config['server'], port)) 104 | tcp_server = tcprelay.TCPRelay(a_config, self.dns_resolver, False) 105 | tcp_server.add_to_loop(self.loop) 106 | self.tcp_servers_pool[port] = tcp_server 107 | #udp_server = udprelay.UDPRelay(a_config, self.dns_resolver, False) 108 | #udp_server.add_to_loop(self.loop) 109 | #self.udp_servers_pool.update({port: udp_server}) 110 | except Exception, e: 111 | logging.warn(e) 112 | return True 113 | 114 | def del_server(self, port): 115 | port = int(port) 116 | logging.info("del server at %d" % port) 117 | try: 118 | udpsock = socket(AF_INET, SOCK_DGRAM) 119 | udpsock.sendto('%s:%s:0:0' % (Config.MANAGE_PASS, port), (Config.MANAGE_BIND_IP, Config.MANAGE_PORT)) 120 | udpsock.close() 121 | except Exception, e: 122 | logging.warn(e) 123 | return True 124 | 125 | def cb_del_server(self, port): 126 | port = int(port) 127 | 128 | if port not in self.tcp_servers_pool: 129 | logging.info("stopped server at %s:%d already stop" % (self.config['server'], port)) 130 | else: 131 | logging.info("stopped server at %s:%d" % (self.config['server'], port)) 132 | try: 133 | server = self.tcp_servers_pool[port] 134 | del self.tcp_servers_pool[port] 135 | server.destroy() 136 | except Exception, e: 137 | logging.warn(e) 138 | return True 139 | 140 | def get_servers_transfer(self): 141 | ret = {} 142 | #this is different thread but safe 143 | servers = self.tcp_servers_pool.copy() 144 | for port in servers.keys(): 145 | ret[port] = [servers[port].server_transfer_ul, servers[port].server_transfer_dl] 146 | return ret -------------------------------------------------------------------------------- /shadowsocks/shadowsocks.sql: -------------------------------------------------------------------------------- 1 | SET FOREIGN_KEY_CHECKS=0; 2 | 3 | CREATE TABLE `user` ( 4 | `id` int(11) NOT NULL AUTO_INCREMENT, 5 | `email` varchar(32) NOT NULL, 6 | `pass` varchar(16) NOT NULL, 7 | `passwd` varchar(16) NOT NULL, 8 | `t` int(11) NOT NULL DEFAULT '0', 9 | `u` bigint(20) NOT NULL, 10 | `d` bigint(20) NOT NULL, 11 | `transfer_enable` bigint(20) NOT NULL, 12 | `port` int(11) NOT NULL, 13 | `switch` tinyint(4) NOT NULL DEFAULT '1', 14 | `enable` tinyint(4) NOT NULL DEFAULT '1', 15 | `type` tinyint(4) NOT NULL DEFAULT '1', 16 | `last_get_gift_time` int(11) NOT NULL DEFAULT '0', 17 | `last_rest_pass_time` int(11) NOT NULL DEFAULT '0', 18 | PRIMARY KEY (`id`,`port`) 19 | ) ENGINE=InnoDB AUTO_INCREMENT=415 DEFAULT CHARSET=utf8; 20 | 21 | -- ---------------------------- 22 | -- Records of user 23 | -- ---------------------------- 24 | INSERT INTO `user` VALUES ('7', 'test@test.com', '123456', '0000000', '1410609560', '0', '0', '9320666234', '50000', '1', '1', '7', '0', '0'); -------------------------------------------------------------------------------- /shadowsocks/tcprelay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import time 25 | import socket 26 | import errno 27 | import struct 28 | import logging 29 | import traceback 30 | import random 31 | import encrypt 32 | import eventloop 33 | import utils 34 | from common import parse_header 35 | 36 | 37 | TIMEOUTS_CLEAN_SIZE = 512 38 | TIMEOUT_PRECISION = 4 39 | 40 | MSG_FASTOPEN = 0x20000000 41 | 42 | CMD_CONNECT = 1 43 | CMD_BIND = 2 44 | CMD_UDP_ASSOCIATE = 3 45 | 46 | # local: 47 | # stage 0 init 48 | # stage 1 hello received, hello sent 49 | # stage 2 UDP assoc 50 | # stage 3 DNS 51 | # stage 4 addr received, reply sent 52 | # stage 5 remote connected 53 | 54 | # remote: 55 | # stage 0 init 56 | # stage 3 DNS 57 | # stage 4 addr received, reply sent 58 | # stage 5 remote connected 59 | 60 | STAGE_INIT = 0 61 | STAGE_HELLO = 1 62 | STAGE_UDP_ASSOC = 2 63 | STAGE_DNS = 3 64 | STAGE_REPLY = 4 65 | STAGE_STREAM = 5 66 | STAGE_DESTROYED = -1 67 | 68 | # stream direction 69 | STREAM_UP = 0 70 | STREAM_DOWN = 1 71 | 72 | # stream wait status 73 | WAIT_STATUS_INIT = 0 74 | WAIT_STATUS_READING = 1 75 | WAIT_STATUS_WRITING = 2 76 | WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING 77 | 78 | BUF_SIZE = 32 * 1024 79 | 80 | 81 | class TCPRelayHandler(object): 82 | def __init__(self, server, fd_to_handlers, loop, local_sock, config, 83 | dns_resolver, is_local): 84 | self._server = server 85 | self._fd_to_handlers = fd_to_handlers 86 | self._loop = loop 87 | self._local_sock = local_sock 88 | self._remote_sock = None 89 | self._config = config 90 | self._dns_resolver = dns_resolver 91 | self._is_local = is_local 92 | self._stage = STAGE_INIT 93 | self._encryptor = encrypt.Encryptor(config['password'], 94 | config['method']) 95 | self._fastopen_connected = False 96 | self._data_to_write_to_local = [] 97 | self._data_to_write_to_remote = [] 98 | self._upstream_status = WAIT_STATUS_READING 99 | self._downstream_status = WAIT_STATUS_INIT 100 | self._remote_address = None 101 | if is_local: 102 | self._chosen_server = self._get_a_server() 103 | fd_to_handlers[local_sock.fileno()] = self 104 | local_sock.setblocking(False) 105 | local_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) 106 | loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR) 107 | self.last_activity = 0 108 | self._update_activity() 109 | 110 | def __hash__(self): 111 | # default __hash__ is id / 16 112 | # we want to eliminate collisions 113 | return id(self) 114 | 115 | @property 116 | def remote_address(self): 117 | return self._remote_address 118 | 119 | def _get_a_server(self): 120 | server = self._config['server'] 121 | server_port = self._config['server_port'] 122 | if type(server_port) == list: 123 | server_port = random.choice(server_port) 124 | logging.debug('chosen server: %s:%d', server, server_port) 125 | # TODO support multiple server IP 126 | return server, server_port 127 | 128 | def _update_activity(self): 129 | self._server.update_activity(self) 130 | 131 | def _update_stream(self, stream, status): 132 | dirty = False 133 | if stream == STREAM_DOWN: 134 | if self._downstream_status != status: 135 | self._downstream_status = status 136 | dirty = True 137 | elif stream == STREAM_UP: 138 | if self._upstream_status != status: 139 | self._upstream_status = status 140 | dirty = True 141 | if dirty: 142 | if self._local_sock: 143 | event = eventloop.POLL_ERR 144 | if self._downstream_status & WAIT_STATUS_WRITING: 145 | event |= eventloop.POLL_OUT 146 | if self._upstream_status & WAIT_STATUS_READING: 147 | event |= eventloop.POLL_IN 148 | self._loop.modify(self._local_sock, event) 149 | if self._remote_sock: 150 | event = eventloop.POLL_ERR 151 | if self._downstream_status & WAIT_STATUS_READING: 152 | event |= eventloop.POLL_IN 153 | if self._upstream_status & WAIT_STATUS_WRITING: 154 | event |= eventloop.POLL_OUT 155 | self._loop.modify(self._remote_sock, event) 156 | 157 | def _write_to_sock(self, data, sock): 158 | if not data or not sock: 159 | return False 160 | uncomplete = False 161 | try: 162 | l = len(data) 163 | s = sock.send(data) 164 | if s < l: 165 | data = data[s:] 166 | uncomplete = True 167 | except (OSError, IOError) as e: 168 | error_no = eventloop.errno_from_exception(e) 169 | if error_no in (errno.EAGAIN, errno.EINPROGRESS, 170 | errno.EWOULDBLOCK): 171 | uncomplete = True 172 | else: 173 | logging.error(e) 174 | if self._config['verbose']: 175 | traceback.print_exc() 176 | self.destroy() 177 | return False 178 | if uncomplete: 179 | if sock == self._local_sock: 180 | self._data_to_write_to_local.append(data) 181 | self._update_stream(STREAM_DOWN, WAIT_STATUS_WRITING) 182 | elif sock == self._remote_sock: 183 | self._data_to_write_to_remote.append(data) 184 | self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) 185 | else: 186 | logging.error('write_all_to_sock:unknown socket') 187 | else: 188 | if sock == self._local_sock: 189 | self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) 190 | elif sock == self._remote_sock: 191 | self._update_stream(STREAM_UP, WAIT_STATUS_READING) 192 | else: 193 | logging.error('write_all_to_sock:unknown socket') 194 | return True 195 | 196 | def _handle_stage_reply(self, data): 197 | if self._is_local: 198 | data = self._encryptor.encrypt(data) 199 | self._data_to_write_to_remote.append(data) 200 | if self._is_local and not self._fastopen_connected and \ 201 | self._config['fast_open']: 202 | try: 203 | self._fastopen_connected = True 204 | remote_sock = \ 205 | self._create_remote_socket(self._chosen_server[0], 206 | self._chosen_server[1]) 207 | self._loop.add(remote_sock, eventloop.POLL_ERR) 208 | data = ''.join(self._data_to_write_to_local) 209 | l = len(data) 210 | s = remote_sock.sendto(data, MSG_FASTOPEN, self._chosen_server) 211 | if s < l: 212 | data = data[s:] 213 | self._data_to_write_to_local = [data] 214 | self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) 215 | else: 216 | self._data_to_write_to_local = [] 217 | self._update_stream(STREAM_UP, WAIT_STATUS_READING) 218 | self._stage = STAGE_STREAM 219 | except (OSError, IOError) as e: 220 | if eventloop.errno_from_exception(e) == errno.EINPROGRESS: 221 | self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) 222 | elif eventloop.errno_from_exception(e) == errno.ENOTCONN: 223 | logging.error('fast open not supported on this OS') 224 | self._config['fast_open'] = False 225 | self.destroy() 226 | else: 227 | logging.error(e) 228 | if self._config['verbose']: 229 | traceback.print_exc() 230 | self.destroy() 231 | 232 | def _handle_stage_hello(self, data): 233 | try: 234 | if self._is_local: 235 | cmd = ord(data[1]) 236 | if cmd == CMD_UDP_ASSOCIATE: 237 | logging.debug('UDP associate') 238 | if self._local_sock.family == socket.AF_INET6: 239 | header = '\x05\x00\x00\x04' 240 | else: 241 | header = '\x05\x00\x00\x01' 242 | addr, port = self._local_sock.getsockname() 243 | addr_to_send = socket.inet_pton(self._local_sock.family, 244 | addr) 245 | port_to_send = struct.pack('>H', port) 246 | self._write_to_sock(header + addr_to_send + port_to_send, 247 | self._local_sock) 248 | self._stage = STAGE_UDP_ASSOC 249 | # just wait for the client to disconnect 250 | return 251 | elif cmd == CMD_CONNECT: 252 | # just trim VER CMD RSV 253 | data = data[3:] 254 | else: 255 | logging.error('unknown command %d', cmd) 256 | self.destroy() 257 | return 258 | header_result = parse_header(data) 259 | if header_result is None: 260 | raise Exception('[%s]can not parse header' % (self._config['server_port'])) 261 | addrtype, remote_addr, remote_port, header_length = header_result 262 | logging.info('connecting %s:%d' % (remote_addr, remote_port)) 263 | self._remote_address = (remote_addr, remote_port) 264 | # pause reading 265 | self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) 266 | self._stage = STAGE_DNS 267 | if self._is_local: 268 | # forward address to remote 269 | self._write_to_sock('\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10', 270 | self._local_sock) 271 | data_to_send = self._encryptor.encrypt(data) 272 | self._data_to_write_to_remote.append(data_to_send) 273 | # notice here may go into _handle_dns_resolved directly 274 | self._dns_resolver.resolve(self._chosen_server[0], 275 | self._handle_dns_resolved) 276 | else: 277 | if len(data) > header_length: 278 | self._data_to_write_to_remote.append(data[header_length:]) 279 | # notice here may go into _handle_dns_resolved directly 280 | self._dns_resolver.resolve(remote_addr, 281 | self._handle_dns_resolved) 282 | except Exception as e: 283 | logging.error(e) 284 | if self._config['verbose']: 285 | traceback.print_exc() 286 | # TODO use logging when debug completed 287 | self.destroy() 288 | 289 | def _create_remote_socket(self, ip, port): 290 | addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, 291 | socket.SOL_TCP) 292 | if len(addrs) == 0: 293 | raise Exception("getaddrinfo failed for %s:%d" % (ip, port)) 294 | af, socktype, proto, canonname, sa = addrs[0] 295 | remote_sock = socket.socket(af, socktype, proto) 296 | self._remote_sock = remote_sock 297 | self._fd_to_handlers[remote_sock.fileno()] = self 298 | remote_sock.setblocking(False) 299 | remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) 300 | return remote_sock 301 | 302 | def _handle_dns_resolved(self, result, error): 303 | if error: 304 | logging.error(error) 305 | self.destroy() 306 | return 307 | if result: 308 | ip = result[1] 309 | if ip: 310 | try: 311 | self._stage = STAGE_REPLY 312 | remote_addr = ip 313 | if self._is_local: 314 | remote_port = self._chosen_server[1] 315 | else: 316 | remote_port = self._remote_address[1] 317 | 318 | if self._is_local and self._config['fast_open']: 319 | # wait for more data to arrive and send them in one SYN 320 | self._stage = STAGE_REPLY 321 | self._update_stream(STREAM_UP, WAIT_STATUS_READING) 322 | # TODO when there is already data in this packet 323 | else: 324 | remote_sock = self._create_remote_socket(remote_addr, 325 | remote_port) 326 | try: 327 | remote_sock.connect((remote_addr, remote_port)) 328 | except (OSError, IOError) as e: 329 | if eventloop.errno_from_exception(e) == \ 330 | errno.EINPROGRESS: 331 | pass 332 | self._loop.add(remote_sock, 333 | eventloop.POLL_ERR | eventloop.POLL_OUT) 334 | self._stage = STAGE_REPLY 335 | self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) 336 | self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) 337 | return 338 | except (OSError, IOError) as e: 339 | logging.error(e) 340 | if self._config['verbose']: 341 | traceback.print_exc() 342 | self.destroy() 343 | 344 | def _on_local_read(self): 345 | self._update_activity() 346 | if not self._local_sock: 347 | return 348 | is_local = self._is_local 349 | data = None 350 | try: 351 | data = self._local_sock.recv(BUF_SIZE) 352 | except (OSError, IOError) as e: 353 | if eventloop.errno_from_exception(e) in \ 354 | (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): 355 | return 356 | if not data: 357 | self.destroy() 358 | return 359 | self._server.server_transfer_ul += len(data) 360 | if not is_local: 361 | data = self._encryptor.decrypt(data) 362 | if not data: 363 | return 364 | if self._stage == STAGE_STREAM: 365 | if self._is_local: 366 | data = self._encryptor.encrypt(data) 367 | self._write_to_sock(data, self._remote_sock) 368 | return 369 | elif is_local and self._stage == STAGE_INIT: 370 | # TODO check auth method 371 | self._write_to_sock('\x05\00', self._local_sock) 372 | self._stage = STAGE_HELLO 373 | return 374 | elif self._stage == STAGE_REPLY: 375 | self._handle_stage_reply(data) 376 | elif (is_local and self._stage == STAGE_HELLO) or \ 377 | (not is_local and self._stage == STAGE_INIT): 378 | self._handle_stage_hello(data) 379 | 380 | def _on_remote_read(self): 381 | self._update_activity() 382 | data = None 383 | try: 384 | data = self._remote_sock.recv(BUF_SIZE) 385 | except (OSError, IOError) as e: 386 | if eventloop.errno_from_exception(e) in \ 387 | (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): 388 | return 389 | if not data: 390 | self.destroy() 391 | return 392 | self._server.server_transfer_dl += len(data) 393 | if self._is_local: 394 | data = self._encryptor.decrypt(data) 395 | else: 396 | data = self._encryptor.encrypt(data) 397 | try: 398 | self._write_to_sock(data, self._local_sock) 399 | except Exception as e: 400 | logging.error(e) 401 | if self._config['verbose']: 402 | traceback.print_exc() 403 | # TODO use logging when debug completed 404 | self.destroy() 405 | 406 | def _on_local_write(self): 407 | if self._data_to_write_to_local: 408 | data = ''.join(self._data_to_write_to_local) 409 | self._data_to_write_to_local = [] 410 | self._write_to_sock(data, self._local_sock) 411 | else: 412 | self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) 413 | 414 | def _on_remote_write(self): 415 | self._stage = STAGE_STREAM 416 | if self._data_to_write_to_remote: 417 | data = ''.join(self._data_to_write_to_remote) 418 | self._data_to_write_to_remote = [] 419 | self._write_to_sock(data, self._remote_sock) 420 | else: 421 | self._update_stream(STREAM_UP, WAIT_STATUS_READING) 422 | 423 | def _on_local_error(self): 424 | logging.debug('got local error') 425 | if self._local_sock: 426 | logging.error(eventloop.get_sock_error(self._local_sock)) 427 | self.destroy() 428 | 429 | def _on_remote_error(self): 430 | logging.debug('got remote error') 431 | if self._remote_sock: 432 | logging.error(eventloop.get_sock_error(self._remote_sock)) 433 | self.destroy() 434 | 435 | def handle_event(self, sock, event): 436 | if self._stage == STAGE_DESTROYED: 437 | logging.debug('ignore handle_event: destroyed') 438 | return 439 | # order is important 440 | if sock == self._remote_sock: 441 | if event & eventloop.POLL_ERR: 442 | self._on_remote_error() 443 | if self._stage == STAGE_DESTROYED: 444 | return 445 | if event & (eventloop.POLL_IN | eventloop.POLL_HUP): 446 | self._on_remote_read() 447 | if self._stage == STAGE_DESTROYED: 448 | return 449 | if event & eventloop.POLL_OUT: 450 | self._on_remote_write() 451 | elif sock == self._local_sock: 452 | if event & eventloop.POLL_ERR: 453 | self._on_local_error() 454 | if self._stage == STAGE_DESTROYED: 455 | return 456 | if event & (eventloop.POLL_IN | eventloop.POLL_HUP): 457 | self._on_local_read() 458 | if self._stage == STAGE_DESTROYED: 459 | return 460 | if event & eventloop.POLL_OUT: 461 | self._on_local_write() 462 | else: 463 | logging.warn('unknown socket') 464 | 465 | def destroy(self): 466 | if self._stage == STAGE_DESTROYED: 467 | logging.debug('already destroyed') 468 | return 469 | self._stage = STAGE_DESTROYED 470 | if self._remote_sock: 471 | try: 472 | logging.debug('destroying remote') 473 | self._loop.remove(self._remote_sock) 474 | del self._fd_to_handlers[self._remote_sock.fileno()] 475 | self._remote_sock.close() 476 | self._remote_sock = None 477 | except: 478 | pass 479 | if self._local_sock: 480 | try: 481 | logging.debug('destroying local') 482 | self._loop.remove(self._local_sock) 483 | del self._fd_to_handlers[self._local_sock.fileno()] 484 | self._local_sock.close() 485 | self._local_sock = None 486 | except: 487 | pass 488 | self._dns_resolver.remove_callback(self._handle_dns_resolved) 489 | self._server.remove_handler(self) 490 | 491 | 492 | class TCPRelay(object): 493 | def __init__(self, config, dns_resolver, is_local): 494 | self._config = config 495 | self._is_local = is_local 496 | self._dns_resolver = dns_resolver 497 | self._closed = False 498 | self._eventloop = None 499 | self._fd_to_handlers = {} 500 | self._last_time = time.time() 501 | self.server_transfer_ul = 0L 502 | self.server_transfer_dl = 0L 503 | 504 | self._timeout = config['timeout'] 505 | self._timeouts = [] # a list for all the handlers 506 | # we trim the timeouts once a while 507 | self._timeout_offset = 0 # last checked position for timeout 508 | self._handler_to_timeouts = {} # key: handler value: index in timeouts 509 | 510 | if is_local: 511 | listen_addr = config['local_address'] 512 | listen_port = config['local_port'] 513 | else: 514 | listen_addr = config['server'] 515 | listen_port = config['server_port'] 516 | 517 | addrs = socket.getaddrinfo(listen_addr, listen_port, 0, 518 | socket.SOCK_STREAM, socket.SOL_TCP) 519 | if len(addrs) == 0: 520 | raise Exception("can't get addrinfo for %s:%d" % 521 | (listen_addr, listen_port)) 522 | af, socktype, proto, canonname, sa = addrs[0] 523 | server_socket = socket.socket(af, socktype, proto) 524 | server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 525 | server_socket.bind(sa) 526 | server_socket.setblocking(False) 527 | if config['fast_open']: 528 | try: 529 | server_socket.setsockopt(socket.SOL_TCP, 23, 5) 530 | except socket.error: 531 | logging.error('warning: fast open is not available') 532 | self._config['fast_open'] = False 533 | server_socket.listen(1024) 534 | self._server_socket = server_socket 535 | 536 | def add_to_loop(self, loop): 537 | if self._eventloop: 538 | raise Exception('already add to loop') 539 | if self._closed: 540 | raise Exception('already closed') 541 | self._eventloop = loop 542 | loop.add_handler(self._handle_events) 543 | 544 | self._eventloop.add(self._server_socket, 545 | eventloop.POLL_IN | eventloop.POLL_ERR) 546 | 547 | def remove_to_loop(self): 548 | self._eventloop.remove(self._server_socket) 549 | self._eventloop.remove_handler(self._handle_events) 550 | 551 | def destroy(self): 552 | #destroy all conn 553 | self.remove_to_loop() 554 | for fd in self._fd_to_handlers.keys(): 555 | try: 556 | self._fd_to_handlers[fd].destroy() 557 | except Exception, e: 558 | #already destroy 559 | pass 560 | self.close() 561 | 562 | def remove_handler(self, handler): 563 | index = self._handler_to_timeouts.get(hash(handler), -1) 564 | if index >= 0: 565 | # delete is O(n), so we just set it to None 566 | self._timeouts[index] = None 567 | del self._handler_to_timeouts[hash(handler)] 568 | 569 | def update_activity(self, handler): 570 | """ set handler to active """ 571 | now = int(time.time()) 572 | if now - handler.last_activity < TIMEOUT_PRECISION: 573 | # thus we can lower timeout modification frequency 574 | return 575 | handler.last_activity = now 576 | index = self._handler_to_timeouts.get(hash(handler), -1) 577 | if index >= 0: 578 | # delete is O(n), so we just set it to None 579 | self._timeouts[index] = None 580 | length = len(self._timeouts) 581 | self._timeouts.append(handler) 582 | self._handler_to_timeouts[hash(handler)] = length 583 | 584 | def _sweep_timeout(self): 585 | # tornado's timeout memory management is more flexible than we need 586 | # we just need a sorted last_activity queue and it's faster than heapq 587 | # in fact we can do O(1) insertion/remove so we invent our own 588 | if self._timeouts: 589 | logging.log(utils.VERBOSE_LEVEL, 'sweeping timeouts') 590 | now = time.time() 591 | length = len(self._timeouts) 592 | pos = self._timeout_offset 593 | while pos < length: 594 | handler = self._timeouts[pos] 595 | if handler: 596 | if now - handler.last_activity < self._timeout: 597 | break 598 | else: 599 | if handler.remote_address: 600 | logging.warn('timed out: %s:%d' % 601 | handler.remote_address) 602 | else: 603 | logging.warn('timed out') 604 | handler.destroy() 605 | self._timeouts[pos] = None # free memory 606 | pos += 1 607 | else: 608 | pos += 1 609 | if pos > TIMEOUTS_CLEAN_SIZE and pos > length >> 1: 610 | # clean up the timeout queue when it gets larger than half 611 | # of the queue 612 | self._timeouts = self._timeouts[pos:] 613 | for key in self._handler_to_timeouts: 614 | self._handler_to_timeouts[key] -= pos 615 | pos = 0 616 | self._timeout_offset = pos 617 | 618 | def _handle_events(self, events): 619 | for sock, fd, event in events: 620 | if sock: 621 | logging.log(utils.VERBOSE_LEVEL, 'fd %d %s', fd, 622 | eventloop.EVENT_NAMES.get(event, event)) 623 | if sock == self._server_socket: 624 | if event & eventloop.POLL_ERR: 625 | # TODO 626 | raise Exception('server_socket error') 627 | try: 628 | logging.debug('accept') 629 | conn = self._server_socket.accept() 630 | TCPRelayHandler(self, self._fd_to_handlers, 631 | self._eventloop, conn[0], self._config, 632 | self._dns_resolver, self._is_local) 633 | except (OSError, IOError) as e: 634 | error_no = eventloop.errno_from_exception(e) 635 | if error_no in (errno.EAGAIN, errno.EINPROGRESS, 636 | errno.EWOULDBLOCK): 637 | continue 638 | else: 639 | logging.error(e) 640 | if self._config['verbose']: 641 | traceback.print_exc() 642 | else: 643 | if sock: 644 | handler = self._fd_to_handlers.get(fd, None) 645 | if handler: 646 | handler.handle_event(sock, event) 647 | else: 648 | logging.warn('poll removed fd') 649 | 650 | now = time.time() 651 | if now - self._last_time > TIMEOUT_PRECISION: 652 | self._sweep_timeout() 653 | self._last_time = now 654 | 655 | def close(self): 656 | self._closed = True 657 | self._server_socket.close() 658 | -------------------------------------------------------------------------------- /shadowsocks/udpmanagetest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import Config 4 | import time 5 | import socket 6 | 7 | udpsock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 8 | udpsock.sendto('%s:456789:123456:1' % (Config.MANAGE_PASS), (Config.MANAGE_BIND_IP, Config.MANAGE_PORT)) 9 | time.sleep(20) 10 | udpsock.sendto('%s:456789:123456:0' % (Config.MANAGE_PASS), (Config.MANAGE_BIND_IP, Config.MANAGE_PORT)) 11 | -------------------------------------------------------------------------------- /shadowsocks/udprelay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | # SOCKS5 UDP Request 25 | # +----+------+------+----------+----------+----------+ 26 | # |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 27 | # +----+------+------+----------+----------+----------+ 28 | # | 2 | 1 | 1 | Variable | 2 | Variable | 29 | # +----+------+------+----------+----------+----------+ 30 | 31 | # SOCKS5 UDP Response 32 | # +----+------+------+----------+----------+----------+ 33 | # |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 34 | # +----+------+------+----------+----------+----------+ 35 | # | 2 | 1 | 1 | Variable | 2 | Variable | 36 | # +----+------+------+----------+----------+----------+ 37 | 38 | # shadowsocks UDP Request (before encrypted) 39 | # +------+----------+----------+----------+ 40 | # | ATYP | DST.ADDR | DST.PORT | DATA | 41 | # +------+----------+----------+----------+ 42 | # | 1 | Variable | 2 | Variable | 43 | # +------+----------+----------+----------+ 44 | 45 | # shadowsocks UDP Response (before encrypted) 46 | # +------+----------+----------+----------+ 47 | # | ATYP | DST.ADDR | DST.PORT | DATA | 48 | # +------+----------+----------+----------+ 49 | # | 1 | Variable | 2 | Variable | 50 | # +------+----------+----------+----------+ 51 | 52 | # shadowsocks UDP Request and Response (after encrypted) 53 | # +-------+--------------+ 54 | # | IV | PAYLOAD | 55 | # +-------+--------------+ 56 | # | Fixed | Variable | 57 | # +-------+--------------+ 58 | 59 | # HOW TO NAME THINGS 60 | # ------------------ 61 | # `dest` means destination server, which is from DST fields in the SOCKS5 62 | # request 63 | # `local` means local server of shadowsocks 64 | # `remote` means remote server of shadowsocks 65 | # `client` means UDP clients that connects to other servers 66 | # `server` means the UDP server that handles user requests 67 | 68 | 69 | import time 70 | import socket 71 | import logging 72 | import struct 73 | import errno 74 | import random 75 | import encrypt 76 | import eventloop 77 | import lru_cache 78 | from common import parse_header, pack_addr 79 | 80 | 81 | BUF_SIZE = 65536 82 | 83 | 84 | def client_key(a, b, c, d): 85 | return '%s:%s:%s:%s' % (a, b, c, d) 86 | 87 | 88 | class UDPRelay(object): 89 | def __init__(self, config, dns_resolver, is_local): 90 | self._config = config 91 | if is_local: 92 | self._listen_addr = config['local_address'] 93 | self._listen_port = config['local_port'] 94 | self._remote_addr = config['server'] 95 | self._remote_port = config['server_port'] 96 | else: 97 | self._listen_addr = config['server'] 98 | self._listen_port = config['server_port'] 99 | self._remote_addr = None 100 | self._remote_port = None 101 | self._dns_resolver = dns_resolver 102 | self._password = config['password'] 103 | self._method = config['method'] 104 | self._timeout = config['timeout'] 105 | self._is_local = is_local 106 | self._cache = lru_cache.LRUCache(timeout=config['timeout'], 107 | close_callback=self._close_client) 108 | self._client_fd_to_server_addr = \ 109 | lru_cache.LRUCache(timeout=config['timeout']) 110 | self._eventloop = None 111 | self._closed = False 112 | self._last_time = time.time() 113 | self._sockets = set() 114 | 115 | addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, 116 | socket.SOCK_DGRAM, socket.SOL_UDP) 117 | if len(addrs) == 0: 118 | raise Exception("can't get addrinfo for %s:%d" % 119 | (self._listen_addr, self._listen_port)) 120 | af, socktype, proto, canonname, sa = addrs[0] 121 | server_socket = socket.socket(af, socktype, proto) 122 | server_socket.bind((self._listen_addr, self._listen_port)) 123 | server_socket.setblocking(False) 124 | self._server_socket = server_socket 125 | 126 | def _get_a_server(self): 127 | server = self._config['server'] 128 | server_port = self._config['server_port'] 129 | if type(server_port) == list: 130 | server_port = random.choice(server_port) 131 | logging.debug('chosen server: %s:%d', server, server_port) 132 | # TODO support multiple server IP 133 | return server, server_port 134 | 135 | def _close_client(self, client): 136 | if hasattr(client, 'close'): 137 | self._sockets.remove(client.fileno()) 138 | self._eventloop.remove(client) 139 | client.close() 140 | else: 141 | # just an address 142 | pass 143 | 144 | def _handle_server(self): 145 | server = self._server_socket 146 | data, r_addr = server.recvfrom(BUF_SIZE) 147 | if not data: 148 | logging.debug('UDP handle_server: data is empty') 149 | if self._is_local: 150 | frag = ord(data[2]) 151 | if frag != 0: 152 | logging.warn('drop a message since frag is not 0') 153 | return 154 | else: 155 | data = data[3:] 156 | else: 157 | data = encrypt.encrypt_all(self._password, self._method, 0, data) 158 | # decrypt data 159 | if not data: 160 | logging.debug('UDP handle_server: data is empty after decrypt') 161 | return 162 | header_result = parse_header(data) 163 | if header_result is None: 164 | return 165 | addrtype, dest_addr, dest_port, header_length = header_result 166 | 167 | if self._is_local: 168 | server_addr, server_port = self._get_a_server() 169 | else: 170 | server_addr, server_port = dest_addr, dest_port 171 | 172 | key = client_key(r_addr[0], r_addr[1], dest_addr, dest_port) 173 | client = self._cache.get(key, None) 174 | if not client: 175 | # TODO async getaddrinfo 176 | addrs = socket.getaddrinfo(server_addr, server_port, 0, 177 | socket.SOCK_DGRAM, socket.SOL_UDP) 178 | if addrs: 179 | af, socktype, proto, canonname, sa = addrs[0] 180 | client = socket.socket(af, socktype, proto) 181 | client.setblocking(False) 182 | self._cache[key] = client 183 | self._client_fd_to_server_addr[client.fileno()] = r_addr 184 | else: 185 | # drop 186 | return 187 | self._sockets.add(client.fileno()) 188 | self._eventloop.add(client, eventloop.POLL_IN) 189 | 190 | if self._is_local: 191 | data = encrypt.encrypt_all(self._password, self._method, 1, data) 192 | if not data: 193 | return 194 | else: 195 | data = data[header_length:] 196 | if not data: 197 | return 198 | try: 199 | client.sendto(data, (server_addr, server_port)) 200 | except IOError as e: 201 | err = eventloop.errno_from_exception(e) 202 | if err in (errno.EINPROGRESS, errno.EAGAIN): 203 | pass 204 | else: 205 | logging.error(e) 206 | 207 | def _handle_client(self, sock): 208 | data, r_addr = sock.recvfrom(BUF_SIZE) 209 | if not data: 210 | logging.debug('UDP handle_client: data is empty') 211 | return 212 | if not self._is_local: 213 | addrlen = len(r_addr[0]) 214 | if addrlen > 255: 215 | # drop 216 | return 217 | data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data 218 | response = encrypt.encrypt_all(self._password, self._method, 1, 219 | data) 220 | if not response: 221 | return 222 | else: 223 | data = encrypt.encrypt_all(self._password, self._method, 0, 224 | data) 225 | if not data: 226 | return 227 | header_result = parse_header(data) 228 | if header_result is None: 229 | return 230 | # addrtype, dest_addr, dest_port, header_length = header_result 231 | response = '\x00\x00\x00' + data 232 | client_addr = self._client_fd_to_server_addr.get(sock.fileno()) 233 | if client_addr: 234 | self._server_socket.sendto(response, client_addr) 235 | else: 236 | # this packet is from somewhere else we know 237 | # simply drop that packet 238 | pass 239 | 240 | def add_to_loop(self, loop): 241 | if self._eventloop: 242 | raise Exception('already add to loop') 243 | if self._closed: 244 | raise Exception('already closed') 245 | self._eventloop = loop 246 | loop.add_handler(self._handle_events) 247 | 248 | server_socket = self._server_socket 249 | self._eventloop.add(server_socket, 250 | eventloop.POLL_IN | eventloop.POLL_ERR) 251 | 252 | def remove_to_loop(self): 253 | self._eventloop.remove(self._server_socket) 254 | self._eventloop.remove_handler(self._handle_events) 255 | 256 | def destroy(self): 257 | #destroy all conn and server conn 258 | self.remove_to_loop() 259 | self.close() 260 | #GC 261 | self._cache = None 262 | 263 | def _handle_events(self, events): 264 | for sock, fd, event in events: 265 | if sock == self._server_socket: 266 | if event & eventloop.POLL_ERR: 267 | logging.error('UDP server_socket err') 268 | self._handle_server() 269 | elif sock and (fd in self._sockets): 270 | if event & eventloop.POLL_ERR: 271 | logging.error('UDP client_socket err') 272 | self._handle_client(sock) 273 | now = time.time() 274 | if now - self._last_time > 3.5: 275 | self._cache.sweep() 276 | if now - self._last_time > 7: 277 | self._client_fd_to_server_addr.sweep() 278 | self._last_time = now 279 | 280 | def close(self): 281 | self._closed = True 282 | self._server_socket.close() 283 | -------------------------------------------------------------------------------- /shadowsocks/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (c) 2014 clowwindy 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import os 25 | import json 26 | import sys 27 | import getopt 28 | import logging 29 | 30 | 31 | VERBOSE_LEVEL = 5 32 | 33 | 34 | def check_python(): 35 | info = sys.version_info 36 | if not (info[0] == 2 and info[1] >= 6): 37 | print 'Python 2.6 or 2.7 required' 38 | sys.exit(1) 39 | 40 | 41 | def print_shadowsocks(): 42 | version = '' 43 | try: 44 | import pkg_resources 45 | version = pkg_resources.get_distribution('shadowsocks').version 46 | except Exception: 47 | pass 48 | print 'shadowsocks %s' % version 49 | 50 | 51 | def find_config(): 52 | config_path = 'config.json' 53 | if os.path.exists(config_path): 54 | return config_path 55 | config_path = os.path.join(os.path.dirname(__file__), '../', 'config.json') 56 | if os.path.exists(config_path): 57 | return config_path 58 | return None 59 | 60 | 61 | def check_config(config): 62 | if config.get('local_address', '') in ['0.0.0.0']: 63 | logging.warn('warning: local set to listen 0.0.0.0, which is not safe') 64 | if config.get('server', '') in ['127.0.0.1', 'localhost']: 65 | logging.warn('warning: server set to listen %s:%s, are you sure?' % 66 | (config['server'], config['server_port'])) 67 | if (config.get('method', '') or '').lower() == '': 68 | logging.warn('warning: table is not safe; please use a safer cipher, ' 69 | 'like AES-256-CFB') 70 | if (config.get('method', '') or '').lower() == 'rc4': 71 | logging.warn('warning: RC4 is not safe; please use a safer cipher, ' 72 | 'like AES-256-CFB') 73 | if config.get('timeout', 300) < 100: 74 | logging.warn('warning: your timeout %d seems too short' % 75 | int(config.get('timeout'))) 76 | if config.get('timeout', 300) > 600: 77 | logging.warn('warning: your timeout %d seems too long' % 78 | int(config.get('timeout'))) 79 | if config.get('password') in ['mypassword', 'barfoo!']: 80 | logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' 81 | 'config.json!') 82 | exit(1) 83 | 84 | 85 | def get_config(is_local): 86 | logging.basicConfig(level=logging.INFO, 87 | format='%(levelname)-s: %(message)s', filemode='a+') 88 | if is_local: 89 | shortopts = 'hs:b:p:k:l:m:c:t:vq' 90 | longopts = ['fast-open'] 91 | else: 92 | shortopts = 'hs:p:k:m:c:t:vq' 93 | longopts = ['fast-open', 'workers:'] 94 | try: 95 | config_path = find_config() 96 | optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) 97 | for key, value in optlist: 98 | if key == '-c': 99 | config_path = value 100 | 101 | if config_path: 102 | logging.info('loading config from %s' % config_path) 103 | with open(config_path, 'rb') as f: 104 | try: 105 | config = json.load(f, object_hook=_decode_dict) 106 | except ValueError as e: 107 | logging.error('found an error in config.json: %s', 108 | e.message) 109 | sys.exit(1) 110 | else: 111 | config = {} 112 | 113 | optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) 114 | v_count = 0 115 | for key, value in optlist: 116 | if key == '-p': 117 | config['server_port'] = int(value) 118 | elif key == '-k': 119 | config['password'] = value 120 | elif key == '-l': 121 | config['local_port'] = int(value) 122 | elif key == '-s': 123 | config['server'] = value 124 | elif key == '-m': 125 | config['method'] = value 126 | elif key == '-b': 127 | config['local_address'] = value 128 | elif key == '-v': 129 | v_count += 1 130 | # '-vv' turns on more verbose mode 131 | config['verbose'] = v_count 132 | elif key == '-t': 133 | config['timeout'] = int(value) 134 | elif key == '--fast-open': 135 | config['fast_open'] = True 136 | elif key == '--workers': 137 | config['workers'] = value 138 | elif key == '-h': 139 | if is_local: 140 | print_local_help() 141 | else: 142 | print_server_help() 143 | sys.exit(0) 144 | elif key == '-q': 145 | v_count -= 1 146 | config['verbose'] = v_count 147 | except getopt.GetoptError as e: 148 | print >>sys.stderr, e 149 | print_help(is_local) 150 | sys.exit(2) 151 | 152 | if not config: 153 | logging.error('config not specified') 154 | print_help(is_local) 155 | sys.exit(2) 156 | 157 | config['password'] = config.get('password', None) 158 | config['method'] = config.get('method', 'aes-256-cfb') 159 | config['port_password'] = config.get('port_password', None) 160 | config['timeout'] = int(config.get('timeout', 300)) 161 | config['fast_open'] = config.get('fast_open', False) 162 | config['workers'] = config.get('workers', 1) 163 | config['verbose'] = config.get('verbose', False) 164 | config['local_address'] = config.get('local_address', '127.0.0.1') 165 | config['local_port'] = config.get('local_port', 1080) 166 | if is_local: 167 | if config.get('server', None) is None: 168 | logging.error('server addr not specified') 169 | print_local_help() 170 | sys.exit(2) 171 | else: 172 | config['server'] = config.get('server', '0.0.0.0') 173 | config['server_port'] = config.get('server_port', 8388) 174 | 175 | if not ('password' in config and config['password']): 176 | logging.error('password not specified') 177 | print_help(is_local) 178 | sys.exit(2) 179 | 180 | logging.getLogger('').handlers = [] 181 | logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') 182 | if config['verbose'] >= 2: 183 | level = VERBOSE_LEVEL 184 | elif config['verbose'] == 1: 185 | level = logging.DEBUG 186 | elif config['verbose'] == -1: 187 | level = logging.WARN 188 | elif config['verbose'] <= -2: 189 | level = logging.ERROR 190 | else: 191 | level = logging.INFO 192 | logging.basicConfig(level=level, 193 | format='%(asctime)s %(levelname)-8s %(message)s', 194 | datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') 195 | 196 | check_config(config) 197 | 198 | return config 199 | 200 | 201 | def print_help(is_local): 202 | if is_local: 203 | print_local_help() 204 | else: 205 | print_server_help() 206 | 207 | 208 | def print_local_help(): 209 | print '''usage: sslocal [-h] -s SERVER_ADDR [-p SERVER_PORT] 210 | [-b LOCAL_ADDR] [-l LOCAL_PORT] -k PASSWORD [-m METHOD] 211 | [-t TIMEOUT] [-c CONFIG] [--fast-open] [-v] [-q] 212 | 213 | optional arguments: 214 | -h, --help show this help message and exit 215 | -s SERVER_ADDR server address 216 | -p SERVER_PORT server port, default: 8388 217 | -b LOCAL_ADDR local binding address, default: 127.0.0.1 218 | -l LOCAL_PORT local port, default: 1080 219 | -k PASSWORD password 220 | -m METHOD encryption method, default: aes-256-cfb 221 | -t TIMEOUT timeout in seconds, default: 300 222 | -c CONFIG path to config file 223 | --fast-open use TCP_FASTOPEN, requires Linux 3.7+ 224 | -v, -vv verbose mode 225 | -q, -qq quiet mode, only show warnings/errors 226 | 227 | Online help: 228 | ''' 229 | 230 | 231 | def print_server_help(): 232 | print '''usage: ssserver [-h] [-s SERVER_ADDR] [-p SERVER_PORT] -k PASSWORD 233 | -m METHOD [-t TIMEOUT] [-c CONFIG] [--fast-open] 234 | [--workers WORKERS] [-v] [-q] 235 | 236 | optional arguments: 237 | -h, --help show this help message and exit 238 | -s SERVER_ADDR server address, default: 0.0.0.0 239 | -p SERVER_PORT server port, default: 8388 240 | -k PASSWORD password 241 | -m METHOD encryption method, default: aes-256-cfb 242 | -t TIMEOUT timeout in seconds, default: 300 243 | -c CONFIG path to config file 244 | --fast-open use TCP_FASTOPEN, requires Linux 3.7+ 245 | --workers WORKERS number of workers, available on Unix/Linux 246 | -v, -vv verbose mode 247 | -q, -qq quiet mode, only show warnings/errors 248 | 249 | Online help: 250 | ''' 251 | 252 | 253 | def _decode_list(data): 254 | rv = [] 255 | for item in data: 256 | if isinstance(item, unicode): 257 | item = item.encode('utf-8') 258 | elif isinstance(item, list): 259 | item = _decode_list(item) 260 | elif isinstance(item, dict): 261 | item = _decode_dict(item) 262 | rv.append(item) 263 | return rv 264 | 265 | 266 | def _decode_dict(data): 267 | rv = {} 268 | for key, value in data.iteritems(): 269 | if isinstance(key, unicode): 270 | key = key.encode('utf-8') 271 | if isinstance(value, unicode): 272 | value = value.encode('utf-8') 273 | elif isinstance(value, list): 274 | value = _decode_list(value) 275 | elif isinstance(value, dict): 276 | value = _decode_dict(value) 277 | rv[key] = value 278 | return rv 279 | --------------------------------------------------------------------------------