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/obfsplugin/http_simple.py b/shadowsocks/obfsplugin/http_simple.py index 592811c..90f184f 100644 --- a/shadowsocks/obfsplugin/http_simple.py +++ b/shadowsocks/obfsplugin/http_simple.py @@ -22,23 +22,35 @@ import sys import hashlib import logging import binascii +import base64 import datetime +from shadowsocks.common import to_bytes, to_str -def create_obfs(method): +def create_http_obfs(method): return http_simple(method) +def create_http2_obfs(method): + return http2_simple(method) + obfs = { - 'http_simple': (create_obfs,), + 'http_simple': (create_http_obfs,), + 'http2_simple': (create_http2_obfs,), } +def match_begin(str1, str2): + if len(str1) >= len(str2): + if str1[:len(str2)] == str2: + return True + return False + class http_simple(object): def __init__(self, method): self.method = method self.has_sent_header = False self.has_recv_header = False - self.host = "" + self.host = None self.port = 0 - self.recv_buffer = "" + self.recv_buffer = b'' def client_encode(self, buf): # TODO @@ -52,19 +64,33 @@ class http_simple(object): if self.has_sent_header: return buf else: - header = "HTTP/1.1 200 OK\r\nServer: openresty\r\nDate: " - header += datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT') - header += '''\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''' + 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) else: buf = self.recv_buffer + buf if len(buf) > 10: - if buf[:5] == "GET /" or buf[:6] == "POST /": + if match_begin(buf, b'GET /') or match_begin(buf, b'POST /'): pass else: #not http header, run on original protocol self.has_sent_header = True @@ -73,26 +99,81 @@ class http_simple(object): return (buf, True, False) else: self.recv_buffer = buf - return ("", True, False) - - datas = buf.split('\r\n\r\n', 1) - if datas and len(datas) > 1 and len(datas[1]) >= 7: - lines = buf.split('\r\n') - if lines and len(lines) > 4: - hex_items = lines[0].split('%') - if hex_items and len(hex_items) > 1: - ret_buf = "" - for index in xrange(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 (b'', True, False) + + datas = buf.split(b'\r\n\r\n', 1) + ret_buf = b'' + 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(object): + 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 + else: + 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) + else: + 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 ("", True, False) + return (b'', True, False) self.has_sent_header = True self.has_recv_header = True return (buf, True, 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/tcprelay.py b/shadowsocks/tcprelay.py index 0438249..1f5647c 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -117,11 +117,11 @@ class TCPRelayHandler(object): self._encryptor = encrypt.Encryptor(config['password'], config['method']) self._encrypt_correct = True - self._obfs = obfs.Obfs(config.get('obfs', 'plain')) + 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] @@ -293,7 +293,7 @@ class TCPRelayHandler(object): def _get_redirect_host(self, client_address, ogn_data): # test - host_list = [("www.bing.com", 80), ("www.microsoft.com", 80), ("www.baidu.com", 443), ("www.qq.com", 80), ("www.csdn.net", 80), ("1.2.3.4", 1000)] + 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] @@ -312,7 +312,7 @@ class TCPRelayHandler(object): self._encrypt_correct = False #create redirect or disconnect by hash code host, port = self._get_redirect_host(client_address, ogn_data) - data = "\x03" + chr(len(host)) + host + struct.pack('>H', port) + 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 @@ -551,7 +551,7 @@ class TCPRelayHandler(object): if self._encrypt_correct: obfs_decode = self._obfs.server_decode(data) if obfs_decode[2]: - self._write_to_sock("", self._local_sock) + self._write_to_sock(b'', self._local_sock) if obfs_decode[1]: data = self._encryptor.decrypt(obfs_decode[0]) else: @@ -588,10 +588,10 @@ class TCPRelayHandler(object): port = 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: @@ -608,7 +608,7 @@ class TCPRelayHandler(object): if self._is_local: obfs_decode = self._obfs.client_decode(data) if obfs_decode[1]: - self._write_to_sock("", self._remote_sock) + self._write_to_sock(b'', self._remote_sock) data = self._encryptor.decrypt(obfs_decode[0]) else: if self._encrypt_correct: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index 5ebf731..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): @@ -309,7 +309,7 @@ class TCPRelayHandler(object): 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 @@ -589,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): @@ -619,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) @@ -642,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) @@ -959,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