├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── client.py ├── config.py ├── mixo.py ├── server.py ├── socks5_test.py ├── xor.c ├── xor_ELF_32bit.so ├── xor_ELF_64bit.so ├── xor_WindowsPE_32bit.so └── xor_WindowsPE_64bit.so /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | lib 16 | lib64 17 | 18 | # Installer logs 19 | pip-log.txt 20 | 21 | # Unit test / coverage reports 22 | .coverage 23 | .tox 24 | nosetests.xml 25 | 26 | # Translations 27 | *.mo 28 | 29 | # Mr Developer 30 | .mr.developer.cfg 31 | .project 32 | .pydevproject 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc -fPIC -shared -o xor.so xor.c 3 | 4 | clean: 5 | rm xor.so 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mixo 2 | ==== 3 | 4 | A secured socks5 proxy over the Great Fucking Wall. 5 | 6 | 一点废话 7 | -------- 8 | 9 | 因为不满ssh tunnel的使用效果,所以2012年12月某天(大概是17号)心血来潮写了这个小东西,由于 [socks5协议](http://www.openssh.com/txt/rfc1928.txt) 本身很简单、加上gevent/greenlet使得异步开发跟同步似的,所以200行就搞定了。但是性能上问题很大——主要是加密有问题。尽管加密就是最简单的xor,但是因为python不适合处理大量的小对象,所以当时写了一个python扩展,性能上就没问题了,但是又多了一项麻烦的依赖。后来发现已经有更成熟的shadowsocks,于是就弃坑了,也一直没有发布。 10 | 11 | 今天[2013.08.16]心血来潮,用ctypes来实现同样的功能,似乎也挺合适的;不过跟shadowsocks比起来有两个地方做得不好,一是没有更“高级”的加密方式(他家用了M2Crypto,代码看起来很复杂),另一个是shadowsocks在本地先回应socks5请求,只把必要的host:port信息发送给server,减少了一个来回,而我原先的实现则是在server端实现完整的socks5(现在把step1搬到client了,因为改动很小)。 12 | 13 | 总之好歹也是个凑合能用的东西了,发布出来晾着吧,也许哪天有人就用上了呢。 14 | 15 | 依赖 16 | -------- 17 | 18 | 1. Python 2.6/2.7 19 | 2. greenlet: http://pypi.python.org/pypi/greenlet 20 | 3. gevent: https://pypi.python.org/pypi/gevent 21 | 22 | 前面提到的c库,mixo自带了4个版本的so文件: 23 | 24 | 1. xor\_ELF\_32bit.so: linux/32bit 25 | 2. xor\_ELF\_64bit.so: linux/64bit 26 | 3. xor\_WindowsPE\_32bit.so: windows/32bit 27 | 4. xor\_WindowsPE\_64bit.so: windows/64bit #未测试:( 28 | 29 | 如果觉得不放心,可以自己编译: 30 | 31 | $ make 32 | 33 | 使用 34 | -------- 35 | 36 | 1. 修改配置:config.py 37 | 38 | 其中seed是密钥,改成任意整数;其他ip、端口什么的根据实际需要填吧。 39 | 40 | 2. 启动Server: 41 | 42 | $ python server.py 43 | 44 | 3. 启动Client: 45 | 46 | $ python client.py 47 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import mixo 4 | mixo.PortForwarder.start_server() 5 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | # for generateing encryption table 4 | seed = 8127389 5 | 6 | #-- client --# 7 | # bind ip 8 | forward_host = '0.0.0.0' 9 | 10 | # bind port 11 | forward_port = 9999 12 | 13 | # connect server ip (only for client; the server would bind on 0.0.0.0) 14 | server_host = '127.0.0.1' 15 | 16 | 17 | #-- server --# 18 | # bind ip 19 | server_port = 9998 20 | 21 | try: 22 | debug = False 23 | from local_config import * 24 | except: 25 | pass 26 | -------------------------------------------------------------------------------- /mixo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import struct 5 | import random 6 | import signal 7 | 8 | try: 9 | import gevent 10 | from gevent import socket 11 | from gevent.server import StreamServer 12 | from gevent.socket import create_connection, gethostbyname 13 | except: 14 | print >>sys.stderr, "please install gevent first!" 15 | sys.exit(1) 16 | 17 | import config 18 | 19 | keys = [] 20 | 21 | if config.seed: 22 | r = random.Random(config.seed) 23 | keys = [r.randint(0, 255) for i in xrange(0, 1024)] 24 | keys += keys 25 | else: 26 | raise Exception("config.seed not set!") 27 | 28 | try: 29 | import ctypes 30 | 31 | try: 32 | filename = "./xor.so" 33 | xor = ctypes.CDLL(filename) 34 | except: 35 | import platform 36 | bits, exetype = platform.architecture() 37 | filename = "./xor_%s_%s.so" % (exetype, bits) 38 | xor = ctypes.CDLL(filename) 39 | 40 | print >>sys.stderr, "loaded %s, using faster xor" % filename 41 | 42 | key_str = ''.join(map(chr, keys)) 43 | 44 | if xor.set_xor_table(key_str, len(key_str)) == 1: 45 | raise Exception("set xor table failed") 46 | 47 | def encrypt(data, pos): 48 | ret = ctypes.create_string_buffer(data) 49 | xor.xor(ret, len(data), pos) 50 | return ret.raw[:-1] 51 | 52 | except: 53 | 54 | print >>sys.stderr, "can't load xor.so, using python native." 55 | def encrypt(data, pos): 56 | return ''.join(map(lambda x, y: chr(ord(x) ^ y), data, keys[pos:pos+len(data)])) 57 | 58 | decrypt = encrypt 59 | 60 | def dumps(x): 61 | return ' '.join(map(lambda t: '%x' % struct.unpack('B', t)[0], x)) 62 | 63 | class XSocket(gevent.socket.socket): 64 | def __init__(self, socket = None, addr = None, secure = False): 65 | if socket is not None: 66 | gevent.socket.socket.__init__(self, _sock = socket) 67 | elif addr is not None: 68 | gevent.socket.socket.__init__(self) 69 | self.connect(addr) 70 | else: 71 | raise Exception("XSocket.init: bad arguments") 72 | 73 | self.secure = secure 74 | self.recv_idx = 0 75 | self.send_idx = 0 76 | 77 | def unpack(self, fmt, length): 78 | data = self.recv(length) 79 | if len(data) < length: 80 | raise Exception("XSocket.unpack: bad formatted stream") 81 | return struct.unpack(fmt, data) 82 | 83 | def pack(self, fmt, *args): 84 | data = struct.pack(fmt, *args) 85 | return self.sendall(data) 86 | 87 | def recv(self, length, *args): 88 | data = gevent.socket.socket.recv(self, length, *args) 89 | if config.debug: print 'Received:', dumps(data) 90 | if self.secure: 91 | data = decrypt(data, self.recv_idx) 92 | self.recv_idx = (self.recv_idx + len(data)) % 1024 93 | if config.debug: print 'Decrypted:', dumps(data), '--', data 94 | return data 95 | 96 | def sendall(self, data, flags = 0): 97 | if config.debug: print 'Send:', dumps(data), '--', data 98 | if self.secure: 99 | data = encrypt(data, self.send_idx) 100 | self.send_idx = (self.send_idx + len(data)) % 1024 101 | if config.debug: print 'Encrypted:', dumps(data) 102 | return gevent.socket.socket.sendall(self, data, flags) 103 | 104 | def forward(self, dest): 105 | try: 106 | while True: 107 | data = self.recv(1024) 108 | if not data: 109 | break 110 | dest.sendall(data) 111 | #except IOError, e: pass 112 | finally: 113 | print 'connection closed' 114 | self.close() 115 | dest.close() 116 | 117 | 118 | class SocksServer(StreamServer): 119 | def __init__(self, listener, **kwargs): 120 | StreamServer.__init__(self, listener, **kwargs) 121 | 122 | def handle(self, sock, addr): 123 | print 'connection from %s:%s' % addr 124 | 125 | src = XSocket(socket = sock, secure = True) 126 | 127 | #socks5 negotiation step2: specify command and destination 128 | ver, cmd, rsv, atype = src.unpack('BBBB', 4) 129 | 130 | if cmd != 0x01: 131 | src.pack('BBBBIH', 0x05, 0x07, 0x00, 0x01, 0, 0) 132 | return 133 | 134 | if atype == 0x01: #ipv4 135 | host, port = src.unpack('!IH', 6) 136 | hostip = socket.inet_ntoa(struct.pack('!I', host)) 137 | elif atype == 0x03: #domain name 138 | length = src.unpack('B', 1)[0] 139 | hostname, port = src.unpack("!%dsH" % length, length + 2) 140 | hostip = gethostbyname(hostname) 141 | host = struct.unpack("!I", socket.inet_aton(hostip))[0] 142 | elif atype == 0x04: #ipv6: TODO 143 | src.pack('!BBBBIH', 0x05, 0x07, 0x00, 0x01, 0, 0) 144 | return 145 | else: 146 | src.pack('!BBBBIH', 0x05, 0x07, 0x00, 0x01, 0, 0) 147 | return 148 | 149 | try: 150 | dest = XSocket(addr = (hostip, port)) 151 | except IOError, ex: 152 | print "%s:%d" % addr, "failed to connect to %s:%d" % (hostip, port) 153 | src.pack('!BBBBIH', 0x05, 0x03, 0x00, 0x01, host, port) 154 | return 155 | 156 | src.pack('!BBBBIH', 0x05, 0x00, 0x00, 0x01, host, port) 157 | 158 | gevent.spawn(src.forward, dest) 159 | gevent.spawn(dest.forward, src) 160 | 161 | def close(self): 162 | sys.exit(0) 163 | 164 | @staticmethod 165 | def start_server(): 166 | server = SocksServer(('0.0.0.0', config.server_port)) 167 | gevent.signal(signal.SIGTERM, server.close) 168 | gevent.signal(signal.SIGINT, server.close) 169 | print "Server is listening on 0.0.0.0:%d" % config.server_port 170 | server.serve_forever() 171 | 172 | 173 | class PortForwarder(StreamServer): 174 | def __init__(self, listener, dest, **kwargs): 175 | StreamServer.__init__(self, listener, **kwargs) 176 | self.destaddr = dest 177 | 178 | def handle(self, sock, addr): 179 | 180 | src = XSocket(socket = sock) 181 | 182 | #socks5 negotiation step1: choose an authentication method 183 | ver, n_method = src.unpack('BB', 2) 184 | 185 | if ver != 0x05: 186 | src.pack('BB', 0x05, 0xff) 187 | return 188 | 189 | if n_method > 0: 190 | src.recv(n_method) 191 | 192 | src.pack('!BB', 0x05, 0x00) #0x00 means no authentication needed 193 | 194 | print "Forwarder: connection from %s:%d" % addr 195 | try: 196 | dest = XSocket(addr = self.destaddr, secure = True) 197 | except IOError, ex: 198 | print "%s:%d" % addr, "failed to connect to SocksServer %s:%d" % self.destaddr 199 | print ex 200 | return 201 | gevent.spawn(src.forward, dest) 202 | gevent.spawn(dest.forward, src) 203 | 204 | def close(self): 205 | sys.exit(0) 206 | 207 | @staticmethod 208 | def start_server(): 209 | forward_addr = (config.forward_host, config.forward_port) 210 | server_addr = (config.server_host, config.server_port) 211 | server = PortForwarder(forward_addr, server_addr) 212 | 213 | gevent.signal(signal.SIGTERM, server.close) 214 | gevent.signal(signal.SIGINT, server.close) 215 | print "Forwarder is listening on %s:%d for Server %s:%d" % \ 216 | (config.forward_host, config.forward_port, \ 217 | config.server_host, config.server_port) 218 | server.serve_forever() 219 | 220 | if __name__ == '__main__': 221 | import sys 222 | if len(sys.argv) == 1: 223 | PortForwarder.start_server() 224 | else: 225 | SocksServer.start_server() 226 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import mixo 4 | mixo.SocksServer.start_server() 5 | -------------------------------------------------------------------------------- /socks5_test.py: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | #SOCKS Protocol Version 5: http://www.openssh.com/txt/rfc1928.txt 3 | 4 | import socket 5 | 6 | #---- socks5 client ----# 7 | def client(): 8 | HOST = '127.0.0.1' 9 | PORT = 9999 10 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | s.connect((HOST, PORT)) 12 | 13 | #ver=5, n_method=1, methods=[0] 14 | s.sendall("\x05\x01\x00") 15 | print repr(s.recv(1024)) 16 | 17 | #ver=5, cmd=1(connect), reserved=0 + atype=1(ip), host=127.0.0.1 + port=80 ;;p.s. ATYPE(03=host,04=ipv6) 18 | s.sendall("\x05\x01\x00" + "\x01\x7f\x00\x00\x01" + "\x00\x50") 19 | print repr(s.recv(1024)) 20 | 21 | #http request 22 | s.sendall("GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n") 23 | print s.recv(4096) 24 | 25 | #---- socks5 server ----# 26 | def server(): 27 | HOST = '0.0.0.0' 28 | PORT = 9999 29 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 30 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 31 | s.bind((HOST, PORT)) 32 | s.listen(1024) 33 | t, a = s.accept() 34 | print repr(t.recv(1024)) 35 | 36 | #ver=5, method=0(no authentication) 37 | t.send("\x05\x00") 38 | print repr(t.recv(1024)) 39 | 40 | #ver=5, Reply=0(succeeded), reserved=0, atype=1(ip), host=127.0.0.1 + port=80 41 | t.send("\x05\x00\x00" + "\x01\x7f\x00\x00\x01" + "\x00\x50") 42 | 43 | #http-request from client 44 | x = t.recv(4096) 45 | print x 46 | 47 | #http-response 48 | t.send("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nhello!") 49 | 50 | client() 51 | #server() 52 | -------------------------------------------------------------------------------- /xor.c: -------------------------------------------------------------------------------- 1 | #ifdef __WIN32 2 | #define DLLEXPORT __declspec(dllexport) 3 | #else 4 | #define DLLEXPORT 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | unsigned char *table = NULL; 12 | 13 | DLLEXPORT int set_xor_table(const unsigned char *str, int len) 14 | { 15 | int i; 16 | table = (unsigned char *)malloc(len); 17 | if (table == NULL) 18 | return 1; 19 | memcpy(table, str, len); 20 | return 0; 21 | } 22 | 23 | DLLEXPORT int xor(unsigned char *buf, int len, int pos) 24 | { 25 | int i; 26 | for (i = 0; i < len; i++, pos++) 27 | buf[i] ^= table[pos]; 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /xor_ELF_32bit.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felix021/mixo/fab8a650db972b73c3340aaa12832ab52294324e/xor_ELF_32bit.so -------------------------------------------------------------------------------- /xor_ELF_64bit.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felix021/mixo/fab8a650db972b73c3340aaa12832ab52294324e/xor_ELF_64bit.so -------------------------------------------------------------------------------- /xor_WindowsPE_32bit.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felix021/mixo/fab8a650db972b73c3340aaa12832ab52294324e/xor_WindowsPE_32bit.so -------------------------------------------------------------------------------- /xor_WindowsPE_64bit.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felix021/mixo/fab8a650db972b73c3340aaa12832ab52294324e/xor_WindowsPE_64bit.so --------------------------------------------------------------------------------