├── .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
--------------------------------------------------------------------------------