├── ELF └── Kail-64.zip ├── README.md ├── config.json ├── gui ├── __init__.py ├── gui.ui └── ssw128.png ├── main.py ├── shadowsocks ├── __init__.py ├── asyncdns.py ├── common.py ├── crypto │ ├── __init__.py │ ├── __init__.pyc │ ├── ctypes_libsodium.py │ ├── ctypes_openssl.py │ ├── openssl.py │ ├── openssl.pyc │ ├── rc4_md5.py │ ├── rc4_md5.pyc │ ├── sodium.py │ ├── sodium.pyc │ ├── table.py │ ├── table.pyc │ ├── util.py │ └── util.pyc ├── daemon.py ├── encrypt.py ├── eventloop.py ├── local.py ├── lru_cache.py ├── obfs.py ├── obfsplugin │ ├── __init__.py │ ├── __init__.pyc │ ├── auth.py │ ├── auth.pyc │ ├── auth_chain.py │ ├── auth_chain.pyc │ ├── http_simple.py │ ├── http_simple.pyc │ ├── obfs_tls.py │ ├── obfs_tls.pyc │ ├── plain.py │ ├── plain.pyc │ ├── verify.py │ └── verify.pyc ├── ordereddict.py ├── shell.py ├── tcprelay.py ├── udprelay.py └── version.py └── ssr.py /ELF/Kail-64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/ELF/Kail-64.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shadowsocksr-pyqt 2 | 3 | 该项目使用Python+PuQt4进行编写,可在linux、windows下进行使用 4 | 兼容ShadowsocksR、Shadowsocks目前所有协议,Json配置文件与Windows C#版客户端通用,但目前不支持中文字符。 5 | 6 | 欢迎大家使用pyinstaller进行打包后提交给我,请注明操作系统环境,为不会配置python环境的用户提供方便。 7 | 8 | 目前拥有的编译后版本为: 9 | Kail 64位 10 | 11 | 12 | # https://blog.bbsec.xyz 13 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "configs" : [ 3 | { 4 | "remarks" : "Test", 5 | "id" : "C91B24D0DED3351E65AE832BBFD85F11", 6 | "server" : "172.104.96.109", 7 | "server_port" : 80, 8 | "server_udp_port" : 0, 9 | "password" : ")Io=KDQ1", 10 | "method" : "none", 11 | "protocol" : "auth_chain_a", 12 | "protocolparam" : "", 13 | "obfs" : "http_simple", 14 | "obfsparam" : "172.104.96.109", 15 | "remarks_base64" : "6auY6YCf", 16 | "group" : "Hello-SSer", 17 | "enable" : true, 18 | "udp_over_tcp" : false 19 | } 20 | 21 | ], 22 | "index" : 4, 23 | "random" : true, 24 | "sysProxyMode" : 2, 25 | "shareOverLan" : false, 26 | "localPort" : 1080, 27 | "localAuthPassword" : "lPvbWXZiWTTTNDjZu1AS", 28 | "dnsServer" : "8.8.8.8", 29 | "reconnectTimes" : 4, 30 | "randomAlgorithm" : 0, 31 | "randomInGroup" : false, 32 | "TTL" : 60, 33 | "connectTimeout" : 5, 34 | "proxyRuleMode" : 2, 35 | "proxyEnable" : false, 36 | "pacDirectGoProxy" : false, 37 | "proxyType" : 0, 38 | "proxyHost" : "", 39 | "proxyPort" : 0, 40 | "proxyAuthUser" : "", 41 | "proxyAuthPass" : "", 42 | "proxyUserAgent" : "", 43 | "authUser" : "", 44 | "authPass" : "", 45 | "autoBan" : true, 46 | "sameHostForSameTarget" : false, 47 | "keepVisitTime" : 180, 48 | "isHideTips" : true, 49 | "nodeFeedAutoUpdate" : true, 50 | "serverSubscribes" : [ 51 | { 52 | "URL" : "https://raw.githubusercontent.com/breakwa11/breakwa11.github.io/master/free/freenodeplain.txt", 53 | "Group" : "FreeSSR-public" 54 | } 55 | ], 56 | "token" : { 57 | 58 | }, 59 | "portMap" : { 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2012-2017 xiaojian 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | -------------------------------------------------------------------------------- /gui/gui.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 879 10 | 335 11 | 12 | 13 | 14 | ShadowsocksR PyQt by XiaoJian 15 | 16 | 17 | 18 | ssw128.pngssw128.png 19 | 20 | 21 | 1.000000000000000 22 | 23 | 24 | 25 | 26 | 27 | 150 28 | 240 29 | 111 30 | 31 31 | 32 | 33 | 34 | 断开 35 | 36 | 37 | 38 | 39 | 40 | 30 41 | 240 42 | 111 43 | 31 44 | 45 | 46 | 47 | 连接 48 | 49 | 50 | 51 | 52 | 53 | 290 54 | 20 55 | 581 56 | 251 57 | 58 | 59 | 60 | 61 | Courier 62 | 8 63 | true 64 | 65 | 66 | 67 | QFrame::WinPanel 68 | 69 | 70 | 71 | 72 | 73 | 10 74 | 20 75 | 271 76 | 192 77 | 78 | 79 | 80 | 81 | 82 | 83 | QFrame::Panel 84 | 85 | 86 | QFrame::Plain 87 | 88 | 89 | 1 90 | 91 | 92 | QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed 93 | 94 | 95 | true 96 | 97 | 98 | true 99 | 100 | 101 | false 102 | 103 | 104 | Qt::ElideLeft 105 | 106 | 107 | true 108 | 109 | 110 | Qt::SolidLine 111 | 112 | 113 | false 114 | 115 | 116 | true 117 | 118 | 119 | true 120 | 121 | 122 | 0 123 | 124 | 125 | false 126 | 127 | 128 | false 129 | 130 | 131 | 132 | server 133 | 134 | 135 | 136 | 137 | enable 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 0 146 | 0 147 | 879 148 | 26 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /gui/ssw128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/gui/ssw128.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import subprocess 4 | 5 | from PyQt4 import QtCore, QtGui, uic 6 | from shadowsocks import shell 7 | Ui_MainWindow, QtBaseClass = uic.loadUiType("gui/gui.ui") 8 | 9 | ssr_main = subprocess.Popen(["python", "ssr.py", "-v"],stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 10 | 11 | class server(): 12 | def __init__(self, ip , port, enable): 13 | self.ip = ip 14 | self.port = port 15 | self.enable = QtGui.QCheckBox() 16 | self.enable.setChecked(enable) 17 | 18 | class Main(QtGui.QMainWindow, Ui_MainWindow): 19 | def __init__(self): 20 | QtGui.QMainWindow.__init__(self) 21 | Ui_MainWindow.__init__(self) 22 | self.setupUi(self) 23 | self.server_table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) 24 | self.server_table.setColumnWidth(0, 210) 25 | self.server_table.setColumnWidth(1, 30) 26 | self.show_servers() 27 | self.button_connect.clicked.connect(run) 28 | self.button_disconnect.clicked.connect(stop) 29 | ssr_log.log.connect(self.ssrlog) 30 | 31 | 32 | def show_servers(self): 33 | global config 34 | servers = config._config['configs'] 35 | i = 0 36 | for x in servers: 37 | self.server_table.insertRow(i) 38 | servertmp = server(x['server'], x['server_port'], x['enable']) 39 | serveritem = QtGui.QTableWidgetItem(str(servertmp.ip)+":"+str(servertmp.port)) 40 | self.server_table.setItem(i, 0, serveritem) 41 | self.server_table.setCellWidget(i, 1, servertmp.enable) 42 | i += 1 43 | 44 | def closeEvent(self,event): 45 | global ssr_main 46 | global ssr_log 47 | ssr_main.kill() 48 | ssr_log.exit(0) 49 | 50 | 51 | def close(self): 52 | global ssr_main 53 | global ssr_log 54 | ssr_main.kill() 55 | ssr_log.exit(0) 56 | 57 | def ssrlog(self, string): 58 | self.ssr_log.append(string) 59 | 60 | 61 | class SSRLog(QtCore.QThread): 62 | 63 | log = QtCore.pyqtSignal(str) 64 | 65 | def __int__(self): 66 | super(SSRLog,self).__init__() 67 | 68 | def run(self): 69 | global ssr_main 70 | if ssr_main is None: 71 | return 72 | returncode = ssr_main.poll() 73 | while returncode is None: 74 | if ssr_main is None: 75 | return 76 | else: 77 | line = ssr_main.stdout.readline() 78 | returncode = ssr_main.poll() 79 | self.log.emit(str(line)) 80 | self.log.emit(str("Stop ....")) 81 | 82 | 83 | class cfgjson(): 84 | 85 | _config = shell.get_config(True) 86 | 87 | def __int__(self): 88 | self._config = shell.get_config(True) 89 | 90 | def printcfg(self): 91 | print(str(self._config)) 92 | 93 | 94 | 95 | def stop(): 96 | global ssr_main 97 | global ssr_log 98 | ssr_log.exit(0) 99 | ssr_main.kill() 100 | 101 | 102 | def run(): 103 | global ssr_main 104 | global ssr_log 105 | ssr_log.exit(0) 106 | ssr_main.kill() 107 | ssr_main = subprocess.Popen(["python", "ssr.py", "-v"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 108 | ssr_log.start() 109 | 110 | 111 | ssr_log = SSRLog() 112 | config = cfgjson() 113 | 114 | if __name__ == '__main__': 115 | ssr_log.start() 116 | app = QtGui.QApplication(sys.argv) 117 | main_window = Main() 118 | main_window.show() 119 | sys.exit(app.exec_()) 120 | -------------------------------------------------------------------------------- /shadowsocks/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2012-2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | -------------------------------------------------------------------------------- /shadowsocks/asyncdns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2014-2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import os 22 | import socket 23 | import struct 24 | import re 25 | import logging 26 | 27 | if __name__ == '__main__': 28 | import sys 29 | import inspect 30 | file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) 31 | sys.path.insert(0, os.path.join(file_path, '../')) 32 | 33 | from shadowsocks import common, lru_cache, eventloop, shell 34 | 35 | 36 | CACHE_SWEEP_INTERVAL = 30 37 | 38 | VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d_-]{1,63}(? 63: 101 | return None 102 | results.append(common.chr(l)) 103 | results.append(label) 104 | results.append(b'\0') 105 | return b''.join(results) 106 | 107 | 108 | def build_request(address, qtype): 109 | request_id = os.urandom(2) 110 | header = struct.pack('!BBHHHH', 1, 0, 1, 0, 0, 0) 111 | addr = build_address(address) 112 | qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN) 113 | return request_id + header + addr + qtype_qclass 114 | 115 | 116 | def parse_ip(addrtype, data, length, offset): 117 | if addrtype == QTYPE_A: 118 | return socket.inet_ntop(socket.AF_INET, data[offset:offset + length]) 119 | elif addrtype == QTYPE_AAAA: 120 | return socket.inet_ntop(socket.AF_INET6, data[offset:offset + length]) 121 | elif addrtype in [QTYPE_CNAME, QTYPE_NS]: 122 | return parse_name(data, offset)[1] 123 | else: 124 | return data[offset:offset + length] 125 | 126 | 127 | def parse_name(data, offset): 128 | p = offset 129 | labels = [] 130 | l = common.ord(data[p]) 131 | while l > 0: 132 | if (l & (128 + 64)) == (128 + 64): 133 | # pointer 134 | pointer = struct.unpack('!H', data[p:p + 2])[0] 135 | pointer &= 0x3FFF 136 | r = parse_name(data, pointer) 137 | labels.append(r[1]) 138 | p += 2 139 | # pointer is the end 140 | return p - offset, b'.'.join(labels) 141 | else: 142 | labels.append(data[p + 1:p + 1 + l]) 143 | p += 1 + l 144 | l = common.ord(data[p]) 145 | return p - offset + 1, b'.'.join(labels) 146 | 147 | 148 | # rfc1035 149 | # record 150 | # 1 1 1 1 1 1 151 | # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 152 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 153 | # | | 154 | # / / 155 | # / NAME / 156 | # | | 157 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 158 | # | TYPE | 159 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 160 | # | CLASS | 161 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 162 | # | TTL | 163 | # | | 164 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 165 | # | RDLENGTH | 166 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| 167 | # / RDATA / 168 | # / / 169 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 170 | def parse_record(data, offset, question=False): 171 | nlen, name = parse_name(data, offset) 172 | if not question: 173 | record_type, record_class, record_ttl, record_rdlength = struct.unpack( 174 | '!HHiH', data[offset + nlen:offset + nlen + 10] 175 | ) 176 | ip = parse_ip(record_type, data, record_rdlength, offset + nlen + 10) 177 | return nlen + 10 + record_rdlength, \ 178 | (name, ip, record_type, record_class, record_ttl) 179 | else: 180 | record_type, record_class = struct.unpack( 181 | '!HH', data[offset + nlen:offset + nlen + 4] 182 | ) 183 | return nlen + 4, (name, None, record_type, record_class, None, None) 184 | 185 | 186 | def parse_header(data): 187 | if len(data) >= 12: 188 | header = struct.unpack('!HBBHHHH', data[:12]) 189 | res_id = header[0] 190 | res_qr = header[1] & 128 191 | res_tc = header[1] & 2 192 | res_ra = header[2] & 128 193 | res_rcode = header[2] & 15 194 | # assert res_tc == 0 195 | # assert res_rcode in [0, 3] 196 | res_qdcount = header[3] 197 | res_ancount = header[4] 198 | res_nscount = header[5] 199 | res_arcount = header[6] 200 | return (res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, 201 | res_ancount, res_nscount, res_arcount) 202 | return None 203 | 204 | 205 | def parse_response(data): 206 | try: 207 | if len(data) >= 12: 208 | header = parse_header(data) 209 | if not header: 210 | return None 211 | res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, \ 212 | res_ancount, res_nscount, res_arcount = header 213 | 214 | qds = [] 215 | ans = [] 216 | offset = 12 217 | for i in range(0, res_qdcount): 218 | l, r = parse_record(data, offset, True) 219 | offset += l 220 | if r: 221 | qds.append(r) 222 | for i in range(0, res_ancount): 223 | l, r = parse_record(data, offset) 224 | offset += l 225 | if r: 226 | ans.append(r) 227 | for i in range(0, res_nscount): 228 | l, r = parse_record(data, offset) 229 | offset += l 230 | for i in range(0, res_arcount): 231 | l, r = parse_record(data, offset) 232 | offset += l 233 | response = DNSResponse() 234 | if qds: 235 | response.hostname = qds[0][0] 236 | for an in qds: 237 | response.questions.append((an[1], an[2], an[3])) 238 | for an in ans: 239 | response.answers.append((an[1], an[2], an[3])) 240 | return response 241 | except Exception as e: 242 | shell.print_exception(e) 243 | return None 244 | 245 | 246 | def is_valid_hostname(hostname): 247 | if len(hostname) > 255: 248 | return False 249 | if hostname[-1] == b'.': 250 | hostname = hostname[:-1] 251 | return all(VALID_HOSTNAME.match(x) for x in hostname.split(b'.')) 252 | 253 | 254 | class DNSResponse(object): 255 | def __init__(self): 256 | self.hostname = None 257 | self.questions = [] # each: (addr, type, class) 258 | self.answers = [] # each: (addr, type, class) 259 | 260 | def __str__(self): 261 | return '%s: %s' % (self.hostname, str(self.answers)) 262 | 263 | 264 | STATUS_IPV4 = 0 265 | STATUS_IPV6 = 1 266 | 267 | 268 | class DNSResolver(object): 269 | 270 | def __init__(self): 271 | self._loop = None 272 | self._hosts = {} 273 | self._hostname_status = {} 274 | self._hostname_to_cb = {} 275 | self._cb_to_hostname = {} 276 | self._cache = lru_cache.LRUCache(timeout=300) 277 | self._sock = None 278 | self._servers = None 279 | self._parse_resolv() 280 | self._parse_hosts() 281 | # TODO monitor hosts change and reload hosts 282 | # TODO parse /etc/gai.conf and follow its rules 283 | 284 | def _parse_resolv(self): 285 | self._servers = [] 286 | try: 287 | with open('dns.conf', 'rb') as f: 288 | content = f.readlines() 289 | for line in content: 290 | line = line.strip() 291 | if line: 292 | parts = line.split(b' ', 1) 293 | if len(parts) >= 2: 294 | server = parts[0] 295 | port = int(parts[1]) 296 | else: 297 | server = parts[0] 298 | port = 53 299 | if common.is_ip(server) == socket.AF_INET: 300 | if type(server) != str: 301 | server = server.decode('utf8') 302 | self._servers.append((server, port)) 303 | except IOError: 304 | pass 305 | if not self._servers: 306 | try: 307 | with open('/etc/resolv.conf', 'rb') as f: 308 | content = f.readlines() 309 | for line in content: 310 | line = line.strip() 311 | if line: 312 | if line.startswith(b'nameserver'): 313 | parts = line.split() 314 | if len(parts) >= 2: 315 | server = parts[1] 316 | if common.is_ip(server) == socket.AF_INET: 317 | if type(server) != str: 318 | server = server.decode('utf8') 319 | self._servers.append((server, 53)) 320 | except IOError: 321 | pass 322 | if not self._servers: 323 | self._servers = [('8.8.4.4', 53), ('8.8.8.8', 53)] 324 | logging.info('dns server: %s' % (self._servers,)) 325 | 326 | def _parse_hosts(self): 327 | etc_path = '/etc/hosts' 328 | if 'WINDIR' in os.environ: 329 | etc_path = os.environ['WINDIR'] + '/system32/drivers/etc/hosts' 330 | try: 331 | with open(etc_path, 'rb') as f: 332 | for line in f.readlines(): 333 | line = line.strip() 334 | if b"#" in line: 335 | line = line[:line.find(b'#')] 336 | parts = line.split() 337 | if len(parts) >= 2: 338 | ip = parts[0] 339 | if common.is_ip(ip): 340 | for i in range(1, len(parts)): 341 | hostname = parts[i] 342 | if hostname: 343 | self._hosts[hostname] = ip 344 | except IOError: 345 | self._hosts['localhost'] = '127.0.0.1' 346 | 347 | def add_to_loop(self, loop): 348 | if self._loop: 349 | raise Exception('already add to loop') 350 | self._loop = loop 351 | # TODO when dns server is IPv6 352 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 353 | socket.SOL_UDP) 354 | self._sock.setblocking(False) 355 | loop.add(self._sock, eventloop.POLL_IN, self) 356 | loop.add_periodic(self.handle_periodic) 357 | 358 | def _call_callback(self, hostname, ip, error=None): 359 | callbacks = self._hostname_to_cb.get(hostname, []) 360 | for callback in callbacks: 361 | if callback in self._cb_to_hostname: 362 | del self._cb_to_hostname[callback] 363 | if ip or error: 364 | callback((hostname, ip), error) 365 | else: 366 | callback((hostname, None), 367 | Exception('unable to parse hostname %s' % hostname)) 368 | if hostname in self._hostname_to_cb: 369 | del self._hostname_to_cb[hostname] 370 | if hostname in self._hostname_status: 371 | del self._hostname_status[hostname] 372 | 373 | def _handle_data(self, data): 374 | response = parse_response(data) 375 | if response and response.hostname: 376 | hostname = response.hostname 377 | ip = None 378 | for answer in response.answers: 379 | if answer[1] in (QTYPE_A, QTYPE_AAAA) and \ 380 | answer[2] == QCLASS_IN: 381 | ip = answer[0] 382 | break 383 | if IPV6_CONNECTION_SUPPORT: 384 | if not ip and self._hostname_status.get(hostname, STATUS_IPV4) \ 385 | == STATUS_IPV6: 386 | self._hostname_status[hostname] = STATUS_IPV4 387 | self._send_req(hostname, QTYPE_A) 388 | else: 389 | if ip: 390 | self._cache[hostname] = ip 391 | self._call_callback(hostname, ip) 392 | elif self._hostname_status.get(hostname, None) == STATUS_IPV4: 393 | for question in response.questions: 394 | if question[1] == QTYPE_A: 395 | self._call_callback(hostname, None) 396 | break 397 | else: 398 | if not ip and self._hostname_status.get(hostname, STATUS_IPV6) \ 399 | == STATUS_IPV4: 400 | self._hostname_status[hostname] = STATUS_IPV6 401 | self._send_req(hostname, QTYPE_AAAA) 402 | else: 403 | if ip: 404 | self._cache[hostname] = ip 405 | self._call_callback(hostname, ip) 406 | elif self._hostname_status.get(hostname, None) == STATUS_IPV6: 407 | for question in response.questions: 408 | if question[1] == QTYPE_AAAA: 409 | self._call_callback(hostname, None) 410 | break 411 | 412 | def handle_event(self, sock, fd, event): 413 | if sock != self._sock: 414 | return 415 | if event & eventloop.POLL_ERR: 416 | logging.error('dns socket err') 417 | self._loop.remove(self._sock) 418 | self._sock.close() 419 | # TODO when dns server is IPv6 420 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 421 | socket.SOL_UDP) 422 | self._sock.setblocking(False) 423 | self._loop.add(self._sock, eventloop.POLL_IN, self) 424 | else: 425 | data, addr = sock.recvfrom(1024) 426 | if addr not in self._servers: 427 | logging.warn('received a packet other than our dns') 428 | return 429 | self._handle_data(data) 430 | 431 | def handle_periodic(self): 432 | self._cache.sweep() 433 | 434 | def remove_callback(self, callback): 435 | hostname = self._cb_to_hostname.get(callback) 436 | if hostname: 437 | del self._cb_to_hostname[callback] 438 | arr = self._hostname_to_cb.get(hostname, None) 439 | if arr: 440 | arr.remove(callback) 441 | if not arr: 442 | del self._hostname_to_cb[hostname] 443 | if hostname in self._hostname_status: 444 | del self._hostname_status[hostname] 445 | 446 | def _send_req(self, hostname, qtype): 447 | req = build_request(hostname, qtype) 448 | for server in self._servers: 449 | logging.debug('resolving %s with type %d using server %s', 450 | hostname, qtype, server) 451 | self._sock.sendto(req, server) 452 | 453 | def resolve(self, hostname, callback): 454 | if type(hostname) != bytes: 455 | hostname = hostname.encode('utf8') 456 | if not hostname: 457 | callback(None, Exception('empty hostname')) 458 | elif common.is_ip(hostname): 459 | callback((hostname, hostname), None) 460 | elif hostname in self._hosts: 461 | logging.debug('hit hosts: %s', hostname) 462 | ip = self._hosts[hostname] 463 | callback((hostname, ip), None) 464 | elif hostname in self._cache: 465 | logging.debug('hit cache: %s', hostname) 466 | ip = self._cache[hostname] 467 | callback((hostname, ip), None) 468 | else: 469 | if not is_valid_hostname(hostname): 470 | callback(None, Exception('invalid hostname: %s' % hostname)) 471 | return 472 | if False: 473 | addrs = socket.getaddrinfo(hostname, 0, 0, 474 | socket.SOCK_DGRAM, socket.SOL_UDP) 475 | if addrs: 476 | af, socktype, proto, canonname, sa = addrs[0] 477 | logging.debug('DNS resolve %s %s' % (hostname, sa[0]) ) 478 | self._cache[hostname] = sa[0] 479 | callback((hostname, sa[0]), None) 480 | return 481 | arr = self._hostname_to_cb.get(hostname, None) 482 | if not arr: 483 | if IPV6_CONNECTION_SUPPORT: 484 | self._hostname_status[hostname] = STATUS_IPV6 485 | self._send_req(hostname, QTYPE_AAAA) 486 | else: 487 | self._hostname_status[hostname] = STATUS_IPV4 488 | self._send_req(hostname, QTYPE_A) 489 | self._hostname_to_cb[hostname] = [callback] 490 | self._cb_to_hostname[callback] = hostname 491 | else: 492 | arr.append(callback) 493 | # TODO send again only if waited too long 494 | if IPV6_CONNECTION_SUPPORT: 495 | self._send_req(hostname, QTYPE_AAAA) 496 | else: 497 | self._send_req(hostname, QTYPE_A) 498 | 499 | def close(self): 500 | if self._sock: 501 | if self._loop: 502 | self._loop.remove_periodic(self.handle_periodic) 503 | self._loop.remove(self._sock) 504 | self._sock.close() 505 | self._sock = None 506 | 507 | 508 | def test(): 509 | dns_resolver = DNSResolver() 510 | loop = eventloop.EventLoop() 511 | dns_resolver.add_to_loop(loop) 512 | 513 | global counter 514 | counter = 0 515 | 516 | def make_callback(): 517 | global counter 518 | 519 | def callback(result, error): 520 | global counter 521 | # TODO: what can we assert? 522 | print(result, error) 523 | counter += 1 524 | if counter == 9: 525 | dns_resolver.close() 526 | loop.stop() 527 | a_callback = callback 528 | return a_callback 529 | 530 | assert(make_callback() != make_callback()) 531 | 532 | dns_resolver.resolve(b'google.com', make_callback()) 533 | dns_resolver.resolve('google.com', make_callback()) 534 | dns_resolver.resolve('example.com', make_callback()) 535 | dns_resolver.resolve('ipv6.google.com', make_callback()) 536 | dns_resolver.resolve('www.facebook.com', make_callback()) 537 | dns_resolver.resolve('ns2.google.com', make_callback()) 538 | dns_resolver.resolve('invalid.@!#$%^&$@.hostname', make_callback()) 539 | dns_resolver.resolve('toooooooooooooooooooooooooooooooooooooooooooooooooo' 540 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 541 | 'long.hostname', make_callback()) 542 | dns_resolver.resolve('toooooooooooooooooooooooooooooooooooooooooooooooooo' 543 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 544 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 545 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 546 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 547 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 548 | 'long.hostname', make_callback()) 549 | 550 | loop.run() 551 | 552 | 553 | if __name__ == '__main__': 554 | test() 555 | 556 | -------------------------------------------------------------------------------- /shadowsocks/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2013-2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import socket 22 | import struct 23 | import logging 24 | import binascii 25 | import re 26 | 27 | from shadowsocks import lru_cache 28 | 29 | def compat_ord(s): 30 | if type(s) == int: 31 | return s 32 | return _ord(s) 33 | 34 | 35 | def compat_chr(d): 36 | if bytes == str: 37 | return _chr(d) 38 | return bytes([d]) 39 | 40 | 41 | _ord = ord 42 | _chr = chr 43 | ord = compat_ord 44 | chr = compat_chr 45 | 46 | connect_log = logging.debug 47 | 48 | def to_bytes(s): 49 | if bytes != str: 50 | if type(s) == str: 51 | return s.encode('utf-8') 52 | return s 53 | 54 | 55 | def to_str(s): 56 | if bytes != str: 57 | if type(s) == bytes: 58 | return s.decode('utf-8') 59 | return s 60 | 61 | def int32(x): 62 | if x > 0xFFFFFFFF or x < 0: 63 | x &= 0xFFFFFFFF 64 | if x > 0x7FFFFFFF: 65 | x = int(0x100000000 - x) 66 | if x < 0x80000000: 67 | return -x 68 | else: 69 | return -2147483648 70 | return x 71 | 72 | def inet_ntop(family, ipstr): 73 | if family == socket.AF_INET: 74 | return to_bytes(socket.inet_ntoa(ipstr)) 75 | elif family == socket.AF_INET6: 76 | import re 77 | v6addr = ':'.join(('%02X%02X' % (ord(i), ord(j))).lstrip('0') 78 | for i, j in zip(ipstr[::2], ipstr[1::2])) 79 | v6addr = re.sub('::+', '::', v6addr, count=1) 80 | return to_bytes(v6addr) 81 | 82 | 83 | def inet_pton(family, addr): 84 | addr = to_str(addr) 85 | if family == socket.AF_INET: 86 | return socket.inet_aton(addr) 87 | elif family == socket.AF_INET6: 88 | if '.' in addr: # a v4 addr 89 | v4addr = addr[addr.rindex(':') + 1:] 90 | v4addr = socket.inet_aton(v4addr) 91 | v4addr = ['%02X' % ord(x) for x in v4addr] 92 | v4addr.insert(2, ':') 93 | newaddr = addr[:addr.rindex(':') + 1] + ''.join(v4addr) 94 | return inet_pton(family, newaddr) 95 | dbyts = [0] * 8 # 8 groups 96 | grps = addr.split(':') 97 | for i, v in enumerate(grps): 98 | if v: 99 | dbyts[i] = int(v, 16) 100 | else: 101 | for j, w in enumerate(grps[::-1]): 102 | if w: 103 | dbyts[7 - j] = int(w, 16) 104 | else: 105 | break 106 | break 107 | return b''.join((chr(i // 256) + chr(i % 256)) for i in dbyts) 108 | else: 109 | raise RuntimeError("What family?") 110 | 111 | 112 | def is_ip(address): 113 | for family in (socket.AF_INET, socket.AF_INET6): 114 | try: 115 | if type(address) != str: 116 | address = address.decode('utf8') 117 | inet_pton(family, address) 118 | return family 119 | except (TypeError, ValueError, OSError, IOError): 120 | pass 121 | return False 122 | 123 | 124 | def match_regex(regex, text): 125 | regex = re.compile(regex) 126 | for item in regex.findall(text): 127 | return True 128 | return False 129 | 130 | 131 | def patch_socket(): 132 | if not hasattr(socket, 'inet_pton'): 133 | socket.inet_pton = inet_pton 134 | 135 | if not hasattr(socket, 'inet_ntop'): 136 | socket.inet_ntop = inet_ntop 137 | 138 | 139 | patch_socket() 140 | 141 | 142 | ADDRTYPE_IPV4 = 1 143 | ADDRTYPE_IPV6 = 4 144 | ADDRTYPE_HOST = 3 145 | 146 | 147 | def pack_addr(address): 148 | address_str = to_str(address) 149 | for family in (socket.AF_INET, socket.AF_INET6): 150 | try: 151 | r = socket.inet_pton(family, address_str) 152 | if family == socket.AF_INET6: 153 | return b'\x04' + r 154 | else: 155 | return b'\x01' + r 156 | except (TypeError, ValueError, OSError, IOError): 157 | pass 158 | if len(address) > 255: 159 | address = address[:255] # TODO 160 | return b'\x03' + chr(len(address)) + address 161 | 162 | def pre_parse_header(data): 163 | if not data: 164 | return None 165 | datatype = ord(data[0]) 166 | if datatype == 0x80: 167 | if len(data) <= 2: 168 | return None 169 | rand_data_size = ord(data[1]) 170 | if rand_data_size + 2 >= len(data): 171 | logging.warn('header too short, maybe wrong password or ' 172 | 'encryption method') 173 | return None 174 | data = data[rand_data_size + 2:] 175 | elif datatype == 0x81: 176 | data = data[1:] 177 | elif datatype == 0x82: 178 | if len(data) <= 3: 179 | return None 180 | rand_data_size = struct.unpack('>H', data[1:3])[0] 181 | if rand_data_size + 3 >= len(data): 182 | logging.warn('header too short, maybe wrong password or ' 183 | 'encryption method') 184 | return None 185 | data = data[rand_data_size + 3:] 186 | elif datatype == 0x88 or (~datatype & 0xff) == 0x88: 187 | if len(data) <= 7 + 7: 188 | return None 189 | data_size = struct.unpack('>H', data[1:3])[0] 190 | ogn_data = data 191 | data = data[:data_size] 192 | crc = binascii.crc32(data) & 0xffffffff 193 | if crc != 0xffffffff: 194 | logging.warn('uncorrect CRC32, maybe wrong password or ' 195 | 'encryption method') 196 | return None 197 | start_pos = 3 + ord(data[3]) 198 | data = data[start_pos:-4] 199 | if data_size < len(ogn_data): 200 | data += ogn_data[data_size:] 201 | return data 202 | 203 | def parse_header(data): 204 | addrtype = ord(data[0]) 205 | dest_addr = None 206 | dest_port = None 207 | header_length = 0 208 | connecttype = (addrtype & 0x8) and 1 or 0 209 | addrtype &= ~0x8 210 | if addrtype == ADDRTYPE_IPV4: 211 | if len(data) >= 7: 212 | dest_addr = socket.inet_ntoa(data[1:5]) 213 | dest_port = struct.unpack('>H', data[5:7])[0] 214 | header_length = 7 215 | else: 216 | logging.warn('header is too short') 217 | elif addrtype == ADDRTYPE_HOST: 218 | if len(data) > 2: 219 | addrlen = ord(data[1]) 220 | if len(data) >= 4 + addrlen: 221 | dest_addr = data[2:2 + addrlen] 222 | dest_port = struct.unpack('>H', data[2 + addrlen:4 + 223 | addrlen])[0] 224 | header_length = 4 + addrlen 225 | else: 226 | logging.warn('header is too short') 227 | else: 228 | logging.warn('header is too short') 229 | elif addrtype == ADDRTYPE_IPV6: 230 | if len(data) >= 19: 231 | dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17]) 232 | dest_port = struct.unpack('>H', data[17:19])[0] 233 | header_length = 19 234 | else: 235 | logging.warn('header is too short') 236 | else: 237 | logging.warn('unsupported addrtype %d, maybe wrong password or ' 238 | 'encryption method' % addrtype) 239 | if dest_addr is None: 240 | return None 241 | return connecttype, addrtype, to_bytes(dest_addr), dest_port, header_length 242 | 243 | 244 | class IPNetwork(object): 245 | ADDRLENGTH = {socket.AF_INET: 32, socket.AF_INET6: 128, False: 0} 246 | 247 | def __init__(self, addrs): 248 | self.addrs_str = addrs 249 | self._network_list_v4 = [] 250 | self._network_list_v6 = [] 251 | if type(addrs) == str: 252 | addrs = addrs.split(',') 253 | list(map(self.add_network, addrs)) 254 | 255 | def add_network(self, addr): 256 | if addr is "": 257 | return 258 | block = addr.split('/') 259 | addr_family = is_ip(block[0]) 260 | addr_len = IPNetwork.ADDRLENGTH[addr_family] 261 | if addr_family is socket.AF_INET: 262 | ip, = struct.unpack("!I", socket.inet_aton(block[0])) 263 | elif addr_family is socket.AF_INET6: 264 | hi, lo = struct.unpack("!QQ", inet_pton(addr_family, block[0])) 265 | ip = (hi << 64) | lo 266 | else: 267 | raise Exception("Not a valid CIDR notation: %s" % addr) 268 | if len(block) is 1: 269 | prefix_size = 0 270 | while (ip & 1) == 0 and ip is not 0: 271 | ip >>= 1 272 | prefix_size += 1 273 | logging.warn("You did't specify CIDR routing prefix size for %s, " 274 | "implicit treated as %s/%d" % (addr, addr, addr_len)) 275 | elif block[1].isdigit() and int(block[1]) <= addr_len: 276 | prefix_size = addr_len - int(block[1]) 277 | ip >>= prefix_size 278 | else: 279 | raise Exception("Not a valid CIDR notation: %s" % addr) 280 | if addr_family is socket.AF_INET: 281 | self._network_list_v4.append((ip, prefix_size)) 282 | else: 283 | self._network_list_v6.append((ip, prefix_size)) 284 | 285 | def __contains__(self, addr): 286 | addr_family = is_ip(addr) 287 | if addr_family is socket.AF_INET: 288 | ip, = struct.unpack("!I", socket.inet_aton(addr)) 289 | return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1], 290 | self._network_list_v4)) 291 | elif addr_family is socket.AF_INET6: 292 | hi, lo = struct.unpack("!QQ", inet_pton(addr_family, addr)) 293 | ip = (hi << 64) | lo 294 | return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1], 295 | self._network_list_v6)) 296 | else: 297 | return False 298 | 299 | def __cmp__(self, other): 300 | return cmp(self.addrs_str, other.addrs_str) 301 | 302 | def __eq__(self, other): 303 | return self.addrs_str == other.addrs_str 304 | 305 | def __ne__(self, other): 306 | return self.addrs_str != other.addrs_str 307 | 308 | class PortRange(object): 309 | def __init__(self, range_str): 310 | self.range_str = to_str(range_str) 311 | self.range = set() 312 | range_str = to_str(range_str).split(',') 313 | for item in range_str: 314 | try: 315 | int_range = item.split('-') 316 | if len(int_range) == 1: 317 | if item: 318 | self.range.add(int(item)) 319 | elif len(int_range) == 2: 320 | int_range[0] = int(int_range[0]) 321 | int_range[1] = int(int_range[1]) 322 | if int_range[0] < 0: 323 | int_range[0] = 0 324 | if int_range[1] > 65535: 325 | int_range[1] = 65535 326 | i = int_range[0] 327 | while i <= int_range[1]: 328 | self.range.add(i) 329 | i += 1 330 | except Exception as e: 331 | logging.error(e) 332 | 333 | def __contains__(self, val): 334 | return val in self.range 335 | 336 | def __cmp__(self, other): 337 | return cmp(self.range_str, other.range_str) 338 | 339 | def __eq__(self, other): 340 | return self.range_str == other.range_str 341 | 342 | def __ne__(self, other): 343 | return self.range_str != other.range_str 344 | 345 | class UDPAsyncDNSHandler(object): 346 | dns_cache = lru_cache.LRUCache(timeout=1800) 347 | def __init__(self, params): 348 | self.params = params 349 | self.remote_addr = None 350 | self.call_back = None 351 | 352 | def resolve(self, dns_resolver, remote_addr, call_back): 353 | if remote_addr in UDPAsyncDNSHandler.dns_cache: 354 | if call_back: 355 | call_back("", remote_addr, UDPAsyncDNSHandler.dns_cache[remote_addr], self.params) 356 | else: 357 | self.call_back = call_back 358 | self.remote_addr = remote_addr 359 | dns_resolver.resolve(remote_addr[0], self._handle_dns_resolved) 360 | UDPAsyncDNSHandler.dns_cache.sweep() 361 | 362 | def _handle_dns_resolved(self, result, error): 363 | if error: 364 | logging.error("%s when resolve DNS" % (error,)) #drop 365 | return self.call_back(error, self.remote_addr, None, self.params) 366 | if result: 367 | ip = result[1] 368 | if ip: 369 | return self.call_back("", self.remote_addr, ip, self.params) 370 | logging.warning("can't resolve %s" % (self.remote_addr,)) 371 | return self.call_back("fail to resolve", self.remote_addr, None, self.params) 372 | 373 | def test_inet_conv(): 374 | ipv4 = b'8.8.4.4' 375 | b = inet_pton(socket.AF_INET, ipv4) 376 | assert inet_ntop(socket.AF_INET, b) == ipv4 377 | ipv6 = b'2404:6800:4005:805::1011' 378 | b = inet_pton(socket.AF_INET6, ipv6) 379 | assert inet_ntop(socket.AF_INET6, b) == ipv6 380 | 381 | 382 | def test_parse_header(): 383 | assert parse_header(b'\x03\x0ewww.google.com\x00\x50') == \ 384 | (0, b'www.google.com', 80, 18) 385 | assert parse_header(b'\x01\x08\x08\x08\x08\x00\x35') == \ 386 | (0, b'8.8.8.8', 53, 7) 387 | assert parse_header((b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00' 388 | b'\x00\x10\x11\x00\x50')) == \ 389 | (0, b'2404:6800:4005:805::1011', 80, 19) 390 | 391 | 392 | def test_pack_header(): 393 | assert pack_addr(b'8.8.8.8') == b'\x01\x08\x08\x08\x08' 394 | assert pack_addr(b'2404:6800:4005:805::1011') == \ 395 | b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00\x00\x10\x11' 396 | assert pack_addr(b'www.google.com') == b'\x03\x0ewww.google.com' 397 | 398 | 399 | def test_ip_network(): 400 | ip_network = IPNetwork('127.0.0.0/24,::ff:1/112,::1,192.168.1.1,192.0.2.0') 401 | assert '127.0.0.1' in ip_network 402 | assert '127.0.1.1' not in ip_network 403 | assert ':ff:ffff' in ip_network 404 | assert '::ffff:1' not in ip_network 405 | assert '::1' in ip_network 406 | assert '::2' not in ip_network 407 | assert '192.168.1.1' in ip_network 408 | assert '192.168.1.2' not in ip_network 409 | assert '192.0.2.1' in ip_network 410 | assert '192.0.3.1' in ip_network # 192.0.2.0 is treated as 192.0.2.0/23 411 | assert 'www.google.com' not in ip_network 412 | 413 | 414 | if __name__ == '__main__': 415 | test_inet_conv() 416 | test_parse_header() 417 | test_pack_header() 418 | test_ip_network() 419 | -------------------------------------------------------------------------------- /shadowsocks/crypto/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | -------------------------------------------------------------------------------- /shadowsocks/crypto/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/crypto/__init__.pyc -------------------------------------------------------------------------------- /shadowsocks/crypto/ctypes_libsodium.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 | from __future__ import absolute_import, division, print_function, \ 24 | with_statement 25 | 26 | import logging 27 | from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \ 28 | create_string_buffer, c_void_p 29 | 30 | __all__ = ['ciphers'] 31 | 32 | libsodium = None 33 | loaded = False 34 | 35 | buf_size = 2048 36 | 37 | # for salsa20 and chacha20 38 | BLOCK_SIZE = 64 39 | 40 | 41 | def load_libsodium(): 42 | global loaded, libsodium, buf 43 | 44 | from ctypes.util import find_library 45 | for p in ('sodium',): 46 | libsodium_path = find_library(p) 47 | if libsodium_path: 48 | break 49 | else: 50 | raise Exception('libsodium not found') 51 | logging.info('loading libsodium from %s', libsodium_path) 52 | libsodium = CDLL(libsodium_path) 53 | libsodium.sodium_init.restype = c_int 54 | libsodium.crypto_stream_salsa20_xor_ic.restype = c_int 55 | libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, 56 | c_ulonglong, 57 | c_char_p, c_ulonglong, 58 | c_char_p) 59 | libsodium.crypto_stream_chacha20_xor_ic.restype = c_int 60 | libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p, 61 | c_ulonglong, 62 | c_char_p, c_ulonglong, 63 | c_char_p) 64 | 65 | libsodium.sodium_init() 66 | 67 | buf = create_string_buffer(buf_size) 68 | loaded = True 69 | 70 | 71 | class Salsa20Crypto(object): 72 | def __init__(self, cipher_name, key, iv, op): 73 | if not loaded: 74 | load_libsodium() 75 | self.key = key 76 | self.iv = iv 77 | self.key_ptr = c_char_p(key) 78 | self.iv_ptr = c_char_p(iv) 79 | if cipher_name == b'salsa20': 80 | self.cipher = libsodium.crypto_stream_salsa20_xor_ic 81 | elif cipher_name == b'chacha20': 82 | self.cipher = libsodium.crypto_stream_chacha20_xor_ic 83 | else: 84 | raise Exception('Unknown cipher') 85 | # byte counter, not block counter 86 | self.counter = 0 87 | 88 | def update(self, data): 89 | global buf_size, buf 90 | l = len(data) 91 | 92 | # we can only prepend some padding to make the encryption align to 93 | # blocks 94 | padding = self.counter % BLOCK_SIZE 95 | if buf_size < padding + l: 96 | buf_size = (padding + l) * 2 97 | buf = create_string_buffer(buf_size) 98 | 99 | if padding: 100 | data = (b'\0' * padding) + data 101 | self.cipher(byref(buf), c_char_p(data), padding + l, 102 | self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr) 103 | self.counter += l 104 | # buf is copied to a str object when we access buf.raw 105 | # strip off the padding 106 | return buf.raw[padding:padding + l] 107 | 108 | 109 | ciphers = { 110 | b'salsa20': (32, 8, Salsa20Crypto), 111 | b'chacha20': (32, 8, Salsa20Crypto), 112 | } 113 | 114 | 115 | def test_salsa20(): 116 | from shadowsocks.crypto import util 117 | 118 | cipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 1) 119 | decipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 0) 120 | 121 | util.run_cipher(cipher, decipher) 122 | 123 | 124 | def test_chacha20(): 125 | from shadowsocks.crypto import util 126 | 127 | cipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 1) 128 | decipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 0) 129 | 130 | util.run_cipher(cipher, decipher) 131 | 132 | 133 | if __name__ == '__main__': 134 | test_chacha20() 135 | test_salsa20() 136 | -------------------------------------------------------------------------------- /shadowsocks/crypto/ctypes_openssl.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 | from __future__ import absolute_import, division, print_function, \ 24 | with_statement 25 | 26 | import logging 27 | from ctypes import CDLL, c_char_p, c_int, c_long, byref,\ 28 | create_string_buffer, c_void_p 29 | 30 | __all__ = ['ciphers'] 31 | 32 | libcrypto = None 33 | loaded = False 34 | 35 | buf_size = 2048 36 | 37 | 38 | def load_openssl(): 39 | global loaded, libcrypto, buf 40 | 41 | from ctypes.util import find_library 42 | for p in ('crypto', 'eay32', 'libeay32'): 43 | libcrypto_path = find_library(p) 44 | if libcrypto_path: 45 | break 46 | else: 47 | raise Exception('libcrypto(OpenSSL) not found') 48 | logging.info('loading libcrypto from %s', libcrypto_path) 49 | libcrypto = CDLL(libcrypto_path) 50 | libcrypto.EVP_get_cipherbyname.restype = c_void_p 51 | libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p 52 | 53 | libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, 54 | c_char_p, c_char_p, c_int) 55 | 56 | libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, 57 | c_char_p, c_int) 58 | 59 | libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) 60 | libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) 61 | if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): 62 | libcrypto.OpenSSL_add_all_ciphers() 63 | 64 | buf = create_string_buffer(buf_size) 65 | loaded = True 66 | 67 | 68 | def load_cipher(cipher_name): 69 | func_name = b'EVP_' + cipher_name.replace(b'-', b'_') 70 | if bytes != str: 71 | func_name = str(func_name, 'utf-8') 72 | cipher = getattr(libcrypto, func_name, None) 73 | if cipher: 74 | cipher.restype = c_void_p 75 | return cipher() 76 | return None 77 | 78 | 79 | class CtypesCrypto(object): 80 | def __init__(self, cipher_name, key, iv, op): 81 | if not loaded: 82 | load_openssl() 83 | self._ctx = None 84 | cipher = libcrypto.EVP_get_cipherbyname(cipher_name) 85 | if not cipher: 86 | cipher = load_cipher(cipher_name) 87 | if not cipher: 88 | raise Exception('cipher %s not found in libcrypto' % cipher_name) 89 | key_ptr = c_char_p(key) 90 | iv_ptr = c_char_p(iv) 91 | self._ctx = libcrypto.EVP_CIPHER_CTX_new() 92 | if not self._ctx: 93 | raise Exception('can not create cipher context') 94 | r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None, 95 | key_ptr, iv_ptr, c_int(op)) 96 | if not r: 97 | self.clean() 98 | raise Exception('can not initialize cipher context') 99 | 100 | def update(self, data): 101 | global buf_size, buf 102 | cipher_out_len = c_long(0) 103 | l = len(data) 104 | if buf_size < l: 105 | buf_size = l * 2 106 | buf = create_string_buffer(buf_size) 107 | libcrypto.EVP_CipherUpdate(self._ctx, byref(buf), 108 | byref(cipher_out_len), c_char_p(data), l) 109 | # buf is copied to a str object when we access buf.raw 110 | return buf.raw[:cipher_out_len.value] 111 | 112 | def __del__(self): 113 | self.clean() 114 | 115 | def clean(self): 116 | if self._ctx: 117 | libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx) 118 | libcrypto.EVP_CIPHER_CTX_free(self._ctx) 119 | 120 | 121 | ciphers = { 122 | b'aes-128-cfb': (16, 16, CtypesCrypto), 123 | b'aes-192-cfb': (24, 16, CtypesCrypto), 124 | b'aes-256-cfb': (32, 16, CtypesCrypto), 125 | b'aes-128-ofb': (16, 16, CtypesCrypto), 126 | b'aes-192-ofb': (24, 16, CtypesCrypto), 127 | b'aes-256-ofb': (32, 16, CtypesCrypto), 128 | b'aes-128-ctr': (16, 16, CtypesCrypto), 129 | b'aes-192-ctr': (24, 16, CtypesCrypto), 130 | b'aes-256-ctr': (32, 16, CtypesCrypto), 131 | b'aes-128-cfb8': (16, 16, CtypesCrypto), 132 | b'aes-192-cfb8': (24, 16, CtypesCrypto), 133 | b'aes-256-cfb8': (32, 16, CtypesCrypto), 134 | b'aes-128-cfb1': (16, 16, CtypesCrypto), 135 | b'aes-192-cfb1': (24, 16, CtypesCrypto), 136 | b'aes-256-cfb1': (32, 16, CtypesCrypto), 137 | b'bf-cfb': (16, 8, CtypesCrypto), 138 | b'camellia-128-cfb': (16, 16, CtypesCrypto), 139 | b'camellia-192-cfb': (24, 16, CtypesCrypto), 140 | b'camellia-256-cfb': (32, 16, CtypesCrypto), 141 | b'cast5-cfb': (16, 8, CtypesCrypto), 142 | b'des-cfb': (8, 8, CtypesCrypto), 143 | b'idea-cfb': (16, 8, CtypesCrypto), 144 | b'rc2-cfb': (16, 8, CtypesCrypto), 145 | b'rc4': (16, 0, CtypesCrypto), 146 | b'seed-cfb': (16, 16, CtypesCrypto), 147 | } 148 | 149 | 150 | def run_method(method): 151 | from shadowsocks.crypto import util 152 | 153 | cipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 1) 154 | decipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 0) 155 | 156 | util.run_cipher(cipher, decipher) 157 | 158 | 159 | def test_aes_128_cfb(): 160 | run_method(b'aes-128-cfb') 161 | 162 | 163 | def test_aes_256_cfb(): 164 | run_method(b'aes-256-cfb') 165 | 166 | 167 | def test_aes_128_cfb8(): 168 | run_method(b'aes-128-cfb8') 169 | 170 | 171 | def test_aes_256_ofb(): 172 | run_method(b'aes-256-ofb') 173 | 174 | 175 | def test_aes_256_ctr(): 176 | run_method(b'aes-256-ctr') 177 | 178 | 179 | def test_bf_cfb(): 180 | run_method(b'bf-cfb') 181 | 182 | 183 | def test_rc4(): 184 | run_method(b'rc4') 185 | 186 | 187 | if __name__ == '__main__': 188 | test_aes_128_cfb() 189 | -------------------------------------------------------------------------------- /shadowsocks/crypto/openssl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | from ctypes import c_char_p, c_int, c_long, byref,\ 21 | create_string_buffer, c_void_p 22 | 23 | from shadowsocks import common 24 | from shadowsocks.crypto import util 25 | 26 | __all__ = ['ciphers'] 27 | 28 | libcrypto = None 29 | loaded = False 30 | 31 | buf_size = 2048 32 | 33 | 34 | def load_openssl(): 35 | global loaded, libcrypto, buf 36 | 37 | libcrypto = util.find_library(('crypto', 'eay32'), 38 | 'EVP_get_cipherbyname', 39 | 'libcrypto') 40 | if libcrypto is None: 41 | raise Exception('libcrypto(OpenSSL) not found') 42 | 43 | libcrypto.EVP_get_cipherbyname.restype = c_void_p 44 | libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p 45 | 46 | libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, 47 | c_char_p, c_char_p, c_int) 48 | 49 | libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, 50 | c_char_p, c_int) 51 | 52 | if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"): 53 | libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) 54 | else: 55 | libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,) 56 | libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) 57 | 58 | libcrypto.RAND_bytes.restype = c_int 59 | libcrypto.RAND_bytes.argtypes = (c_void_p, c_int) 60 | 61 | if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): 62 | libcrypto.OpenSSL_add_all_ciphers() 63 | 64 | buf = create_string_buffer(buf_size) 65 | loaded = True 66 | 67 | 68 | def load_cipher(cipher_name): 69 | func_name = 'EVP_' + cipher_name.replace('-', '_') 70 | cipher = getattr(libcrypto, func_name, None) 71 | if cipher: 72 | cipher.restype = c_void_p 73 | return cipher() 74 | return None 75 | 76 | def rand_bytes(length): 77 | if not loaded: 78 | load_openssl() 79 | buf = create_string_buffer(length) 80 | r = libcrypto.RAND_bytes(buf, length) 81 | if r <= 0: 82 | raise Exception('RAND_bytes return error') 83 | return buf.raw 84 | 85 | class OpenSSLCrypto(object): 86 | def __init__(self, cipher_name, key, iv, op): 87 | self._ctx = None 88 | if not loaded: 89 | load_openssl() 90 | cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(cipher_name)) 91 | if not cipher: 92 | cipher = load_cipher(cipher_name) 93 | if not cipher: 94 | raise Exception('cipher %s not found in libcrypto' % cipher_name) 95 | key_ptr = c_char_p(key) 96 | iv_ptr = c_char_p(iv) 97 | self._ctx = libcrypto.EVP_CIPHER_CTX_new() 98 | if not self._ctx: 99 | raise Exception('can not create cipher context') 100 | r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None, 101 | key_ptr, iv_ptr, c_int(op)) 102 | if not r: 103 | self.clean() 104 | raise Exception('can not initialize cipher context') 105 | 106 | def update(self, data): 107 | global buf_size, buf 108 | cipher_out_len = c_long(0) 109 | l = len(data) 110 | if buf_size < l: 111 | buf_size = l * 2 112 | buf = create_string_buffer(buf_size) 113 | libcrypto.EVP_CipherUpdate(self._ctx, byref(buf), 114 | byref(cipher_out_len), c_char_p(data), l) 115 | # buf is copied to a str object when we access buf.raw 116 | return buf.raw[:cipher_out_len.value] 117 | 118 | def __del__(self): 119 | self.clean() 120 | 121 | def clean(self): 122 | if self._ctx: 123 | if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"): 124 | libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx) 125 | else: 126 | libcrypto.EVP_CIPHER_CTX_reset(self._ctx) 127 | libcrypto.EVP_CIPHER_CTX_free(self._ctx) 128 | 129 | 130 | ciphers = { 131 | 'aes-128-cbc': (16, 16, OpenSSLCrypto), 132 | 'aes-192-cbc': (24, 16, OpenSSLCrypto), 133 | 'aes-256-cbc': (32, 16, OpenSSLCrypto), 134 | 'aes-128-cfb': (16, 16, OpenSSLCrypto), 135 | 'aes-192-cfb': (24, 16, OpenSSLCrypto), 136 | 'aes-256-cfb': (32, 16, OpenSSLCrypto), 137 | 'aes-128-ofb': (16, 16, OpenSSLCrypto), 138 | 'aes-192-ofb': (24, 16, OpenSSLCrypto), 139 | 'aes-256-ofb': (32, 16, OpenSSLCrypto), 140 | 'aes-128-ctr': (16, 16, OpenSSLCrypto), 141 | 'aes-192-ctr': (24, 16, OpenSSLCrypto), 142 | 'aes-256-ctr': (32, 16, OpenSSLCrypto), 143 | 'aes-128-cfb8': (16, 16, OpenSSLCrypto), 144 | 'aes-192-cfb8': (24, 16, OpenSSLCrypto), 145 | 'aes-256-cfb8': (32, 16, OpenSSLCrypto), 146 | 'aes-128-cfb1': (16, 16, OpenSSLCrypto), 147 | 'aes-192-cfb1': (24, 16, OpenSSLCrypto), 148 | 'aes-256-cfb1': (32, 16, OpenSSLCrypto), 149 | 'bf-cfb': (16, 8, OpenSSLCrypto), 150 | 'camellia-128-cfb': (16, 16, OpenSSLCrypto), 151 | 'camellia-192-cfb': (24, 16, OpenSSLCrypto), 152 | 'camellia-256-cfb': (32, 16, OpenSSLCrypto), 153 | 'cast5-cfb': (16, 8, OpenSSLCrypto), 154 | 'des-cfb': (8, 8, OpenSSLCrypto), 155 | 'idea-cfb': (16, 8, OpenSSLCrypto), 156 | 'rc2-cfb': (16, 8, OpenSSLCrypto), 157 | 'rc4': (16, 0, OpenSSLCrypto), 158 | 'seed-cfb': (16, 16, OpenSSLCrypto), 159 | } 160 | 161 | 162 | def run_method(method): 163 | 164 | cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1) 165 | decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0) 166 | 167 | util.run_cipher(cipher, decipher) 168 | 169 | 170 | def test_aes_128_cfb(): 171 | run_method('aes-128-cfb') 172 | 173 | 174 | def test_aes_256_cfb(): 175 | run_method('aes-256-cfb') 176 | 177 | 178 | def test_aes_128_cfb8(): 179 | run_method('aes-128-cfb8') 180 | 181 | 182 | def test_aes_256_ofb(): 183 | run_method('aes-256-ofb') 184 | 185 | 186 | def test_aes_256_ctr(): 187 | run_method('aes-256-ctr') 188 | 189 | 190 | def test_bf_cfb(): 191 | run_method('bf-cfb') 192 | 193 | 194 | def test_rc4(): 195 | run_method('rc4') 196 | 197 | 198 | if __name__ == '__main__': 199 | test_aes_128_cfb() 200 | -------------------------------------------------------------------------------- /shadowsocks/crypto/openssl.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/crypto/openssl.pyc -------------------------------------------------------------------------------- /shadowsocks/crypto/rc4_md5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import hashlib 21 | 22 | from shadowsocks.crypto import openssl 23 | 24 | __all__ = ['ciphers'] 25 | 26 | 27 | def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, 28 | i=1, padding=1): 29 | md5 = hashlib.md5() 30 | md5.update(key) 31 | md5.update(iv) 32 | rc4_key = md5.digest() 33 | return openssl.OpenSSLCrypto(b'rc4', rc4_key, b'', op) 34 | 35 | 36 | ciphers = { 37 | 'rc4-md5': (16, 16, create_cipher), 38 | 'rc4-md5-6': (16, 6, create_cipher), 39 | } 40 | 41 | 42 | def test(): 43 | from shadowsocks.crypto import util 44 | 45 | cipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 1) 46 | decipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 0) 47 | 48 | util.run_cipher(cipher, decipher) 49 | 50 | 51 | if __name__ == '__main__': 52 | test() 53 | -------------------------------------------------------------------------------- /shadowsocks/crypto/rc4_md5.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/crypto/rc4_md5.pyc -------------------------------------------------------------------------------- /shadowsocks/crypto/sodium.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | from ctypes import c_char_p, c_int, c_ulong, c_ulonglong, byref, \ 21 | create_string_buffer, c_void_p 22 | 23 | from shadowsocks.crypto import util 24 | 25 | __all__ = ['ciphers'] 26 | 27 | libsodium = None 28 | loaded = False 29 | 30 | buf_size = 2048 31 | 32 | # for salsa20 and chacha20 and chacha20-ietf 33 | BLOCK_SIZE = 64 34 | 35 | 36 | def load_libsodium(): 37 | global loaded, libsodium, buf 38 | 39 | libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', 40 | 'libsodium') 41 | if libsodium is None: 42 | raise Exception('libsodium not found') 43 | 44 | libsodium.crypto_stream_salsa20_xor_ic.restype = c_int 45 | libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, 46 | c_ulonglong, 47 | c_char_p, c_ulonglong, 48 | c_char_p) 49 | libsodium.crypto_stream_chacha20_xor_ic.restype = c_int 50 | libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p, 51 | c_ulonglong, 52 | c_char_p, c_ulonglong, 53 | c_char_p) 54 | 55 | try: 56 | libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int 57 | libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (c_void_p, c_char_p, 58 | c_ulonglong, 59 | c_char_p, c_ulong, 60 | c_char_p) 61 | except: 62 | pass 63 | 64 | buf = create_string_buffer(buf_size) 65 | loaded = True 66 | 67 | 68 | class SodiumCrypto(object): 69 | def __init__(self, cipher_name, key, iv, op): 70 | if not loaded: 71 | load_libsodium() 72 | self.key = key 73 | self.iv = iv 74 | self.key_ptr = c_char_p(key) 75 | self.iv_ptr = c_char_p(iv) 76 | if cipher_name == 'salsa20': 77 | self.cipher = libsodium.crypto_stream_salsa20_xor_ic 78 | elif cipher_name == 'chacha20': 79 | self.cipher = libsodium.crypto_stream_chacha20_xor_ic 80 | elif cipher_name == 'chacha20-ietf': 81 | self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic 82 | else: 83 | raise Exception('Unknown cipher') 84 | # byte counter, not block counter 85 | self.counter = 0 86 | 87 | def update(self, data): 88 | global buf_size, buf 89 | l = len(data) 90 | 91 | # we can only prepend some padding to make the encryption align to 92 | # blocks 93 | padding = self.counter % BLOCK_SIZE 94 | if buf_size < padding + l: 95 | buf_size = (padding + l) * 2 96 | buf = create_string_buffer(buf_size) 97 | 98 | if padding: 99 | data = (b'\0' * padding) + data 100 | self.cipher(byref(buf), c_char_p(data), padding + l, 101 | self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr) 102 | self.counter += l 103 | # buf is copied to a str object when we access buf.raw 104 | # strip off the padding 105 | return buf.raw[padding:padding + l] 106 | 107 | 108 | ciphers = { 109 | 'salsa20': (32, 8, SodiumCrypto), 110 | 'chacha20': (32, 8, SodiumCrypto), 111 | 'chacha20-ietf': (32, 12, SodiumCrypto), 112 | } 113 | 114 | 115 | def test_salsa20(): 116 | cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1) 117 | decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0) 118 | 119 | util.run_cipher(cipher, decipher) 120 | 121 | 122 | def test_chacha20(): 123 | 124 | cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) 125 | decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) 126 | 127 | util.run_cipher(cipher, decipher) 128 | 129 | 130 | def test_chacha20_ietf(): 131 | 132 | cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) 133 | decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) 134 | 135 | util.run_cipher(cipher, decipher) 136 | 137 | if __name__ == '__main__': 138 | test_chacha20_ietf() 139 | test_chacha20() 140 | test_salsa20() 141 | -------------------------------------------------------------------------------- /shadowsocks/crypto/sodium.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/crypto/sodium.pyc -------------------------------------------------------------------------------- /shadowsocks/crypto/table.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import string 21 | import struct 22 | import hashlib 23 | 24 | 25 | __all__ = ['ciphers'] 26 | 27 | cached_tables = {} 28 | 29 | if hasattr(string, 'maketrans'): 30 | maketrans = string.maketrans 31 | translate = string.translate 32 | else: 33 | maketrans = bytes.maketrans 34 | translate = bytes.translate 35 | 36 | 37 | def get_table(key): 38 | m = hashlib.md5() 39 | m.update(key) 40 | s = m.digest() 41 | a, b = struct.unpack(' 0: 108 | # parent waits for its child 109 | time.sleep(5) 110 | sys.exit(0) 111 | 112 | # child signals its parent to exit 113 | ppid = os.getppid() 114 | pid = os.getpid() 115 | if write_pid_file(pid_file, pid) != 0: 116 | os.kill(ppid, signal.SIGINT) 117 | sys.exit(1) 118 | 119 | os.setsid() 120 | signal.signal(signal.SIG_IGN, signal.SIGHUP) 121 | 122 | print('started') 123 | os.kill(ppid, signal.SIGTERM) 124 | 125 | sys.stdin.close() 126 | try: 127 | freopen(log_file, 'a', sys.stdout) 128 | freopen(log_file, 'a', sys.stderr) 129 | except IOError as e: 130 | shell.print_exception(e) 131 | sys.exit(1) 132 | 133 | 134 | def daemon_stop(pid_file): 135 | import errno 136 | try: 137 | with open(pid_file) as f: 138 | buf = f.read() 139 | pid = common.to_str(buf) 140 | if not buf: 141 | logging.error('not running') 142 | except IOError as e: 143 | shell.print_exception(e) 144 | if e.errno == errno.ENOENT: 145 | # always exit 0 if we are sure daemon is not running 146 | logging.error('not running') 147 | return 148 | sys.exit(1) 149 | pid = int(pid) 150 | if pid > 0: 151 | try: 152 | os.kill(pid, signal.SIGTERM) 153 | except OSError as e: 154 | if e.errno == errno.ESRCH: 155 | logging.error('not running') 156 | # always exit 0 if we are sure daemon is not running 157 | return 158 | shell.print_exception(e) 159 | sys.exit(1) 160 | else: 161 | logging.error('pid is not positive: %d', pid) 162 | 163 | # sleep for maximum 10s 164 | for i in range(0, 200): 165 | try: 166 | # query for the pid 167 | os.kill(pid, 0) 168 | except OSError as e: 169 | if e.errno == errno.ESRCH: 170 | break 171 | time.sleep(0.05) 172 | else: 173 | logging.error('timed out when stopping pid %d', pid) 174 | sys.exit(1) 175 | print('stopped') 176 | os.unlink(pid_file) 177 | 178 | 179 | def set_user(username): 180 | if username is None: 181 | return 182 | 183 | import pwd 184 | import grp 185 | 186 | try: 187 | pwrec = pwd.getpwnam(username) 188 | except KeyError: 189 | logging.error('user not found: %s' % username) 190 | raise 191 | user = pwrec[0] 192 | uid = pwrec[2] 193 | gid = pwrec[3] 194 | 195 | cur_uid = os.getuid() 196 | if uid == cur_uid: 197 | return 198 | if cur_uid != 0: 199 | logging.error('can not set user as nonroot user') 200 | # will raise later 201 | 202 | # inspired by supervisor 203 | if hasattr(os, 'setgroups'): 204 | groups = [grprec[2] for grprec in grp.getgrall() if user in grprec[3]] 205 | groups.insert(0, gid) 206 | os.setgroups(groups) 207 | os.setgid(gid) 208 | os.setuid(uid) 209 | -------------------------------------------------------------------------------- /shadowsocks/encrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2012-2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | 25 | from shadowsocks import common 26 | from shadowsocks.crypto import rc4_md5, openssl, sodium, table 27 | 28 | 29 | method_supported = {} 30 | method_supported.update(rc4_md5.ciphers) 31 | method_supported.update(openssl.ciphers) 32 | method_supported.update(sodium.ciphers) 33 | method_supported.update(table.ciphers) 34 | 35 | 36 | def random_string(length): 37 | try: 38 | return os.urandom(length) 39 | except NotImplementedError as e: 40 | return openssl.rand_bytes(length) 41 | 42 | cached_keys = {} 43 | 44 | 45 | def try_cipher(key, method=None): 46 | Encryptor(key, method) 47 | 48 | 49 | def EVP_BytesToKey(password, key_len, iv_len): 50 | # equivalent to OpenSSL's EVP_BytesToKey() with count 1 51 | # so that we make the same key and iv as nodejs version 52 | if hasattr(password, 'encode'): 53 | password = password.encode('utf-8') 54 | cached_key = '%s-%d-%d' % (password, key_len, iv_len) 55 | r = cached_keys.get(cached_key, None) 56 | if r: 57 | return r 58 | m = [] 59 | i = 0 60 | while len(b''.join(m)) < (key_len + iv_len): 61 | md5 = hashlib.md5() 62 | data = password 63 | if i > 0: 64 | data = m[i - 1] + password 65 | md5.update(data) 66 | m.append(md5.digest()) 67 | i += 1 68 | ms = b''.join(m) 69 | key = ms[:key_len] 70 | iv = ms[key_len:key_len + iv_len] 71 | cached_keys[cached_key] = (key, iv) 72 | return key, iv 73 | 74 | 75 | class Encryptor(object): 76 | def __init__(self, key, method, iv = None): 77 | self.key = key 78 | self.method = method 79 | self.iv = None 80 | self.iv_sent = False 81 | self.cipher_iv = b'' 82 | self.iv_buf = b'' 83 | self.cipher_key = b'' 84 | self.decipher = None 85 | method = method.lower() 86 | self._method_info = self.get_method_info(method) 87 | if self._method_info: 88 | if iv is None or len(iv) != self._method_info[1]: 89 | self.cipher = self.get_cipher(key, method, 1, 90 | random_string(self._method_info[1])) 91 | else: 92 | self.cipher = self.get_cipher(key, method, 1, iv) 93 | else: 94 | logging.error('method %s not supported' % method) 95 | sys.exit(1) 96 | 97 | def get_method_info(self, method): 98 | method = method.lower() 99 | m = method_supported.get(method) 100 | return m 101 | 102 | def iv_len(self): 103 | return len(self.cipher_iv) 104 | 105 | def get_cipher(self, password, method, op, iv): 106 | password = common.to_bytes(password) 107 | m = self._method_info 108 | if m[0] > 0: 109 | key, iv_ = EVP_BytesToKey(password, m[0], m[1]) 110 | else: 111 | # key_length == 0 indicates we should use the key directly 112 | key, iv = password, b'' 113 | 114 | iv = iv[:m[1]] 115 | if op == 1: 116 | # this iv is for cipher not decipher 117 | self.cipher_iv = iv[:m[1]] 118 | self.cipher_key = key 119 | return m[2](method, key, iv, op) 120 | 121 | def encrypt(self, buf): 122 | if len(buf) == 0: 123 | return buf 124 | if self.iv_sent: 125 | return self.cipher.update(buf) 126 | else: 127 | self.iv_sent = True 128 | return self.cipher_iv + self.cipher.update(buf) 129 | 130 | def decrypt(self, buf): 131 | if len(buf) == 0: 132 | return buf 133 | if self.decipher is not None: #optimize 134 | return self.decipher.update(buf) 135 | 136 | decipher_iv_len = self._method_info[1] 137 | if len(self.iv_buf) <= decipher_iv_len: 138 | self.iv_buf += buf 139 | if len(self.iv_buf) > decipher_iv_len: 140 | decipher_iv = self.iv_buf[:decipher_iv_len] 141 | self.decipher = self.get_cipher(self.key, self.method, 0, 142 | iv=decipher_iv) 143 | buf = self.iv_buf[decipher_iv_len:] 144 | del self.iv_buf 145 | return self.decipher.update(buf) 146 | else: 147 | return b'' 148 | 149 | def encrypt_all(password, method, op, data): 150 | result = [] 151 | method = method.lower() 152 | (key_len, iv_len, m) = method_supported[method] 153 | if key_len > 0: 154 | key, _ = EVP_BytesToKey(password, key_len, iv_len) 155 | else: 156 | key = password 157 | if op: 158 | iv = random_string(iv_len) 159 | result.append(iv) 160 | else: 161 | iv = data[:iv_len] 162 | data = data[iv_len:] 163 | cipher = m(method, key, iv, op) 164 | result.append(cipher.update(data)) 165 | return b''.join(result) 166 | 167 | def encrypt_key(password, method): 168 | method = method.lower() 169 | (key_len, iv_len, m) = method_supported[method] 170 | if key_len > 0: 171 | key, _ = EVP_BytesToKey(password, key_len, iv_len) 172 | else: 173 | key = password 174 | return key 175 | 176 | def encrypt_iv_len(method): 177 | method = method.lower() 178 | (key_len, iv_len, m) = method_supported[method] 179 | return iv_len 180 | 181 | def encrypt_new_iv(method): 182 | method = method.lower() 183 | (key_len, iv_len, m) = method_supported[method] 184 | return random_string(iv_len) 185 | 186 | def encrypt_all_iv(key, method, op, data, ref_iv): 187 | result = [] 188 | method = method.lower() 189 | (key_len, iv_len, m) = method_supported[method] 190 | if op: 191 | iv = ref_iv[0] 192 | result.append(iv) 193 | else: 194 | iv = data[:iv_len] 195 | data = data[iv_len:] 196 | ref_iv[0] = iv 197 | cipher = m(method, key, iv, op) 198 | result.append(cipher.update(data)) 199 | return b''.join(result) 200 | 201 | 202 | CIPHERS_TO_TEST = [ 203 | 'aes-128-cfb', 204 | 'aes-256-cfb', 205 | 'rc4-md5', 206 | 'salsa20', 207 | 'chacha20', 208 | 'table', 209 | ] 210 | 211 | 212 | def test_encryptor(): 213 | from os import urandom 214 | plain = urandom(10240) 215 | for method in CIPHERS_TO_TEST: 216 | logging.warn(method) 217 | encryptor = Encryptor(b'key', method) 218 | decryptor = Encryptor(b'key', method) 219 | cipher = encryptor.encrypt(plain) 220 | plain2 = decryptor.decrypt(cipher) 221 | assert plain == plain2 222 | 223 | 224 | def test_encrypt_all(): 225 | from os import urandom 226 | plain = urandom(10240) 227 | for method in CIPHERS_TO_TEST: 228 | logging.warn(method) 229 | cipher = encrypt_all(b'key', method, 1, plain) 230 | plain2 = encrypt_all(b'key', method, 0, cipher) 231 | assert plain == plain2 232 | 233 | 234 | if __name__ == '__main__': 235 | test_encrypt_all() 236 | test_encryptor() 237 | -------------------------------------------------------------------------------- /shadowsocks/eventloop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2013-2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | # from ssloop 19 | # https://github.com/clowwindy/ssloop 20 | 21 | from __future__ import absolute_import, division, print_function, \ 22 | with_statement 23 | 24 | 25 | import errno 26 | import logging 27 | import os 28 | import select 29 | import socket 30 | import time 31 | from collections import defaultdict 32 | 33 | from shadowsocks import shell 34 | 35 | __all__ = ['EventLoop', 'POLL_NULL', 'POLL_IN', 'POLL_OUT', 'POLL_ERR', 36 | 'POLL_HUP', 'POLL_NVAL', 'EVENT_NAMES'] 37 | 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 | # we check timeouts every TIMEOUT_PRECISION seconds 57 | TIMEOUT_PRECISION = 2 58 | 59 | 60 | class KqueueLoop(object): 61 | 62 | MAX_EVENTS = 1024 63 | 64 | def __init__(self): 65 | self._kqueue = select.kqueue() 66 | self._fds = {} 67 | 68 | def _control(self, fd, mode, flags): 69 | events = [] 70 | if mode & POLL_IN: 71 | events.append(select.kevent(fd, select.KQ_FILTER_READ, flags)) 72 | if mode & POLL_OUT: 73 | events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags)) 74 | for e in events: 75 | self._kqueue.control([e], 0) 76 | 77 | def poll(self, timeout): 78 | if timeout < 0: 79 | timeout = None # kqueue behaviour 80 | events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout) 81 | results = defaultdict(lambda: POLL_NULL) 82 | for e in events: 83 | fd = e.ident 84 | if e.filter == select.KQ_FILTER_READ: 85 | results[fd] |= POLL_IN 86 | elif e.filter == select.KQ_FILTER_WRITE: 87 | results[fd] |= POLL_OUT 88 | return results.items() 89 | 90 | def register(self, fd, mode): 91 | self._fds[fd] = mode 92 | self._control(fd, mode, select.KQ_EV_ADD) 93 | 94 | def unregister(self, fd): 95 | self._control(fd, self._fds[fd], select.KQ_EV_DELETE) 96 | del self._fds[fd] 97 | 98 | def modify(self, fd, mode): 99 | self.unregister(fd) 100 | self.register(fd, mode) 101 | 102 | def close(self): 103 | self._kqueue.close() 104 | 105 | 106 | class SelectLoop(object): 107 | 108 | def __init__(self): 109 | self._r_list = set() 110 | self._w_list = set() 111 | self._x_list = set() 112 | 113 | def poll(self, timeout): 114 | r, w, x = select.select(self._r_list, self._w_list, self._x_list, 115 | timeout) 116 | results = defaultdict(lambda: POLL_NULL) 117 | for p in [(r, POLL_IN), (w, POLL_OUT), (x, POLL_ERR)]: 118 | for fd in p[0]: 119 | results[fd] |= p[1] 120 | return results.items() 121 | 122 | def register(self, fd, mode): 123 | if mode & POLL_IN: 124 | self._r_list.add(fd) 125 | if mode & POLL_OUT: 126 | self._w_list.add(fd) 127 | if mode & POLL_ERR: 128 | self._x_list.add(fd) 129 | 130 | def unregister(self, fd): 131 | if fd in self._r_list: 132 | self._r_list.remove(fd) 133 | if fd in self._w_list: 134 | self._w_list.remove(fd) 135 | if fd in self._x_list: 136 | self._x_list.remove(fd) 137 | 138 | def modify(self, fd, mode): 139 | self.unregister(fd) 140 | self.register(fd, mode) 141 | 142 | def close(self): 143 | pass 144 | 145 | 146 | class EventLoop(object): 147 | def __init__(self): 148 | if hasattr(select, 'epoll'): 149 | self._impl = select.epoll() 150 | model = 'epoll' 151 | elif hasattr(select, 'kqueue'): 152 | self._impl = KqueueLoop() 153 | model = 'kqueue' 154 | elif hasattr(select, 'select'): 155 | self._impl = SelectLoop() 156 | model = 'select' 157 | else: 158 | raise Exception('can not find any available functions in select ' 159 | 'package') 160 | self._fdmap = {} # (f, handler) 161 | self._last_time = time.time() 162 | self._periodic_callbacks = [] 163 | self._stopping = False 164 | logging.debug('using event model: %s', model) 165 | 166 | def poll(self, timeout=None): 167 | events = self._impl.poll(timeout) 168 | return [(self._fdmap[fd][0], fd, event) for fd, event in events] 169 | 170 | def add(self, f, mode, handler): 171 | fd = f.fileno() 172 | self._fdmap[fd] = (f, handler) 173 | self._impl.register(fd, mode) 174 | 175 | def remove(self, f): 176 | fd = f.fileno() 177 | del self._fdmap[fd] 178 | self._impl.unregister(fd) 179 | 180 | def removefd(self, fd): 181 | del self._fdmap[fd] 182 | self._impl.unregister(fd) 183 | 184 | def add_periodic(self, callback): 185 | self._periodic_callbacks.append(callback) 186 | 187 | def remove_periodic(self, callback): 188 | self._periodic_callbacks.remove(callback) 189 | 190 | def modify(self, f, mode): 191 | fd = f.fileno() 192 | self._impl.modify(fd, mode) 193 | 194 | def stop(self): 195 | self._stopping = True 196 | 197 | def run(self): 198 | events = [] 199 | while not self._stopping: 200 | asap = False 201 | try: 202 | events = self.poll(TIMEOUT_PRECISION) 203 | except (OSError, IOError) as e: 204 | if errno_from_exception(e) in (errno.EPIPE, errno.EINTR): 205 | # EPIPE: Happens when the client closes the connection 206 | # EINTR: Happens when received a signal 207 | # handles them as soon as possible 208 | asap = True 209 | logging.debug('poll:%s', e) 210 | else: 211 | logging.error('poll:%s', e) 212 | import traceback 213 | traceback.print_exc() 214 | continue 215 | 216 | handle = False 217 | for sock, fd, event in events: 218 | handler = self._fdmap.get(fd, None) 219 | if handler is not None: 220 | handler = handler[1] 221 | try: 222 | handle = handler.handle_event(sock, fd, event) or handle 223 | except (OSError, IOError) as e: 224 | shell.print_exception(e) 225 | now = time.time() 226 | if asap or now - self._last_time >= TIMEOUT_PRECISION: 227 | for callback in self._periodic_callbacks: 228 | callback() 229 | self._last_time = now 230 | if events and not handle: 231 | time.sleep(0.001) 232 | print("stop!") 233 | 234 | def __del__(self): 235 | self._impl.close() 236 | 237 | 238 | # from tornado 239 | def errno_from_exception(e): 240 | """Provides the errno from an Exception object. 241 | 242 | There are cases that the errno attribute was not set so we pull 243 | the errno out of the args but if someone instatiates an Exception 244 | without any args you will get a tuple error. So this function 245 | abstracts all that behavior to give you a safe way to get the 246 | errno. 247 | """ 248 | 249 | if hasattr(e, 'errno'): 250 | return e.errno 251 | elif e.args: 252 | return e.args[0] 253 | else: 254 | return None 255 | 256 | 257 | # from tornado 258 | def get_sock_error(sock): 259 | error_number = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) 260 | return socket.error(error_number, os.strerror(error_number)) 261 | -------------------------------------------------------------------------------- /shadowsocks/local.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2012-2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import sys 22 | import os 23 | import logging 24 | import signal 25 | 26 | if __name__ == '__main__': 27 | import inspect 28 | file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) 29 | sys.path.insert(0, os.path.join(file_path, '../')) 30 | 31 | from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns 32 | 33 | 34 | def main(): 35 | shell.check_python() 36 | 37 | # fix py2exe 38 | if hasattr(sys, "frozen") and sys.frozen in \ 39 | ("windows_exe", "console_exe"): 40 | p = os.path.dirname(os.path.abspath(sys.executable)) 41 | os.chdir(p) 42 | 43 | config = shell.get_config(True) 44 | 45 | if not config.get('dns_ipv6', False): 46 | asyncdns.IPV6_CONNECTION_SUPPORT = False 47 | 48 | daemon.daemon_exec(config) 49 | logging.info("local start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" % 50 | (config['protocol'], config['password'], config['method'], config['obfs'], config['obfs_param'])) 51 | 52 | try: 53 | logging.info("starting local at %s:%d" % 54 | (config['local_address'], config['local_port'])) 55 | 56 | dns_resolver = asyncdns.DNSResolver() 57 | tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) 58 | udp_server = udprelay.UDPRelay(config, dns_resolver, True) 59 | loop = eventloop.EventLoop() 60 | dns_resolver.add_to_loop(loop) 61 | tcp_server.add_to_loop(loop) 62 | udp_server.add_to_loop(loop) 63 | 64 | def handler(signum, _): 65 | logging.warn('received SIGQUIT, doing graceful shutting down..') 66 | tcp_server.close(next_tick=True) 67 | udp_server.close(next_tick=True) 68 | signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) 69 | 70 | def int_handler(signum, _): 71 | sys.exit(1) 72 | signal.signal(signal.SIGINT, int_handler) 73 | 74 | daemon.set_user(config.get('user', None)) 75 | loop.run() 76 | except Exception as e: 77 | shell.print_exception(e) 78 | sys.exit(1) 79 | 80 | if __name__ == '__main__': 81 | main() 82 | -------------------------------------------------------------------------------- /shadowsocks/lru_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import collections 22 | import logging 23 | import time 24 | 25 | if __name__ == '__main__': 26 | import os, sys, inspect 27 | file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) 28 | sys.path.insert(0, os.path.join(file_path, '../')) 29 | 30 | try: 31 | from collections import OrderedDict 32 | print("loaded collections.OrderedDict") 33 | except: 34 | from shadowsocks.ordereddict import OrderedDict 35 | 36 | # this LRUCache is optimized for concurrency, not QPS 37 | # n: concurrency, keys stored in the cache 38 | # m: visits not timed out, proportional to QPS * timeout 39 | # get & set is O(1), not O(n). thus we can support very large n 40 | # sweep is O((n - m)) or O(1024) at most, 41 | # no metter how large the cache or timeout value is 42 | 43 | SWEEP_MAX_ITEMS = 1024 44 | 45 | class LRUCache(collections.MutableMapping): 46 | """This class is not thread safe""" 47 | 48 | def __init__(self, timeout=60, close_callback=None, *args, **kwargs): 49 | self.timeout = timeout 50 | self.close_callback = close_callback 51 | self._store = {} 52 | self._keys_to_last_time = OrderedDict() 53 | self.update(dict(*args, **kwargs)) # use the free update to set keys 54 | 55 | def __getitem__(self, key): 56 | # O(1) 57 | t = time.time() 58 | last_t = self._keys_to_last_time[key] 59 | del self._keys_to_last_time[key] 60 | self._keys_to_last_time[key] = t 61 | return self._store[key] 62 | 63 | def __setitem__(self, key, value): 64 | # O(1) 65 | t = time.time() 66 | if key in self._keys_to_last_time: 67 | del self._keys_to_last_time[key] 68 | self._keys_to_last_time[key] = t 69 | self._store[key] = value 70 | 71 | def __delitem__(self, key): 72 | # O(1) 73 | last_t = self._keys_to_last_time[key] 74 | del self._store[key] 75 | del self._keys_to_last_time[key] 76 | 77 | def __contains__(self, key): 78 | return key in self._store 79 | 80 | def __iter__(self): 81 | return iter(self._store) 82 | 83 | def __len__(self): 84 | return len(self._store) 85 | 86 | def first(self): 87 | if len(self._keys_to_last_time) > 0: 88 | for key in self._keys_to_last_time: 89 | return key 90 | 91 | def sweep(self, sweep_item_cnt = SWEEP_MAX_ITEMS): 92 | # O(n - m) 93 | now = time.time() 94 | c = 0 95 | while c < sweep_item_cnt: 96 | if len(self._keys_to_last_time) == 0: 97 | break 98 | for key in self._keys_to_last_time: 99 | break 100 | last_t = self._keys_to_last_time[key] 101 | if now - last_t <= self.timeout: 102 | break 103 | value = self._store[key] 104 | del self._store[key] 105 | del self._keys_to_last_time[key] 106 | if self.close_callback is not None: 107 | self.close_callback(value) 108 | c += 1 109 | if c: 110 | logging.debug('%d keys swept' % c) 111 | return c < SWEEP_MAX_ITEMS 112 | 113 | def clear(self, keep): 114 | now = time.time() 115 | c = 0 116 | while len(self._keys_to_last_time) > keep: 117 | if len(self._keys_to_last_time) == 0: 118 | break 119 | for key in self._keys_to_last_time: 120 | break 121 | last_t = self._keys_to_last_time[key] 122 | value = self._store[key] 123 | if self.close_callback is not None: 124 | self.close_callback(value) 125 | del self._store[key] 126 | del self._keys_to_last_time[key] 127 | c += 1 128 | if c: 129 | logging.debug('%d keys swept' % c) 130 | return c < SWEEP_MAX_ITEMS 131 | 132 | def test(): 133 | c = LRUCache(timeout=0.3) 134 | 135 | c['a'] = 1 136 | assert c['a'] == 1 137 | c['a'] = 1 138 | 139 | time.sleep(0.5) 140 | c.sweep() 141 | assert 'a' not in c 142 | 143 | c['a'] = 2 144 | c['b'] = 3 145 | time.sleep(0.2) 146 | c.sweep() 147 | assert c['a'] == 2 148 | assert c['b'] == 3 149 | 150 | time.sleep(0.2) 151 | c.sweep() 152 | c['b'] 153 | time.sleep(0.2) 154 | c.sweep() 155 | assert 'a' not in c 156 | assert c['b'] == 3 157 | 158 | time.sleep(0.5) 159 | c.sweep() 160 | assert 'a' not in c 161 | assert 'b' not in c 162 | 163 | global close_cb_called 164 | close_cb_called = False 165 | 166 | def close_cb(t): 167 | global close_cb_called 168 | assert not close_cb_called 169 | close_cb_called = True 170 | 171 | c = LRUCache(timeout=0.1, close_callback=close_cb) 172 | c['s'] = 1 173 | c['s'] 174 | time.sleep(0.1) 175 | c['s'] 176 | time.sleep(0.3) 177 | c.sweep() 178 | 179 | if __name__ == '__main__': 180 | test() 181 | -------------------------------------------------------------------------------- /shadowsocks/obfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2015 breakwa11 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | 25 | from shadowsocks import common 26 | from shadowsocks.obfsplugin import plain, http_simple, obfs_tls, verify, auth, auth_chain 27 | 28 | 29 | method_supported = {} 30 | method_supported.update(plain.obfs_map) 31 | method_supported.update(http_simple.obfs_map) 32 | method_supported.update(obfs_tls.obfs_map) 33 | method_supported.update(verify.obfs_map) 34 | method_supported.update(auth.obfs_map) 35 | method_supported.update(auth_chain.obfs_map) 36 | 37 | def mu_protocol(): 38 | return ["auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a"] 39 | 40 | class server_info(object): 41 | def __init__(self, data): 42 | self.data = data 43 | 44 | class obfs(object): 45 | def __init__(self, method): 46 | method = common.to_str(method) 47 | self.method = method 48 | self._method_info = self.get_method_info(method) 49 | if self._method_info: 50 | self.obfs = self.get_obfs(method) 51 | else: 52 | raise Exception('obfs plugin [%s] not supported' % method) 53 | 54 | def init_data(self): 55 | return self.obfs.init_data() 56 | 57 | def set_server_info(self, server_info): 58 | return self.obfs.set_server_info(server_info) 59 | 60 | def get_server_info(self): 61 | return self.obfs.get_server_info() 62 | 63 | def get_method_info(self, method): 64 | method = method.lower() 65 | m = method_supported.get(method) 66 | return m 67 | 68 | def get_obfs(self, method): 69 | m = self._method_info 70 | return m[0](method) 71 | 72 | def get_overhead(self, direction): 73 | return self.obfs.get_overhead(direction) 74 | 75 | def client_pre_encrypt(self, buf): 76 | return self.obfs.client_pre_encrypt(buf) 77 | 78 | def client_encode(self, buf): 79 | return self.obfs.client_encode(buf) 80 | 81 | def client_decode(self, buf): 82 | return self.obfs.client_decode(buf) 83 | 84 | def client_post_decrypt(self, buf): 85 | return self.obfs.client_post_decrypt(buf) 86 | 87 | def server_pre_encrypt(self, buf): 88 | return self.obfs.server_pre_encrypt(buf) 89 | 90 | def server_encode(self, buf): 91 | return self.obfs.server_encode(buf) 92 | 93 | def server_decode(self, buf): 94 | return self.obfs.server_decode(buf) 95 | 96 | def server_post_decrypt(self, buf): 97 | return self.obfs.server_post_decrypt(buf) 98 | 99 | def client_udp_pre_encrypt(self, buf): 100 | return self.obfs.client_udp_pre_encrypt(buf) 101 | 102 | def client_udp_post_decrypt(self, buf): 103 | return self.obfs.client_udp_post_decrypt(buf) 104 | 105 | def server_udp_pre_encrypt(self, buf, uid): 106 | return self.obfs.server_udp_pre_encrypt(buf, uid) 107 | 108 | def server_udp_post_decrypt(self, buf): 109 | return self.obfs.server_udp_post_decrypt(buf) 110 | 111 | def dispose(self): 112 | self.obfs.dispose() 113 | del self.obfs 114 | 115 | -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 clowwindy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/obfsplugin/__init__.pyc -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/auth.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/obfsplugin/auth.pyc -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/auth_chain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2015 breakwa11 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | import binascii 25 | import base64 26 | import time 27 | import datetime 28 | import random 29 | import math 30 | import struct 31 | import zlib 32 | import hmac 33 | import hashlib 34 | 35 | import shadowsocks 36 | from shadowsocks import common, lru_cache, encrypt 37 | from shadowsocks.obfsplugin import plain 38 | from shadowsocks.common import to_bytes, to_str, ord, chr 39 | 40 | def create_auth_chain_a(method): 41 | return auth_chain_a(method) 42 | 43 | 44 | obfs_map = { 45 | 'auth_chain_a': (create_auth_chain_a,), 46 | } 47 | 48 | class xorshift128plus(object): 49 | max_int = (1 << 64) - 1 50 | mov_mask = (1 << (64 - 23)) - 1 51 | 52 | def __init__(self): 53 | self.v0 = 0 54 | self.v1 = 0 55 | 56 | def next(self): 57 | x = self.v0 58 | y = self.v1 59 | self.v0 = y 60 | x ^= ((x & xorshift128plus.mov_mask) << 23) 61 | x ^= (y ^ (x >> 17) ^ (y >> 26)) & xorshift128plus.max_int 62 | self.v1 = x 63 | return (x + y) & xorshift128plus.max_int 64 | 65 | def init_from_bin(self, bin): 66 | bin += b'\0' * 16 67 | self.v0 = struct.unpack('= len(str2): 81 | if str1[:len(str2)] == str2: 82 | return True 83 | return False 84 | 85 | class auth_base(plain.plain): 86 | def __init__(self, method): 87 | super(auth_base, self).__init__(method) 88 | self.method = method 89 | self.no_compatible_method = '' 90 | self.overhead = 4 91 | 92 | def init_data(self): 93 | return '' 94 | 95 | def get_overhead(self, direction): # direction: true for c->s false for s->c 96 | return self.overhead 97 | 98 | def set_server_info(self, server_info): 99 | self.server_info = server_info 100 | 101 | def client_encode(self, buf): 102 | return buf 103 | 104 | def client_decode(self, buf): 105 | return (buf, False) 106 | 107 | def server_encode(self, buf): 108 | return buf 109 | 110 | def server_decode(self, buf): 111 | return (buf, True, False) 112 | 113 | def not_match_return(self, buf): 114 | self.raw_trans = True 115 | self.overhead = 0 116 | if self.method == self.no_compatible_method: 117 | return (b'E'*2048, False) 118 | return (buf, False) 119 | 120 | class client_queue(object): 121 | def __init__(self, begin_id): 122 | self.front = begin_id - 64 123 | self.back = begin_id + 1 124 | self.alloc = {} 125 | self.enable = True 126 | self.last_update = time.time() 127 | self.ref = 0 128 | 129 | def update(self): 130 | self.last_update = time.time() 131 | 132 | def addref(self): 133 | self.ref += 1 134 | 135 | def delref(self): 136 | if self.ref > 0: 137 | self.ref -= 1 138 | 139 | def is_active(self): 140 | return (self.ref > 0) and (time.time() - self.last_update < 60 * 10) 141 | 142 | def re_enable(self, connection_id): 143 | self.enable = True 144 | self.front = connection_id - 64 145 | self.back = connection_id + 1 146 | self.alloc = {} 147 | 148 | def insert(self, connection_id): 149 | if not self.enable: 150 | logging.warn('obfs auth: not enable') 151 | return False 152 | if not self.is_active(): 153 | self.re_enable(connection_id) 154 | self.update() 155 | if connection_id < self.front: 156 | logging.warn('obfs auth: deprecated id, someone replay attack') 157 | return False 158 | if connection_id > self.front + 0x4000: 159 | logging.warn('obfs auth: wrong id') 160 | return False 161 | if connection_id in self.alloc: 162 | logging.warn('obfs auth: duplicate id, someone replay attack') 163 | return False 164 | if self.back <= connection_id: 165 | self.back = connection_id + 1 166 | self.alloc[connection_id] = 1 167 | while (self.front in self.alloc) or self.front + 0x1000 < self.back: 168 | if self.front in self.alloc: 169 | del self.alloc[self.front] 170 | self.front += 1 171 | self.addref() 172 | return True 173 | 174 | class obfs_auth_chain_data(object): 175 | def __init__(self, name): 176 | self.name = name 177 | self.user_id = {} 178 | self.local_client_id = b'' 179 | self.connection_id = 0 180 | self.set_max_client(64) # max active client count 181 | 182 | def update(self, user_id, client_id, connection_id): 183 | if user_id not in self.user_id: 184 | self.user_id[user_id] = lru_cache.LRUCache() 185 | local_client_id = self.user_id[user_id] 186 | 187 | if client_id in local_client_id: 188 | local_client_id[client_id].update() 189 | 190 | def set_max_client(self, max_client): 191 | self.max_client = max_client 192 | self.max_buffer = max(self.max_client * 2, 1024) 193 | 194 | def insert(self, user_id, client_id, connection_id): 195 | if user_id not in self.user_id: 196 | self.user_id[user_id] = lru_cache.LRUCache() 197 | local_client_id = self.user_id[user_id] 198 | 199 | if local_client_id.get(client_id, None) is None or not local_client_id[client_id].enable: 200 | if local_client_id.first() is None or len(local_client_id) < self.max_client: 201 | if client_id not in local_client_id: 202 | #TODO: check 203 | local_client_id[client_id] = client_queue(connection_id) 204 | else: 205 | local_client_id[client_id].re_enable(connection_id) 206 | return local_client_id[client_id].insert(connection_id) 207 | 208 | if not local_client_id[local_client_id.first()].is_active(): 209 | del local_client_id[local_client_id.first()] 210 | if client_id not in local_client_id: 211 | #TODO: check 212 | local_client_id[client_id] = client_queue(connection_id) 213 | else: 214 | local_client_id[client_id].re_enable(connection_id) 215 | return local_client_id[client_id].insert(connection_id) 216 | 217 | logging.warn(self.name + ': no inactive client') 218 | return False 219 | else: 220 | return local_client_id[client_id].insert(connection_id) 221 | 222 | def remove(self, user_id, client_id): 223 | if user_id in self.user_id: 224 | local_client_id = self.user_id[user_id] 225 | if client_id in local_client_id: 226 | local_client_id[client_id].delref() 227 | 228 | class auth_chain_a(auth_base): 229 | def __init__(self, method): 230 | super(auth_chain_a, self).__init__(method) 231 | self.hashfunc = hashlib.md5 232 | self.recv_buf = b'' 233 | self.unit_len = 2800 234 | self.raw_trans = False 235 | self.has_sent_header = False 236 | self.has_recv_header = False 237 | self.client_id = 0 238 | self.connection_id = 0 239 | self.max_time_dif = 60 * 60 * 24 # time dif (second) setting 240 | self.salt = b"auth_chain_a" 241 | self.no_compatible_method = 'auth_chain_a' 242 | self.pack_id = 1 243 | self.recv_id = 1 244 | self.user_id = None 245 | self.user_id_num = 0 246 | self.user_key = None 247 | self.overhead = 4 248 | self.client_over_head = 4 249 | self.last_client_hash = b'' 250 | self.last_server_hash = b'' 251 | self.random_client = xorshift128plus() 252 | self.random_server = xorshift128plus() 253 | self.encryptor = None 254 | 255 | def init_data(self): 256 | return obfs_auth_chain_data(self.method) 257 | 258 | def get_overhead(self, direction): # direction: true for c->s false for s->c 259 | return self.overhead 260 | 261 | def set_server_info(self, server_info): 262 | self.server_info = server_info 263 | try: 264 | max_client = int(server_info.protocol_param.split('#')[0]) 265 | except: 266 | max_client = 64 267 | self.server_info.data.set_max_client(max_client) 268 | 269 | def trapezoid_random_float(self, d): 270 | if d == 0: 271 | return random.random() 272 | s = random.random() 273 | a = 1 - d 274 | return (math.sqrt(a * a + 4 * d * s) - a) / (2 * d) 275 | 276 | def trapezoid_random_int(self, max_val, d): 277 | v = self.trapezoid_random_float(d) 278 | return int(v * max_val) 279 | 280 | def rnd_data_len(self, buf_size, last_hash, random): 281 | if buf_size > 1440: 282 | return 0 283 | random.init_from_bin_len(last_hash, buf_size) 284 | if buf_size > 1300: 285 | return random.next() % 31 286 | if buf_size > 900: 287 | return random.next() % 127 288 | if buf_size > 400: 289 | return random.next() % 521 290 | return random.next() % 1021 291 | 292 | def udp_rnd_data_len(self, last_hash, random): 293 | random.init_from_bin(last_hash) 294 | return random.next() % 127 295 | 296 | def rnd_start_pos(self, rand_len, random): 297 | if rand_len > 0: 298 | return random.next() % 8589934609 % rand_len 299 | return 0 300 | 301 | def rnd_data(self, buf_size, buf, last_hash, random): 302 | rand_len = self.rnd_data_len(buf_size, last_hash, random) 303 | 304 | rnd_data_buf = os.urandom(rand_len) 305 | 306 | if buf_size == 0: 307 | return rnd_data_buf 308 | else: 309 | if rand_len > 0: 310 | start_pos = self.rnd_start_pos(rand_len, random) 311 | return rnd_data_buf[:start_pos] + buf + rnd_data_buf[start_pos:] 312 | else: 313 | return buf 314 | 315 | def pack_client_data(self, buf): 316 | buf = self.encryptor.encrypt(buf) 317 | data = self.rnd_data(len(buf), buf, self.last_client_hash, self.random_client) 318 | data_len = len(data) + 8 319 | mac_key = self.user_key + struct.pack(' 0xFF000000: 374 | self.server_info.data.local_client_id = b'' 375 | if not self.server_info.data.local_client_id: 376 | self.server_info.data.local_client_id = os.urandom(4) 377 | logging.debug("local_client_id %s" % (binascii.hexlify(self.server_info.data.local_client_id),)) 378 | self.server_info.data.connection_id = struct.unpack(' self.unit_len: 394 | ret += self.pack_client_data(buf[:self.unit_len]) 395 | buf = buf[self.unit_len:] 396 | ret += self.pack_client_data(buf) 397 | return ret 398 | 399 | def client_post_decrypt(self, buf): 400 | if self.raw_trans: 401 | return buf 402 | self.recv_buf += buf 403 | out_buf = b'' 404 | while len(self.recv_buf) > 4: 405 | mac_key = self.user_key + struct.pack('= 4096: 410 | self.raw_trans = True 411 | self.recv_buf = b'' 412 | raise Exception('client_post_decrypt data error') 413 | 414 | if length + 4 > len(self.recv_buf): 415 | break 416 | 417 | server_hash = hmac.new(mac_key, self.recv_buf[:length + 2], self.hashfunc).digest() 418 | if server_hash[:2] != self.recv_buf[length + 2 : length + 4]: 419 | logging.info('%s: checksum error, data %s' % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length]))) 420 | self.raw_trans = True 421 | self.recv_buf = b'' 422 | raise Exception('client_post_decrypt data uncorrect checksum') 423 | 424 | pos = 2 425 | if data_len > 0 and rand_len > 0: 426 | pos = 2 + self.rnd_start_pos(rand_len, self.random_server) 427 | out_buf += self.encryptor.decrypt(self.recv_buf[pos : data_len + pos]) 428 | self.last_server_hash = server_hash 429 | if self.recv_id == 1: 430 | self.server_info.tcp_mss = struct.unpack(' self.unit_len: 447 | ret += self.pack_server_data(buf[:self.unit_len]) 448 | buf = buf[self.unit_len:] 449 | ret += self.pack_server_data(buf) 450 | return ret 451 | 452 | def server_post_decrypt(self, buf): 453 | if self.raw_trans: 454 | return (buf, False) 455 | self.recv_buf += buf 456 | out_buf = b'' 457 | sendback = False 458 | 459 | if not self.has_recv_header: 460 | if len(self.recv_buf) >= 12 or len(self.recv_buf) in [7, 8]: 461 | recv_len = min(len(self.recv_buf), 12) 462 | mac_key = self.server_info.recv_iv + self.server_info.key 463 | md5data = hmac.new(mac_key, self.recv_buf[:4], self.hashfunc).digest() 464 | if md5data[:recv_len - 4] != self.recv_buf[4:recv_len]: 465 | return self.not_match_return(self.recv_buf) 466 | 467 | if len(self.recv_buf) < 12 + 24: 468 | return (b'', False) 469 | 470 | self.last_client_hash = md5data 471 | uid = struct.unpack(' self.max_time_dif: 502 | logging.info('%s: wrong timestamp, time_dif %d, data %s' % (self.no_compatible_method, time_dif, binascii.hexlify(head))) 503 | return self.not_match_return(self.recv_buf) 504 | elif self.server_info.data.insert(self.user_id, client_id, connection_id): 505 | self.has_recv_header = True 506 | self.client_id = client_id 507 | self.connection_id = connection_id 508 | else: 509 | logging.info('%s: auth fail, data %s' % (self.no_compatible_method, binascii.hexlify(out_buf))) 510 | return self.not_match_return(self.recv_buf) 511 | 512 | self.encryptor = encrypt.Encryptor(to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(self.last_client_hash)), 'rc4') 513 | self.recv_buf = self.recv_buf[36:] 514 | self.has_recv_header = True 515 | sendback = True 516 | 517 | while len(self.recv_buf) > 4: 518 | mac_key = self.user_key + struct.pack('= 4096: 523 | self.raw_trans = True 524 | self.recv_buf = b'' 525 | if self.recv_id == 0: 526 | logging.info(self.no_compatible_method + ': over size') 527 | return (b'E'*2048, False) 528 | else: 529 | raise Exception('server_post_decrype data error') 530 | 531 | if length + 4 > len(self.recv_buf): 532 | break 533 | 534 | client_hash = hmac.new(mac_key, self.recv_buf[:length + 2], self.hashfunc).digest() 535 | if client_hash[:2] != self.recv_buf[length + 2 : length + 4]: 536 | logging.info('%s: checksum error, data %s' % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length]))) 537 | self.raw_trans = True 538 | self.recv_buf = b'' 539 | if self.recv_id == 0: 540 | return (b'E'*2048, False) 541 | else: 542 | raise Exception('server_post_decrype data uncorrect checksum') 543 | 544 | self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF 545 | pos = 2 546 | if data_len > 0 and rand_len > 0: 547 | pos = 2 + self.rnd_start_pos(rand_len, self.random_client) 548 | out_buf += self.encryptor.decrypt(self.recv_buf[pos : data_len + pos]) 549 | self.last_client_hash = client_hash 550 | self.recv_buf = self.recv_buf[length + 4:] 551 | if data_len == 0: 552 | sendback = True 553 | 554 | if out_buf: 555 | self.server_info.data.update(self.user_id, self.client_id, self.connection_id) 556 | return (out_buf, sendback) 557 | 558 | def client_udp_pre_encrypt(self, buf): 559 | if self.user_key is None: 560 | if b':' in to_bytes(self.server_info.protocol_param): 561 | try: 562 | items = to_bytes(self.server_info.protocol_param).split(':') 563 | self.user_key = self.hashfunc(items[1]).digest() 564 | self.user_id = struct.pack('= len(str2): 54 | if str1[:len(str2)] == str2: 55 | return True 56 | return False 57 | 58 | class http_simple(plain.plain): 59 | def __init__(self, method): 60 | self.method = method 61 | self.has_sent_header = False 62 | self.has_recv_header = False 63 | self.host = None 64 | self.port = 0 65 | self.recv_buffer = b'' 66 | self.user_agent = [b"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0", 67 | b"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0", 68 | b"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", 69 | b"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36", 70 | b"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0", 71 | b"Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)", 72 | b"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", 73 | b"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)", 74 | b"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko", 75 | b"Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/BuildID) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36", 76 | b"Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3", 77 | b"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"] 78 | 79 | def encode_head(self, buf): 80 | hexstr = binascii.hexlify(buf) 81 | chs = [] 82 | for i in range(0, len(hexstr), 2): 83 | chs.append(b"%" + hexstr[i:i+2]) 84 | return b''.join(chs) 85 | 86 | def client_encode(self, buf): 87 | if self.has_sent_header: 88 | return buf 89 | head_size = len(self.server_info.iv) + self.server_info.head_len 90 | if len(buf) - head_size > 64: 91 | headlen = head_size + random.randint(0, 64) 92 | else: 93 | headlen = len(buf) 94 | headdata = buf[:headlen] 95 | buf = buf[headlen:] 96 | port = b'' 97 | if self.server_info.port != 80: 98 | port = b':' + to_bytes(str(self.server_info.port)) 99 | body = None 100 | hosts = (self.server_info.obfs_param or self.server_info.host) 101 | pos = hosts.find("#") 102 | if pos >= 0: 103 | body = hosts[pos + 1:].replace("\n", "\r\n") 104 | body = body.replace("\\n", "\r\n") 105 | hosts = hosts[:pos] 106 | hosts = hosts.split(',') 107 | host = random.choice(hosts) 108 | http_head = b"GET /" + self.encode_head(headdata) + b" HTTP/1.1\r\n" 109 | http_head += b"Host: " + to_bytes(host) + port + b"\r\n" 110 | if body: 111 | http_head += body + "\r\n\r\n" 112 | else: 113 | http_head += b"User-Agent: " + random.choice(self.user_agent) + b"\r\n" 114 | http_head += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\nDNT: 1\r\nConnection: keep-alive\r\n\r\n" 115 | self.has_sent_header = True 116 | return http_head + buf 117 | 118 | def client_decode(self, buf): 119 | if self.has_recv_header: 120 | return (buf, False) 121 | pos = buf.find(b'\r\n\r\n') 122 | if pos >= 0: 123 | self.has_recv_header = True 124 | return (buf[pos + 4:], False) 125 | else: 126 | return (b'', False) 127 | 128 | def server_encode(self, buf): 129 | if self.has_sent_header: 130 | return buf 131 | 132 | header = b'HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Encoding: gzip\r\nContent-Type: text/html\r\nDate: ' 133 | header += to_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')) 134 | header += b'\r\nServer: nginx\r\nVary: Accept-Encoding\r\n\r\n' 135 | self.has_sent_header = True 136 | return header + buf 137 | 138 | def get_data_from_http_header(self, buf): 139 | ret_buf = b'' 140 | lines = buf.split(b'\r\n') 141 | if lines and len(lines) > 1: 142 | hex_items = lines[0].split(b'%') 143 | if hex_items and len(hex_items) > 1: 144 | for index in range(1, len(hex_items)): 145 | if len(hex_items[index]) < 2: 146 | ret_buf += binascii.unhexlify('0' + hex_items[index]) 147 | break 148 | elif len(hex_items[index]) > 2: 149 | ret_buf += binascii.unhexlify(hex_items[index][:2]) 150 | break 151 | else: 152 | ret_buf += binascii.unhexlify(hex_items[index]) 153 | return ret_buf 154 | return b'' 155 | 156 | def get_host_from_http_header(self, buf): 157 | ret_buf = b'' 158 | lines = buf.split(b'\r\n') 159 | if lines and len(lines) > 1: 160 | for line in lines: 161 | if match_begin(line, b"Host: "): 162 | return common.to_str(line[6:]) 163 | 164 | def not_match_return(self, buf): 165 | self.has_sent_header = True 166 | self.has_recv_header = True 167 | if self.method == 'http_simple': 168 | return (b'E'*2048, False, False) 169 | return (buf, True, False) 170 | 171 | def error_return(self, buf): 172 | self.has_sent_header = True 173 | self.has_recv_header = True 174 | return (b'E'*2048, False, False) 175 | 176 | def server_decode(self, buf): 177 | if self.has_recv_header: 178 | return (buf, True, False) 179 | 180 | self.recv_buffer += buf 181 | buf = self.recv_buffer 182 | if len(buf) > 10: 183 | if match_begin(buf, b'GET ') or match_begin(buf, b'POST '): 184 | if len(buf) > 65536: 185 | self.recv_buffer = None 186 | logging.warn('http_simple: over size') 187 | return self.not_match_return(buf) 188 | else: #not http header, run on original protocol 189 | self.recv_buffer = None 190 | logging.debug('http_simple: not match begin') 191 | return self.not_match_return(buf) 192 | else: 193 | return (b'', True, False) 194 | 195 | if b'\r\n\r\n' in buf: 196 | datas = buf.split(b'\r\n\r\n', 1) 197 | ret_buf = self.get_data_from_http_header(buf) 198 | host = self.get_host_from_http_header(buf) 199 | if host and self.server_info.obfs_param: 200 | pos = host.find(":") 201 | if pos >= 0: 202 | host = host[:pos] 203 | hosts = self.server_info.obfs_param.split(',') 204 | if host not in hosts: 205 | return self.not_match_return(buf) 206 | if len(ret_buf) < 4: 207 | return self.error_return(buf) 208 | if len(datas) > 1: 209 | ret_buf += datas[1] 210 | if len(ret_buf) >= 13: 211 | self.has_recv_header = True 212 | return (ret_buf, True, False) 213 | return self.not_match_return(buf) 214 | else: 215 | return (b'', True, False) 216 | 217 | class http_post(http_simple): 218 | def __init__(self, method): 219 | super(http_post, self).__init__(method) 220 | 221 | def boundary(self): 222 | return to_bytes(''.join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") for i in range(32)])) 223 | 224 | def client_encode(self, buf): 225 | if self.has_sent_header: 226 | return buf 227 | head_size = len(self.server_info.iv) + self.server_info.head_len 228 | if len(buf) - head_size > 64: 229 | headlen = head_size + random.randint(0, 64) 230 | else: 231 | headlen = len(buf) 232 | headdata = buf[:headlen] 233 | buf = buf[headlen:] 234 | port = b'' 235 | if self.server_info.port != 80: 236 | port = b':' + to_bytes(str(self.server_info.port)) 237 | body = None 238 | hosts = (self.server_info.obfs_param or self.server_info.host) 239 | pos = hosts.find("#") 240 | if pos >= 0: 241 | body = hosts[pos + 1:].replace("\\n", "\r\n") 242 | hosts = hosts[:pos] 243 | hosts = hosts.split(',') 244 | host = random.choice(hosts) 245 | http_head = b"POST /" + self.encode_head(headdata) + b" HTTP/1.1\r\n" 246 | http_head += b"Host: " + to_bytes(host) + port + b"\r\n" 247 | if body: 248 | http_head += body + "\r\n\r\n" 249 | else: 250 | http_head += b"User-Agent: " + random.choice(self.user_agent) + b"\r\n" 251 | http_head += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n" 252 | http_head += b"Content-Type: multipart/form-data; boundary=" + self.boundary() + b"\r\nDNT: 1\r\n" 253 | http_head += b"Connection: keep-alive\r\n\r\n" 254 | self.has_sent_header = True 255 | return http_head + buf 256 | 257 | def not_match_return(self, buf): 258 | self.has_sent_header = True 259 | self.has_recv_header = True 260 | if self.method == 'http_post': 261 | return (b'E'*2048, False, False) 262 | return (buf, True, False) 263 | 264 | class random_head(plain.plain): 265 | def __init__(self, method): 266 | self.method = method 267 | self.has_sent_header = False 268 | self.has_recv_header = False 269 | self.raw_trans_sent = False 270 | self.raw_trans_recv = False 271 | self.send_buffer = b'' 272 | 273 | def client_encode(self, buf): 274 | if self.raw_trans_sent: 275 | return buf 276 | self.send_buffer += buf 277 | if not self.has_sent_header: 278 | self.has_sent_header = True 279 | data = os.urandom(common.ord(os.urandom(1)[0]) % 96 + 4) 280 | crc = (0xffffffff - binascii.crc32(data)) & 0xffffffff 281 | return data + struct.pack('= len(str2): 50 | if str1[:len(str2)] == str2: 51 | return True 52 | return False 53 | 54 | class obfs_auth_data(object): 55 | def __init__(self): 56 | self.client_data = lru_cache.LRUCache(60 * 5) 57 | self.client_id = os.urandom(32) 58 | self.startup_time = int(time.time() - 60 * 30) & 0xFFFFFFFF 59 | self.ticket_buf = {} 60 | 61 | class tls_ticket_auth(plain.plain): 62 | def __init__(self, method): 63 | self.method = method 64 | self.handshake_status = 0 65 | self.send_buffer = b'' 66 | self.recv_buffer = b'' 67 | self.client_id = b'' 68 | self.max_time_dif = 60 * 60 * 24 # time dif (second) setting 69 | self.tls_version = b'\x03\x03' 70 | self.overhead = 5 71 | 72 | def init_data(self): 73 | return obfs_auth_data() 74 | 75 | def get_overhead(self, direction): # direction: true for c->s false for s->c 76 | return self.overhead 77 | 78 | def sni(self, url): 79 | url = common.to_bytes(url) 80 | data = b"\x00" + struct.pack('>H', len(url)) + url 81 | data = b"\x00\x00" + struct.pack('>H', len(data) + 2) + struct.pack('>H', len(data)) + data 82 | return data 83 | 84 | def pack_auth_data(self, client_id): 85 | utc_time = int(time.time()) & 0xFFFFFFFF 86 | data = struct.pack('>I', utc_time) + os.urandom(18) 87 | data += hmac.new(self.server_info.key + client_id, data, hashlib.sha1).digest()[:10] 88 | return data 89 | 90 | def client_encode(self, buf): 91 | if self.handshake_status == -1: 92 | return buf 93 | if self.handshake_status == 8: 94 | ret = b'' 95 | while len(buf) > 2048: 96 | size = min(struct.unpack('>H', os.urandom(2))[0] % 4096 + 100, len(buf)) 97 | ret += b"\x17" + self.tls_version + struct.pack('>H', size) + buf[:size] 98 | buf = buf[size:] 99 | if len(buf) > 0: 100 | ret += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf 101 | return ret 102 | if len(buf) > 0: 103 | self.send_buffer += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf 104 | if self.handshake_status == 0: 105 | self.handshake_status = 1 106 | data = self.tls_version + self.pack_auth_data(self.server_info.data.client_id) + b"\x20" + self.server_info.data.client_id + binascii.unhexlify(b"001cc02bc02fcca9cca8cc14cc13c00ac014c009c013009c0035002f000a" + b"0100") 107 | ext = binascii.unhexlify(b"ff01000100") 108 | host = self.server_info.obfs_param or self.server_info.host 109 | if host and host[-1] in string.digits: 110 | host = '' 111 | hosts = host.split(',') 112 | host = random.choice(hosts) 113 | ext += self.sni(host) 114 | ext += b"\x00\x17\x00\x00" 115 | if host not in self.server_info.data.ticket_buf: 116 | self.server_info.data.ticket_buf[host] = os.urandom((struct.unpack('>H', os.urandom(2))[0] % 17 + 8) * 16) 117 | ext += b"\x00\x23" + struct.pack('>H', len(self.server_info.data.ticket_buf[host])) + self.server_info.data.ticket_buf[host] 118 | ext += binascii.unhexlify(b"000d001600140601060305010503040104030301030302010203") 119 | ext += binascii.unhexlify(b"000500050100000000") 120 | ext += binascii.unhexlify(b"00120000") 121 | ext += binascii.unhexlify(b"75500000") 122 | ext += binascii.unhexlify(b"000b00020100") 123 | ext += binascii.unhexlify(b"000a0006000400170018") 124 | data += struct.pack('>H', len(ext)) + ext 125 | data = b"\x01\x00" + struct.pack('>H', len(data)) + data 126 | data = b"\x16\x03\x01" + struct.pack('>H', len(data)) + data 127 | return data 128 | elif self.handshake_status == 1 and len(buf) == 0: 129 | data = b"\x14" + self.tls_version + b"\x00\x01\x01" #ChangeCipherSpec 130 | data += b"\x16" + self.tls_version + b"\x00\x20" + os.urandom(22) #Finished 131 | data += hmac.new(self.server_info.key + self.server_info.data.client_id, data, hashlib.sha1).digest()[:10] 132 | ret = data + self.send_buffer 133 | self.send_buffer = b'' 134 | self.handshake_status = 8 135 | return ret 136 | return b'' 137 | 138 | def client_decode(self, buf): 139 | if self.handshake_status == -1: 140 | return (buf, False) 141 | 142 | if self.handshake_status == 8: 143 | ret = b'' 144 | self.recv_buffer += buf 145 | while len(self.recv_buffer) > 5: 146 | if ord(self.recv_buffer[0]) != 0x17: 147 | logging.info("data = %s" % (binascii.hexlify(self.recv_buffer))) 148 | raise Exception('server_decode appdata error') 149 | size = struct.unpack('>H', self.recv_buffer[3:5])[0] 150 | if len(self.recv_buffer) < size + 5: 151 | break 152 | buf = self.recv_buffer[5:size+5] 153 | ret += buf 154 | self.recv_buffer = self.recv_buffer[size+5:] 155 | return (ret, False) 156 | 157 | if len(buf) < 11 + 32 + 1 + 32: 158 | raise Exception('client_decode data error') 159 | verify = buf[11:33] 160 | if hmac.new(self.server_info.key + self.server_info.data.client_id, verify, hashlib.sha1).digest()[:10] != buf[33:43]: 161 | raise Exception('client_decode data error') 162 | if hmac.new(self.server_info.key + self.server_info.data.client_id, buf[:-10], hashlib.sha1).digest()[:10] != buf[-10:]: 163 | raise Exception('client_decode data error') 164 | return (b'', True) 165 | 166 | def server_encode(self, buf): 167 | if self.handshake_status == -1: 168 | return buf 169 | if (self.handshake_status & 8) == 8: 170 | ret = b'' 171 | while len(buf) > 2048: 172 | size = min(struct.unpack('>H', os.urandom(2))[0] % 4096 + 100, len(buf)) 173 | ret += b"\x17" + self.tls_version + struct.pack('>H', size) + buf[:size] 174 | buf = buf[size:] 175 | if len(buf) > 0: 176 | ret += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf 177 | return ret 178 | self.handshake_status |= 8 179 | data = self.tls_version + self.pack_auth_data(self.client_id) + b"\x20" + self.client_id + binascii.unhexlify(b"c02f000005ff01000100") 180 | data = b"\x02\x00" + struct.pack('>H', len(data)) + data #server hello 181 | data = b"\x16" + self.tls_version + struct.pack('>H', len(data)) + data 182 | if random.randint(0, 8) < 1: 183 | ticket = os.urandom((struct.unpack('>H', os.urandom(2))[0] % 164) * 2 + 64) 184 | ticket = struct.pack('>H', len(ticket) + 4) + b"\x04\x00" + struct.pack('>H', len(ticket)) + ticket 185 | data += b"\x16" + self.tls_version + ticket #New session ticket 186 | data += b"\x14" + self.tls_version + b"\x00\x01\x01" #ChangeCipherSpec 187 | finish_len = random.choice([32, 40]) 188 | data += b"\x16" + self.tls_version + struct.pack('>H', finish_len) + os.urandom(finish_len - 10) #Finished 189 | data += hmac.new(self.server_info.key + self.client_id, data, hashlib.sha1).digest()[:10] 190 | if buf: 191 | data += self.server_encode(buf) 192 | return data 193 | 194 | def decode_error_return(self, buf): 195 | self.handshake_status = -1 196 | self.overhead = 0 197 | if self.method == 'tls1.2_ticket_auth': 198 | return (b'E'*2048, False, False) 199 | return (buf, True, False) 200 | 201 | def server_decode(self, buf): 202 | if self.handshake_status == -1: 203 | return (buf, True, False) 204 | 205 | if (self.handshake_status & 4) == 4: 206 | ret = b'' 207 | self.recv_buffer += buf 208 | while len(self.recv_buffer) > 5: 209 | if ord(self.recv_buffer[0]) != 0x17 or ord(self.recv_buffer[1]) != 0x3 or ord(self.recv_buffer[2]) != 0x3: 210 | logging.info("data = %s" % (binascii.hexlify(self.recv_buffer))) 211 | raise Exception('server_decode appdata error') 212 | size = struct.unpack('>H', self.recv_buffer[3:5])[0] 213 | if len(self.recv_buffer) < size + 5: 214 | break 215 | ret += self.recv_buffer[5:size+5] 216 | self.recv_buffer = self.recv_buffer[size+5:] 217 | return (ret, True, False) 218 | 219 | if (self.handshake_status & 1) == 1: 220 | self.recv_buffer += buf 221 | buf = self.recv_buffer 222 | verify = buf 223 | if len(buf) < 11: 224 | raise Exception('server_decode data error') 225 | if not match_begin(buf, b"\x14" + self.tls_version + b"\x00\x01\x01"): #ChangeCipherSpec 226 | raise Exception('server_decode data error') 227 | buf = buf[6:] 228 | if not match_begin(buf, b"\x16" + self.tls_version + b"\x00"): #Finished 229 | raise Exception('server_decode data error') 230 | verify_len = struct.unpack('>H', buf[3:5])[0] + 1 # 11 - 10 231 | if len(verify) < verify_len + 10: 232 | return (b'', False, False) 233 | if hmac.new(self.server_info.key + self.client_id, verify[:verify_len], hashlib.sha1).digest()[:10] != verify[verify_len:verify_len+10]: 234 | raise Exception('server_decode data error') 235 | self.recv_buffer = verify[verify_len + 10:] 236 | status = self.handshake_status 237 | self.handshake_status |= 4 238 | ret = self.server_decode(b'') 239 | return ret; 240 | 241 | #raise Exception("handshake data = %s" % (binascii.hexlify(buf))) 242 | self.recv_buffer += buf 243 | buf = self.recv_buffer 244 | ogn_buf = buf 245 | if len(buf) < 3: 246 | return (b'', False, False) 247 | if not match_begin(buf, b'\x16\x03\x01'): 248 | return self.decode_error_return(ogn_buf) 249 | buf = buf[3:] 250 | header_len = struct.unpack('>H', buf[:2])[0] 251 | if header_len > len(buf) - 2: 252 | return (b'', False, False) 253 | 254 | self.recv_buffer = self.recv_buffer[header_len + 5:] 255 | self.handshake_status = 1 256 | buf = buf[2:header_len + 2] 257 | if not match_begin(buf, b'\x01\x00'): #client hello 258 | logging.info("tls_auth not client hello message") 259 | return self.decode_error_return(ogn_buf) 260 | buf = buf[2:] 261 | if struct.unpack('>H', buf[:2])[0] != len(buf) - 2: 262 | logging.info("tls_auth wrong message size") 263 | return self.decode_error_return(ogn_buf) 264 | buf = buf[2:] 265 | if not match_begin(buf, self.tls_version): 266 | logging.info("tls_auth wrong tls version") 267 | return self.decode_error_return(ogn_buf) 268 | buf = buf[2:] 269 | verifyid = buf[:32] 270 | buf = buf[32:] 271 | sessionid_len = ord(buf[0]) 272 | if sessionid_len < 32: 273 | logging.info("tls_auth wrong sessionid_len") 274 | return self.decode_error_return(ogn_buf) 275 | sessionid = buf[1:sessionid_len + 1] 276 | buf = buf[sessionid_len+1:] 277 | self.client_id = sessionid 278 | sha1 = hmac.new(self.server_info.key + sessionid, verifyid[:22], hashlib.sha1).digest()[:10] 279 | utc_time = struct.unpack('>I', verifyid[:4])[0] 280 | time_dif = common.int32((int(time.time()) & 0xffffffff) - utc_time) 281 | if self.server_info.obfs_param: 282 | try: 283 | self.max_time_dif = int(self.server_info.obfs_param) 284 | except: 285 | pass 286 | if self.max_time_dif > 0 and (time_dif < -self.max_time_dif or time_dif > self.max_time_dif \ 287 | or common.int32(utc_time - self.server_info.data.startup_time) < -self.max_time_dif / 2): 288 | logging.info("tls_auth wrong time") 289 | return self.decode_error_return(ogn_buf) 290 | if sha1 != verifyid[22:]: 291 | logging.info("tls_auth wrong sha1") 292 | return self.decode_error_return(ogn_buf) 293 | if self.server_info.data.client_data.get(verifyid[:22]): 294 | logging.info("replay attack detect, id = %s" % (binascii.hexlify(verifyid))) 295 | return self.decode_error_return(ogn_buf) 296 | self.server_info.data.client_data.sweep() 297 | self.server_info.data.client_data[verifyid[:22]] = sessionid 298 | if len(self.recv_buffer) >= 11: 299 | ret = self.server_decode(b'') 300 | return (ret[0], True, True) 301 | # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) 302 | return (b'', False, True) 303 | 304 | -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/obfs_tls.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/obfsplugin/obfs_tls.pyc -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/plain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2015 breakwa11 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | 25 | from shadowsocks.common import ord 26 | 27 | def create_obfs(method): 28 | return plain(method) 29 | 30 | obfs_map = { 31 | 'plain': (create_obfs,), 32 | 'origin': (create_obfs,), 33 | } 34 | 35 | class plain(object): 36 | def __init__(self, method): 37 | self.method = method 38 | self.server_info = None 39 | 40 | def init_data(self): 41 | return b'' 42 | 43 | def get_overhead(self, direction): # direction: true for c->s false for s->c 44 | return 0 45 | 46 | def get_server_info(self): 47 | return self.server_info 48 | 49 | def set_server_info(self, server_info): 50 | self.server_info = server_info 51 | 52 | def client_pre_encrypt(self, buf): 53 | return buf 54 | 55 | def client_encode(self, buf): 56 | return buf 57 | 58 | def client_decode(self, buf): 59 | # (buffer_to_recv, is_need_to_encode_and_send_back) 60 | return (buf, False) 61 | 62 | def client_post_decrypt(self, buf): 63 | return buf 64 | 65 | def server_pre_encrypt(self, buf): 66 | return buf 67 | 68 | def server_encode(self, buf): 69 | return buf 70 | 71 | def server_decode(self, buf): 72 | # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) 73 | return (buf, True, False) 74 | 75 | def server_post_decrypt(self, buf): 76 | return (buf, False) 77 | 78 | def client_udp_pre_encrypt(self, buf): 79 | return buf 80 | 81 | def client_udp_post_decrypt(self, buf): 82 | return buf 83 | 84 | def server_udp_pre_encrypt(self, buf, uid): 85 | return buf 86 | 87 | def server_udp_post_decrypt(self, buf): 88 | return (buf, None) 89 | 90 | def dispose(self): 91 | pass 92 | 93 | def get_head_size(self, buf, def_value): 94 | if len(buf) < 2: 95 | return def_value 96 | head_type = ord(buf[0]) & 0x7 97 | if head_type == 1: 98 | return 7 99 | if head_type == 4: 100 | return 19 101 | if head_type == 3: 102 | return 4 + ord(buf[1]) 103 | return def_value 104 | 105 | -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/plain.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/obfsplugin/plain.pyc -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/verify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015-2015 breakwa11 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import absolute_import, division, print_function, \ 18 | with_statement 19 | 20 | import os 21 | import sys 22 | import hashlib 23 | import logging 24 | import binascii 25 | import base64 26 | import time 27 | import datetime 28 | import random 29 | import struct 30 | import zlib 31 | import hmac 32 | import hashlib 33 | 34 | import shadowsocks 35 | from shadowsocks import common 36 | from shadowsocks.obfsplugin import plain 37 | from shadowsocks.common import to_bytes, to_str, ord, chr 38 | 39 | def create_verify_deflate(method): 40 | return verify_deflate(method) 41 | 42 | obfs_map = { 43 | 'verify_deflate': (create_verify_deflate,), 44 | } 45 | 46 | def match_begin(str1, str2): 47 | if len(str1) >= len(str2): 48 | if str1[:len(str2)] == str2: 49 | return True 50 | return False 51 | 52 | class obfs_verify_data(object): 53 | def __init__(self): 54 | pass 55 | 56 | class verify_base(plain.plain): 57 | def __init__(self, method): 58 | super(verify_base, self).__init__(method) 59 | self.method = method 60 | 61 | def init_data(self): 62 | return obfs_verify_data() 63 | 64 | def set_server_info(self, server_info): 65 | self.server_info = server_info 66 | 67 | def client_encode(self, buf): 68 | return buf 69 | 70 | def client_decode(self, buf): 71 | return (buf, False) 72 | 73 | def server_encode(self, buf): 74 | return buf 75 | 76 | def server_decode(self, buf): 77 | return (buf, True, False) 78 | 79 | class verify_deflate(verify_base): 80 | def __init__(self, method): 81 | super(verify_deflate, self).__init__(method) 82 | self.recv_buf = b'' 83 | self.unit_len = 32700 84 | self.decrypt_packet_num = 0 85 | self.raw_trans = False 86 | 87 | def pack_data(self, buf): 88 | if len(buf) == 0: 89 | return b'' 90 | data = zlib.compress(buf) 91 | data = struct.pack('>H', len(data)) + data[2:] 92 | return data 93 | 94 | def client_pre_encrypt(self, buf): 95 | ret = b'' 96 | while len(buf) > self.unit_len: 97 | ret += self.pack_data(buf[:self.unit_len]) 98 | buf = buf[self.unit_len:] 99 | ret += self.pack_data(buf) 100 | return ret 101 | 102 | def client_post_decrypt(self, buf): 103 | if self.raw_trans: 104 | return buf 105 | self.recv_buf += buf 106 | out_buf = b'' 107 | while len(self.recv_buf) > 2: 108 | length = struct.unpack('>H', self.recv_buf[:2])[0] 109 | if length >= 32768 or length < 6: 110 | self.raw_trans = True 111 | self.recv_buf = b'' 112 | raise Exception('client_post_decrypt data error') 113 | if length > len(self.recv_buf): 114 | break 115 | 116 | out_buf += zlib.decompress(b'x\x9c' + self.recv_buf[2:length]) 117 | self.recv_buf = self.recv_buf[length:] 118 | 119 | if out_buf: 120 | self.decrypt_packet_num += 1 121 | return out_buf 122 | 123 | def server_pre_encrypt(self, buf): 124 | ret = b'' 125 | while len(buf) > self.unit_len: 126 | ret += self.pack_data(buf[:self.unit_len]) 127 | buf = buf[self.unit_len:] 128 | ret += self.pack_data(buf) 129 | return ret 130 | 131 | def server_post_decrypt(self, buf): 132 | if self.raw_trans: 133 | return (buf, False) 134 | self.recv_buf += buf 135 | out_buf = b'' 136 | while len(self.recv_buf) > 2: 137 | length = struct.unpack('>H', self.recv_buf[:2])[0] 138 | if length >= 32768 or length < 6: 139 | self.raw_trans = True 140 | self.recv_buf = b'' 141 | if self.decrypt_packet_num == 0: 142 | return (b'E'*2048, False) 143 | else: 144 | raise Exception('server_post_decrype data error') 145 | if length > len(self.recv_buf): 146 | break 147 | 148 | out_buf += zlib.decompress(b'\x78\x9c' + self.recv_buf[2:length]) 149 | self.recv_buf = self.recv_buf[length:] 150 | 151 | if out_buf: 152 | self.decrypt_packet_num += 1 153 | return (out_buf, False) 154 | 155 | -------------------------------------------------------------------------------- /shadowsocks/obfsplugin/verify.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojianbiz/shadowsocksr-pyqt/266bb933372b68d5af573098756aa7df75a2c661/shadowsocks/obfsplugin/verify.pyc -------------------------------------------------------------------------------- /shadowsocks/ordereddict.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | ################################################################################ 4 | ### OrderedDict 5 | ################################################################################ 6 | 7 | class OrderedDict(dict): 8 | 'Dictionary that remembers insertion order' 9 | # An inherited dict maps keys to values. 10 | # The inherited dict provides __getitem__, __len__, __contains__, and get. 11 | # The remaining methods are order-aware. 12 | # Big-O running times for all methods are the same as regular dictionaries. 13 | 14 | # The internal self.__map dict maps keys to links in a doubly linked list. 15 | # The circular doubly linked list starts and ends with a sentinel element. 16 | # The sentinel element never gets deleted (this simplifies the algorithm). 17 | # Each link is stored as a list of length three: [PREV, NEXT, KEY]. 18 | 19 | def __init__(*args, **kwds): 20 | '''Initialize an ordered dictionary. The signature is the same as 21 | regular dictionaries, but keyword arguments are not recommended because 22 | their insertion order is arbitrary. 23 | 24 | ''' 25 | if not args: 26 | raise TypeError("descriptor '__init__' of 'OrderedDict' object " 27 | "needs an argument") 28 | self = args[0] 29 | args = args[1:] 30 | if len(args) > 1: 31 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 32 | try: 33 | self.__root 34 | except AttributeError: 35 | self.__root = root = [] # sentinel node 36 | root[:] = [root, root, None] 37 | self.__map = {} 38 | self.__update(*args, **kwds) 39 | 40 | def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 41 | 'od.__setitem__(i, y) <==> od[i]=y' 42 | # Setting a new item creates a new link at the end of the linked list, 43 | # and the inherited dictionary is updated with the new key/value pair. 44 | if key not in self: 45 | root = self.__root 46 | last = root[0] 47 | last[1] = root[0] = self.__map[key] = [last, root, key] 48 | return dict_setitem(self, key, value) 49 | 50 | def __delitem__(self, key, dict_delitem=dict.__delitem__): 51 | 'od.__delitem__(y) <==> del od[y]' 52 | # Deleting an existing item uses self.__map to find the link which gets 53 | # removed by updating the links in the predecessor and successor nodes. 54 | dict_delitem(self, key) 55 | link_prev, link_next, _ = self.__map.pop(key) 56 | link_prev[1] = link_next # update link_prev[NEXT] 57 | link_next[0] = link_prev # update link_next[PREV] 58 | 59 | def __iter__(self): 60 | 'od.__iter__() <==> iter(od)' 61 | # Traverse the linked list in order. 62 | root = self.__root 63 | curr = root[1] # start at the first node 64 | while curr is not root: 65 | yield curr[2] # yield the curr[KEY] 66 | curr = curr[1] # move to next node 67 | 68 | def __reversed__(self): 69 | 'od.__reversed__() <==> reversed(od)' 70 | # Traverse the linked list in reverse order. 71 | root = self.__root 72 | curr = root[0] # start at the last node 73 | while curr is not root: 74 | yield curr[2] # yield the curr[KEY] 75 | curr = curr[0] # move to previous node 76 | 77 | def clear(self): 78 | 'od.clear() -> None. Remove all items from od.' 79 | root = self.__root 80 | root[:] = [root, root, None] 81 | self.__map.clear() 82 | dict.clear(self) 83 | 84 | # -- the following methods do not depend on the internal structure -- 85 | 86 | def keys(self): 87 | 'od.keys() -> list of keys in od' 88 | return list(self) 89 | 90 | def values(self): 91 | 'od.values() -> list of values in od' 92 | return [self[key] for key in self] 93 | 94 | def items(self): 95 | 'od.items() -> list of (key, value) pairs in od' 96 | return [(key, self[key]) for key in self] 97 | 98 | def iterkeys(self): 99 | 'od.iterkeys() -> an iterator over the keys in od' 100 | return iter(self) 101 | 102 | def itervalues(self): 103 | 'od.itervalues -> an iterator over the values in od' 104 | for k in self: 105 | yield self[k] 106 | 107 | def iteritems(self): 108 | 'od.iteritems -> an iterator over the (key, value) pairs in od' 109 | for k in self: 110 | yield (k, self[k]) 111 | 112 | update = collections.MutableMapping.update 113 | 114 | __update = update # let subclasses override update without breaking __init__ 115 | 116 | __marker = object() 117 | 118 | def pop(self, key, default=__marker): 119 | '''od.pop(k[,d]) -> v, remove specified key and return the corresponding 120 | value. If key is not found, d is returned if given, otherwise KeyError 121 | is raised. 122 | 123 | ''' 124 | if key in self: 125 | result = self[key] 126 | del self[key] 127 | return result 128 | if default is self.__marker: 129 | raise KeyError(key) 130 | return default 131 | 132 | def setdefault(self, key, default=None): 133 | 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' 134 | if key in self: 135 | return self[key] 136 | self[key] = default 137 | return default 138 | 139 | def popitem(self, last=True): 140 | '''od.popitem() -> (k, v), return and remove a (key, value) pair. 141 | Pairs are returned in LIFO order if last is true or FIFO order if false. 142 | 143 | ''' 144 | if not self: 145 | raise KeyError('dictionary is empty') 146 | key = next(reversed(self) if last else iter(self)) 147 | value = self.pop(key) 148 | return key, value 149 | 150 | def __repr__(self, _repr_running={}): 151 | 'od.__repr__() <==> repr(od)' 152 | call_key = id(self), _get_ident() 153 | if call_key in _repr_running: 154 | return '...' 155 | _repr_running[call_key] = 1 156 | try: 157 | if not self: 158 | return '%s()' % (self.__class__.__name__,) 159 | return '%s(%r)' % (self.__class__.__name__, self.items()) 160 | finally: 161 | del _repr_running[call_key] 162 | 163 | def __reduce__(self): 164 | 'Return state information for pickling' 165 | items = [[k, self[k]] for k in self] 166 | inst_dict = vars(self).copy() 167 | for k in vars(OrderedDict()): 168 | inst_dict.pop(k, None) 169 | if inst_dict: 170 | return (self.__class__, (items,), inst_dict) 171 | return self.__class__, (items,) 172 | 173 | def copy(self): 174 | 'od.copy() -> a shallow copy of od' 175 | return self.__class__(self) 176 | 177 | @classmethod 178 | def fromkeys(cls, iterable, value=None): 179 | '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S. 180 | If not specified, the value defaults to None. 181 | 182 | ''' 183 | self = cls() 184 | for key in iterable: 185 | self[key] = value 186 | return self 187 | 188 | def __eq__(self, other): 189 | '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive 190 | while comparison to a regular mapping is order-insensitive. 191 | 192 | ''' 193 | if isinstance(other, OrderedDict): 194 | return dict.__eq__(self, other) and all(_imap(_eq, self, other)) 195 | return dict.__eq__(self, other) 196 | 197 | def __ne__(self, other): 198 | 'od.__ne__(y) <==> od!=y' 199 | return not self == other 200 | 201 | # -- the following methods support python 3.x style dictionary views -- 202 | 203 | def viewkeys(self): 204 | "od.viewkeys() -> a set-like object providing a view on od's keys" 205 | return KeysView(self) 206 | 207 | def viewvalues(self): 208 | "od.viewvalues() -> an object providing a view on od's values" 209 | return ValuesView(self) 210 | 211 | def viewitems(self): 212 | "od.viewitems() -> a set-like object providing a view on od's items" 213 | return ItemsView(self) 214 | 215 | -------------------------------------------------------------------------------- /shadowsocks/shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | import getopt 22 | import json 23 | import logging 24 | import os 25 | import sys 26 | 27 | from shadowsocks import encrypt 28 | from shadowsocks.common import to_bytes, to_str, IPNetwork, PortRange 29 | 30 | VERBOSE_LEVEL = 5 31 | 32 | verbose = 0 33 | 34 | 35 | def check_python(): 36 | info = sys.version_info 37 | if info[0] == 2 and not info[1] >= 6: 38 | print('Python 2.6+ required') 39 | sys.exit(1) 40 | elif info[0] == 3 and not info[1] >= 3: 41 | print('Python 3.3+ required') 42 | sys.exit(1) 43 | elif info[0] not in [2, 3]: 44 | print('Python version not supported') 45 | sys.exit(1) 46 | 47 | 48 | def print_exception(e): 49 | global verbose 50 | logging.error(e) 51 | if verbose > 0: 52 | import traceback 53 | traceback.print_exc() 54 | 55 | def __version(): 56 | version_str = '' 57 | try: 58 | import pkg_resources 59 | version_str = pkg_resources.get_distribution('shadowsocks').version 60 | except Exception: 61 | try: 62 | from shadowsocks import version 63 | version_str = version.version() 64 | except Exception: 65 | pass 66 | return version_str 67 | 68 | def print_shadowsocks(): 69 | print('ShadowsocksR %s' % __version()) 70 | 71 | def log_shadowsocks_version(): 72 | logging.info('ShadowsocksR %s' % __version()) 73 | 74 | 75 | def find_config(): 76 | user_config_path = 'user-config.json' 77 | config_path = 'config.json' 78 | 79 | def sub_find(file_name): 80 | if os.path.exists(file_name): 81 | return file_name 82 | file_name = os.path.join(os.path.abspath('..'), file_name) 83 | return file_name if os.path.exists(file_name) else None 84 | 85 | return sub_find(user_config_path) or sub_find(config_path) 86 | 87 | def check_config(config, is_local): 88 | if config.get('daemon', None) == 'stop': 89 | # no need to specify configuration for daemon stop 90 | return 91 | 92 | if is_local and not config.get('password', None) and not config.get('configs',None): 93 | logging.error('password not specified') 94 | print_help(is_local) 95 | sys.exit(2) 96 | 97 | if not is_local and not config.get('password', None) \ 98 | and not config.get('port_password', None): 99 | logging.error('password or port_password not specified') 100 | print_help(is_local) 101 | sys.exit(2) 102 | 103 | if 'local_port' in config: 104 | config['local_port'] = int(config['local_port']) 105 | 106 | if 'server_port' in config and type(config['server_port']) != list: 107 | config['server_port'] = int(config['server_port']) 108 | 109 | if config.get('local_address', '') in [b'0.0.0.0']: 110 | logging.warning('warning: local set to listen on 0.0.0.0, it\'s not safe') 111 | if config.get('server', '') in ['127.0.0.1', 'localhost']: 112 | logging.warning('warning: server set to listen on %s:%s, are you sure?' % 113 | (to_str(config['server']), config['server_port'])) 114 | if config.get('timeout', 300) < 100: 115 | logging.warning('warning: your timeout %d seems too short' % 116 | int(config.get('timeout'))) 117 | if config.get('timeout', 300) > 600: 118 | logging.warning('warning: your timeout %d seems too long' % 119 | int(config.get('timeout'))) 120 | if config.get('password') in [b'mypassword']: 121 | logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' 122 | 'config.json!') 123 | sys.exit(1) 124 | if config.get('user', None) is not None: 125 | if os.name != 'posix': 126 | logging.error('user can be used only on Unix') 127 | sys.exit(1) 128 | 129 | encrypt.try_cipher(config['password'], config['method']) 130 | 131 | 132 | def get_config(is_local): 133 | global verbose 134 | config = {} 135 | config_path = None 136 | logging.basicConfig(level=logging.INFO, 137 | format='%(levelname)-s: %(message)s') 138 | if is_local: 139 | shortopts = 'hd:s:b:p:k:l:m:O:o:G:g:c:t:vq' 140 | longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=', 141 | 'version'] 142 | else: 143 | shortopts = 'hd:s:p:k:m:O:o:G:g:c:t:vq' 144 | longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', 145 | 'forbidden-ip=', 'user=', 'manager-address=', 'version'] 146 | try: 147 | optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) 148 | for key, value in optlist: 149 | if key == '-c': 150 | config_path = value 151 | elif key in ('-h', '--help'): 152 | print_help(is_local) 153 | sys.exit(0) 154 | elif key == '--version': 155 | print_shadowsocks() 156 | sys.exit(0) 157 | else: 158 | continue 159 | 160 | if config_path is None: 161 | config_path = find_config() 162 | 163 | 164 | if config_path: 165 | logging.info('loading config from %s' % config_path) 166 | with open(config_path, 'rb') as f: 167 | try: 168 | config = parse_json_in_str(remove_comment(f.read().decode('utf8'))) 169 | except ValueError as e: 170 | logging.error('found an error in config.json: %s', str(e)) 171 | sys.exit(1) 172 | 173 | 174 | v_count = 0 175 | for key, value in optlist: 176 | if key == '-p': 177 | config['server_port'] = int(value) 178 | elif key == '-k': 179 | config['password'] = to_bytes(value) 180 | elif key == '-l': 181 | config['local_port'] = int(value) 182 | elif key == '-s': 183 | config['server'] = to_str(value) 184 | elif key == '-m': 185 | config['method'] = to_str(value) 186 | elif key == '-O': 187 | config['protocol'] = to_str(value) 188 | elif key == '-o': 189 | config['obfs'] = to_str(value) 190 | elif key == '-G': 191 | config['protocol_param'] = to_str(value) 192 | elif key == '-g': 193 | config['obfs_param'] = to_str(value) 194 | elif key == '-b': 195 | config['local_address'] = to_str(value) 196 | elif key == '-v': 197 | v_count += 1 198 | # '-vv' turns on more verbose mode 199 | config['verbose'] = v_count 200 | elif key == '-t': 201 | config['timeout'] = int(value) 202 | elif key == '--fast-open': 203 | config['fast_open'] = True 204 | elif key == '--workers': 205 | config['workers'] = int(value) 206 | elif key == '--manager-address': 207 | config['manager_address'] = value 208 | elif key == '--user': 209 | config['user'] = to_str(value) 210 | elif key == '--forbidden-ip': 211 | config['forbidden_ip'] = to_str(value) 212 | 213 | elif key == '-d': 214 | config['daemon'] = to_str(value) 215 | elif key == '--pid-file': 216 | config['pid-file'] = to_str(value) 217 | elif key == '--log-file': 218 | config['log-file'] = to_str(value) 219 | elif key == '-q': 220 | v_count -= 1 221 | config['verbose'] = v_count 222 | else: 223 | continue 224 | except getopt.GetoptError as e: 225 | print(e, file=sys.stderr) 226 | print_help(is_local) 227 | sys.exit(2) 228 | 229 | if not config: 230 | logging.error('config not specified') 231 | print_help(is_local) 232 | sys.exit(2) 233 | 234 | config['password'] = to_bytes(config.get('password', b'')) 235 | config['method'] = to_str(config.get('method', 'aes-256-cfb')) 236 | config['protocol'] = to_str(config.get('protocol', 'origin')) 237 | config['protocol_param'] = to_str(config.get('protocol_param', '')) 238 | config['obfs'] = to_str(config.get('obfs', 'plain')) 239 | config['obfs_param'] = to_str(config.get('obfs_param', '')) 240 | config['port_password'] = config.get('port_password', None) 241 | config['additional_ports'] = config.get('additional_ports', {}) 242 | config['additional_ports_only'] = config.get('additional_ports_only', False) 243 | config['timeout'] = int(config.get('timeout', 300)) 244 | config['udp_timeout'] = int(config.get('udp_timeout', 120)) 245 | config['udp_cache'] = int(config.get('udp_cache', 64)) 246 | config['fast_open'] = config.get('fast_open', False) 247 | config['workers'] = config.get('workers', 1) 248 | config['pid-file'] = config.get('pid-file', '/var/run/shadowsocksr.pid') 249 | config['log-file'] = config.get('log-file', '/var/log/shadowsocksr.log') 250 | config['verbose'] = config.get('verbose', False) 251 | config['connect_verbose_info'] = config.get('connect_verbose_info', 0) 252 | config['local_address'] = to_str(config.get('local_address', '127.0.0.1')) 253 | config['local_port'] = config.get('local_port', 1080) 254 | if is_local: 255 | if config.get('configs',None) is None: 256 | if config.get('server', None) is None: 257 | logging.error('server addr not specified') 258 | print_local_help() 259 | sys.exit(2) 260 | else: 261 | config['server'] = to_str(config['server']) 262 | else: 263 | config['configs'] = to_str(config['configs']) 264 | else: 265 | config['server'] = to_str(config.get('server', '0.0.0.0')) 266 | try: 267 | config['forbidden_ip'] = \ 268 | IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) 269 | except Exception as e: 270 | logging.error(e) 271 | sys.exit(2) 272 | try: 273 | config['forbidden_port'] = PortRange(config.get('forbidden_port', '')) 274 | except Exception as e: 275 | logging.error(e) 276 | sys.exit(2) 277 | try: 278 | config['ignore_bind'] = \ 279 | IPNetwork(config.get('ignore_bind', '127.0.0.0/8,::1/128,10.0.0.0/8,192.168.0.0/16')) 280 | except Exception as e: 281 | logging.error(e) 282 | sys.exit(2) 283 | config['server_port'] = config.get('server_port', 8388) 284 | 285 | logging.getLogger('').handlers = [] 286 | logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') 287 | if config['verbose'] >= 2: 288 | level = VERBOSE_LEVEL 289 | elif config['verbose'] == 1: 290 | level = logging.DEBUG 291 | elif config['verbose'] == -1: 292 | level = logging.WARN 293 | elif config['verbose'] <= -2: 294 | level = logging.ERROR 295 | else: 296 | level = logging.INFO 297 | verbose = config['verbose'] 298 | logging.basicConfig(level=level, 299 | format='%(asctime)s %(levelname)-8s %(filename)s:%(lineno)s %(message)s', 300 | datefmt='%Y-%m-%d %H:%M:%S') 301 | 302 | check_config(config, is_local) 303 | 304 | return config 305 | 306 | 307 | def print_help(is_local): 308 | if is_local: 309 | print_local_help() 310 | else: 311 | print_server_help() 312 | 313 | 314 | def print_local_help(): 315 | print('''usage: sslocal [OPTION]... 316 | A fast tunnel proxy that helps you bypass firewalls. 317 | 318 | You can supply configurations via either config file or command line arguments. 319 | 320 | Proxy options: 321 | -c CONFIG path to config file 322 | -s SERVER_ADDR server address 323 | -p SERVER_PORT server port, default: 8388 324 | -b LOCAL_ADDR local binding address, default: 127.0.0.1 325 | -l LOCAL_PORT local port, default: 1080 326 | -k PASSWORD password 327 | -m METHOD encryption method, default: aes-256-cfb 328 | -o OBFS obfsplugin, default: http_simple 329 | -t TIMEOUT timeout in seconds, default: 300 330 | --fast-open use TCP_FASTOPEN, requires Linux 3.7+ 331 | 332 | General options: 333 | -h, --help show this help message and exit 334 | -d start/stop/restart daemon mode 335 | --pid-file PID_FILE pid file for daemon mode 336 | --log-file LOG_FILE log file for daemon mode 337 | --user USER username to run as 338 | -v, -vv verbose mode 339 | -q, -qq quiet mode, only show warnings/errors 340 | --version show version information 341 | 342 | Online help: 343 | ''') 344 | 345 | 346 | def print_server_help(): 347 | print('''usage: ssserver [OPTION]... 348 | A fast tunnel proxy that helps you bypass firewalls. 349 | 350 | You can supply configurations via either config file or command line arguments. 351 | 352 | Proxy options: 353 | -c CONFIG path to config file 354 | -s SERVER_ADDR server address, default: 0.0.0.0 355 | -p SERVER_PORT server port, default: 8388 356 | -k PASSWORD password 357 | -m METHOD encryption method, default: aes-256-cfb 358 | -o OBFS obfsplugin, default: http_simple 359 | -t TIMEOUT timeout in seconds, default: 300 360 | --fast-open use TCP_FASTOPEN, requires Linux 3.7+ 361 | --workers WORKERS number of workers, available on Unix/Linux 362 | --forbidden-ip IPLIST comma seperated IP list forbidden to connect 363 | --manager-address ADDR optional server manager UDP address, see wiki 364 | 365 | General options: 366 | -h, --help show this help message and exit 367 | -d start/stop/restart daemon mode 368 | --pid-file PID_FILE pid file for daemon mode 369 | --log-file LOG_FILE log file for daemon mode 370 | --user USER username to run as 371 | -v, -vv verbose mode 372 | -q, -qq quiet mode, only show warnings/errors 373 | --version show version information 374 | 375 | Online help: 376 | ''') 377 | 378 | 379 | def _decode_list(data): 380 | rv = [] 381 | for item in data: 382 | if hasattr(item, 'encode'): 383 | item = item.encode('utf-8') 384 | elif isinstance(item, list): 385 | item = _decode_list(item) 386 | elif isinstance(item, dict): 387 | item = _decode_dict(item) 388 | rv.append(item) 389 | return rv 390 | 391 | 392 | def _decode_dict(data): 393 | rv = {} 394 | for key, value in data.items(): 395 | if hasattr(value, 'encode'): 396 | value = value.encode('utf-8') 397 | elif isinstance(value, list): 398 | value = _decode_list(value) 399 | elif isinstance(value, dict): 400 | value = _decode_dict(value) 401 | rv[key] = value 402 | return rv 403 | 404 | class JSFormat: 405 | def __init__(self): 406 | self.state = 0 407 | 408 | def push(self, ch): 409 | ch = ord(ch) 410 | if self.state == 0: 411 | if ch == ord('"'): 412 | self.state = 1 413 | return to_str(chr(ch)) 414 | elif ch == ord('/'): 415 | self.state = 3 416 | else: 417 | return to_str(chr(ch)) 418 | elif self.state == 1: 419 | if ch == ord('"'): 420 | self.state = 0 421 | return to_str(chr(ch)) 422 | elif ch == ord('\\'): 423 | self.state = 2 424 | return to_str(chr(ch)) 425 | elif self.state == 2: 426 | self.state = 1 427 | if ch == ord('"'): 428 | return to_str(chr(ch)) 429 | return "\\" + to_str(chr(ch)) 430 | elif self.state == 3: 431 | if ch == ord('/'): 432 | self.state = 4 433 | else: 434 | return "/" + to_str(chr(ch)) 435 | elif self.state == 4: 436 | if ch == ord('\n'): 437 | self.state = 0 438 | return "\n" 439 | return "" 440 | 441 | def remove_comment(json): 442 | fmt = JSFormat() 443 | return "".join([fmt.push(c) for c in json]) 444 | 445 | 446 | def parse_json_in_str(data): 447 | # parse json and convert everything from unicode to str 448 | return json.loads(data, object_hook=_decode_dict) 449 | -------------------------------------------------------------------------------- /shadowsocks/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2017 breakwa11 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | def version(): 19 | return '3.3.3 2017-06-03' 20 | 21 | -------------------------------------------------------------------------------- /ssr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2012-2015 clowwindy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, \ 19 | with_statement 20 | 21 | 22 | import logging 23 | import os 24 | import signal 25 | import sys 26 | 27 | 28 | 29 | if __name__ == '__main__': 30 | import inspect 31 | file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))) 32 | sys.path.insert(0, os.path.join(file_path, '../')) 33 | 34 | from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns 35 | 36 | 37 | def run(): 38 | shell.check_python() 39 | 40 | # fix py2exe 41 | if hasattr(sys, "frozen") and sys.frozen in \ 42 | ("windows_exe", "console_exe"): 43 | p = os.path.dirname(os.path.abspath(sys.executable)) 44 | os.chdir(p) 45 | 46 | config = shell.get_config(True) 47 | 48 | if not config.get('dns_ipv6', False): 49 | asyncdns.IPV6_CONNECTION_SUPPORT = False 50 | 51 | daemon.daemon_exec(config) 52 | 53 | try: 54 | logging.info("starting local at %s:%d" % 55 | (config['local_address'], config['local_port'])) 56 | 57 | dns_resolver = asyncdns.DNSResolver() 58 | tcp_server = tcprelay.TCPRelay(config, dns_resolver, True) 59 | udp_server = udprelay.UDPRelay(config, dns_resolver, True) 60 | loop = eventloop.EventLoop() 61 | dns_resolver.add_to_loop(loop) 62 | tcp_server.add_to_loop(loop) 63 | udp_server.add_to_loop(loop) 64 | 65 | def handler(signum, _): 66 | logging.warn('received SIGQUIT, doing graceful shutting down..') 67 | tcp_server.close(next_tick=True) 68 | udp_server.close(next_tick=True) 69 | signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler) 70 | 71 | 72 | def int_handler(signum, _): 73 | sys.exit(1) 74 | signal.signal(signal.SIGINT, int_handler) 75 | 76 | 77 | daemon.set_user(config.get('user', None)) 78 | 79 | loop.run() 80 | 81 | except Exception as e: 82 | shell.print_exception(e) 83 | sys.exit(1) 84 | 85 | if __name__ == '__main__': 86 | run() 87 | 88 | 89 | 90 | --------------------------------------------------------------------------------