diff --git a/config.json b/config.json index 57e9cc1..11b34c2 100644 --- a/config.json +++ b/config.json @@ -7,6 +7,7 @@ "password":"m", "timeout":300, "method":"aes-256-cfb", + "obfs":"http_simple", "fast_open": false, "workers": 1 } diff --git a/shadowsocks/obfs.py b/shadowsocks/obfs.py new file mode 100644 index 0000000..c54eaa8 --- /dev/null +++ b/shadowsocks/obfs.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 + +from shadowsocks import common +from shadowsocks.obfsplugin import plain, http_simple + + +method_supported = {} +method_supported.update(plain.obfs) +method_supported.update(http_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 encode(self, buf): + #if len(buf) == 0: + # return buf + return self.obfs.encode(buf) + + def decode(self, buf): + #if len(buf) == 0: + # return (buf, True, False) + return self.obfs.decode(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..36e9028 --- /dev/null +++ b/shadowsocks/obfsplugin/http_simple.py @@ -0,0 +1,91 @@ +#!/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 datetime + +def create_obfs(method): + return http_simple(method) + +obfs = { + 'http_simple': (create_obfs,), +} + +class http_simple(object): + def __init__(self, method): + self.method = method + self.has_sent_header = False + self.has_recv_header = False + self.host = "" + self.port = 0 + self.recv_buffer = "" + + def encode(self, buf): + 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''' + self.has_sent_header = True + return header + buf + + def 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 /": + 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 ("", 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]) + ret_buf += datas[1] + self.has_recv_header = True + return (ret_buf, True, False) + else: + self.recv_buffer = buf + return ("", True, False) + self.has_sent_header = True + self.has_recv_header = True + return (buf, True, False) + diff --git a/shadowsocks/obfsplugin/plain.py b/shadowsocks/obfsplugin/plain.py new file mode 100644 index 0000000..90b5ea0 --- /dev/null +++ b/shadowsocks/obfsplugin/plain.py @@ -0,0 +1,41 @@ +#!/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 encode(self, buf): + return buf + + def decode(self, buf): + # (buffer_to_recv, is_need_decrypt, is_need_to_encode_and_send_back) + return (buf, True, False) diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index cecd017..3b073dc 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -27,7 +27,7 @@ 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 @@ -115,6 +115,7 @@ class TCPRelayHandler(object): self._encryptor = encrypt.Encryptor(config['password'], config['method']) self._encrypt_correct = True + self._obfs = obfs.Obfs(config.get('obfs', 'plain')) self._fastopen_connected = False self._data_to_write_to_local = [] self._data_to_write_to_remote = [] @@ -197,7 +198,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 @@ -249,6 +250,9 @@ class TCPRelayHandler(object): return True else: try: + if sock == self._local_sock and self._encrypt_correct: + obfs_encode = self._obfs.encode(data) + data = obfs_encode l = len(data) s = sock.send(data) if s < l: @@ -298,13 +302,13 @@ class TCPRelayHandler(object): 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" % (binascii.hexlify(ogn_data), )) 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) logging.warn("TCP data redir %s:%d %s" % (host, port, binascii.hexlify(data))) - #raise Exception('can not parse header') return data + ogn_data def _handle_stage_connecting(self, data): @@ -530,7 +534,13 @@ class TCPRelayHandler(object): self._update_activity(len(data)) if not is_local: if self._encrypt_correct: - data = self._encryptor.decrypt(data) + obfs_decode = self._obfs.decode(data) + if obfs_decode[2]: + self._write_to_sock("", self._local_sock) + if obfs_decode[1]: + data = self._encryptor.decrypt(obfs_decode[0]) + else: + data = obfs_decode[0] if not data: return self._server.server_transfer_ul += len(data)