├── .gitignore ├── CHANGES ├── LICENSE ├── README.md ├── args.py ├── asyncdns.py ├── common.py ├── conf.py ├── conf └── config.json ├── crypto ├── __init__.py ├── ctypes_libsodium.py ├── ctypes_openssl.py ├── openssl.py ├── rc4_md5.py ├── sodium.py ├── table.py └── util.py ├── encrypt.py ├── encrypt_test.py ├── eventloop.py ├── exit.py ├── initcfg.sh ├── lru_cache.py ├── obfs.py ├── obfsplugin ├── __init__.py ├── auth.py ├── auth_chain.py ├── http_simple.py ├── obfs_tls.py ├── plain.py └── verify.py ├── ordereddict.py ├── require.py ├── run.sh ├── server.py ├── shell.py ├── stop.sh ├── tcprelay.py ├── udprelay.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | ## Configuration files 2 | user-config.json 3 | 4 | ## Darwin files 5 | .DS_Store 6 | 7 | ## IntelliJ files 8 | .idea 9 | 10 | ## Python files 11 | # Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # Environment 17 | # use remote interpreter 18 | 19 | ## Logging files 20 | *.log 21 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | v4.1.2 Snapshot at 2019-08-22 2 | - Simplify code and add comments. 3 | - Deprecate multi-user models and speed limited. 4 | :: meaningless that use multi-user models and speed limited for personal versions. 5 | 6 | v4.1.1 Snapshot at 2019-08-21 7 | - Configuring dns server via parsing json instead of '/etc/resolv.conf'. 8 | 9 | v4.1.0 Snapshot at 2019-08-01 10 | - Simplify code and configuration parameters. 11 | - Use single port instead of multiple ports. 12 | :: meaningless that use multiple ports for personal versions. 13 | 14 | v4.0.1 Snapshot at 2019-07-22 15 | - Fix running without logs. 16 | 17 | v4.0.0 Snapshot at 2019-07-20 18 | - Refactor from branch 'manyuser' and simplify code. 19 | - Split service and local edition. 20 | :: Service edition only support 'posix'(Linux/Darwin). 21 | - Python 2.x deprecated. Use 3.6+ instead. 22 | - Only supply configurations via a json file. 23 | :: Not supported supply configurations via command line args. 24 | 25 | ===================================================================== 26 | Note: Changes before v4.0 is in branch 'manyuser'. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ShadowsocksR - Refactor 2 | =========== 3 | 4 | A fast tunnel proxy that helps you bypass firewalls. 5 | 6 | Info 7 | ------ 8 | 9 | |Edition |Change logs | 10 | |:----- |:------ | 11 | |Server |[Change logs](CHANGES) | 12 | 13 | Require 14 | ------ 15 | 16 | * Python 3.6+ 17 | * Linux && Require 'epoll' model 18 | 19 | Usage 20 | ------ 21 | 22 | #### Get python source code 23 | 24 | # clone this branch via git 25 | git clone -b refactor-server https://github.com/valord577/shadowsocksr-refactor.git 26 | 27 | # or download as zip file and unzip it 28 | 29 | #### Initialize the project 30 | 31 | # change directory into the root path of project and initialize the project 32 | cd ./shadowsocksr-refactor && bash initcfg.sh 33 | 34 | #### Edit the configuration file 35 | 36 | # ./conf/config.json is just a template 37 | # editting ./user-config.json is ok 38 | vi user-config.json 39 | 40 | # then save it 41 | 42 | #### Running and stopping 43 | 44 | # running 45 | bash run.sh 46 | 47 | # stopping 48 | bash stop.sh 49 | 50 | #### Tail logs file 51 | 52 | tail -500f ssr-refactor.log 53 | 54 | GUI Client 55 | ------ 56 | 57 | Use GUI clients on your local PC/phones. 58 | 59 | Check the README of your client for more information. 60 | 61 | * [Windows](https://github.com/shadowsocksr-backup/shadowsocksr-csharp) 62 | * [MacOS](https://github.com/qinyuhang/ShadowsocksX-NG-R/releases) 63 | * [Android](https://github.com/shadowsocksr-backup/shadowsocksr-android) 64 | * [Linux](https://github.com/qingshuisiyuan/electron-ssr-backup) 65 | * IOS (App named 'Shadowrocket', Non-Chinese mainland) 66 | 67 | License 68 | ------- 69 | 70 | Copyright 2019 valord577 71 | 72 | Licensed under the Apache License, Version 2.0 (the "License"); you may 73 | not use this file except in compliance with the License. You may obtain 74 | a copy of the License at 75 | 76 | http://www.apache.org/licenses/LICENSE-2.0 77 | 78 | Unless required by applicable law or agreed to in writing, software 79 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 80 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 81 | License for the specific language governing permissions and limitations 82 | under the License. 83 | -------------------------------------------------------------------------------- /args.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: valor. 6 | @file: args.py 7 | 8 | Copyright 2019 valord577 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | """ 22 | 23 | import sys 24 | import getopt 25 | # -- import from shadowsockesr-v 26 | import conf 27 | import exit 28 | 29 | 30 | def check(): 31 | try: 32 | opts, args = getopt.getopt(sys.argv[1:], 'hv', ['help', 'version']) 33 | for opt, param in opts: 34 | if opt in ('-h', '--help'): 35 | print(conf.help_msg()) 36 | exit.ok() 37 | if opt in ('-v', '--version'): 38 | print(conf.version()) 39 | exit.ok() 40 | except getopt.GetoptError: 41 | print(' Error: args checked.') 42 | print(conf.help_msg()) 43 | exit.error() 44 | -------------------------------------------------------------------------------- /asyncdns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: clowwindy 6 | @modify: valor. 7 | @file: asyncdns.py 8 | 9 | Copyright 2014-2015 clowwindy 10 | Copyright 2019 valord577 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); you may 13 | not use this file except in compliance with the License. You may obtain 14 | a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 20 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 21 | License for the specific language governing permissions and limitations 22 | under the License. 23 | """ 24 | 25 | import os 26 | import socket 27 | import struct 28 | import re 29 | import logging 30 | from typing import List 31 | # -- import from shadowsockesr-v 32 | import common, lru_cache, eventloop 33 | 34 | CACHE_SWEEP_INTERVAL = 30 35 | 36 | VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d_-]{1,63}(? 63: 99 | return None 100 | results.append(common.chr(l)) 101 | results.append(label) 102 | results.append(b'\0') 103 | return b''.join(results) 104 | 105 | 106 | def build_request(address, qtype): 107 | request_id = os.urandom(2) 108 | header = struct.pack('!BBHHHH', 1, 0, 1, 0, 0, 0) 109 | addr = build_address(address) 110 | qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN) 111 | return request_id + header + addr + qtype_qclass 112 | 113 | 114 | def parse_ip(addrtype, data, length, offset): 115 | if addrtype == QTYPE_A: 116 | return socket.inet_ntop(socket.AF_INET, data[offset:offset + length]) 117 | elif addrtype == QTYPE_AAAA: 118 | return socket.inet_ntop(socket.AF_INET6, data[offset:offset + length]) 119 | elif addrtype in [QTYPE_CNAME, QTYPE_NS]: 120 | return parse_name(data, offset)[1] 121 | else: 122 | return data[offset:offset + length] 123 | 124 | 125 | def parse_name(data, offset): 126 | p = offset 127 | labels = [] 128 | l = common.ord(data[p]) 129 | while l > 0: 130 | if (l & (128 + 64)) == (128 + 64): 131 | # pointer 132 | pointer = struct.unpack('!H', data[p:p + 2])[0] 133 | pointer &= 0x3FFF 134 | r = parse_name(data, pointer) 135 | labels.append(r[1]) 136 | p += 2 137 | # pointer is the end 138 | return p - offset, b'.'.join(labels) 139 | else: 140 | labels.append(data[p + 1:p + 1 + l]) 141 | p += 1 + l 142 | l = common.ord(data[p]) 143 | return p - offset + 1, b'.'.join(labels) 144 | 145 | 146 | # rfc1035 147 | # record 148 | # 1 1 1 1 1 1 149 | # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 150 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 151 | # | | 152 | # / / 153 | # / NAME / 154 | # | | 155 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 156 | # | TYPE | 157 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 158 | # | CLASS | 159 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 160 | # | TTL | 161 | # | | 162 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 163 | # | RDLENGTH | 164 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| 165 | # / RDATA / 166 | # / / 167 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 168 | def parse_record(data, offset, question=False): 169 | nlen, name = parse_name(data, offset) 170 | if not question: 171 | record_type, record_class, record_ttl, record_rdlength = struct.unpack( 172 | '!HHiH', data[offset + nlen:offset + nlen + 10] 173 | ) 174 | ip = parse_ip(record_type, data, record_rdlength, offset + nlen + 10) 175 | return nlen + 10 + record_rdlength, \ 176 | (name, ip, record_type, record_class, record_ttl) 177 | else: 178 | record_type, record_class = struct.unpack( 179 | '!HH', data[offset + nlen:offset + nlen + 4] 180 | ) 181 | return nlen + 4, (name, None, record_type, record_class, None, None) 182 | 183 | 184 | def parse_header(data): 185 | if len(data) >= 12: 186 | header = struct.unpack('!HBBHHHH', data[:12]) 187 | res_id = header[0] 188 | res_qr = header[1] & 128 189 | res_tc = header[1] & 2 190 | res_ra = header[2] & 128 191 | res_rcode = header[2] & 15 192 | # assert res_tc == 0 193 | # assert res_rcode in [0, 3] 194 | res_qdcount = header[3] 195 | res_ancount = header[4] 196 | res_nscount = header[5] 197 | res_arcount = header[6] 198 | return (res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, 199 | res_ancount, res_nscount, res_arcount) 200 | return None 201 | 202 | 203 | def parse_response(data): 204 | try: 205 | if len(data) >= 12: 206 | header = parse_header(data) 207 | if not header: 208 | return None 209 | res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, \ 210 | res_ancount, res_nscount, res_arcount = header 211 | 212 | qds = [] 213 | ans = [] 214 | offset = 12 215 | for i in range(0, res_qdcount): 216 | l, r = parse_record(data, offset, True) 217 | offset += l 218 | if r: 219 | qds.append(r) 220 | for i in range(0, res_ancount): 221 | l, r = parse_record(data, offset) 222 | offset += l 223 | if r: 224 | ans.append(r) 225 | for i in range(0, res_nscount): 226 | l, r = parse_record(data, offset) 227 | offset += l 228 | for i in range(0, res_arcount): 229 | l, r = parse_record(data, offset) 230 | offset += l 231 | response = DNSResponse() 232 | if qds: 233 | response.hostname = qds[0][0] 234 | for an in qds: 235 | response.questions.append((an[1], an[2], an[3])) 236 | for an in ans: 237 | response.answers.append((an[1], an[2], an[3])) 238 | return response 239 | except Exception as e: 240 | logging.error(e) 241 | return None 242 | 243 | 244 | def is_valid_hostname(hostname): 245 | if len(hostname) > 255: 246 | return False 247 | if hostname[-1] == b'.': 248 | hostname = hostname[:-1] 249 | return all(VALID_HOSTNAME.match(x) for x in hostname.split(b'.')) 250 | 251 | 252 | class DNSResponse(object): 253 | def __init__(self): 254 | self.hostname = None 255 | self.questions = [] # each: (addr, type, class) 256 | self.answers = [] # each: (addr, type, class) 257 | 258 | def __str__(self): 259 | return '%s: %s' % (self.hostname, str(self.answers)) 260 | 261 | 262 | STATUS_IPV4 = 0 263 | STATUS_IPV6 = 1 264 | 265 | 266 | class DNSResolver: 267 | 268 | _instance = None 269 | 270 | # singleton 271 | def __new__(cls, dns_list: List): 272 | if not cls._instance: 273 | cls._instance = super().__new__(cls) 274 | return cls._instance 275 | 276 | def __init__(self, dns_list: List): 277 | self._event_loop = None 278 | # local host 279 | self._hosts = {"localhost": "127.0.0.1"} 280 | self._hostname_status = {} 281 | self._hostname_to_cb = {} 282 | self._cb_to_hostname = {} 283 | self._cache = lru_cache.LRUCache(timeout=300) 284 | self._sock = None 285 | # dns servers 286 | self._servers = [] 287 | self.__parse_resolv(dns_list) 288 | 289 | def __parse_resolv(self, dns_list: List): 290 | # insert dns server 291 | for dns in dns_list: 292 | host = dns['host'] 293 | port = dns['port'] 294 | self._servers.append((host, port), ) 295 | logging.info(f"dns server: [{host}]:{port}") 296 | 297 | def add_to_loop(self, loop): 298 | if self._event_loop: 299 | logging.warning("DNSResolver already has been added to loop.") 300 | else: 301 | self._event_loop = loop 302 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP) 303 | self._sock.setblocking(False) 304 | loop.add(self._sock, eventloop.POLL_IN, self) 305 | loop.add_periodic(self.handle_periodic) 306 | 307 | def _call_callback(self, hostname, ip, error=None): 308 | callbacks = self._hostname_to_cb.get(hostname, []) 309 | for callback in callbacks: 310 | if callback in self._cb_to_hostname: 311 | del self._cb_to_hostname[callback] 312 | if ip or error: 313 | callback((hostname, ip), error) 314 | else: 315 | callback((hostname, None), 316 | Exception('unable to parse hostname %s' % hostname)) 317 | if hostname in self._hostname_to_cb: 318 | del self._hostname_to_cb[hostname] 319 | if hostname in self._hostname_status: 320 | del self._hostname_status[hostname] 321 | 322 | def _handle_data(self, data): 323 | response = parse_response(data) 324 | if response and response.hostname: 325 | hostname = response.hostname 326 | ip = None 327 | for answer in response.answers: 328 | if answer[1] in (QTYPE_A, QTYPE_AAAA) and \ 329 | answer[2] == QCLASS_IN: 330 | ip = answer[0] 331 | break 332 | if IPV6_CONNECTION_SUPPORT: 333 | if not ip and self._hostname_status.get(hostname, STATUS_IPV4) \ 334 | == STATUS_IPV6: 335 | self._hostname_status[hostname] = STATUS_IPV4 336 | self._send_req(hostname, QTYPE_A) 337 | else: 338 | if ip: 339 | self._cache[hostname] = ip 340 | self._call_callback(hostname, ip) 341 | elif self._hostname_status.get(hostname, None) == STATUS_IPV4: 342 | for question in response.questions: 343 | if question[1] == QTYPE_A: 344 | self._call_callback(hostname, None) 345 | break 346 | else: 347 | if not ip and self._hostname_status.get(hostname, STATUS_IPV6) \ 348 | == STATUS_IPV4: 349 | self._hostname_status[hostname] = STATUS_IPV6 350 | self._send_req(hostname, QTYPE_AAAA) 351 | else: 352 | if ip: 353 | self._cache[hostname] = ip 354 | self._call_callback(hostname, ip) 355 | elif self._hostname_status.get(hostname, None) == STATUS_IPV6: 356 | for question in response.questions: 357 | if question[1] == QTYPE_AAAA: 358 | self._call_callback(hostname, None) 359 | break 360 | 361 | def handle_event(self, sock, fd, event): 362 | if sock != self._sock: 363 | return 364 | if event & eventloop.POLL_ERR: 365 | logging.error('dns socket err') 366 | self._event_loop.remove(self._sock) 367 | self._sock.close() 368 | # TODO when dns server is IPv6 369 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 370 | socket.SOL_UDP) 371 | self._sock.setblocking(False) 372 | self._event_loop.add(self._sock, eventloop.POLL_IN, self) 373 | else: 374 | data, addr = sock.recvfrom(1024) 375 | if addr not in self._servers: 376 | logging.warn('received a packet other than our dns') 377 | return 378 | self._handle_data(data) 379 | 380 | def handle_periodic(self): 381 | self._cache.sweep() 382 | 383 | def remove_callback(self, callback): 384 | hostname = self._cb_to_hostname.get(callback) 385 | if hostname: 386 | del self._cb_to_hostname[callback] 387 | arr = self._hostname_to_cb.get(hostname, None) 388 | if arr: 389 | arr.remove(callback) 390 | if not arr: 391 | del self._hostname_to_cb[hostname] 392 | if hostname in self._hostname_status: 393 | del self._hostname_status[hostname] 394 | 395 | def _send_req(self, hostname, qtype): 396 | req = build_request(hostname, qtype) 397 | for server in self._servers: 398 | logging.debug('resolving %s with type %d using server %s', 399 | hostname, qtype, server) 400 | self._sock.sendto(req, server) 401 | 402 | def resolve(self, hostname, callback): 403 | if type(hostname) != bytes: 404 | hostname = hostname.encode('utf8') 405 | if not hostname: 406 | callback(None, Exception('empty hostname')) 407 | elif common.is_ip(hostname): 408 | callback((hostname, hostname), None) 409 | elif hostname in self._hosts: 410 | logging.debug('hit hosts: %s', hostname) 411 | ip = self._hosts[hostname] 412 | callback((hostname, ip), None) 413 | elif hostname in self._cache: 414 | logging.debug('hit cache: %s', hostname) 415 | ip = self._cache[hostname] 416 | callback((hostname, ip), None) 417 | else: 418 | if not is_valid_hostname(hostname): 419 | callback(None, Exception('invalid hostname: %s' % hostname)) 420 | return 421 | if False: 422 | addrs = socket.getaddrinfo(hostname, 0, 0, 423 | socket.SOCK_DGRAM, socket.SOL_UDP) 424 | if addrs: 425 | af, socktype, proto, canonname, sa = addrs[0] 426 | logging.debug('DNS resolve %s %s' % (hostname, sa[0]) ) 427 | self._cache[hostname] = sa[0] 428 | callback((hostname, sa[0]), None) 429 | return 430 | arr = self._hostname_to_cb.get(hostname, None) 431 | if not arr: 432 | if IPV6_CONNECTION_SUPPORT: 433 | self._hostname_status[hostname] = STATUS_IPV6 434 | self._send_req(hostname, QTYPE_AAAA) 435 | else: 436 | self._hostname_status[hostname] = STATUS_IPV4 437 | self._send_req(hostname, QTYPE_A) 438 | self._hostname_to_cb[hostname] = [callback] 439 | self._cb_to_hostname[callback] = hostname 440 | else: 441 | arr.append(callback) 442 | # TODO send again only if waited too long 443 | if IPV6_CONNECTION_SUPPORT: 444 | self._send_req(hostname, QTYPE_AAAA) 445 | else: 446 | self._send_req(hostname, QTYPE_A) 447 | 448 | def close(self): 449 | if self._sock: 450 | if self._event_loop: 451 | self._event_loop.remove_periodic(self.handle_periodic) 452 | self._event_loop.remove(self._sock) 453 | self._sock.close() 454 | self._sock = None 455 | 456 | 457 | def test(): 458 | dns_resolver = DNSResolver() 459 | loop = eventloop.EventLoop() 460 | dns_resolver.add_to_loop(loop) 461 | 462 | global counter 463 | counter = 0 464 | 465 | def make_callback(): 466 | global counter 467 | 468 | def callback(result, error): 469 | global counter 470 | # TODO: what can we assert? 471 | print(result, error) 472 | counter += 1 473 | if counter == 9: 474 | dns_resolver.close() 475 | loop.stop() 476 | a_callback = callback 477 | return a_callback 478 | 479 | assert(make_callback() != make_callback()) 480 | 481 | dns_resolver.resolve(b'google.com', make_callback()) 482 | dns_resolver.resolve('google.com', make_callback()) 483 | dns_resolver.resolve('example.com', make_callback()) 484 | dns_resolver.resolve('ipv6.google.com', make_callback()) 485 | dns_resolver.resolve('www.facebook.com', make_callback()) 486 | dns_resolver.resolve('ns2.google.com', make_callback()) 487 | dns_resolver.resolve('invalid.@!#$%^&$@.hostname', make_callback()) 488 | dns_resolver.resolve('toooooooooooooooooooooooooooooooooooooooooooooooooo' 489 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 490 | 'long.hostname', make_callback()) 491 | dns_resolver.resolve('toooooooooooooooooooooooooooooooooooooooooooooooooo' 492 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 493 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 494 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 495 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 496 | 'ooooooooooooooooooooooooooooooooooooooooooooooooooo' 497 | 'long.hostname', make_callback()) 498 | 499 | loop.run() 500 | 501 | 502 | if __name__ == '__main__': 503 | test() 504 | 505 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: clowwindy 6 | @modify: valor. 7 | @file: common.py 8 | 9 | Copyright 2013-2015 clowwindy 10 | Copyright 2019 valord577 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); you may 13 | not use this file except in compliance with the License. You may obtain 14 | a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 20 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 21 | License for the specific language governing permissions and limitations 22 | under the License. 23 | """ 24 | 25 | import socket 26 | import struct 27 | import logging 28 | import binascii 29 | import re 30 | import lru_cache 31 | 32 | 33 | def compat_ord(s): 34 | if type(s) == int: 35 | return s 36 | return _ord(s) 37 | 38 | 39 | def compat_chr(d): 40 | if bytes == str: 41 | return _chr(d) 42 | return bytes([d]) 43 | 44 | 45 | _ord = ord 46 | _chr = chr 47 | ord = compat_ord 48 | chr = compat_chr 49 | 50 | 51 | def is_not_blank(string: str) -> bool: 52 | return not is_blank(string) 53 | 54 | 55 | def is_blank(string: str) -> bool: 56 | return string is None or '' == string 57 | 58 | 59 | def to_bytes(s: str): 60 | # compatible with Python 2.x 61 | if bytes != str: 62 | if type(s) == str: 63 | return s.encode('utf-8') 64 | return s 65 | 66 | 67 | def to_str(s: bytes): 68 | # compatible with Python 2.x 69 | if bytes != str: 70 | if type(s) == bytes: 71 | return s.decode('utf-8') 72 | return s 73 | 74 | 75 | # @return False if str is not a ipv4 76 | # @return bytes if str is a ipv4 77 | def is_ipv4(ip: str): 78 | if ip is None: 79 | return False 80 | 81 | try: 82 | return socket.inet_pton(socket.AF_INET, ip) 83 | except: 84 | return False 85 | 86 | 87 | # @return False if str is not a ipv6 88 | # @return bytes if str is a ipv6 89 | def is_ipv6(ip: str): 90 | if ip is None: 91 | return False 92 | 93 | try: 94 | return socket.inet_pton(socket.AF_INET6, ip) 95 | except: 96 | return False 97 | 98 | 99 | def int32(x): 100 | if x > 0xFFFFFFFF or x < 0: 101 | x &= 0xFFFFFFFF 102 | if x > 0x7FFFFFFF: 103 | x = int(0x100000000 - x) 104 | if x < 0x80000000: 105 | return -x 106 | else: 107 | return -2147483648 108 | return x 109 | 110 | def inet_ntop(family, ipstr): 111 | if family == socket.AF_INET: 112 | return to_bytes(socket.inet_ntoa(ipstr)) 113 | elif family == socket.AF_INET6: 114 | import re 115 | v6addr = ':'.join(('%02X%02X' % (ord(i), ord(j))).lstrip('0') 116 | for i, j in zip(ipstr[::2], ipstr[1::2])) 117 | v6addr = re.sub('::+', '::', v6addr, count=1) 118 | return to_bytes(v6addr) 119 | 120 | 121 | def inet_pton(family, addr): 122 | addr = to_str(addr) 123 | if family == socket.AF_INET: 124 | return socket.inet_aton(addr) 125 | elif family == socket.AF_INET6: 126 | if '.' in addr: # a v4 addr 127 | v4addr = addr[addr.rindex(':') + 1:] 128 | v4addr = socket.inet_aton(v4addr) 129 | v4addr = ['%02X' % ord(x) for x in v4addr] 130 | v4addr.insert(2, ':') 131 | newaddr = addr[:addr.rindex(':') + 1] + ''.join(v4addr) 132 | return inet_pton(family, newaddr) 133 | dbyts = [0] * 8 # 8 groups 134 | grps = addr.split(':') 135 | for i, v in enumerate(grps): 136 | if v: 137 | dbyts[i] = int(v, 16) 138 | else: 139 | for j, w in enumerate(grps[::-1]): 140 | if w: 141 | dbyts[7 - j] = int(w, 16) 142 | else: 143 | break 144 | break 145 | return b''.join((chr(i // 256) + chr(i % 256)) for i in dbyts) 146 | else: 147 | raise RuntimeError("What family?") 148 | 149 | 150 | def is_ip(address): 151 | for family in (socket.AF_INET, socket.AF_INET6): 152 | try: 153 | if type(address) != str: 154 | address = address.decode('utf8') 155 | inet_pton(family, address) 156 | return family 157 | except (TypeError, ValueError, OSError, IOError): 158 | pass 159 | return False 160 | 161 | 162 | def match_regex(regex, text): 163 | regex = re.compile(regex) 164 | for item in regex.findall(text): 165 | return True 166 | return False 167 | 168 | 169 | def patch_socket(): 170 | if not hasattr(socket, 'inet_pton'): 171 | socket.inet_pton = inet_pton 172 | 173 | if not hasattr(socket, 'inet_ntop'): 174 | socket.inet_ntop = inet_ntop 175 | 176 | 177 | patch_socket() 178 | 179 | 180 | ADDRTYPE_IPV4 = 1 181 | ADDRTYPE_IPV6 = 4 182 | ADDRTYPE_HOST = 3 183 | 184 | 185 | def pack_addr(address): 186 | address_str = to_str(address) 187 | for family in (socket.AF_INET, socket.AF_INET6): 188 | try: 189 | r = socket.inet_pton(family, address_str) 190 | if family == socket.AF_INET6: 191 | return b'\x04' + r 192 | else: 193 | return b'\x01' + r 194 | except (TypeError, ValueError, OSError, IOError): 195 | pass 196 | if len(address) > 255: 197 | address = address[:255] # TODO 198 | return b'\x03' + chr(len(address)) + address 199 | 200 | def pre_parse_header(data): 201 | if not data: 202 | return None 203 | datatype = ord(data[0]) 204 | if datatype == 0x80: 205 | if len(data) <= 2: 206 | return None 207 | rand_data_size = ord(data[1]) 208 | if rand_data_size + 2 >= len(data): 209 | logging.warn('header too short, maybe wrong password or ' 210 | 'encryption method') 211 | return None 212 | data = data[rand_data_size + 2:] 213 | elif datatype == 0x81: 214 | data = data[1:] 215 | elif datatype == 0x82: 216 | if len(data) <= 3: 217 | return None 218 | rand_data_size = struct.unpack('>H', data[1:3])[0] 219 | if rand_data_size + 3 >= len(data): 220 | logging.warn('header too short, maybe wrong password or ' 221 | 'encryption method') 222 | return None 223 | data = data[rand_data_size + 3:] 224 | elif datatype == 0x88 or (~datatype & 0xff) == 0x88: 225 | if len(data) <= 7 + 7: 226 | return None 227 | data_size = struct.unpack('>H', data[1:3])[0] 228 | ogn_data = data 229 | data = data[:data_size] 230 | crc = binascii.crc32(data) & 0xffffffff 231 | if crc != 0xffffffff: 232 | logging.warn('uncorrect CRC32, maybe wrong password or ' 233 | 'encryption method') 234 | return None 235 | start_pos = 3 + ord(data[3]) 236 | data = data[start_pos:-4] 237 | if data_size < len(ogn_data): 238 | data += ogn_data[data_size:] 239 | return data 240 | 241 | def parse_header(data): 242 | addrtype = ord(data[0]) 243 | dest_addr = None 244 | dest_port = None 245 | header_length = 0 246 | connecttype = (addrtype & 0x8) and 1 or 0 247 | addrtype &= ~0x8 248 | if addrtype == ADDRTYPE_IPV4: 249 | if len(data) >= 7: 250 | dest_addr = socket.inet_ntoa(data[1:5]) 251 | dest_port = struct.unpack('>H', data[5:7])[0] 252 | header_length = 7 253 | else: 254 | logging.warn('header is too short') 255 | elif addrtype == ADDRTYPE_HOST: 256 | if len(data) > 2: 257 | addrlen = ord(data[1]) 258 | if len(data) >= 4 + addrlen: 259 | dest_addr = data[2:2 + addrlen] 260 | dest_port = struct.unpack('>H', data[2 + addrlen:4 + 261 | addrlen])[0] 262 | header_length = 4 + addrlen 263 | else: 264 | logging.warn('header is too short') 265 | else: 266 | logging.warn('header is too short') 267 | elif addrtype == ADDRTYPE_IPV6: 268 | if len(data) >= 19: 269 | dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17]) 270 | dest_port = struct.unpack('>H', data[17:19])[0] 271 | header_length = 19 272 | else: 273 | logging.warn('header is too short') 274 | else: 275 | logging.warn('unsupported addrtype %d, maybe wrong password or ' 276 | 'encryption method' % addrtype) 277 | if dest_addr is None: 278 | return None 279 | return connecttype, addrtype, to_bytes(dest_addr), dest_port, header_length 280 | 281 | 282 | class IPNetwork(object): 283 | ADDRLENGTH = {socket.AF_INET: 32, socket.AF_INET6: 128, False: 0} 284 | 285 | def __init__(self, addrs): 286 | self.addrs_str = addrs 287 | self._network_list_v4 = [] 288 | self._network_list_v6 = [] 289 | if type(addrs) == str: 290 | addrs = addrs.split(',') 291 | list(map(self.add_network, addrs)) 292 | 293 | def add_network(self, addr): 294 | if addr is "": 295 | return 296 | block = addr.split('/') 297 | addr_family = is_ip(block[0]) 298 | addr_len = IPNetwork.ADDRLENGTH[addr_family] 299 | if addr_family is socket.AF_INET: 300 | ip, = struct.unpack("!I", socket.inet_aton(block[0])) 301 | elif addr_family is socket.AF_INET6: 302 | hi, lo = struct.unpack("!QQ", inet_pton(addr_family, block[0])) 303 | ip = (hi << 64) | lo 304 | else: 305 | raise Exception("Not a valid CIDR notation: %s" % addr) 306 | if len(block) is 1: 307 | prefix_size = 0 308 | while (ip & 1) == 0 and ip is not 0: 309 | ip >>= 1 310 | prefix_size += 1 311 | logging.warn("You did't specify CIDR routing prefix size for %s, " 312 | "implicit treated as %s/%d" % (addr, addr, addr_len)) 313 | elif block[1].isdigit() and int(block[1]) <= addr_len: 314 | prefix_size = addr_len - int(block[1]) 315 | ip >>= prefix_size 316 | else: 317 | raise Exception("Not a valid CIDR notation: %s" % addr) 318 | if addr_family is socket.AF_INET: 319 | self._network_list_v4.append((ip, prefix_size)) 320 | else: 321 | self._network_list_v6.append((ip, prefix_size)) 322 | 323 | def __contains__(self, addr): 324 | addr_family = is_ip(addr) 325 | if addr_family is socket.AF_INET: 326 | ip, = struct.unpack("!I", socket.inet_aton(addr)) 327 | return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1], 328 | self._network_list_v4)) 329 | elif addr_family is socket.AF_INET6: 330 | hi, lo = struct.unpack("!QQ", inet_pton(addr_family, addr)) 331 | ip = (hi << 64) | lo 332 | return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1], 333 | self._network_list_v6)) 334 | else: 335 | return False 336 | 337 | def __cmp__(self, other): 338 | return cmp(self.addrs_str, other.addrs_str) 339 | 340 | def __eq__(self, other): 341 | return self.addrs_str == other.addrs_str 342 | 343 | def __ne__(self, other): 344 | return self.addrs_str != other.addrs_str 345 | 346 | class PortRange(object): 347 | def __init__(self, range_str): 348 | self.range_str = to_str(range_str) 349 | self.range = set() 350 | range_str = to_str(range_str).split(',') 351 | for item in range_str: 352 | try: 353 | int_range = item.split('-') 354 | if len(int_range) == 1: 355 | if item: 356 | self.range.add(int(item)) 357 | elif len(int_range) == 2: 358 | int_range[0] = int(int_range[0]) 359 | int_range[1] = int(int_range[1]) 360 | if int_range[0] < 0: 361 | int_range[0] = 0 362 | if int_range[1] > 65535: 363 | int_range[1] = 65535 364 | i = int_range[0] 365 | while i <= int_range[1]: 366 | self.range.add(i) 367 | i += 1 368 | except Exception as e: 369 | logging.error(e) 370 | 371 | def __contains__(self, val): 372 | return val in self.range 373 | 374 | def __cmp__(self, other): 375 | return cmp(self.range_str, other.range_str) 376 | 377 | def __eq__(self, other): 378 | return self.range_str == other.range_str 379 | 380 | def __ne__(self, other): 381 | return self.range_str != other.range_str 382 | 383 | class UDPAsyncDNSHandler(object): 384 | dns_cache = lru_cache.LRUCache(timeout=1800) 385 | def __init__(self, params): 386 | self.params = params 387 | self.remote_addr = None 388 | self.call_back = None 389 | 390 | def resolve(self, dns_resolver, remote_addr, call_back): 391 | if remote_addr in UDPAsyncDNSHandler.dns_cache: 392 | if call_back: 393 | call_back("", remote_addr, UDPAsyncDNSHandler.dns_cache[remote_addr], self.params) 394 | else: 395 | self.call_back = call_back 396 | self.remote_addr = remote_addr 397 | dns_resolver.resolve(remote_addr[0], self._handle_dns_resolved) 398 | UDPAsyncDNSHandler.dns_cache.sweep() 399 | 400 | def _handle_dns_resolved(self, result, error): 401 | if error: 402 | logging.error("%s when resolve DNS" % (error,)) #drop 403 | return self.call_back(error, self.remote_addr, None, self.params) 404 | if result: 405 | ip = result[1] 406 | if ip: 407 | return self.call_back("", self.remote_addr, ip, self.params) 408 | logging.warning("can't resolve %s" % (self.remote_addr,)) 409 | return self.call_back("fail to resolve", self.remote_addr, None, self.params) 410 | 411 | def test_inet_conv(): 412 | ipv4 = b'8.8.4.4' 413 | b = inet_pton(socket.AF_INET, ipv4) 414 | assert inet_ntop(socket.AF_INET, b) == ipv4 415 | ipv6 = b'2404:6800:4005:805::1011' 416 | b = inet_pton(socket.AF_INET6, ipv6) 417 | assert inet_ntop(socket.AF_INET6, b) == ipv6 418 | 419 | 420 | def test_parse_header(): 421 | assert parse_header(b'\x03\x0ewww.google.com\x00\x50') == \ 422 | (0, b'www.google.com', 80, 18) 423 | assert parse_header(b'\x01\x08\x08\x08\x08\x00\x35') == \ 424 | (0, b'8.8.8.8', 53, 7) 425 | assert parse_header((b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00' 426 | b'\x00\x10\x11\x00\x50')) == \ 427 | (0, b'2404:6800:4005:805::1011', 80, 19) 428 | 429 | 430 | def test_pack_header(): 431 | assert pack_addr(b'8.8.8.8') == b'\x01\x08\x08\x08\x08' 432 | assert pack_addr(b'2404:6800:4005:805::1011') == \ 433 | b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00\x00\x10\x11' 434 | assert pack_addr(b'www.google.com') == b'\x03\x0ewww.google.com' 435 | 436 | 437 | def test_ip_network(): 438 | ip_network = IPNetwork('127.0.0.0/24,::ff:1/112,::1,192.168.1.1,192.0.2.0') 439 | assert '127.0.0.1' in ip_network 440 | assert '127.0.1.1' not in ip_network 441 | assert ':ff:ffff' in ip_network 442 | assert '::ffff:1' not in ip_network 443 | assert '::1' in ip_network 444 | assert '::2' not in ip_network 445 | assert '192.168.1.1' in ip_network 446 | assert '192.168.1.2' not in ip_network 447 | assert '192.0.2.1' in ip_network 448 | assert '192.0.3.1' in ip_network # 192.0.2.0 is treated as 192.0.2.0/23 449 | assert 'www.google.com' not in ip_network 450 | 451 | 452 | if __name__ == '__main__': 453 | test_inet_conv() 454 | test_parse_header() 455 | test_pack_header() 456 | test_ip_network() 457 | -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: clowwindy 6 | @modify: valor. 7 | @file: conf.py (renamed from 'version.py') 8 | 9 | Copyright 2017 breakwa11 10 | Copyright 2019 valord577 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); you may 13 | not use this file except in compliance with the License. You may obtain 14 | a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 20 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 21 | License for the specific language governing permissions and limitations 22 | under the License. 23 | """ 24 | 25 | 26 | def version() -> str: 27 | return 'ShadowsocksR-Refactor [server] v4.1.2 Snapshot at 2019-08-22' 28 | 29 | 30 | # @return: the path of ssr conf file 31 | def ssr_conf_path() -> str: 32 | # relative path 33 | # or you can use absolute path 34 | return 'user-config.json' 35 | 36 | 37 | def logger_init(): 38 | import logging 39 | logging.basicConfig(level=1, 40 | format='%(asctime)s [%(levelname)-8s] :%(lineno)-4d %(filename)-20s %(message)s', 41 | datefmt='%Y-%m-%d %H:%M:%S') 42 | 43 | 44 | def help_msg() -> str: 45 | return '''Usage: ShadowsocksR-Refactor [server]... 46 | A fast tunnel proxy that helps you bypass firewalls. 47 | 48 | You can supply configurations via a configuration file (json file) only. 49 | 50 | General options: 51 | -h, --help show this help message and exit 52 | -v, --version show version information 53 | 54 | Online help: 55 | ''' 56 | -------------------------------------------------------------------------------- /conf/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns": [ 3 | {"host": "8.8.8.8", "port": 53}, 4 | {"host": "8.8.4.4", "port": 53} 5 | ], 6 | 7 | "server_port": 8388, 8 | "password": "m", 9 | "method": "aes-128-ctr", 10 | "protocol": "auth_aes128_md5", 11 | "protocol_param": "", 12 | "obfs": "tls1.2_ticket_auth_compatible", 13 | "obfs_param": "", 14 | 15 | "timeout": 120, 16 | 17 | "udp_enable": false, 18 | "udp_timeout": 60, 19 | "udp_cache": 64, 20 | 21 | "redirect": "" 22 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | import logging 24 | from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \ 25 | create_string_buffer, c_void_p 26 | 27 | __all__ = ['ciphers'] 28 | 29 | libsodium = None 30 | loaded = False 31 | 32 | buf_size = 2048 33 | 34 | # for salsa20 and chacha20 35 | BLOCK_SIZE = 64 36 | 37 | 38 | def load_libsodium(): 39 | global loaded, libsodium, buf 40 | 41 | from ctypes.util import find_library 42 | for p in ('sodium',): 43 | libsodium_path = find_library(p) 44 | if libsodium_path: 45 | break 46 | else: 47 | raise Exception('libsodium not found') 48 | logging.info('loading libsodium from %s', libsodium_path) 49 | libsodium = CDLL(libsodium_path) 50 | libsodium.sodium_init.restype = c_int 51 | libsodium.crypto_stream_salsa20_xor_ic.restype = c_int 52 | libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, 53 | c_ulonglong, 54 | c_char_p, c_ulonglong, 55 | c_char_p) 56 | libsodium.crypto_stream_chacha20_xor_ic.restype = c_int 57 | libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p, 58 | c_ulonglong, 59 | c_char_p, c_ulonglong, 60 | c_char_p) 61 | 62 | libsodium.sodium_init() 63 | 64 | buf = create_string_buffer(buf_size) 65 | loaded = True 66 | 67 | 68 | class Salsa20Crypto(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 == b'salsa20': 77 | self.cipher = libsodium.crypto_stream_salsa20_xor_ic 78 | elif cipher_name == b'chacha20': 79 | self.cipher = libsodium.crypto_stream_chacha20_xor_ic 80 | else: 81 | raise Exception('Unknown cipher') 82 | # byte counter, not block counter 83 | self.counter = 0 84 | 85 | def update(self, data): 86 | global buf_size, buf 87 | l = len(data) 88 | 89 | # we can only prepend some padding to make the encryption align to 90 | # blocks 91 | padding = self.counter % BLOCK_SIZE 92 | if buf_size < padding + l: 93 | buf_size = (padding + l) * 2 94 | buf = create_string_buffer(buf_size) 95 | 96 | if padding: 97 | data = (b'\0' * padding) + data 98 | self.cipher(byref(buf), c_char_p(data), padding + l, 99 | self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr) 100 | self.counter += l 101 | # buf is copied to a str object when we access buf.raw 102 | # strip off the padding 103 | return buf.raw[padding:padding + l] 104 | 105 | 106 | ciphers = { 107 | b'salsa20': (32, 8, Salsa20Crypto), 108 | b'chacha20': (32, 8, Salsa20Crypto), 109 | } 110 | 111 | 112 | def test_salsa20(): 113 | from crypto import util 114 | 115 | cipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 1) 116 | decipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 0) 117 | 118 | util.run_cipher(cipher, decipher) 119 | 120 | 121 | def test_chacha20(): 122 | from crypto import util 123 | 124 | cipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 1) 125 | decipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 0) 126 | 127 | util.run_cipher(cipher, decipher) 128 | 129 | 130 | if __name__ == '__main__': 131 | test_chacha20() 132 | test_salsa20() 133 | -------------------------------------------------------------------------------- /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 | import logging 24 | from ctypes import CDLL, c_char_p, c_int, c_long, byref,\ 25 | create_string_buffer, c_void_p 26 | 27 | __all__ = ['ciphers'] 28 | 29 | libcrypto = None 30 | loaded = False 31 | 32 | buf_size = 2048 33 | 34 | 35 | def load_openssl(): 36 | global loaded, libcrypto, buf 37 | 38 | from ctypes.util import find_library 39 | for p in ('crypto', 'eay32', 'libeay32'): 40 | libcrypto_path = find_library(p) 41 | if libcrypto_path: 42 | break 43 | else: 44 | raise Exception('libcrypto(OpenSSL) not found') 45 | logging.info('loading libcrypto from %s', libcrypto_path) 46 | libcrypto = CDLL(libcrypto_path) 47 | libcrypto.EVP_get_cipherbyname.restype = c_void_p 48 | libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p 49 | 50 | libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, 51 | c_char_p, c_char_p, c_int) 52 | 53 | libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, 54 | c_char_p, c_int) 55 | 56 | libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) 57 | libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) 58 | if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): 59 | libcrypto.OpenSSL_add_all_ciphers() 60 | 61 | buf = create_string_buffer(buf_size) 62 | loaded = True 63 | 64 | 65 | def load_cipher(cipher_name): 66 | func_name = b'EVP_' + cipher_name.replace(b'-', b'_') 67 | if bytes != str: 68 | func_name = str(func_name, 'utf-8') 69 | cipher = getattr(libcrypto, func_name, None) 70 | if cipher: 71 | cipher.restype = c_void_p 72 | return cipher() 73 | return None 74 | 75 | 76 | class CtypesCrypto(object): 77 | def __init__(self, cipher_name, key, iv, op): 78 | if not loaded: 79 | load_openssl() 80 | self._ctx = None 81 | cipher = libcrypto.EVP_get_cipherbyname(cipher_name) 82 | if not cipher: 83 | cipher = load_cipher(cipher_name) 84 | if not cipher: 85 | raise Exception('cipher %s not found in libcrypto' % cipher_name) 86 | key_ptr = c_char_p(key) 87 | iv_ptr = c_char_p(iv) 88 | self._ctx = libcrypto.EVP_CIPHER_CTX_new() 89 | if not self._ctx: 90 | raise Exception('can not create cipher context') 91 | r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None, 92 | key_ptr, iv_ptr, c_int(op)) 93 | if not r: 94 | self.clean() 95 | raise Exception('can not initialize cipher context') 96 | 97 | def update(self, data): 98 | global buf_size, buf 99 | cipher_out_len = c_long(0) 100 | l = len(data) 101 | if buf_size < l: 102 | buf_size = l * 2 103 | buf = create_string_buffer(buf_size) 104 | libcrypto.EVP_CipherUpdate(self._ctx, byref(buf), 105 | byref(cipher_out_len), c_char_p(data), l) 106 | # buf is copied to a str object when we access buf.raw 107 | return buf.raw[:cipher_out_len.value] 108 | 109 | def __del__(self): 110 | self.clean() 111 | 112 | def clean(self): 113 | if self._ctx: 114 | libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx) 115 | libcrypto.EVP_CIPHER_CTX_free(self._ctx) 116 | 117 | 118 | ciphers = { 119 | b'aes-128-cfb': (16, 16, CtypesCrypto), 120 | b'aes-192-cfb': (24, 16, CtypesCrypto), 121 | b'aes-256-cfb': (32, 16, CtypesCrypto), 122 | b'aes-128-ofb': (16, 16, CtypesCrypto), 123 | b'aes-192-ofb': (24, 16, CtypesCrypto), 124 | b'aes-256-ofb': (32, 16, CtypesCrypto), 125 | b'aes-128-ctr': (16, 16, CtypesCrypto), 126 | b'aes-192-ctr': (24, 16, CtypesCrypto), 127 | b'aes-256-ctr': (32, 16, CtypesCrypto), 128 | b'aes-128-cfb8': (16, 16, CtypesCrypto), 129 | b'aes-192-cfb8': (24, 16, CtypesCrypto), 130 | b'aes-256-cfb8': (32, 16, CtypesCrypto), 131 | b'aes-128-cfb1': (16, 16, CtypesCrypto), 132 | b'aes-192-cfb1': (24, 16, CtypesCrypto), 133 | b'aes-256-cfb1': (32, 16, CtypesCrypto), 134 | b'bf-cfb': (16, 8, CtypesCrypto), 135 | b'camellia-128-cfb': (16, 16, CtypesCrypto), 136 | b'camellia-192-cfb': (24, 16, CtypesCrypto), 137 | b'camellia-256-cfb': (32, 16, CtypesCrypto), 138 | b'cast5-cfb': (16, 8, CtypesCrypto), 139 | b'des-cfb': (8, 8, CtypesCrypto), 140 | b'idea-cfb': (16, 8, CtypesCrypto), 141 | b'rc2-cfb': (16, 8, CtypesCrypto), 142 | b'rc4': (16, 0, CtypesCrypto), 143 | b'seed-cfb': (16, 16, CtypesCrypto), 144 | } 145 | 146 | 147 | def run_method(method): 148 | from crypto import util 149 | 150 | cipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 1) 151 | decipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 0) 152 | 153 | util.run_cipher(cipher, decipher) 154 | 155 | 156 | def test_aes_128_cfb(): 157 | run_method(b'aes-128-cfb') 158 | 159 | 160 | def test_aes_256_cfb(): 161 | run_method(b'aes-256-cfb') 162 | 163 | 164 | def test_aes_128_cfb8(): 165 | run_method(b'aes-128-cfb8') 166 | 167 | 168 | def test_aes_256_ofb(): 169 | run_method(b'aes-256-ofb') 170 | 171 | 172 | def test_aes_256_ctr(): 173 | run_method(b'aes-256-ctr') 174 | 175 | 176 | def test_bf_cfb(): 177 | run_method(b'bf-cfb') 178 | 179 | 180 | def test_rc4(): 181 | run_method(b'rc4') 182 | 183 | 184 | if __name__ == '__main__': 185 | test_aes_128_cfb() 186 | -------------------------------------------------------------------------------- /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 ctypes import c_char_p, c_int, c_long, byref,\ 18 | create_string_buffer, c_void_p 19 | 20 | import common 21 | from crypto import util 22 | 23 | __all__ = ['ciphers'] 24 | 25 | libcrypto = None 26 | loaded = False 27 | 28 | buf_size = 2048 29 | 30 | 31 | def load_openssl(): 32 | global loaded, libcrypto, buf 33 | 34 | libcrypto = util.find_library(('crypto', 'eay32'), 35 | 'EVP_get_cipherbyname', 36 | 'libcrypto') 37 | if libcrypto is None: 38 | raise Exception('libcrypto(OpenSSL) not found') 39 | 40 | libcrypto.EVP_get_cipherbyname.restype = c_void_p 41 | libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p 42 | 43 | libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, 44 | c_char_p, c_char_p, c_int) 45 | 46 | libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, 47 | c_char_p, c_int) 48 | 49 | if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"): 50 | libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) 51 | else: 52 | libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,) 53 | libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) 54 | 55 | libcrypto.RAND_bytes.restype = c_int 56 | libcrypto.RAND_bytes.argtypes = (c_void_p, c_int) 57 | 58 | if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): 59 | libcrypto.OpenSSL_add_all_ciphers() 60 | 61 | buf = create_string_buffer(buf_size) 62 | loaded = True 63 | 64 | 65 | def load_cipher(cipher_name): 66 | func_name = 'EVP_' + cipher_name.replace('-', '_') 67 | cipher = getattr(libcrypto, func_name, None) 68 | if cipher: 69 | cipher.restype = c_void_p 70 | return cipher() 71 | return None 72 | 73 | def rand_bytes(length): 74 | if not loaded: 75 | load_openssl() 76 | buf = create_string_buffer(length) 77 | r = libcrypto.RAND_bytes(buf, length) 78 | if r <= 0: 79 | raise Exception('RAND_bytes return error') 80 | return buf.raw 81 | 82 | class OpenSSLCrypto(object): 83 | def __init__(self, cipher_name, key, iv, op): 84 | self._ctx = None 85 | if not loaded: 86 | load_openssl() 87 | cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(cipher_name)) 88 | if not cipher: 89 | cipher = load_cipher(cipher_name) 90 | if not cipher: 91 | raise Exception('cipher %s not found in libcrypto' % cipher_name) 92 | key_ptr = c_char_p(key) 93 | iv_ptr = c_char_p(iv) 94 | self._ctx = libcrypto.EVP_CIPHER_CTX_new() 95 | if not self._ctx: 96 | raise Exception('can not create cipher context') 97 | r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None, 98 | key_ptr, iv_ptr, c_int(op)) 99 | if not r: 100 | self.clean() 101 | raise Exception('can not initialize cipher context') 102 | 103 | def update(self, data): 104 | global buf_size, buf 105 | cipher_out_len = c_long(0) 106 | l = len(data) 107 | if buf_size < l: 108 | buf_size = l * 2 109 | buf = create_string_buffer(buf_size) 110 | libcrypto.EVP_CipherUpdate(self._ctx, byref(buf), 111 | byref(cipher_out_len), c_char_p(data), l) 112 | # buf is copied to a str object when we access buf.raw 113 | return buf.raw[:cipher_out_len.value] 114 | 115 | def __del__(self): 116 | self.clean() 117 | 118 | def clean(self): 119 | if self._ctx: 120 | if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"): 121 | libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx) 122 | else: 123 | libcrypto.EVP_CIPHER_CTX_reset(self._ctx) 124 | libcrypto.EVP_CIPHER_CTX_free(self._ctx) 125 | 126 | 127 | ciphers = { 128 | 'aes-128-cbc': (16, 16, OpenSSLCrypto), 129 | 'aes-192-cbc': (24, 16, OpenSSLCrypto), 130 | 'aes-256-cbc': (32, 16, OpenSSLCrypto), 131 | 'aes-128-cfb': (16, 16, OpenSSLCrypto), 132 | 'aes-192-cfb': (24, 16, OpenSSLCrypto), 133 | 'aes-256-cfb': (32, 16, OpenSSLCrypto), 134 | 'aes-128-ofb': (16, 16, OpenSSLCrypto), 135 | 'aes-192-ofb': (24, 16, OpenSSLCrypto), 136 | 'aes-256-ofb': (32, 16, OpenSSLCrypto), 137 | 'aes-128-ctr': (16, 16, OpenSSLCrypto), 138 | 'aes-192-ctr': (24, 16, OpenSSLCrypto), 139 | 'aes-256-ctr': (32, 16, OpenSSLCrypto), 140 | 'aes-128-cfb8': (16, 16, OpenSSLCrypto), 141 | 'aes-192-cfb8': (24, 16, OpenSSLCrypto), 142 | 'aes-256-cfb8': (32, 16, OpenSSLCrypto), 143 | 'aes-128-cfb1': (16, 16, OpenSSLCrypto), 144 | 'aes-192-cfb1': (24, 16, OpenSSLCrypto), 145 | 'aes-256-cfb1': (32, 16, OpenSSLCrypto), 146 | 'bf-cfb': (16, 8, OpenSSLCrypto), 147 | 'camellia-128-cfb': (16, 16, OpenSSLCrypto), 148 | 'camellia-192-cfb': (24, 16, OpenSSLCrypto), 149 | 'camellia-256-cfb': (32, 16, OpenSSLCrypto), 150 | 'cast5-cfb': (16, 8, OpenSSLCrypto), 151 | 'des-cfb': (8, 8, OpenSSLCrypto), 152 | 'idea-cfb': (16, 8, OpenSSLCrypto), 153 | 'rc2-cfb': (16, 8, OpenSSLCrypto), 154 | 'rc4': (16, 0, OpenSSLCrypto), 155 | 'seed-cfb': (16, 16, OpenSSLCrypto), 156 | } 157 | 158 | 159 | def run_method(method): 160 | 161 | cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1) 162 | decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0) 163 | 164 | util.run_cipher(cipher, decipher) 165 | 166 | 167 | def test_aes_128_cfb(): 168 | run_method('aes-128-cfb') 169 | 170 | 171 | def test_aes_256_cfb(): 172 | run_method('aes-256-cfb') 173 | 174 | 175 | def test_aes_128_cfb8(): 176 | run_method('aes-128-cfb8') 177 | 178 | 179 | def test_aes_256_ofb(): 180 | run_method('aes-256-ofb') 181 | 182 | 183 | def test_aes_256_ctr(): 184 | run_method('aes-256-ctr') 185 | 186 | 187 | def test_bf_cfb(): 188 | run_method('bf-cfb') 189 | 190 | 191 | def test_rc4(): 192 | run_method('rc4') 193 | 194 | 195 | if __name__ == '__main__': 196 | test_aes_128_cfb() 197 | -------------------------------------------------------------------------------- /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 | import hashlib 18 | 19 | from crypto import openssl 20 | 21 | __all__ = ['ciphers'] 22 | 23 | 24 | def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, 25 | i=1, padding=1): 26 | md5 = hashlib.md5() 27 | md5.update(key) 28 | md5.update(iv) 29 | rc4_key = md5.digest() 30 | return openssl.OpenSSLCrypto(b'rc4', rc4_key, b'', op) 31 | 32 | 33 | ciphers = { 34 | 'rc4-md5': (16, 16, create_cipher), 35 | 'rc4-md5-6': (16, 6, create_cipher), 36 | } 37 | 38 | 39 | def test(): 40 | from crypto import util 41 | 42 | cipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 1) 43 | decipher = create_cipher('rc4-md5', b'k' * 32, b'i' * 16, 0) 44 | 45 | util.run_cipher(cipher, decipher) 46 | 47 | 48 | if __name__ == '__main__': 49 | test() 50 | -------------------------------------------------------------------------------- /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 ctypes import c_char_p, c_int, c_ulong, c_ulonglong, byref, \ 18 | create_string_buffer, c_void_p 19 | 20 | from crypto import util 21 | 22 | __all__ = ['ciphers'] 23 | 24 | libsodium = None 25 | loaded = False 26 | 27 | buf_size = 2048 28 | 29 | # for salsa20 and chacha20 and chacha20-ietf 30 | BLOCK_SIZE = 64 31 | 32 | 33 | def load_libsodium(): 34 | global loaded, libsodium, buf 35 | 36 | libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', 37 | 'libsodium') 38 | if libsodium is None: 39 | raise Exception('libsodium not found') 40 | 41 | libsodium.crypto_stream_salsa20_xor_ic.restype = c_int 42 | libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, 43 | c_ulonglong, 44 | c_char_p, c_ulonglong, 45 | c_char_p) 46 | libsodium.crypto_stream_chacha20_xor_ic.restype = c_int 47 | libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p, 48 | c_ulonglong, 49 | c_char_p, c_ulonglong, 50 | c_char_p) 51 | 52 | try: 53 | libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int 54 | libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (c_void_p, c_char_p, 55 | c_ulonglong, 56 | c_char_p, c_ulong, 57 | c_char_p) 58 | except: 59 | pass 60 | 61 | buf = create_string_buffer(buf_size) 62 | loaded = True 63 | 64 | 65 | class SodiumCrypto(object): 66 | def __init__(self, cipher_name, key, iv, op): 67 | if not loaded: 68 | load_libsodium() 69 | self.key = key 70 | self.iv = iv 71 | self.key_ptr = c_char_p(key) 72 | self.iv_ptr = c_char_p(iv) 73 | if cipher_name == 'salsa20': 74 | self.cipher = libsodium.crypto_stream_salsa20_xor_ic 75 | elif cipher_name == 'chacha20': 76 | self.cipher = libsodium.crypto_stream_chacha20_xor_ic 77 | elif cipher_name == 'chacha20-ietf': 78 | self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic 79 | else: 80 | raise Exception('Unknown cipher') 81 | # byte counter, not block counter 82 | self.counter = 0 83 | 84 | def update(self, data): 85 | global buf_size, buf 86 | l = len(data) 87 | 88 | # we can only prepend some padding to make the encryption align to 89 | # blocks 90 | padding = self.counter % BLOCK_SIZE 91 | if buf_size < padding + l: 92 | buf_size = (padding + l) * 2 93 | buf = create_string_buffer(buf_size) 94 | 95 | if padding: 96 | data = (b'\0' * padding) + data 97 | self.cipher(byref(buf), c_char_p(data), padding + l, 98 | self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr) 99 | self.counter += l 100 | # buf is copied to a str object when we access buf.raw 101 | # strip off the padding 102 | return buf.raw[padding:padding + l] 103 | 104 | 105 | ciphers = { 106 | 'salsa20': (32, 8, SodiumCrypto), 107 | 'chacha20': (32, 8, SodiumCrypto), 108 | 'chacha20-ietf': (32, 12, SodiumCrypto), 109 | } 110 | 111 | 112 | def test_salsa20(): 113 | cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1) 114 | decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0) 115 | 116 | util.run_cipher(cipher, decipher) 117 | 118 | 119 | def test_chacha20(): 120 | 121 | cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) 122 | decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) 123 | 124 | util.run_cipher(cipher, decipher) 125 | 126 | 127 | def test_chacha20_ietf(): 128 | 129 | cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) 130 | decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) 131 | 132 | util.run_cipher(cipher, decipher) 133 | 134 | if __name__ == '__main__': 135 | test_chacha20_ietf() 136 | test_chacha20() 137 | test_salsa20() 138 | -------------------------------------------------------------------------------- /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 | import string 18 | import struct 19 | import hashlib 20 | 21 | 22 | __all__ = ['ciphers'] 23 | 24 | cached_tables = {} 25 | 26 | if hasattr(string, 'maketrans'): 27 | maketrans = string.maketrans 28 | translate = string.translate 29 | else: 30 | maketrans = bytes.maketrans 31 | translate = bytes.translate 32 | 33 | 34 | def get_table(key): 35 | m = hashlib.md5() 36 | m.update(key) 37 | s = m.digest() 38 | a, b = struct.unpack(' bool: 42 | m = method_supported.get(method) 43 | if m is None: 44 | return False 45 | return True 46 | 47 | 48 | def random_string(length): 49 | try: 50 | return os.urandom(length) 51 | except NotImplementedError as e: 52 | return openssl.rand_bytes(length) 53 | 54 | cached_keys = {} 55 | 56 | 57 | def try_cipher(key, method=None): 58 | Encryptor(key, method) 59 | 60 | 61 | def EVP_BytesToKey(password, key_len, iv_len): 62 | # equivalent to OpenSSL's EVP_BytesToKey() with count 1 63 | # so that we make the same key and iv as nodejs version 64 | if hasattr(password, 'encode'): 65 | password = password.encode('utf-8') 66 | cached_key = '%s-%d-%d' % (password, key_len, iv_len) 67 | r = cached_keys.get(cached_key, None) 68 | if r: 69 | return r 70 | m = [] 71 | i = 0 72 | while len(b''.join(m)) < (key_len + iv_len): 73 | md5 = hashlib.md5() 74 | data = password 75 | if i > 0: 76 | data = m[i - 1] + password 77 | md5.update(data) 78 | m.append(md5.digest()) 79 | i += 1 80 | ms = b''.join(m) 81 | key = ms[:key_len] 82 | iv = ms[key_len:key_len + iv_len] 83 | cached_keys[cached_key] = (key, iv) 84 | return key, iv 85 | 86 | 87 | class Encryptor(object): 88 | def __init__(self, key, method, iv = None): 89 | self.key = key 90 | self.method = method 91 | self.iv = None 92 | self.iv_sent = False 93 | self.cipher_iv = b'' 94 | self.iv_buf = b'' 95 | self.cipher_key = b'' 96 | self.decipher = None 97 | method = method.lower() 98 | self._method_info = self.get_method_info(method) 99 | if self._method_info: 100 | if iv is None or len(iv) != self._method_info[1]: 101 | self.cipher = self.get_cipher(key, method, 1, 102 | random_string(self._method_info[1])) 103 | else: 104 | self.cipher = self.get_cipher(key, method, 1, iv) 105 | else: 106 | logging.error('method %s not supported' % method) 107 | sys.exit(1) 108 | 109 | def get_method_info(self, method): 110 | method = method.lower() 111 | m = method_supported.get(method) 112 | return m 113 | 114 | def iv_len(self): 115 | return len(self.cipher_iv) 116 | 117 | def get_cipher(self, password, method, op, iv): 118 | password = common.to_bytes(password) 119 | m = self._method_info 120 | if m[0] > 0: 121 | key, iv_ = EVP_BytesToKey(password, m[0], m[1]) 122 | else: 123 | # key_length == 0 indicates we should use the key directly 124 | key, iv = password, b'' 125 | 126 | iv = iv[:m[1]] 127 | if op == 1: 128 | # this iv is for cipher not decipher 129 | self.cipher_iv = iv[:m[1]] 130 | self.cipher_key = key 131 | return m[2](method, key, iv, op) 132 | 133 | def encrypt(self, buf): 134 | if len(buf) == 0: 135 | return buf 136 | if self.iv_sent: 137 | return self.cipher.update(buf) 138 | else: 139 | self.iv_sent = True 140 | return self.cipher_iv + self.cipher.update(buf) 141 | 142 | def decrypt(self, buf): 143 | if len(buf) == 0: 144 | return buf 145 | if self.decipher is not None: #optimize 146 | return self.decipher.update(buf) 147 | 148 | decipher_iv_len = self._method_info[1] 149 | if len(self.iv_buf) <= decipher_iv_len: 150 | self.iv_buf += buf 151 | if len(self.iv_buf) > decipher_iv_len: 152 | decipher_iv = self.iv_buf[:decipher_iv_len] 153 | self.decipher = self.get_cipher(self.key, self.method, 0, 154 | iv=decipher_iv) 155 | buf = self.iv_buf[decipher_iv_len:] 156 | del self.iv_buf 157 | return self.decipher.update(buf) 158 | else: 159 | return b'' 160 | 161 | def encrypt_all(password, method, op, data): 162 | result = [] 163 | method = method.lower() 164 | (key_len, iv_len, m) = method_supported[method] 165 | if key_len > 0: 166 | key, _ = EVP_BytesToKey(password, key_len, iv_len) 167 | else: 168 | key = password 169 | if op: 170 | iv = random_string(iv_len) 171 | result.append(iv) 172 | else: 173 | iv = data[:iv_len] 174 | data = data[iv_len:] 175 | cipher = m(method, key, iv, op) 176 | result.append(cipher.update(data)) 177 | return b''.join(result) 178 | 179 | def encrypt_key(password, method): 180 | method = method.lower() 181 | (key_len, iv_len, m) = method_supported[method] 182 | if key_len > 0: 183 | key, _ = EVP_BytesToKey(password, key_len, iv_len) 184 | else: 185 | key = password 186 | return key 187 | 188 | def encrypt_iv_len(method): 189 | method = method.lower() 190 | (key_len, iv_len, m) = method_supported[method] 191 | return iv_len 192 | 193 | def encrypt_new_iv(method): 194 | method = method.lower() 195 | (key_len, iv_len, m) = method_supported[method] 196 | return random_string(iv_len) 197 | 198 | def encrypt_all_iv(key, method, op, data, ref_iv): 199 | result = [] 200 | method = method.lower() 201 | (key_len, iv_len, m) = method_supported[method] 202 | if op: 203 | iv = ref_iv[0] 204 | result.append(iv) 205 | else: 206 | iv = data[:iv_len] 207 | data = data[iv_len:] 208 | ref_iv[0] = iv 209 | cipher = m(method, key, iv, op) 210 | result.append(cipher.update(data)) 211 | return b''.join(result) 212 | 213 | 214 | CIPHERS_TO_TEST = [ 215 | 'aes-128-cfb', 216 | 'aes-256-cfb', 217 | 'rc4-md5', 218 | 'salsa20', 219 | 'chacha20', 220 | 'table', 221 | ] 222 | 223 | 224 | def test_encryptor(): 225 | from os import urandom 226 | plain = urandom(10240) 227 | for method in CIPHERS_TO_TEST: 228 | logging.warn(method) 229 | encryptor = Encryptor(b'key', method) 230 | decryptor = Encryptor(b'key', method) 231 | cipher = encryptor.encrypt(plain) 232 | plain2 = decryptor.decrypt(cipher) 233 | assert plain == plain2 234 | 235 | 236 | def test_encrypt_all(): 237 | from os import urandom 238 | plain = urandom(10240) 239 | for method in CIPHERS_TO_TEST: 240 | logging.warn(method) 241 | cipher = encrypt_all(b'key', method, 1, plain) 242 | plain2 = encrypt_all(b'key', method, 0, cipher) 243 | assert plain == plain2 244 | 245 | 246 | if __name__ == '__main__': 247 | test_encrypt_all() 248 | test_encryptor() 249 | -------------------------------------------------------------------------------- /encrypt_test.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, \ 2 | with_statement 3 | 4 | import sys 5 | import os 6 | 7 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../')) 8 | 9 | 10 | from crypto import rc4_md5 11 | from crypto import openssl 12 | from crypto import sodium 13 | 14 | def run(func): 15 | try: 16 | func() 17 | except: 18 | pass 19 | 20 | def run_n(func, name): 21 | try: 22 | func(name) 23 | except: 24 | pass 25 | 26 | def main(): 27 | print("\n""rc4_md5") 28 | rc4_md5.test() 29 | print("\n""aes-256-cfb") 30 | openssl.test_aes_256_cfb() 31 | print("\n""aes-128-cfb") 32 | openssl.test_aes_128_cfb() 33 | print("\n""bf-cfb") 34 | run(openssl.test_bf_cfb) 35 | print("\n""camellia-128-cfb") 36 | run_n(openssl.run_method, "camellia-128-cfb") 37 | print("\n""cast5-cfb") 38 | run_n(openssl.run_method, "cast5-cfb") 39 | print("\n""idea-cfb") 40 | run_n(openssl.run_method, "idea-cfb") 41 | print("\n""seed-cfb") 42 | run_n(openssl.run_method, "seed-cfb") 43 | print("\n""salsa20") 44 | run(sodium.test_salsa20) 45 | print("\n""chacha20") 46 | run(sodium.test_chacha20) 47 | 48 | if __name__ == '__main__': 49 | main() 50 | 51 | -------------------------------------------------------------------------------- /eventloop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: clowwindy 6 | @modify: valor. 7 | @file: eventloop.py 8 | 9 | Copyright 2013-2015 clowwindy 10 | Copyright 2019 valord577 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); you may 13 | not use this file except in compliance with the License. You may obtain 14 | a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 20 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 21 | License for the specific language governing permissions and limitations 22 | under the License. 23 | """ 24 | 25 | # from ssloop 26 | # https://github.com/clowwindy/ssloop 27 | 28 | import os 29 | import time 30 | import socket 31 | import select 32 | import errno 33 | import logging 34 | # -- import from shadowsockesr-v 35 | import exit 36 | 37 | 38 | POLL_NULL = 0x00 39 | POLL_IN = select.EPOLLIN 40 | POLL_OUT = select.EPOLLOUT 41 | POLL_ERR = select.EPOLLERR 42 | POLL_HUP = select.EPOLLHUP 43 | POLL_NVAL = select.POLLNVAL 44 | 45 | 46 | EVENT_NAMES = { 47 | POLL_NULL: 'POLL_NULL', 48 | POLL_IN: 'POLL_IN', 49 | POLL_OUT: 'POLL_OUT', 50 | POLL_ERR: 'POLL_ERR', 51 | POLL_HUP: 'POLL_HUP', 52 | POLL_NVAL: 'POLL_NVAL', 53 | } 54 | 55 | # we check timeouts every TIMEOUT_PRECISION seconds 56 | TIMEOUT_PRECISION = 2 57 | 58 | 59 | class EventLoop: 60 | 61 | _instance = None 62 | 63 | # singleton 64 | def __new__(cls): 65 | if not cls._instance: 66 | cls._instance = super().__new__(cls) 67 | return cls._instance 68 | 69 | def __init__(self): 70 | # only support Linux and require 'epoll' model. 71 | if hasattr(select, 'epoll'): 72 | self.epoll = select.epoll() 73 | logging.info('Using event model: epoll.') 74 | else: 75 | exit.error('Only support Linux and require \'epoll\' model.') 76 | 77 | self._fdmap = {} # (f, handler) 78 | self._last_time = time.time() 79 | self._periodic_callbacks = [] 80 | self._stopping = False 81 | 82 | def poll(self, timeout=None): 83 | events = self.epoll.poll(timeout) 84 | return [(self._fdmap[fd][0], fd, event) for fd, event in events] 85 | 86 | def add(self, socks, mode, handler): 87 | fd = socks.fileno() 88 | self._fdmap[fd] = (socks, handler) 89 | self.epoll.register(fd, mode) 90 | 91 | def remove(self, socks): 92 | fd = socks.fileno() 93 | del self._fdmap[fd] 94 | self.epoll.unregister(fd) 95 | 96 | def removefd(self, fd): 97 | del self._fdmap[fd] 98 | self.epoll.unregister(fd) 99 | 100 | def add_periodic(self, callback): 101 | self._periodic_callbacks.append(callback) 102 | 103 | def remove_periodic(self, callback): 104 | self._periodic_callbacks.remove(callback) 105 | 106 | def modify(self, f, mode): 107 | fd = f.fileno() 108 | self.epoll.modify(fd, mode) 109 | 110 | def stop(self): 111 | self._stopping = True 112 | 113 | def run(self): 114 | events = [] 115 | while not self._stopping: 116 | asap = False 117 | try: 118 | events = self.poll(TIMEOUT_PRECISION) 119 | except (OSError, IOError) as e: 120 | if errno_from_exception(e) in (errno.EPIPE, errno.EINTR): 121 | # EPIPE: Happens when the client closes the connection 122 | # EINTR: Happens when received a signal 123 | # handles them as soon as possible 124 | asap = True 125 | logging.debug('poll:%s', e) 126 | else: 127 | logging.error('poll:%s', e) 128 | import traceback 129 | traceback.print_exc() 130 | continue 131 | 132 | handle = False 133 | for sock, fd, event in events: 134 | handler = self._fdmap.get(fd, None) 135 | if handler is not None: 136 | handler = handler[1] 137 | try: 138 | handle = handler.handle_event(sock, fd, event) or handle 139 | except (OSError, IOError) as e: 140 | logging.error(e) 141 | now = time.time() 142 | if asap or now - self._last_time >= TIMEOUT_PRECISION: 143 | for callback in self._periodic_callbacks: 144 | callback() 145 | self._last_time = now 146 | if events and not handle: 147 | time.sleep(0.001) 148 | 149 | def __del__(self): 150 | self.epoll.close() 151 | 152 | 153 | # from tornado 154 | def errno_from_exception(e): 155 | """Provides the errno from an Exception object. 156 | 157 | There are cases that the errno attribute was not set so we pull 158 | the errno out of the args but if someone instatiates an Exception 159 | without any args you will get a tuple error. So this function 160 | abstracts all that behavior to give you a safe way to get the 161 | errno. 162 | """ 163 | 164 | if hasattr(e, 'errno'): 165 | return e.errno 166 | elif e.args: 167 | return e.args[0] 168 | else: 169 | return None 170 | 171 | 172 | # from tornado 173 | def get_sock_error(sock): 174 | error_number = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) 175 | return socket.error(error_number, os.strerror(error_number)) 176 | -------------------------------------------------------------------------------- /exit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: valor. 6 | @file: exit.py 7 | 8 | Copyright 2019 valord577 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | """ 22 | 23 | import sys 24 | # -- import from shadowsockesr-v 25 | import logging 26 | 27 | 28 | def ok(): 29 | sys.exit(0) 30 | 31 | 32 | def error(*msgs): 33 | if msgs: 34 | for msg in msgs: 35 | logging.error(msg) 36 | sys.exit(1) 37 | -------------------------------------------------------------------------------- /initcfg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | chmod +x *.sh 4 | cp -n conf/config.json user-config.json 5 | -------------------------------------------------------------------------------- /lru_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: clowwindy 6 | @modify: valor. 7 | @file: lru_cache.py 8 | 9 | Copyright 2015 clowwindy 10 | Copyright 2019 valord577 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); you may 13 | not use this file except in compliance with the License. You may obtain 14 | a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 20 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 21 | License for the specific language governing permissions and limitations 22 | under the License. 23 | """ 24 | 25 | import collections 26 | import logging 27 | import time 28 | from collections import OrderedDict 29 | 30 | # this LRUCache is optimized for concurrency, not QPS 31 | # n: concurrency, keys stored in the cache 32 | # m: visits not timed out, proportional to QPS * timeout 33 | # get & set is O(1), not O(n). thus we can support very large n 34 | # sweep is O((n - m)) or O(1024) at most, 35 | # no metter how large the cache or timeout value is 36 | 37 | SWEEP_MAX_ITEMS = 1024 38 | 39 | 40 | class LRUCache(collections.MutableMapping): 41 | """This class is not thread safe""" 42 | 43 | def __init__(self, timeout=60, close_callback=None, *args, **kwargs): 44 | self.timeout = timeout 45 | self.close_callback = close_callback 46 | self._store = {} 47 | self._keys_to_last_time = OrderedDict() 48 | self.update(dict(*args, **kwargs)) # use the free update to set keys 49 | 50 | def __getitem__(self, key): 51 | # O(1) 52 | t = time.time() 53 | last_t = self._keys_to_last_time[key] 54 | del self._keys_to_last_time[key] 55 | self._keys_to_last_time[key] = t 56 | return self._store[key] 57 | 58 | def __setitem__(self, key, value): 59 | # O(1) 60 | t = time.time() 61 | if key in self._keys_to_last_time: 62 | del self._keys_to_last_time[key] 63 | self._keys_to_last_time[key] = t 64 | self._store[key] = value 65 | 66 | def __delitem__(self, key): 67 | # O(1) 68 | last_t = self._keys_to_last_time[key] 69 | del self._store[key] 70 | del self._keys_to_last_time[key] 71 | 72 | def __contains__(self, key): 73 | return key in self._store 74 | 75 | def __iter__(self): 76 | return iter(self._store) 77 | 78 | def __len__(self): 79 | return len(self._store) 80 | 81 | def first(self): 82 | if len(self._keys_to_last_time) > 0: 83 | for key in self._keys_to_last_time: 84 | return key 85 | 86 | def sweep(self, sweep_item_cnt = SWEEP_MAX_ITEMS): 87 | # O(n - m) 88 | now = time.time() 89 | c = 0 90 | while c < sweep_item_cnt: 91 | if len(self._keys_to_last_time) == 0: 92 | break 93 | for key in self._keys_to_last_time: 94 | break 95 | last_t = self._keys_to_last_time[key] 96 | if now - last_t <= self.timeout: 97 | break 98 | value = self._store[key] 99 | del self._store[key] 100 | del self._keys_to_last_time[key] 101 | if self.close_callback is not None: 102 | self.close_callback(value) 103 | c += 1 104 | if c: 105 | logging.debug('%d keys swept' % c) 106 | return c < SWEEP_MAX_ITEMS 107 | 108 | def clear(self, keep): 109 | now = time.time() 110 | c = 0 111 | while len(self._keys_to_last_time) > keep: 112 | if len(self._keys_to_last_time) == 0: 113 | break 114 | for key in self._keys_to_last_time: 115 | break 116 | last_t = self._keys_to_last_time[key] 117 | value = self._store[key] 118 | if self.close_callback is not None: 119 | self.close_callback(value) 120 | del self._store[key] 121 | del self._keys_to_last_time[key] 122 | c += 1 123 | if c: 124 | logging.debug('%d keys swept' % c) 125 | return c < SWEEP_MAX_ITEMS 126 | 127 | def test(): 128 | c = LRUCache(timeout=0.3) 129 | 130 | c['a'] = 1 131 | assert c['a'] == 1 132 | c['a'] = 1 133 | 134 | time.sleep(0.5) 135 | c.sweep() 136 | assert 'a' not in c 137 | 138 | c['a'] = 2 139 | c['b'] = 3 140 | time.sleep(0.2) 141 | c.sweep() 142 | assert c['a'] == 2 143 | assert c['b'] == 3 144 | 145 | time.sleep(0.2) 146 | c.sweep() 147 | c['b'] 148 | time.sleep(0.2) 149 | c.sweep() 150 | assert 'a' not in c 151 | assert c['b'] == 3 152 | 153 | time.sleep(0.5) 154 | c.sweep() 155 | assert 'a' not in c 156 | assert 'b' not in c 157 | 158 | global close_cb_called 159 | close_cb_called = False 160 | 161 | def close_cb(t): 162 | global close_cb_called 163 | assert not close_cb_called 164 | close_cb_called = True 165 | 166 | c = LRUCache(timeout=0.1, close_callback=close_cb) 167 | c['s'] = 1 168 | c['s'] 169 | time.sleep(0.1) 170 | c['s'] 171 | time.sleep(0.3) 172 | c.sweep() 173 | 174 | if __name__ == '__main__': 175 | test() 176 | -------------------------------------------------------------------------------- /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 obfsplugin import plain, http_simple, obfs_tls, verify, auth, auth_chain 18 | 19 | 20 | method_supported = {} 21 | method_supported.update(plain.obfs_map) 22 | method_supported.update(http_simple.obfs_map) 23 | method_supported.update(obfs_tls.obfs_map) 24 | method_supported.update(verify.obfs_map) 25 | method_supported.update(auth.obfs_map) 26 | method_supported.update(auth_chain.obfs_map) 27 | 28 | 29 | def mu_protocol(): 30 | return ["auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a"] 31 | 32 | 33 | class server_info(object): 34 | def __init__(self, data): 35 | self.data = data 36 | 37 | 38 | class obfs(object): 39 | def __init__(self, method): 40 | self.method = method 41 | self._method_info = self.get_method_info(method) 42 | if self._method_info: 43 | self.obfs = self.get_obfs(method) 44 | else: 45 | raise Exception('obfs plugin [%s] not supported' % method) 46 | 47 | def init_data(self): 48 | return self.obfs.init_data() 49 | 50 | def set_server_info(self, server_info): 51 | return self.obfs.set_server_info(server_info) 52 | 53 | def get_server_info(self): 54 | return self.obfs.get_server_info() 55 | 56 | def get_method_info(self, method): 57 | method = method.lower() 58 | m = method_supported.get(method) 59 | return m 60 | 61 | def get_obfs(self, method): 62 | m = self._method_info 63 | return m[0](method) 64 | 65 | def get_overhead(self, direction): 66 | return self.obfs.get_overhead(direction) 67 | 68 | def client_pre_encrypt(self, buf): 69 | return self.obfs.client_pre_encrypt(buf) 70 | 71 | def client_encode(self, buf): 72 | return self.obfs.client_encode(buf) 73 | 74 | def client_decode(self, buf): 75 | return self.obfs.client_decode(buf) 76 | 77 | def client_post_decrypt(self, buf): 78 | return self.obfs.client_post_decrypt(buf) 79 | 80 | def server_pre_encrypt(self, buf): 81 | return self.obfs.server_pre_encrypt(buf) 82 | 83 | def server_encode(self, buf): 84 | return self.obfs.server_encode(buf) 85 | 86 | def server_decode(self, buf): 87 | return self.obfs.server_decode(buf) 88 | 89 | def server_post_decrypt(self, buf): 90 | return self.obfs.server_post_decrypt(buf) 91 | 92 | def client_udp_pre_encrypt(self, buf): 93 | return self.obfs.client_udp_pre_encrypt(buf) 94 | 95 | def client_udp_post_decrypt(self, buf): 96 | return self.obfs.client_udp_post_decrypt(buf) 97 | 98 | def server_udp_pre_encrypt(self, buf, uid): 99 | return self.obfs.server_udp_pre_encrypt(buf, uid) 100 | 101 | def server_udp_post_decrypt(self, buf): 102 | return self.obfs.server_udp_post_decrypt(buf) 103 | 104 | def dispose(self): 105 | self.obfs.dispose() 106 | del self.obfs 107 | 108 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /obfsplugin/http_simple.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 | import os 18 | import logging 19 | import binascii 20 | import struct 21 | import datetime 22 | import random 23 | 24 | import common 25 | from obfsplugin import plain 26 | from common import to_bytes, to_str, ord, chr 27 | 28 | def create_http_simple_obfs(method): 29 | return http_simple(method) 30 | 31 | def create_http_post_obfs(method): 32 | return http_post(method) 33 | 34 | def create_random_head_obfs(method): 35 | return random_head(method) 36 | 37 | obfs_map = { 38 | 'http_simple': (create_http_simple_obfs,), 39 | 'http_simple_compatible': (create_http_simple_obfs,), 40 | 'http_post': (create_http_post_obfs,), 41 | 'http_post_compatible': (create_http_post_obfs,), 42 | 'random_head': (create_random_head_obfs,), 43 | 'random_head_compatible': (create_random_head_obfs,), 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 http_simple(plain.plain): 53 | def __init__(self, method): 54 | self.method = method 55 | self.has_sent_header = False 56 | self.has_recv_header = False 57 | self.host = None 58 | self.port = 0 59 | self.recv_buffer = b'' 60 | self.user_agent = [b"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0", 61 | b"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0", 62 | b"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", 63 | 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", 64 | b"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0", 65 | b"Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)", 66 | 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", 67 | b"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)", 68 | b"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko", 69 | 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", 70 | 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", 71 | 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"] 72 | 73 | def encode_head(self, buf): 74 | hexstr = binascii.hexlify(buf) 75 | chs = [] 76 | for i in range(0, len(hexstr), 2): 77 | chs.append(b"%" + hexstr[i:i+2]) 78 | return b''.join(chs) 79 | 80 | def client_encode(self, buf): 81 | if self.has_sent_header: 82 | return buf 83 | head_size = len(self.server_info.iv) + self.server_info.head_len 84 | if len(buf) - head_size > 64: 85 | headlen = head_size + random.randint(0, 64) 86 | else: 87 | headlen = len(buf) 88 | headdata = buf[:headlen] 89 | buf = buf[headlen:] 90 | port = b'' 91 | if self.server_info.port != 80: 92 | port = b':' + to_bytes(str(self.server_info.port)) 93 | body = None 94 | hosts = (self.server_info.obfs_param or self.server_info.host) 95 | pos = hosts.find("#") 96 | if pos >= 0: 97 | body = hosts[pos + 1:].replace("\n", "\r\n") 98 | body = body.replace("\\n", "\r\n") 99 | hosts = hosts[:pos] 100 | hosts = hosts.split(',') 101 | host = random.choice(hosts) 102 | http_head = b"GET /" + self.encode_head(headdata) + b" HTTP/1.1\r\n" 103 | http_head += b"Host: " + to_bytes(host) + port + b"\r\n" 104 | if body: 105 | http_head += body + "\r\n\r\n" 106 | else: 107 | http_head += b"User-Agent: " + random.choice(self.user_agent) + b"\r\n" 108 | 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" 109 | self.has_sent_header = True 110 | return http_head + buf 111 | 112 | def client_decode(self, buf): 113 | if self.has_recv_header: 114 | return (buf, False) 115 | pos = buf.find(b'\r\n\r\n') 116 | if pos >= 0: 117 | self.has_recv_header = True 118 | return (buf[pos + 4:], False) 119 | else: 120 | return (b'', False) 121 | 122 | def server_encode(self, buf): 123 | if self.has_sent_header: 124 | return buf 125 | 126 | header = b'HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Encoding: gzip\r\nContent-Type: text/html\r\nDate: ' 127 | header += to_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')) 128 | header += b'\r\nServer: nginx\r\nVary: Accept-Encoding\r\n\r\n' 129 | self.has_sent_header = True 130 | return header + buf 131 | 132 | def get_data_from_http_header(self, buf): 133 | ret_buf = b'' 134 | lines = buf.split(b'\r\n') 135 | if lines and len(lines) > 1: 136 | hex_items = lines[0].split(b'%') 137 | if hex_items and len(hex_items) > 1: 138 | for index in range(1, len(hex_items)): 139 | if len(hex_items[index]) < 2: 140 | ret_buf += binascii.unhexlify('0' + hex_items[index]) 141 | break 142 | elif len(hex_items[index]) > 2: 143 | ret_buf += binascii.unhexlify(hex_items[index][:2]) 144 | break 145 | else: 146 | ret_buf += binascii.unhexlify(hex_items[index]) 147 | return ret_buf 148 | return b'' 149 | 150 | def get_host_from_http_header(self, buf): 151 | ret_buf = b'' 152 | lines = buf.split(b'\r\n') 153 | if lines and len(lines) > 1: 154 | for line in lines: 155 | if match_begin(line, b"Host: "): 156 | return common.to_str(line[6:]) 157 | 158 | def not_match_return(self, buf): 159 | self.has_sent_header = True 160 | self.has_recv_header = True 161 | if self.method == 'http_simple': 162 | return (b'E'*2048, False, False) 163 | return (buf, True, False) 164 | 165 | def error_return(self, buf): 166 | self.has_sent_header = True 167 | self.has_recv_header = True 168 | return (b'E'*2048, False, False) 169 | 170 | def server_decode(self, buf): 171 | if self.has_recv_header: 172 | return (buf, True, False) 173 | 174 | self.recv_buffer += buf 175 | buf = self.recv_buffer 176 | if len(buf) > 10: 177 | if match_begin(buf, b'GET ') or match_begin(buf, b'POST '): 178 | if len(buf) > 65536: 179 | self.recv_buffer = None 180 | logging.warn('http_simple: over size') 181 | return self.not_match_return(buf) 182 | else: #not http header, run on original protocol 183 | self.recv_buffer = None 184 | logging.debug('http_simple: not match begin') 185 | return self.not_match_return(buf) 186 | else: 187 | return (b'', True, False) 188 | 189 | if b'\r\n\r\n' in buf: 190 | datas = buf.split(b'\r\n\r\n', 1) 191 | ret_buf = self.get_data_from_http_header(buf) 192 | host = self.get_host_from_http_header(buf) 193 | if host and self.server_info.obfs_param: 194 | pos = host.find(":") 195 | if pos >= 0: 196 | host = host[:pos] 197 | hosts = self.server_info.obfs_param.split(',') 198 | if host not in hosts: 199 | return self.not_match_return(buf) 200 | if len(ret_buf) < 4: 201 | return self.error_return(buf) 202 | if len(datas) > 1: 203 | ret_buf += datas[1] 204 | if len(ret_buf) >= 13: 205 | self.has_recv_header = True 206 | return (ret_buf, True, False) 207 | return self.not_match_return(buf) 208 | else: 209 | return (b'', True, False) 210 | 211 | class http_post(http_simple): 212 | def __init__(self, method): 213 | super(http_post, self).__init__(method) 214 | 215 | def boundary(self): 216 | return to_bytes(''.join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") for i in range(32)])) 217 | 218 | def client_encode(self, buf): 219 | if self.has_sent_header: 220 | return buf 221 | head_size = len(self.server_info.iv) + self.server_info.head_len 222 | if len(buf) - head_size > 64: 223 | headlen = head_size + random.randint(0, 64) 224 | else: 225 | headlen = len(buf) 226 | headdata = buf[:headlen] 227 | buf = buf[headlen:] 228 | port = b'' 229 | if self.server_info.port != 80: 230 | port = b':' + to_bytes(str(self.server_info.port)) 231 | body = None 232 | hosts = (self.server_info.obfs_param or self.server_info.host) 233 | pos = hosts.find("#") 234 | if pos >= 0: 235 | body = hosts[pos + 1:].replace("\\n", "\r\n") 236 | hosts = hosts[:pos] 237 | hosts = hosts.split(',') 238 | host = random.choice(hosts) 239 | http_head = b"POST /" + self.encode_head(headdata) + b" HTTP/1.1\r\n" 240 | http_head += b"Host: " + to_bytes(host) + port + b"\r\n" 241 | if body: 242 | http_head += body + "\r\n\r\n" 243 | else: 244 | http_head += b"User-Agent: " + random.choice(self.user_agent) + b"\r\n" 245 | 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" 246 | http_head += b"Content-Type: multipart/form-data; boundary=" + self.boundary() + b"\r\nDNT: 1\r\n" 247 | http_head += b"Connection: keep-alive\r\n\r\n" 248 | self.has_sent_header = True 249 | return http_head + buf 250 | 251 | def not_match_return(self, buf): 252 | self.has_sent_header = True 253 | self.has_recv_header = True 254 | if self.method == 'http_post': 255 | return (b'E'*2048, False, False) 256 | return (buf, True, False) 257 | 258 | class random_head(plain.plain): 259 | def __init__(self, method): 260 | self.method = method 261 | self.has_sent_header = False 262 | self.has_recv_header = False 263 | self.raw_trans_sent = False 264 | self.raw_trans_recv = False 265 | self.send_buffer = b'' 266 | 267 | def client_encode(self, buf): 268 | if self.raw_trans_sent: 269 | return buf 270 | self.send_buffer += buf 271 | if not self.has_sent_header: 272 | self.has_sent_header = True 273 | data = os.urandom(common.ord(os.urandom(1)[0]) % 96 + 4) 274 | crc = (0xffffffff - binascii.crc32(data)) & 0xffffffff 275 | return data + struct.pack('= len(str2): 44 | if str1[:len(str2)] == str2: 45 | return True 46 | return False 47 | 48 | class obfs_auth_data(object): 49 | def __init__(self): 50 | self.client_data = lru_cache.LRUCache(60 * 5) 51 | self.client_id = os.urandom(32) 52 | self.startup_time = int(time.time() - 60 * 30) & 0xFFFFFFFF 53 | self.ticket_buf = {} 54 | 55 | class tls_ticket_auth(plain.plain): 56 | def __init__(self, method): 57 | self.method = method 58 | self.handshake_status = 0 59 | self.send_buffer = b'' 60 | self.recv_buffer = b'' 61 | self.client_id = b'' 62 | self.max_time_dif = 60 * 60 * 24 # time dif (second) setting 63 | self.tls_version = b'\x03\x03' 64 | self.overhead = 5 65 | 66 | def init_data(self): 67 | return obfs_auth_data() 68 | 69 | def get_overhead(self, direction): # direction: true for c->s false for s->c 70 | return self.overhead 71 | 72 | def sni(self, url): 73 | url = common.to_bytes(url) 74 | data = b"\x00" + struct.pack('>H', len(url)) + url 75 | data = b"\x00\x00" + struct.pack('>H', len(data) + 2) + struct.pack('>H', len(data)) + data 76 | return data 77 | 78 | def pack_auth_data(self, client_id): 79 | utc_time = int(time.time()) & 0xFFFFFFFF 80 | data = struct.pack('>I', utc_time) + os.urandom(18) 81 | data += hmac.new(self.server_info.key + client_id, data, hashlib.sha1).digest()[:10] 82 | return data 83 | 84 | def client_encode(self, buf): 85 | if self.handshake_status == -1: 86 | return buf 87 | if self.handshake_status == 8: 88 | ret = b'' 89 | while len(buf) > 2048: 90 | size = min(struct.unpack('>H', os.urandom(2))[0] % 4096 + 100, len(buf)) 91 | ret += b"\x17" + self.tls_version + struct.pack('>H', size) + buf[:size] 92 | buf = buf[size:] 93 | if len(buf) > 0: 94 | ret += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf 95 | return ret 96 | if len(buf) > 0: 97 | self.send_buffer += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf 98 | if self.handshake_status == 0: 99 | self.handshake_status = 1 100 | 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") 101 | ext = binascii.unhexlify(b"ff01000100") 102 | host = self.server_info.obfs_param or self.server_info.host 103 | if host and host[-1] in string.digits: 104 | host = '' 105 | hosts = host.split(',') 106 | host = random.choice(hosts) 107 | ext += self.sni(host) 108 | ext += b"\x00\x17\x00\x00" 109 | if host not in self.server_info.data.ticket_buf: 110 | self.server_info.data.ticket_buf[host] = os.urandom((struct.unpack('>H', os.urandom(2))[0] % 17 + 8) * 16) 111 | ext += b"\x00\x23" + struct.pack('>H', len(self.server_info.data.ticket_buf[host])) + self.server_info.data.ticket_buf[host] 112 | ext += binascii.unhexlify(b"000d001600140601060305010503040104030301030302010203") 113 | ext += binascii.unhexlify(b"000500050100000000") 114 | ext += binascii.unhexlify(b"00120000") 115 | ext += binascii.unhexlify(b"75500000") 116 | ext += binascii.unhexlify(b"000b00020100") 117 | ext += binascii.unhexlify(b"000a0006000400170018") 118 | data += struct.pack('>H', len(ext)) + ext 119 | data = b"\x01\x00" + struct.pack('>H', len(data)) + data 120 | data = b"\x16\x03\x01" + struct.pack('>H', len(data)) + data 121 | return data 122 | elif self.handshake_status == 1 and len(buf) == 0: 123 | data = b"\x14" + self.tls_version + b"\x00\x01\x01" #ChangeCipherSpec 124 | data += b"\x16" + self.tls_version + b"\x00\x20" + os.urandom(22) #Finished 125 | data += hmac.new(self.server_info.key + self.server_info.data.client_id, data, hashlib.sha1).digest()[:10] 126 | ret = data + self.send_buffer 127 | self.send_buffer = b'' 128 | self.handshake_status = 8 129 | return ret 130 | return b'' 131 | 132 | def client_decode(self, buf): 133 | if self.handshake_status == -1: 134 | return (buf, False) 135 | 136 | if self.handshake_status == 8: 137 | ret = b'' 138 | self.recv_buffer += buf 139 | while len(self.recv_buffer) > 5: 140 | if ord(self.recv_buffer[0]) != 0x17: 141 | logging.info("data = %s" % (binascii.hexlify(self.recv_buffer))) 142 | raise Exception('server_decode appdata error') 143 | size = struct.unpack('>H', self.recv_buffer[3:5])[0] 144 | if len(self.recv_buffer) < size + 5: 145 | break 146 | buf = self.recv_buffer[5:size+5] 147 | ret += buf 148 | self.recv_buffer = self.recv_buffer[size+5:] 149 | return (ret, False) 150 | 151 | if len(buf) < 11 + 32 + 1 + 32: 152 | raise Exception('client_decode data error') 153 | verify = buf[11:33] 154 | if hmac.new(self.server_info.key + self.server_info.data.client_id, verify, hashlib.sha1).digest()[:10] != buf[33:43]: 155 | raise Exception('client_decode data error') 156 | if hmac.new(self.server_info.key + self.server_info.data.client_id, buf[:-10], hashlib.sha1).digest()[:10] != buf[-10:]: 157 | raise Exception('client_decode data error') 158 | return (b'', True) 159 | 160 | def server_encode(self, buf): 161 | if self.handshake_status == -1: 162 | return buf 163 | if (self.handshake_status & 8) == 8: 164 | ret = b'' 165 | while len(buf) > 2048: 166 | size = min(struct.unpack('>H', os.urandom(2))[0] % 4096 + 100, len(buf)) 167 | ret += b"\x17" + self.tls_version + struct.pack('>H', size) + buf[:size] 168 | buf = buf[size:] 169 | if len(buf) > 0: 170 | ret += b"\x17" + self.tls_version + struct.pack('>H', len(buf)) + buf 171 | return ret 172 | self.handshake_status |= 8 173 | data = self.tls_version + self.pack_auth_data(self.client_id) + b"\x20" + self.client_id + binascii.unhexlify(b"c02f000005ff01000100") 174 | data = b"\x02\x00" + struct.pack('>H', len(data)) + data #server hello 175 | data = b"\x16" + self.tls_version + struct.pack('>H', len(data)) + data 176 | if random.randint(0, 8) < 1: 177 | ticket = os.urandom((struct.unpack('>H', os.urandom(2))[0] % 164) * 2 + 64) 178 | ticket = struct.pack('>H', len(ticket) + 4) + b"\x04\x00" + struct.pack('>H', len(ticket)) + ticket 179 | data += b"\x16" + self.tls_version + ticket #New session ticket 180 | data += b"\x14" + self.tls_version + b"\x00\x01\x01" #ChangeCipherSpec 181 | finish_len = random.choice([32, 40]) 182 | data += b"\x16" + self.tls_version + struct.pack('>H', finish_len) + os.urandom(finish_len - 10) #Finished 183 | data += hmac.new(self.server_info.key + self.client_id, data, hashlib.sha1).digest()[:10] 184 | if buf: 185 | data += self.server_encode(buf) 186 | return data 187 | 188 | def decode_error_return(self, buf): 189 | self.handshake_status = -1 190 | if self.overhead > 0: 191 | self.server_info.overhead -= self.overhead 192 | self.overhead = 0 193 | if self.method in ['tls1.2_ticket_auth', 'tls1.2_ticket_fastauth']: 194 | return (b'E'*2048, False, False) 195 | return (buf, True, False) 196 | 197 | def server_decode(self, buf): 198 | if self.handshake_status == -1: 199 | return (buf, True, False) 200 | 201 | if (self.handshake_status & 4) == 4: 202 | ret = b'' 203 | self.recv_buffer += buf 204 | while len(self.recv_buffer) > 5: 205 | if ord(self.recv_buffer[0]) != 0x17 or ord(self.recv_buffer[1]) != 0x3 or ord(self.recv_buffer[2]) != 0x3: 206 | logging.info("data = %s" % (binascii.hexlify(self.recv_buffer))) 207 | raise Exception('server_decode appdata error') 208 | size = struct.unpack('>H', self.recv_buffer[3:5])[0] 209 | if len(self.recv_buffer) < size + 5: 210 | break 211 | ret += self.recv_buffer[5:size+5] 212 | self.recv_buffer = self.recv_buffer[size+5:] 213 | return (ret, True, False) 214 | 215 | if (self.handshake_status & 1) == 1: 216 | self.recv_buffer += buf 217 | buf = self.recv_buffer 218 | verify = buf 219 | if len(buf) < 11: 220 | raise Exception('server_decode data error') 221 | if not match_begin(buf, b"\x14" + self.tls_version + b"\x00\x01\x01"): #ChangeCipherSpec 222 | raise Exception('server_decode data error') 223 | buf = buf[6:] 224 | if not match_begin(buf, b"\x16" + self.tls_version + b"\x00"): #Finished 225 | raise Exception('server_decode data error') 226 | verify_len = struct.unpack('>H', buf[3:5])[0] + 1 # 11 - 10 227 | if len(verify) < verify_len + 10: 228 | return (b'', False, False) 229 | if hmac.new(self.server_info.key + self.client_id, verify[:verify_len], hashlib.sha1).digest()[:10] != verify[verify_len:verify_len+10]: 230 | raise Exception('server_decode data error') 231 | self.recv_buffer = verify[verify_len + 10:] 232 | status = self.handshake_status 233 | self.handshake_status |= 4 234 | ret = self.server_decode(b'') 235 | return ret; 236 | 237 | #raise Exception("handshake data = %s" % (binascii.hexlify(buf))) 238 | self.recv_buffer += buf 239 | buf = self.recv_buffer 240 | ogn_buf = buf 241 | if len(buf) < 3: 242 | return (b'', False, False) 243 | if not match_begin(buf, b'\x16\x03\x01'): 244 | return self.decode_error_return(ogn_buf) 245 | buf = buf[3:] 246 | header_len = struct.unpack('>H', buf[:2])[0] 247 | if header_len > len(buf) - 2: 248 | return (b'', False, False) 249 | 250 | self.recv_buffer = self.recv_buffer[header_len + 5:] 251 | self.handshake_status = 1 252 | buf = buf[2:header_len + 2] 253 | if not match_begin(buf, b'\x01\x00'): #client hello 254 | logging.info("tls_auth not client hello message") 255 | return self.decode_error_return(ogn_buf) 256 | buf = buf[2:] 257 | if struct.unpack('>H', buf[:2])[0] != len(buf) - 2: 258 | logging.info("tls_auth wrong message size") 259 | return self.decode_error_return(ogn_buf) 260 | buf = buf[2:] 261 | if not match_begin(buf, self.tls_version): 262 | logging.info("tls_auth wrong tls version") 263 | return self.decode_error_return(ogn_buf) 264 | buf = buf[2:] 265 | verifyid = buf[:32] 266 | buf = buf[32:] 267 | sessionid_len = ord(buf[0]) 268 | if sessionid_len < 32: 269 | logging.info("tls_auth wrong sessionid_len") 270 | return self.decode_error_return(ogn_buf) 271 | sessionid = buf[1:sessionid_len + 1] 272 | buf = buf[sessionid_len+1:] 273 | self.client_id = sessionid 274 | sha1 = hmac.new(self.server_info.key + sessionid, verifyid[:22], hashlib.sha1).digest()[:10] 275 | utc_time = struct.unpack('>I', verifyid[:4])[0] 276 | time_dif = common.int32((int(time.time()) & 0xffffffff) - utc_time) 277 | if self.server_info.obfs_param: 278 | try: 279 | self.max_time_dif = int(self.server_info.obfs_param) 280 | except: 281 | pass 282 | if self.max_time_dif > 0 and (time_dif < -self.max_time_dif or time_dif > self.max_time_dif \ 283 | or common.int32(utc_time - self.server_info.data.startup_time) < -self.max_time_dif / 2): 284 | logging.info("tls_auth wrong time") 285 | return self.decode_error_return(ogn_buf) 286 | if sha1 != verifyid[22:]: 287 | logging.info("tls_auth wrong sha1") 288 | return self.decode_error_return(ogn_buf) 289 | if self.server_info.data.client_data.get(verifyid[:22]): 290 | logging.info("replay attack detect, id = %s" % (binascii.hexlify(verifyid))) 291 | return self.decode_error_return(ogn_buf) 292 | self.server_info.data.client_data.sweep() 293 | self.server_info.data.client_data[verifyid[:22]] = sessionid 294 | if len(self.recv_buffer) >= 11: 295 | ret = self.server_decode(b'') 296 | return (ret[0], True, True) 297 | # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) 298 | return (b'', False, True) 299 | 300 | -------------------------------------------------------------------------------- /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 common import ord 18 | 19 | def create_obfs(method): 20 | return plain(method) 21 | 22 | obfs_map = { 23 | 'plain': (create_obfs,), 24 | 'origin': (create_obfs,), 25 | } 26 | 27 | class plain(object): 28 | def __init__(self, method): 29 | self.method = method 30 | self.server_info = None 31 | 32 | def init_data(self): 33 | return b'' 34 | 35 | def get_overhead(self, direction): # direction: true for c->s false for s->c 36 | return 0 37 | 38 | def get_server_info(self): 39 | return self.server_info 40 | 41 | def set_server_info(self, server_info): 42 | self.server_info = server_info 43 | 44 | def client_pre_encrypt(self, buf): 45 | return buf 46 | 47 | def client_encode(self, buf): 48 | return buf 49 | 50 | def client_decode(self, buf): 51 | # (buffer_to_recv, is_need_to_encode_and_send_back) 52 | return (buf, False) 53 | 54 | def client_post_decrypt(self, buf): 55 | return buf 56 | 57 | def server_pre_encrypt(self, buf): 58 | return buf 59 | 60 | def server_encode(self, buf): 61 | return buf 62 | 63 | def server_decode(self, buf): 64 | # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) 65 | return (buf, True, False) 66 | 67 | def server_post_decrypt(self, buf): 68 | return (buf, False) 69 | 70 | def client_udp_pre_encrypt(self, buf): 71 | return buf 72 | 73 | def client_udp_post_decrypt(self, buf): 74 | return buf 75 | 76 | def server_udp_pre_encrypt(self, buf, uid): 77 | return buf 78 | 79 | def server_udp_post_decrypt(self, buf): 80 | return (buf, None) 81 | 82 | def dispose(self): 83 | pass 84 | 85 | def get_head_size(self, buf, def_value): 86 | if len(buf) < 2: 87 | return def_value 88 | head_type = ord(buf[0]) & 0x7 89 | if head_type == 1: 90 | return 7 91 | if head_type == 4: 92 | return 19 93 | if head_type == 3: 94 | return 4 + ord(buf[1]) 95 | return def_value 96 | 97 | -------------------------------------------------------------------------------- /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 | import struct 18 | import zlib 19 | 20 | from obfsplugin import plain 21 | 22 | def create_verify_deflate(method): 23 | return verify_deflate(method) 24 | 25 | obfs_map = { 26 | 'verify_deflate': (create_verify_deflate,), 27 | } 28 | 29 | def match_begin(str1, str2): 30 | if len(str1) >= len(str2): 31 | if str1[:len(str2)] == str2: 32 | return True 33 | return False 34 | 35 | class obfs_verify_data(object): 36 | def __init__(self): 37 | pass 38 | 39 | class verify_base(plain.plain): 40 | def __init__(self, method): 41 | super(verify_base, self).__init__(method) 42 | self.method = method 43 | 44 | def init_data(self): 45 | return obfs_verify_data() 46 | 47 | def set_server_info(self, server_info): 48 | self.server_info = server_info 49 | 50 | def client_encode(self, buf): 51 | return buf 52 | 53 | def client_decode(self, buf): 54 | return (buf, False) 55 | 56 | def server_encode(self, buf): 57 | return buf 58 | 59 | def server_decode(self, buf): 60 | return (buf, True, False) 61 | 62 | class verify_deflate(verify_base): 63 | def __init__(self, method): 64 | super(verify_deflate, self).__init__(method) 65 | self.recv_buf = b'' 66 | self.unit_len = 32700 67 | self.decrypt_packet_num = 0 68 | self.raw_trans = False 69 | 70 | def pack_data(self, buf): 71 | if len(buf) == 0: 72 | return b'' 73 | data = zlib.compress(buf) 74 | data = struct.pack('>H', len(data)) + data[2:] 75 | return data 76 | 77 | def client_pre_encrypt(self, buf): 78 | ret = b'' 79 | while len(buf) > self.unit_len: 80 | ret += self.pack_data(buf[:self.unit_len]) 81 | buf = buf[self.unit_len:] 82 | ret += self.pack_data(buf) 83 | return ret 84 | 85 | def client_post_decrypt(self, buf): 86 | if self.raw_trans: 87 | return buf 88 | self.recv_buf += buf 89 | out_buf = b'' 90 | while len(self.recv_buf) > 2: 91 | length = struct.unpack('>H', self.recv_buf[:2])[0] 92 | if length >= 32768 or length < 6: 93 | self.raw_trans = True 94 | self.recv_buf = b'' 95 | raise Exception('client_post_decrypt data error') 96 | if length > len(self.recv_buf): 97 | break 98 | 99 | out_buf += zlib.decompress(b'x\x9c' + self.recv_buf[2:length]) 100 | self.recv_buf = self.recv_buf[length:] 101 | 102 | if out_buf: 103 | self.decrypt_packet_num += 1 104 | return out_buf 105 | 106 | def server_pre_encrypt(self, buf): 107 | ret = b'' 108 | while len(buf) > self.unit_len: 109 | ret += self.pack_data(buf[:self.unit_len]) 110 | buf = buf[self.unit_len:] 111 | ret += self.pack_data(buf) 112 | return ret 113 | 114 | def server_post_decrypt(self, buf): 115 | if self.raw_trans: 116 | return (buf, False) 117 | self.recv_buf += buf 118 | out_buf = b'' 119 | while len(self.recv_buf) > 2: 120 | length = struct.unpack('>H', self.recv_buf[:2])[0] 121 | if length >= 32768 or length < 6: 122 | self.raw_trans = True 123 | self.recv_buf = b'' 124 | if self.decrypt_packet_num == 0: 125 | return (b'E'*2048, False) 126 | else: 127 | raise Exception('server_post_decrype data error') 128 | if length > len(self.recv_buf): 129 | break 130 | 131 | out_buf += zlib.decompress(b'\x78\x9c' + self.recv_buf[2:length]) 132 | self.recv_buf = self.recv_buf[length:] 133 | 134 | if out_buf: 135 | self.decrypt_packet_num += 1 136 | return (out_buf, False) 137 | 138 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /require.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: valor. 6 | @file: require.py 7 | 8 | Copyright 2019 valord577 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | """ 22 | 23 | import sys 24 | import select 25 | 26 | 27 | def check_os(): 28 | if 'linux' != sys.platform: 29 | print(' Note: Only support Linux.') 30 | sys.exit(1) 31 | 32 | if not hasattr(select, 'epoll'): 33 | print(' Note: Require \'epoll\' model.') 34 | sys.exit(1) 35 | 36 | 37 | def check_python(): 38 | info = sys.version_info 39 | 40 | if info[0] not in [2, 3]: 41 | print(' Error: Python 3.6+ required.') 42 | sys.exit(1) 43 | 44 | # deprecate Python 2.x 45 | if info[0] == 2: 46 | print(' Error: Python 2.x Deprecated. Use 3.6+ instead.') 47 | sys.exit(1) 48 | 49 | if info[0] == 3 and not info[1] >= 6: 50 | print(' Error: Python 3.6+ required.') 51 | sys.exit(1) 52 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ulimit -n 65535 && nohup python3 server.py a > ssr-refactor.log 2>&1 & 4 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: clowwindy 6 | @modify: valor. 7 | @file: server.py 8 | 9 | Copyright 2015 clowwindy 10 | Copyright 2019 valord577 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); you may 13 | not use this file except in compliance with the License. You may obtain 14 | a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 20 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 21 | License for the specific language governing permissions and limitations 22 | under the License. 23 | """ 24 | 25 | 26 | def main(): 27 | import logging 28 | # -- import from shadowsockesr-v 29 | import conf 30 | import exit 31 | import shell 32 | import eventloop 33 | import tcprelay 34 | import udprelay 35 | import asyncdns 36 | 37 | # -- init logging -- 38 | conf.logger_init() 39 | # show version 40 | logging.info(conf.version()) 41 | 42 | # -- starting ssr -- 43 | # get ssr configurations 44 | ssr_conf = shell.get_ssr_conf(ssr_conf_path=conf.ssr_conf_path()) 45 | 46 | if not ssr_conf.get('dns_ipv6', False): 47 | asyncdns.IPV6_CONNECTION_SUPPORT = False 48 | 49 | password = ssr_conf['password'] 50 | method = ssr_conf['method'] 51 | protocol = ssr_conf['protocol'] 52 | protocol_param = ssr_conf['protocol_param'] 53 | obfs = ssr_conf['obfs'] 54 | obfs_param = ssr_conf['obfs_param'] 55 | udp_enable = ssr_conf['udp_enable'] 56 | dns_list = ssr_conf['dns'] 57 | 58 | logging.info(f'Server start with ' 59 | f'password [{password}] ' 60 | f'method [{method}] ' 61 | f'protocol[{protocol}] ' 62 | f'protocol_param[{protocol_param}] ' 63 | f'obfs [{obfs}] ' 64 | f'obfs_param [{obfs_param}]') 65 | 66 | server = ssr_conf['server'] 67 | port = ssr_conf['server_port'] 68 | logging.info(f'Starting server at [{server}]:{port}') 69 | 70 | try: 71 | ssr_conf['out_bind'] = '' 72 | ssr_conf['out_bindv6'] = '' 73 | 74 | # create epoll (singleton) 75 | loop = eventloop.EventLoop() 76 | 77 | # dns server (singleton) 78 | dns_resolver = asyncdns.DNSResolver(dns_list) 79 | dns_resolver.add_to_loop(loop) 80 | 81 | stat_counter_dict = {} 82 | # listen tcp && register socket 83 | tcp = tcprelay.TCPRelay(ssr_conf, dns_resolver, stat_counter=stat_counter_dict) 84 | tcp.add_to_loop(loop) 85 | 86 | if udp_enable: 87 | # listen udp && register socket 88 | udp = udprelay.UDPRelay(ssr_conf, dns_resolver, False, stat_counter=stat_counter_dict) 89 | udp.add_to_loop(loop) 90 | 91 | # run epoll to handle socket 92 | loop.run() 93 | except Exception as e: 94 | exit.error(e) 95 | 96 | 97 | if __name__ == '__main__': 98 | # -- require -- 99 | import require 100 | # only support Linux and require 'epoll' model. 101 | require.check_os() 102 | # Python 2.x Deprecated. Use 3.6+ instead. 103 | require.check_python() 104 | 105 | import args 106 | # -- check args -- 107 | args.check() 108 | 109 | # -- do main func -- 110 | main() 111 | -------------------------------------------------------------------------------- /shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: clowwindy 6 | @modify: valor. 7 | @file: shell.py 8 | 9 | Copyright 2015 clowwindy 10 | Copyright 2019 valord577 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); you may 13 | not use this file except in compliance with the License. You may obtain 14 | a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 20 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 21 | License for the specific language governing permissions and limitations 22 | under the License. 23 | """ 24 | 25 | from typing import Dict 26 | # -- import from shadowsockesr-v 27 | import encrypt 28 | import common 29 | import exit 30 | import utils 31 | 32 | 33 | def get_ssr_conf(ssr_conf_path: str) -> Dict: 34 | _ssr_conf: Dict = utils.parse_json(ssr_conf_path) 35 | 36 | if not _ssr_conf: 37 | exit.error('Require ssr-config.') 38 | 39 | # -- check params -- 40 | port = _ssr_conf.get('server_port') 41 | if port is None: 42 | exit.error('Require \'server_port\'.') 43 | if type(port) != int or port <= 0: 44 | exit.error('Illegal \'server_port\'.') 45 | 46 | password = _ssr_conf.get('password') 47 | if common.is_blank(password): 48 | exit.error('Require \'password\'.') 49 | 50 | method = _ssr_conf.get('method') 51 | if common.is_blank(method): 52 | exit.error('Require \'method\'.') 53 | if not encrypt.is_supported(method): 54 | exit.error(f'Not supported method [{method}]') 55 | 56 | protocol = _ssr_conf.get('protocol') 57 | if common.is_blank(protocol): 58 | exit.error('Require \'protocol\'.') 59 | 60 | obfs = _ssr_conf.get('obfs') 61 | if common.is_blank(obfs): 62 | exit.error('Require \'obfs\'.') 63 | 64 | dns_list = _ssr_conf.get('dns') 65 | if dns_list is None or type(dns_list) != list or len(dns_list) == 0: 66 | exit.error('Require \'dns server\'.') 67 | for dns in dns_list: 68 | host = dns.get('host') 69 | if ':' in host: 70 | exit.error("Not support ipv6 dns host.") 71 | if not common.is_ipv4(host): 72 | exit.error("Illegal ipv4 dns host.") 73 | 74 | # -- default params -- 75 | _ssr_conf['server'] = '::' 76 | _ssr_conf['password'] = common.to_bytes(_ssr_conf['password']) 77 | _ssr_conf['protocol_param'] = _ssr_conf.get('protocol_param', '') 78 | _ssr_conf['obfs_param'] = _ssr_conf.get('obfs_param', '') 79 | _ssr_conf['udp_enable'] = _ssr_conf.get('udp_enable', False) 80 | 81 | # process default data 82 | try: 83 | _ssr_conf['forbidden_ip'] = \ 84 | common.IPNetwork(_ssr_conf.get('forbidden_ip', '127.0.0.0/8,::1/128')) 85 | except Exception as e: 86 | exit.error('error configuration \'forbidden_ip\'.') 87 | try: 88 | _ssr_conf['forbidden_port'] = common.PortRange(_ssr_conf.get('forbidden_port', '')) 89 | except Exception as e: 90 | exit.error('error configuration \'forbidden_port\'.') 91 | try: 92 | _ssr_conf['ignore_bind'] = \ 93 | common.IPNetwork(_ssr_conf.get('ignore_bind', '127.0.0.0/8,::1/128,10.0.0.0/8,192.168.0.0/16')) 94 | except Exception as e: 95 | exit.error('error configuration \'ignore_bind\'.') 96 | 97 | return _ssr_conf 98 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #python_ver=$(ls /usr/bin|grep -e "^python[23]\.[1-9]\+$"|tail -1) 4 | eval $(ps -ef | grep "[0-9] python3 server\\.py a" | awk '{print "kill "$2}') 5 | -------------------------------------------------------------------------------- /udprelay.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 | # SOCKS5 UDP Request 19 | # +----+------+------+----------+----------+----------+ 20 | # |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 21 | # +----+------+------+----------+----------+----------+ 22 | # | 2 | 1 | 1 | Variable | 2 | Variable | 23 | # +----+------+------+----------+----------+----------+ 24 | 25 | # SOCKS5 UDP Response 26 | # +----+------+------+----------+----------+----------+ 27 | # |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 28 | # +----+------+------+----------+----------+----------+ 29 | # | 2 | 1 | 1 | Variable | 2 | Variable | 30 | # +----+------+------+----------+----------+----------+ 31 | 32 | # shadowsocks UDP Request (before encrypted) 33 | # +------+----------+----------+----------+ 34 | # | ATYP | DST.ADDR | DST.PORT | DATA | 35 | # +------+----------+----------+----------+ 36 | # | 1 | Variable | 2 | Variable | 37 | # +------+----------+----------+----------+ 38 | 39 | # shadowsocks UDP Response (before encrypted) 40 | # +------+----------+----------+----------+ 41 | # | ATYP | DST.ADDR | DST.PORT | DATA | 42 | # +------+----------+----------+----------+ 43 | # | 1 | Variable | 2 | Variable | 44 | # +------+----------+----------+----------+ 45 | 46 | # shadowsocks UDP Request and Response (after encrypted) 47 | # +-------+--------------+ 48 | # | IV | PAYLOAD | 49 | # +-------+--------------+ 50 | # | Fixed | Variable | 51 | # +-------+--------------+ 52 | 53 | # HOW TO NAME THINGS 54 | # ------------------ 55 | # `dest` means destination server, which is from DST fields in the SOCKS5 56 | # request 57 | # `local` means local server of shadowsocks 58 | # `remote` means remote server of shadowsocks 59 | # `client` means UDP clients that connects to other servers 60 | # `server` means the UDP server that handles user requests 61 | 62 | import socket 63 | import logging 64 | import struct 65 | import errno 66 | import random 67 | import binascii 68 | import traceback 69 | 70 | import encrypt, obfs, eventloop, lru_cache, common 71 | from common import pre_parse_header, parse_header, pack_addr 72 | 73 | # for each handler, we have 2 stream directions: 74 | # upstream: from client to server direction 75 | # read local and write to remote 76 | # downstream: from server to client direction 77 | # read remote and write to local 78 | 79 | STREAM_UP = 0 80 | STREAM_DOWN = 1 81 | 82 | # for each stream, it's waiting for reading, or writing, or both 83 | WAIT_STATUS_INIT = 0 84 | WAIT_STATUS_READING = 1 85 | WAIT_STATUS_WRITING = 2 86 | WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING 87 | 88 | BUF_SIZE = 65536 89 | DOUBLE_SEND_BEG_IDS = 16 90 | POST_MTU_MIN = 500 91 | POST_MTU_MAX = 1400 92 | SENDING_WINDOW_SIZE = 8192 93 | 94 | STAGE_INIT = 0 95 | STAGE_RSP_ID = 1 96 | STAGE_DNS = 2 97 | STAGE_CONNECTING = 3 98 | STAGE_STREAM = 4 99 | STAGE_DESTROYED = -1 100 | 101 | CMD_CONNECT = 0 102 | CMD_RSP_CONNECT = 1 103 | CMD_CONNECT_REMOTE = 2 104 | CMD_RSP_CONNECT_REMOTE = 3 105 | CMD_POST = 4 106 | CMD_SYN_STATUS = 5 107 | CMD_POST_64 = 6 108 | CMD_SYN_STATUS_64 = 7 109 | CMD_DISCONNECT = 8 110 | 111 | CMD_VER_STR = b"\x08" 112 | 113 | RSP_STATE_EMPTY = b"" 114 | RSP_STATE_REJECT = b"\x00" 115 | RSP_STATE_CONNECTED = b"\x01" 116 | RSP_STATE_CONNECTEDREMOTE = b"\x02" 117 | RSP_STATE_ERROR = b"\x03" 118 | RSP_STATE_DISCONNECT = b"\x04" 119 | RSP_STATE_REDIRECT = b"\x05" 120 | 121 | def client_key(source_addr, server_af): 122 | # notice this is server af, not dest af 123 | return '%s:%s:%d' % (source_addr[0], source_addr[1], server_af) 124 | 125 | class UDPRelay(object): 126 | def __init__(self, config, dns_resolver, is_local, stat_callback=None, stat_counter=None): 127 | self._config = config 128 | if is_local: 129 | self._listen_addr = config['local_address'] 130 | self._listen_port = config['local_port'] 131 | self._remote_addr = config['server'] 132 | self._remote_port = config['server_port'] 133 | else: 134 | self._listen_addr = config['server'] 135 | self._listen_port = config['server_port'] 136 | self._remote_addr = None 137 | self._remote_port = None 138 | self._dns_resolver = dns_resolver 139 | self._password = common.to_bytes(config['password']) 140 | self._method = config['method'] 141 | self._timeout = config['timeout'] 142 | self._is_local = is_local 143 | self._udp_cache_size = config['udp_cache'] 144 | self._cache = lru_cache.LRUCache(timeout=config['udp_timeout'], 145 | close_callback=self._close_client_pair) 146 | self._cache_dns_client = lru_cache.LRUCache(timeout=10, 147 | close_callback=self._close_client_pair) 148 | self._client_fd_to_server_addr = {} 149 | #self._dns_cache = lru_cache.LRUCache(timeout=1800) 150 | self._eventloop = None 151 | self._closed = False 152 | self.server_transfer_ul = 0 153 | self.server_transfer_dl = 0 154 | self.server_users = {} 155 | self.server_user_transfer_ul = {} 156 | self.server_user_transfer_dl = {} 157 | 158 | if common.to_bytes(config['protocol']) in obfs.mu_protocol(): 159 | self._update_users(None, None) 160 | 161 | self.protocol_data = obfs.obfs(config['protocol']).init_data() 162 | self._protocol = obfs.obfs(config['protocol']) 163 | server_info = obfs.server_info(self.protocol_data) 164 | server_info.host = self._listen_addr 165 | server_info.port = self._listen_port 166 | server_info.users = self.server_users 167 | server_info.protocol_param = config['protocol_param'] 168 | server_info.obfs_param = '' 169 | server_info.iv = b'' 170 | server_info.recv_iv = b'' 171 | server_info.key_str = common.to_bytes(config['password']) 172 | server_info.key = encrypt.encrypt_key(self._password, self._method) 173 | server_info.head_len = 30 174 | server_info.tcp_mss = 1452 175 | server_info.buffer_size = BUF_SIZE 176 | server_info.overhead = 0 177 | self._protocol.set_server_info(server_info) 178 | 179 | self._sockets = set() 180 | self._fd_to_handlers = {} 181 | self._reqid_to_hd = {} 182 | self._data_to_write_to_server_socket = [] 183 | 184 | self._timeout_cache = lru_cache.LRUCache(timeout=self._timeout, 185 | close_callback=self._close_tcp_client) 186 | 187 | self._bind = config.get('out_bind', '') 188 | self._bindv6 = config.get('out_bindv6', '') 189 | self._ignore_bind_list = config.get('ignore_bind', []) 190 | 191 | if 'forbidden_ip' in config: 192 | self._forbidden_iplist = config['forbidden_ip'] 193 | else: 194 | self._forbidden_iplist = None 195 | if 'forbidden_port' in config: 196 | self._forbidden_portset = config['forbidden_port'] 197 | else: 198 | self._forbidden_portset = None 199 | 200 | addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, 201 | socket.SOCK_DGRAM, socket.SOL_UDP) 202 | if len(addrs) == 0: 203 | raise Exception("can't get addrinfo for %s:%d" % 204 | (self._listen_addr, self._listen_port)) 205 | af, socktype, proto, canonname, sa = addrs[0] 206 | server_socket = socket.socket(af, socktype, proto) 207 | server_socket.bind((self._listen_addr, self._listen_port)) 208 | server_socket.setblocking(False) 209 | self._server_socket = server_socket 210 | self._stat_callback = stat_callback 211 | 212 | def _get_a_server(self): 213 | server = self._config['server'] 214 | server_port = self._config['server_port'] 215 | if type(server_port) == list: 216 | server_port = random.choice(server_port) 217 | if type(server) == list: 218 | server = random.choice(server) 219 | logging.debug('chosen server: %s:%d', server, server_port) 220 | return server, server_port 221 | 222 | def get_ud(self): 223 | return (self.server_transfer_ul, self.server_transfer_dl) 224 | 225 | def get_users_ud(self): 226 | ret = (self.server_user_transfer_ul.copy(), self.server_user_transfer_dl.copy()) 227 | return ret 228 | 229 | def _update_users(self, protocol_param, acl): 230 | if protocol_param is None: 231 | protocol_param = self._config['protocol_param'] 232 | param = common.to_bytes(protocol_param).split(b'#') 233 | if len(param) == 2: 234 | user_list = param[1].split(b',') 235 | if user_list: 236 | for user in user_list: 237 | items = user.split(b':') 238 | if len(items) == 2: 239 | user_int_id = int(items[0]) 240 | uid = struct.pack(' header_length + 13 and data[header_length + 4 : header_length + 12] == b"\x00\x01\x00\x00\x00\x00\x00\x00": 423 | is_dns = True 424 | else: 425 | pass 426 | if sa[1] == 53 and is_dns: #DNS 427 | logging.debug("DNS query %s from %s:%d" % (common.to_str(sa[0]), r_addr[0], r_addr[1])) 428 | self._cache_dns_client[key] = (client, uid) 429 | else: 430 | self._cache[key] = (client, uid) 431 | self._client_fd_to_server_addr[client.fileno()] = (r_addr, af) 432 | 433 | self._sockets.add(client.fileno()) 434 | self._eventloop.add(client, eventloop.POLL_IN, self) 435 | 436 | logging.debug('UDP port %5d sockets %d' % (self._listen_port, len(self._sockets))) 437 | 438 | if uid is not None: 439 | user_id = struct.unpack(' 255: 498 | # drop 499 | return 500 | data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data 501 | ref_iv = [encrypt.encrypt_new_iv(self._method)] 502 | self._protocol.obfs.server_info.iv = ref_iv[0] 503 | data = self._protocol.server_udp_pre_encrypt(data, client_uid) 504 | response = encrypt.encrypt_all_iv(self._protocol.obfs.server_info.key, self._method, 1, 505 | data, ref_iv) 506 | if not response: 507 | return 508 | else: 509 | ref_iv = [0] 510 | data = encrypt.encrypt_all_iv(self._protocol.obfs.server_info.key, self._method, 0, 511 | data, ref_iv) 512 | if not data: 513 | return 514 | self._protocol.obfs.server_info.recv_iv = ref_iv[0] 515 | data = self._protocol.client_udp_post_decrypt(data) 516 | header_result = parse_header(data) 517 | if header_result is None: 518 | return 519 | #connecttype, dest_addr, dest_port, header_length = header_result 520 | #logging.debug('UDP handle_client %s:%d to %s:%d' % (common.to_str(r_addr[0]), r_addr[1], dest_addr, dest_port)) 521 | 522 | response = b'\x00\x00\x00' + data 523 | 524 | if client_addr: 525 | if client_uid: 526 | self.add_transfer_d(client_uid, len(response)) 527 | else: 528 | self.server_transfer_dl += len(response) 529 | self.write_to_server_socket(response, client_addr[0]) 530 | if client_dns_pair: 531 | logging.debug("remove dns client %s:%d" % (client_addr[0][0], client_addr[0][1])) 532 | del self._cache_dns_client[key] 533 | self._close_client(client_dns_pair[0]) 534 | else: 535 | # this packet is from somewhere else we know 536 | # simply drop that packet 537 | pass 538 | 539 | def write_to_server_socket(self, data, addr): 540 | uncomplete = False 541 | retry = 0 542 | try: 543 | self._server_socket.sendto(data, addr) 544 | data = None 545 | while self._data_to_write_to_server_socket: 546 | data_buf = self._data_to_write_to_server_socket[0] 547 | retry = data_buf[1] + 1 548 | del self._data_to_write_to_server_socket[0] 549 | data, addr = data_buf[0] 550 | self._server_socket.sendto(data, addr) 551 | except (OSError, IOError) as e: 552 | error_no = eventloop.errno_from_exception(e) 553 | uncomplete = True 554 | if error_no in (errno.EWOULDBLOCK,): 555 | pass 556 | else: 557 | logging.error(e) 558 | return False 559 | #if uncomplete and data is not None and retry < 3: 560 | # self._data_to_write_to_server_socket.append([(data, addr), retry]) 561 | #''' 562 | 563 | def add_to_loop(self, loop): 564 | if self._eventloop: 565 | raise Exception('already add to loop') 566 | if self._closed: 567 | raise Exception('already closed') 568 | self._eventloop = loop 569 | 570 | server_socket = self._server_socket 571 | self._eventloop.add(server_socket, 572 | eventloop.POLL_IN | eventloop.POLL_ERR, self) 573 | loop.add_periodic(self.handle_periodic) 574 | 575 | def remove_handler(self, client): 576 | if hash(client) in self._timeout_cache: 577 | del self._timeout_cache[hash(client)] 578 | 579 | def update_activity(self, client): 580 | self._timeout_cache[hash(client)] = client 581 | 582 | def _sweep_timeout(self): 583 | self._timeout_cache.sweep() 584 | 585 | def _close_tcp_client(self, client): 586 | if client.remote_address: 587 | logging.debug('timed out: %s:%d' % 588 | client.remote_address) 589 | else: 590 | logging.debug('timed out') 591 | client.destroy() 592 | client.destroy_local() 593 | 594 | def handle_event(self, sock, fd, event): 595 | if sock == self._server_socket: 596 | if event & eventloop.POLL_ERR: 597 | logging.error('UDP server_socket err') 598 | try: 599 | self._handle_server() 600 | except Exception as e: 601 | logging.error(e) 602 | if self._config['debug']: 603 | traceback.print_exc() 604 | elif sock and (fd in self._sockets): 605 | if event & eventloop.POLL_ERR: 606 | logging.error('UDP client_socket err') 607 | try: 608 | self._handle_client(sock) 609 | except Exception as e: 610 | logging.error(e) 611 | if self._config['verbose']: 612 | traceback.print_exc() 613 | else: 614 | if sock: 615 | handler = self._fd_to_handlers.get(fd, None) 616 | if handler: 617 | handler.handle_event(sock, event) 618 | else: 619 | logging.warn('poll removed fd') 620 | 621 | def handle_periodic(self): 622 | if self._closed: 623 | self._cache.clear(0) 624 | self._cache_dns_client.clear(0) 625 | if self._eventloop: 626 | self._eventloop.remove_periodic(self.handle_periodic) 627 | self._eventloop.remove(self._server_socket) 628 | if self._server_socket: 629 | self._server_socket.close() 630 | self._server_socket = None 631 | logging.info('closed UDP port %d', self._listen_port) 632 | else: 633 | before_sweep_size = len(self._sockets) 634 | self._cache.sweep() 635 | self._cache_dns_client.sweep() 636 | if before_sweep_size != len(self._sockets): 637 | logging.debug('UDP port %5d sockets %d' % (self._listen_port, len(self._sockets))) 638 | self._sweep_timeout() 639 | 640 | def close(self, next_tick=False): 641 | logging.debug('UDP close') 642 | self._closed = True 643 | if not next_tick: 644 | if self._eventloop: 645 | self._eventloop.remove_periodic(self.handle_periodic) 646 | self._eventloop.remove(self._server_socket) 647 | self._server_socket.close() 648 | self._cache.clear(0) 649 | self._cache_dns_client.clear(0) 650 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | """ 5 | @author: valor. 6 | @file: utils.py 7 | 8 | Copyright 2019 valord577 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | """ 22 | 23 | import os 24 | from typing import Dict 25 | # -- import from shadowsockesr-v 26 | from common import is_blank 27 | import exit 28 | 29 | 30 | def check_file_path(file: str): 31 | if is_blank(file): 32 | exit.error(f'Blank file path. [arg -> {file}]') 33 | 34 | if not os.path.exists(file): 35 | exit.error('Not found file.') 36 | 37 | 38 | def parse_json(file: str) -> Dict: 39 | import json 40 | 41 | check_file_path(file) 42 | 43 | d = {} 44 | with open(file=file, encoding='utf-8') as f: 45 | d = json.load(f) 46 | f.close() 47 | 48 | return d 49 | 50 | 51 | def parse_xml(file: str) -> Dict: 52 | import xml.etree.cElementTree as xmlParser 53 | 54 | check_file_path(file) 55 | root = xmlParser.ElementTree(file=file).getroot() 56 | 57 | # get configurations 58 | conf = {} 59 | # parse xml file 60 | traversing_nodes(root, conf) 61 | return conf[root.tag] 62 | 63 | 64 | # recursive 65 | def traversing_nodes(node, conf: Dict): 66 | # no child, insert node into Dict 67 | if len(node) == 0: 68 | if not conf.__contains__(node.tag): 69 | conf[node.tag] = node.text 70 | else: 71 | # convert value to list 72 | if isinstance(conf[node.tag], list): 73 | conf[node.tag].append(node.text) 74 | else: 75 | conf[node.tag] = [conf[node.tag], node.text] 76 | return 77 | 78 | if not conf.__contains__(node.tag): 79 | conf[node.tag] = {} 80 | _temp_dict = conf[node.tag] 81 | else: 82 | if isinstance(conf[node.tag], list): 83 | conf[node.tag].append({}) 84 | else: 85 | conf[node.tag] = [conf[node.tag], {}] 86 | _temp_dict = conf[node.tag][len(conf[node.tag]) - 1] 87 | 88 | for elem in list(node): 89 | traversing_nodes(elem, _temp_dict) 90 | --------------------------------------------------------------------------------