diff --git a/shadowsocks/manager.py b/shadowsocks/manager.py index e8009b4..d5d9d12 100644 --- a/shadowsocks/manager.py +++ b/shadowsocks/manager.py @@ -44,7 +44,7 @@ class Manager(object): self._statistics = collections.defaultdict(int) self._control_client_addr = None try: - manager_address = config['manager_address'] + manager_address = common.to_str(config['manager_address']) if ':' in manager_address: addr = manager_address.rsplit(':', 1) addr = addr[0], int(addr[1]) diff --git a/shadowsocks/obfs.py b/shadowsocks/obfs.py new file mode 100644 index 0000000..69eed0b --- /dev/null +++ b/shadowsocks/obfs.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# +# Copyright 2015-2015 breakwa11 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import os +import sys +import hashlib +import logging + +from shadowsocks import common +from shadowsocks.obfsplugin import plain, http_simple, verify_simple + + +method_supported = {} +method_supported.update(plain.obfs) +method_supported.update(http_simple.obfs) +method_supported.update(verify_simple.obfs) + +class Obfs(object): + def __init__(self, method): + self.method = method + self._method_info = self.get_method_info(method) + if self._method_info: + self.obfs = self.get_obfs(method) + else: + logging.error('method %s not supported' % method) + sys.exit(1) + + def get_method_info(self, method): + method = method.lower() + m = method_supported.get(method) + return m + + def get_obfs(self, method): + m = self._method_info + return m[0](method) + + def client_pre_encrypt(self, buf): + return self.obfs.client_pre_encrypt(buf) + + def client_encode(self, buf): + return self.obfs.client_encode(buf) + + def client_decode(self, buf): + return self.obfs.client_decode(buf) + + def client_post_decrypt(self, buf): + return self.obfs.client_post_decrypt(buf) + + def server_pre_encrypt(self, buf): + return self.obfs.server_pre_encrypt(buf) + + def server_encode(self, buf): + return self.obfs.server_encode(buf) + + def server_decode(self, buf): + return self.obfs.server_decode(buf) + + def server_post_decrypt(self, buf): + return self.obfs.server_post_decrypt(buf) + diff --git a/shadowsocks/obfsplugin/__init__.py b/shadowsocks/obfsplugin/__init__.py new file mode 100644 index 0000000..401c7b7 --- /dev/null +++ b/shadowsocks/obfsplugin/__init__.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# +# Copyright 2015 clowwindy +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import, division, print_function, \ + with_statement diff --git a/shadowsocks/obfsplugin/http_simple.py b/shadowsocks/obfsplugin/http_simple.py new file mode 100644 index 0000000..9695136 --- /dev/null +++ b/shadowsocks/obfsplugin/http_simple.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python +# +# Copyright 2015-2015 breakwa11 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import os +import sys +import hashlib +import logging +import binascii +import base64 +import datetime + +from shadowsocks.obfsplugin import plain +from shadowsocks import common +from shadowsocks.common import to_bytes, to_str, ord + +def create_http_obfs(method): + return http_simple(method) + +def create_http2_obfs(method): + return http2_simple(method) + +def create_tls_obfs(method): + return tls_simple(method) + +def create_random_head_obfs(method): + return random_head(method) + +obfs = { + 'http_simple': (create_http_obfs,), + 'http2_simple': (create_http2_obfs,), + 'tls_simple': (create_tls_obfs,), + 'random_head': (create_random_head_obfs,), +} + +def match_begin(str1, str2): + if len(str1) >= len(str2): + if str1[:len(str2)] == str2: + return True + return False + +class http_simple(plain.plain): + def __init__(self, method): + self.method = method + self.has_sent_header = False + self.has_recv_header = False + self.host = None + self.port = 0 + self.recv_buffer = b'' + + def client_encode(self, buf): + # TODO + return buf + + def client_decode(self, buf): + # TODO + return (buf, False) + + def server_encode(self, buf): + if self.has_sent_header: + return buf + + header = b'HTTP/1.1 200 OK\r\nServer: openresty\r\nDate: ' + header += to_bytes(datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')) + header += b'\r\nContent-Type: text/plain; charset=utf-8\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\nKeep-Alive: timeout=20\r\nVary: Accept-Encoding\r\nContent-Encoding: gzip\r\n\r\n' + self.has_sent_header = True + return header + buf + + def get_data_from_http_header(self, buf): + ret_buf = b'' + lines = buf.split(b'\r\n') + if lines and len(lines) > 4: + hex_items = lines[0].split(b'%') + if hex_items and len(hex_items) > 1: + for index in range(1, len(hex_items)): + if len(hex_items[index]) != 2: + ret_buf += binascii.unhexlify(hex_items[index][:2]) + break + ret_buf += binascii.unhexlify(hex_items[index]) + return ret_buf + return b'' + + def server_decode(self, buf): + if self.has_recv_header: + return (buf, True, False) + + buf = self.recv_buffer + buf + if len(buf) > 10: + if match_begin(buf, b'GET /') or match_begin(buf, b'POST /'): + if len(buf) > 65536: + self.has_sent_header = True + self.has_recv_header = True + self.recv_buffer = None + return (buf, True, False) + else: #not http header, run on original protocol + self.has_sent_header = True + self.has_recv_header = True + self.recv_buffer = None + return (buf, True, False) + else: + self.recv_buffer = buf + return (b'', True, False) + + datas = buf.split(b'\r\n\r\n', 1) + if datas and len(datas) > 1: + ret_buf = self.get_data_from_http_header(buf) + ret_buf += datas[1] + if len(ret_buf) >= 15: + self.has_recv_header = True + return (ret_buf, True, False) + self.recv_buffer = buf + return (b'', True, False) + else: + self.recv_buffer = buf + return (b'', True, False) + self.has_sent_header = True + self.has_recv_header = True + return (buf, True, False) + +class http2_simple(plain.plain): + def __init__(self, method): + self.method = method + self.has_sent_header = False + self.has_recv_header = False + self.host = None + self.port = 0 + self.recv_buffer = b'' + + def client_encode(self, buf): + # TODO + return buf + + def client_decode(self, buf): + # TODO + return (buf, False) + + def server_encode(self, buf): + if self.has_sent_header: + return buf + + header = b'HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n' + self.has_sent_header = True + return header + buf + + def server_decode(self, buf): + if self.has_recv_header: + return (buf, True, False) + + buf = self.recv_buffer + buf + if len(buf) > 10: + if match_begin(buf, b'GET /'): + pass + else: #not http header, run on original protocol + self.has_sent_header = True + self.has_recv_header = True + self.recv_buffer = None + return (buf, True, False) + else: + self.recv_buffer = buf + return (b'', True, False) + + datas = buf.split(b'\r\n\r\n', 1) + if datas and len(datas) > 1 and len(datas[0]) >= 4: + lines = buf.split(b'\r\n') + if lines and len(lines) >= 4: + if match_begin(lines[4], b'HTTP2-Settings: '): + ret_buf = base64.urlsafe_b64decode(lines[4][16:]) + ret_buf += datas[1] + self.has_recv_header = True + return (ret_buf, True, False) + self.recv_buffer = buf + return (b'', True, False) + else: + self.recv_buffer = buf + return (b'', True, False) + self.has_sent_header = True + self.has_recv_header = True + return (buf, True, False) + +class tls_simple(plain.plain): + def __init__(self, method): + self.method = method + self.has_sent_header = False + self.has_recv_header = False + + def client_encode(self, buf): + return buf + + def client_decode(self, buf): + # (buffer_to_recv, is_need_to_encode_and_send_back) + return (buf, False) + + def server_encode(self, buf): + if self.has_sent_header: + return buf + self.has_sent_header = True + # TODO + #server_hello = b'' + return b'\x16\x03\x01' + + def server_decode(self, buf): + if self.has_recv_header: + return (buf, True, False) + + self.has_recv_header = True + if not match_begin(buf, b'\x16\x03\x01'): + self.has_sent_header = True + return (buf, True, False) + # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) + return (b'', False, True) + +class random_head(plain.plain): + def __init__(self, method): + self.method = method + self.has_sent_header = False + self.has_recv_header = False + + def client_encode(self, buf): + return buf + + def client_decode(self, buf): + # (buffer_to_recv, is_need_to_encode_and_send_back) + return (buf, False) + + def server_encode(self, buf): + if self.has_sent_header: + return buf + self.has_sent_header = True + return os.urandom(common.ord(os.urandom(1)[0]) % 96 + 4) + + def server_decode(self, buf): + if self.has_recv_header: + return (buf, True, False) + + self.has_recv_header = True + crc = binascii.crc32(buf) & 0xffffffff + if crc != 0xffffffff: + self.has_sent_header = True + return (buf, True, False) + # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) + return (b'', False, True) + diff --git a/shadowsocks/obfsplugin/plain.py b/shadowsocks/obfsplugin/plain.py new file mode 100644 index 0000000..a40b993 --- /dev/null +++ b/shadowsocks/obfsplugin/plain.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# +# Copyright 2015-2015 breakwa11 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import os +import sys +import hashlib +import logging + +def create_obfs(method): + return plain(method) + +obfs = { + 'plain': (create_obfs,), +} + +class plain(object): + def __init__(self, method): + self.method = method + + def client_pre_encrypt(self, buf): + return buf + + def client_encode(self, buf): + return buf + + def client_decode(self, buf): + # (buffer_to_recv, is_need_to_encode_and_send_back) + return (buf, False) + + def client_post_decrypt(self, buf): + return buf + + def server_pre_encrypt(self, buf): + return buf + + def server_encode(self, buf): + return buf + + def server_decode(self, buf): + # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) + return (buf, True, False) + + def server_post_decrypt(self, buf): + return buf + diff --git a/shadowsocks/obfsplugin/verify_simple.py b/shadowsocks/obfsplugin/verify_simple.py new file mode 100644 index 0000000..ed22b62 --- /dev/null +++ b/shadowsocks/obfsplugin/verify_simple.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# +# Copyright 2015-2015 breakwa11 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import os +import sys +import hashlib +import logging +import binascii +import base64 +import datetime +import struct + +from shadowsocks.obfsplugin import plain +from shadowsocks import common +from shadowsocks.common import to_bytes, to_str, ord + +def create_verify_obfs(method): + return verify_simple(method) + +obfs = { + 'verify_simple': (create_verify_obfs,), +} + +def match_begin(str1, str2): + if len(str1) >= len(str2): + if str1[:len(str2)] == str2: + return True + return False + +class verify_simple(plain.plain): + def __init__(self, method): + self.method = method + self.recv_buf = b'' + self.unit_len = 8100 + + def pack_data(self, buf): + if len(buf) == 0: + return b'' + rnd_data = os.urandom(common.ord(os.urandom(1)[0]) % 16) + data = common.chr(len(rnd_data) + 1) + rnd_data + buf + data = struct.pack('>H', len(data) + 6) + data + crc = (0xffffffff - binascii.crc32(data)) & 0xffffffff + data += struct.pack(' self.unit_len: + ret += self.pack_data(buf[:self.unit_len]) + buf = buf[self.unit_len:] + ret += self.pack_data(buf) + return ret + + def client_encode(self, buf): + return buf + + def client_decode(self, buf): + # (buffer_to_recv, is_need_to_encode_and_send_back) + return (buf, False) + + def client_post_decrypt(self, buf): + return buf + + def server_pre_encrypt(self, buf): + ret = b'' + while len(buf) > self.unit_len: + ret += self.pack_data(buf[:self.unit_len]) + buf = buf[self.unit_len:] + ret += self.pack_data(buf) + return ret + + def server_encode(self, buf): + return buf + + def server_decode(self, buf): + # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) + return (buf, True, False) + + def server_post_decrypt(self, buf): + self.recv_buf += buf + out_buf = b'' + while len(self.recv_buf) > 2: + length = struct.unpack('>H', self.recv_buf[:2])[0] + if length >= 8192: + raise Exception('server_post_decrype data error') + if length > len(self.recv_buf): + break + + if (binascii.crc32(self.recv_buf[:length]) & 0xffffffff) != 0xffffffff: + raise Exception('server_post_decrype data uncorrect CRC32') + + pos = common.ord(self.recv_buf[2]) + 2 + out_buf += self.recv_buf[pos:length - 4] + self.recv_buf = self.recv_buf[length:] + + return out_buf + diff --git a/shadowsocks/run.sh b/shadowsocks/run.sh new file mode 100644 index 0000000..497ceb8 --- /dev/null +++ b/shadowsocks/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash +cd `dirname $0` +eval $(ps -ef | grep "[0-9] python server\\.py a" | awk '{print "kill "$2}') +nohup python server.py a >> ssserver.log 2>&1 & + diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 68a6716..006a372 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -59,22 +59,29 @@ def main(): dns_resolver = asyncdns.DNSResolver() port_password = config['port_password'] del config['port_password'] - for port, password in port_password.items(): + for port, password_obfs in port_password.items(): + if type(password_obfs) == list: + password = password_obfs[0] + obfs = password_obfs[1] + else: + password = password_obfs + obfs = config["obfs"] a_config = config.copy() ipv6_ok = False - logging.info("server start with password [%s] method [%s]" % (password, a_config['method'])) + logging.info("server start with password [%s] obfs [%s] method [%s]" % (password, obfs, a_config['method'])) if 'server_ipv6' in a_config: try: if len(a_config['server_ipv6']) > 2 and a_config['server_ipv6'][0] == "[" and a_config['server_ipv6'][-1] == "]": a_config['server_ipv6'] = a_config['server_ipv6'][1:-1] a_config['server_port'] = int(port) a_config['password'] = password + a_config['obfs'] = obfs a_config['server'] = a_config['server_ipv6'] logging.info("starting server at %s:%d" % (a_config['server'], int(port))) tcp_servers.append(tcprelay.TCPRelay(a_config, dns_resolver, False)) udp_servers.append(udprelay.UDPRelay(a_config, dns_resolver, False)) - if a_config['server_ipv6'] == "::": + if a_config['server_ipv6'] == b"::": ipv6_ok = True except Exception as e: shell.print_exception(e) @@ -83,6 +90,7 @@ def main(): a_config = config.copy() a_config['server_port'] = int(port) a_config['password'] = password + a_config['obfs'] = obfs logging.info("starting server at %s:%d" % (a_config['server'], int(port))) tcp_servers.append(tcprelay.TCPRelay(a_config, dns_resolver, False)) diff --git a/shadowsocks/shell.py b/shadowsocks/shell.py index c91fc22..e5f5bb5 100644 --- a/shadowsocks/shell.py +++ b/shadowsocks/shell.py @@ -216,6 +216,7 @@ def get_config(is_local): config['password'] = to_bytes(config.get('password', b'')) config['method'] = to_str(config.get('method', 'aes-256-cfb')) + config['obfs'] = to_str(config.get('obfs', 'plain')) config['port_password'] = config.get('port_password', None) config['timeout'] = int(config.get('timeout', 300)) config['fast_open'] = config.get('fast_open', False) diff --git a/shadowsocks/stop.sh b/shadowsocks/stop.sh new file mode 100644 index 0000000..af1fbf9 --- /dev/null +++ b/shadowsocks/stop.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +eval $(ps -ef | grep "[0-9] python server\\.py a" | awk '{print "kill "$2}') diff --git a/shadowsocks/tail.sh b/shadowsocks/tail.sh new file mode 100644 index 0000000..aa37139 --- /dev/null +++ b/shadowsocks/tail.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +tail -f ssserver.log diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 314e96d..6062996 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -27,12 +27,14 @@ import binascii import traceback import random -from shadowsocks import encrypt, eventloop, shell, common +from shadowsocks import encrypt, obfs, eventloop, shell, common from shadowsocks.common import pre_parse_header, parse_header # set it 'False' to use both new protocol and the original shadowsocks protocal # set it 'True' to use new protocol ONLY, to avoid GFW detecting FORCE_NEW_PROTOCOL = False +# set it 'True' if run as a local client and connect to a server which support new protocol +CLIENT_NEW_PROTOCOL = False # we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time TIMEOUTS_CLEAN_SIZE = 512 @@ -114,10 +116,12 @@ class TCPRelayHandler(object): self._stage = STAGE_INIT self._encryptor = encrypt.Encryptor(config['password'], config['method']) + self._encrypt_correct = True + self._obfs = obfs.Obfs(config['obfs']) self._fastopen_connected = False self._data_to_write_to_local = [] self._data_to_write_to_remote = [] - self._udp_data_send_buffer = '' + self._udp_data_send_buffer = b'' self._upstream_status = WAIT_STATUS_READING self._downstream_status = WAIT_STATUS_INIT self._client_address = local_sock.getpeername()[:2] @@ -196,7 +200,7 @@ class TCPRelayHandler(object): # write data to sock # if only some of the data are written, put remaining in the buffer # and update the stream to wait for writing - if not data or not sock: + if not sock: return False #logging.debug("_write_to_sock %s %s %s" % (self._remote_sock, sock, self._remote_udp)) uncomplete = False @@ -248,11 +252,20 @@ class TCPRelayHandler(object): return True else: try: + if self._is_local: + pass + else: + if sock == self._local_sock and self._encrypt_correct: + obfs_encode = self._obfs.server_encode(data) + data = obfs_encode l = len(data) - s = sock.send(data) - if s < l: - data = data[s:] - uncomplete = True + if l > 0: + s = sock.send(data) + if s < l: + data = data[s:] + uncomplete = True + else: + return except (OSError, IOError) as e: error_no = eventloop.errno_from_exception(e) if error_no in (errno.EAGAIN, errno.EINPROGRESS, @@ -281,9 +294,36 @@ class TCPRelayHandler(object): logging.error('write_all_to_sock:unknown socket') return True + def _get_redirect_host(self, client_address, ogn_data): + # test + host_list = [(b"www.bing.com", 80), (b"www.microsoft.com", 80), (b"www.baidu.com", 443), (b"www.qq.com", 80), (b"www.csdn.net", 80), (b"1.2.3.4", 1000)] + hash_code = binascii.crc32(ogn_data) + addrs = socket.getaddrinfo(client_address[0], client_address[1], 0, socket.SOCK_STREAM, socket.SOL_TCP) + af, socktype, proto, canonname, sa = addrs[0] + address_bytes = common.inet_pton(af, sa[0]) + if len(address_bytes) == 16: + addr = struct.unpack('>Q', address_bytes[8:])[0] + if len(address_bytes) == 4: + addr = struct.unpack('>I', address_bytes)[0] + else: + addr = 0 + return host_list[((hash_code & 0xffffffff) + addr + 3) % len(host_list)] + + def _handel_protocol_error(self, client_address, ogn_data): + #raise Exception('can not parse header') + logging.warn("Protocol ERROR, TCP ogn data %s from %s:%d" % (binascii.hexlify(ogn_data), client_address[0], client_address[1])) + self._encrypt_correct = False + #create redirect or disconnect by hash code + host, port = self._get_redirect_host(client_address, ogn_data) + data = b"\x03" + common.chr(len(host)) + host + struct.pack('>H', port) + logging.warn("TCP data redir %s:%d %s" % (host, port, binascii.hexlify(data))) + return data + ogn_data + def _handle_stage_connecting(self, data): if self._is_local: + data = self._obfs.client_pre_encrypt(data) data = self._encryptor.encrypt(data) + data = self._obfs.client_encode(data) self._data_to_write_to_remote.append(data) if self._is_local and not self._fastopen_connected and \ self._config['fast_open']: @@ -347,17 +387,18 @@ class TCPRelayHandler(object): return before_parse_data = data - if FORCE_NEW_PROTOCOL and ord(data[0]) != 0x88: - logging.warn("TCP data %s decrypt %s" % (binascii.hexlify(ogn_data), binascii.hexlify(before_parse_data))) - raise Exception('can not parse header') - data = pre_parse_header(data) - if data is None: - logging.warn("TCP data %s decrypt %s" % (binascii.hexlify(ogn_data), binascii.hexlify(before_parse_data))) - raise Exception('can not parse header') - header_result = parse_header(data) - if header_result is None: - logging.warn("TCP data %s decrypt %s" % (binascii.hexlify(ogn_data), binascii.hexlify(before_parse_data))) - raise Exception('can not parse header') + if self._is_local: + header_result = parse_header(data) + else: + if FORCE_NEW_PROTOCOL and common.ord(data[0]) != 0x88: + data = self._handel_protocol_error(self._client_address, ogn_data) + data = pre_parse_header(data) + if data is None: + data = self._handel_protocol_error(self._client_address, ogn_data) + header_result = parse_header(data) + if header_result is None: + data = self._handel_protocol_error(self._client_address, ogn_data) + header_result = parse_header(data) connecttype, remote_addr, remote_port, header_length = header_result logging.info('%s connecting %s:%d from %s:%d' % ((connecttype == 0) and 'TCP' or 'UDP', @@ -373,6 +414,13 @@ class TCPRelayHandler(object): self._write_to_sock((b'\x05\x00\x00\x01' b'\x00\x00\x00\x00\x10\x10'), self._local_sock) + if CLIENT_NEW_PROTOCOL: + rnd_len = random.randint(1, 32) + total_len = 7 + rnd_len + len(data) + data = b'\x88' + struct.pack('>H', total_len) + chr(rnd_len) + (b' ' * (rnd_len - 1)) + data + crc = (0xffffffff - binascii.crc32(data)) & 0xffffffff + data += struct.pack('H', addr[1]) try: ip = socket.inet_aton(addr[0]) - data = '\x00\x01' + ip + port + data + data = b'\x00\x01' + ip + port + data except Exception as e: ip = socket.inet_pton(socket.AF_INET6, addr[0]) - data = '\x00\x04' + ip + port + data + data = b'\x00\x04' + ip + port + data data = struct.pack('>H', len(data) + 2) + data #logging.info('UDP over TCP recvfrom %s:%d %d bytes to %s:%d' % (addr[0], addr[1], len(data), self._client_address[0], self._client_address[1])) else: @@ -555,9 +617,15 @@ class TCPRelayHandler(object): self._server.server_transfer_dl += len(data) self._update_activity(len(data)) if self._is_local: - data = self._encryptor.decrypt(data) + obfs_decode = self._obfs.client_decode(data) + if obfs_decode[1]: + self._write_to_sock(b'', self._remote_sock) + data = self._encryptor.decrypt(obfs_decode[0]) + data = self._obfs.client_post_decrypt(data) else: - data = self._encryptor.encrypt(data) + if self._encrypt_correct: + data = self._obfs.server_pre_encrypt(data) + data = self._encryptor.encrypt(data) try: self._write_to_sock(data, self._local_sock) except Exception as e: @@ -672,7 +740,6 @@ class TCPRelayHandler(object): self._dns_resolver.remove_callback(self._handle_dns_resolved) self._server.remove_handler(self) - class TCPRelay(object): def __init__(self, config, dns_resolver, is_local, stat_callback=None): self._config = config @@ -681,8 +748,8 @@ class TCPRelay(object): self._closed = False self._eventloop = None self._fd_to_handlers = {} - self.server_transfer_ul = 0L - self.server_transfer_dl = 0L + self.server_transfer_ul = 0 + self.server_transfer_dl = 0 self._timeout = config['timeout'] self._timeouts = [] # a list for all the handlers diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 3ea9b6d..d7626c9 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -115,15 +115,15 @@ CMD_POST_64 = 6 CMD_SYN_STATUS_64 = 7 CMD_DISCONNECT = 8 -CMD_VER_STR = "\x08" +CMD_VER_STR = b"\x08" -RSP_STATE_EMPTY = "" -RSP_STATE_REJECT = "\x00" -RSP_STATE_CONNECTED = "\x01" -RSP_STATE_CONNECTEDREMOTE = "\x02" -RSP_STATE_ERROR = "\x03" -RSP_STATE_DISCONNECT = "\x04" -RSP_STATE_REDIRECT = "\x05" +RSP_STATE_EMPTY = b"" +RSP_STATE_REJECT = b"\x00" +RSP_STATE_CONNECTED = b"\x01" +RSP_STATE_CONNECTEDREMOTE = b"\x02" +RSP_STATE_ERROR = b"\x03" +RSP_STATE_DISCONNECT = b"\x04" +RSP_STATE_REDIRECT = b"\x05" class UDPLocalAddress(object): def __init__(self, addr): @@ -196,16 +196,20 @@ class RecvQueue(object): if self.end_id == pack_id: self.end_id = pack_id + 1 elif self.end_id < pack_id: - for eid in xrange(self.end_id, pack_id): + eid = self.end_id + while eid < pack_id: self.miss_queue.add(eid) + eid += 1 self.end_id = pack_id + 1 else: self.miss_queue.remove(pack_id) def set_end(self, end_id): if end_id > self.end_id: - for eid in xrange(self.end_id, end_id): + eid = self.end_id + while eid < pack_id: self.miss_queue.add(eid) + eid += 1 self.end_id = end_id def get_begin_id(self): @@ -302,10 +306,10 @@ class TCPRelayHandler(object): #loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR) self.last_activity = 0 self._update_activity() - self._random_mtu_size = [random.randint(POST_MTU_MIN, POST_MTU_MAX) for i in xrange(1024)] + self._random_mtu_size = [random.randint(POST_MTU_MIN, POST_MTU_MAX) for i in range(1024)] self._random_mtu_index = 0 - self._rand_data = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" * 4 + self._rand_data = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" * 4 def __hash__(self): # default __hash__ is id / 16 @@ -474,7 +478,7 @@ class TCPRelayHandler(object): addr = self.get_local_address() - for i in xrange(2): + for i in range(2): rsp_data = self._pack_rsp_data(CMD_RSP_CONNECT_REMOTE, RSP_STATE_CONNECTEDREMOTE) self._write_to_sock(rsp_data, self._local_sock, addr) @@ -585,29 +589,29 @@ class TCPRelayHandler(object): def _pack_rsp_data(self, cmd, data): reqid_str = struct.pack(">H", self._request_id) - return ''.join([CMD_VER_STR, chr(cmd), reqid_str, data, self._rand_data[:random.randint(0, len(self._rand_data))], reqid_str]) + return b''.join([CMD_VER_STR, common.chr(cmd), reqid_str, data, self._rand_data[:random.randint(0, len(self._rand_data))], reqid_str]) def _pack_rnd_data(self, data): length = random.randint(0, len(self._rand_data)) if length == 0: return data elif length == 1: - return "\x81" + data + return b"\x81" + data elif length < 256: - return "\x80" + chr(length) + self._rand_data[:length - 2] + data + return b"\x80" + common.chr(length) + self._rand_data[:length - 2] + data else: - return "\x82" + struct.pack(">H", length) + self._rand_data[:length - 3] + data + return b"\x82" + struct.pack(">H", length) + self._rand_data[:length - 3] + data def _pack_post_data(self, cmd, pack_id, data): reqid_str = struct.pack(">H", self._request_id) recv_id = self._recvqueue.get_begin_id() - rsp_data = ''.join([CMD_VER_STR, chr(cmd), reqid_str, struct.pack(">I", recv_id), struct.pack(">I", pack_id), data, reqid_str]) + rsp_data = b''.join([CMD_VER_STR, common.chr(cmd), reqid_str, struct.pack(">I", recv_id), struct.pack(">I", pack_id), data, reqid_str]) return rsp_data def _pack_post_data_64(self, cmd, send_id, pack_id, data): reqid_str = struct.pack(">H", self._request_id) recv_id = self._recvqueue.get_begin_id() - rsp_data = ''.join([CMD_VER_STR, chr(cmd), reqid_str, struct.pack(">Q", recv_id), struct.pack(">Q", pack_id), data, reqid_str]) + rsp_data = b''.join([CMD_VER_STR, common.chr(cmd), reqid_str, struct.pack(">Q", recv_id), struct.pack(">Q", pack_id), data, reqid_str]) return rsp_data def sweep_timeout(self): @@ -615,7 +619,7 @@ class TCPRelayHandler(object): if self._stage == STAGE_STREAM: pack_id, missing = self._recvqueue.get_missing_id(0) logging.info("sweep_timeout %s %s" % (pack_id, missing)) - data = '' + data = b'' for pid in missing: data += struct.pack(">H", pid) rsp_data = self._pack_post_data(CMD_SYN_STATUS, pack_id, data) @@ -638,7 +642,7 @@ class TCPRelayHandler(object): # post CMD_SYN_STATUS send_id = self._sendingqueue.get_end_id() post_pack_id, missing = self._recvqueue.get_missing_id(0) - pack_ids_data = '' + pack_ids_data = b'' for pid in missing: pack_ids_data += struct.pack(">H", pid) @@ -671,7 +675,7 @@ class TCPRelayHandler(object): if self._stage == STAGE_RSP_ID: if cmd == CMD_CONNECT: - for i in xrange(2): + for i in range(2): rsp_data = self._pack_rsp_data(CMD_RSP_CONNECT, RSP_STATE_CONNECTED) self._write_to_sock(rsp_data, self._local_sock, addr) elif cmd == CMD_CONNECT_REMOTE: @@ -695,7 +699,7 @@ class TCPRelayHandler(object): if cmd == CMD_CONNECT_REMOTE: local_id = data[0:4] if self._local_id == local_id: - for i in xrange(2): + for i in range(2): rsp_data = self._pack_rsp_data(CMD_RSP_CONNECT_REMOTE, RSP_STATE_CONNECTEDREMOTE) self._write_to_sock(rsp_data, self._local_sock, addr) else: @@ -873,8 +877,8 @@ class UDPRelay(object): self._dns_cache = lru_cache.LRUCache(timeout=300) self._eventloop = None self._closed = False - self.server_transfer_ul = 0L - self.server_transfer_dl = 0L + self.server_transfer_ul = 0 + self.server_transfer_dl = 0 self._sockets = set() self._fd_to_handlers = {} @@ -927,7 +931,7 @@ class UDPRelay(object): def _pre_parse_udp_header(self, data): if data is None: return - datatype = ord(data[0]) + datatype = common.ord(data[0]) if datatype == 0x8: if len(data) >= 8: crc = binascii.crc32(data) & 0xffffffff @@ -935,17 +939,17 @@ class UDPRelay(object): logging.warn('uncorrect CRC32, maybe wrong password or ' 'encryption method') return None - cmd = ord(data[1]) + cmd = common.ord(data[1]) request_id = struct.unpack('>H', data[2:4])[0] data = data[4:-4] return (cmd, request_id, data) - elif len(data) >= 6 and ord(data[1]) == 0x0: + elif len(data) >= 6 and common.ord(data[1]) == 0x0: crc = binascii.crc32(data) & 0xffffffff if crc != 0xffffffff: logging.warn('uncorrect CRC32, maybe wrong password or ' 'encryption method') return None - cmd = ord(data[1]) + cmd = common.ord(data[1]) data = data[2:-4] return (cmd, 0, data) else: @@ -955,9 +959,9 @@ class UDPRelay(object): return data def _pack_rsp_data(self, cmd, request_id, data): - _rand_data = "123456789abcdefghijklmnopqrstuvwxyz" * 2 + _rand_data = b"123456789abcdefghijklmnopqrstuvwxyz" * 2 reqid_str = struct.pack(">H", request_id) - return ''.join([CMD_VER_STR, chr(cmd), reqid_str, data, _rand_data[:random.randint(0, len(_rand_data))], reqid_str]) + return b''.join([CMD_VER_STR, common.chr(cmd), reqid_str, data, _rand_data[:random.randint(0, len(_rand_data))], reqid_str]) def _handle_server(self): server = self._server_socket @@ -994,12 +998,12 @@ class UDPRelay(object): try: if data[0] == 0: if len(data[2]) >= 4: - for i in xrange(64): + for i in range(64): req_id = random.randint(1, 65535) if req_id not in self._reqid_to_hd: break if req_id in self._reqid_to_hd: - for i in xrange(64): + for i in range(64): req_id = random.randint(1, 65535) if type(self._reqid_to_hd[req_id]) is tuple: break