├── .gitignore ├── LICENSE ├── README.md ├── msocks5.py ├── socks5_test.py └── ssocks5.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | msocks5.py: totally public. 2 | 3 | ssocks5.py: please refer to [license of shadowsocks](https://github.com/clowwindy/shadowsocks/blob/master/LICENSE) 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ssocks5 2 | ======= 3 | 4 | socks5 proxy simplified from shadowsocks and mixo. 5 | 6 | 好吧,我把自己的[mixo](https://github.com/felix021/mixo)也简化了一下,两个版本的socks5 proxy,换着玩吧…… 7 | 8 | usage 9 | ---- 10 | msocks5.py 或者 ssocks5.py 随便挑一个: 11 | 12 | python msocks5.py #监听7070 13 | 14 | python msocks5.py 1080 #监听1080 15 | 16 | 把shadowsocks/mixo简化只留下最基本的socks5代理功能。msocks5.py是完全public的;至于ssocks.py……如果一定需要个什么协议的话,参考[shadowsocks的协议](https://github.com/clowwindy/shadowsocks/blob/master/LICENSE)吧 :) 17 | 18 | p.s. msocks5 依赖于 gevent,ssocks5 则可以在无gevent的情况下运行。 19 | 20 | p.p.s. 如果你需要一个带加密、转发功能的翻墙socks5代理,你也可以用我写的 [mixo](https://github.com/felix021/mixo) :D 21 | -------------------------------------------------------------------------------- /msocks5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # @2013.08.22 by felix021 4 | # This file is modified fron github/felix021/mixo 5 | # to act as a pure socks5 proxy. 6 | # 7 | # usage: 8 | # python msocks5.py #listens on 7070 9 | # python msocks5.py 1080 #listens on 1080 10 | 11 | import sys 12 | import struct 13 | import signal 14 | 15 | try: 16 | import gevent 17 | from gevent import socket 18 | from gevent.server import StreamServer 19 | from gevent.socket import create_connection, gethostbyname 20 | except: 21 | print >>sys.stderr, "please install gevent first!" 22 | sys.exit(1) 23 | 24 | class XSocket(gevent.socket.socket): 25 | def __init__(self, socket = None, addr = None): 26 | if socket is not None: 27 | gevent.socket.socket.__init__(self, _sock = socket) 28 | elif addr is not None: 29 | gevent.socket.socket.__init__(self) 30 | self.connect(addr) 31 | else: 32 | raise Exception("XSocket.init: bad arguments") 33 | 34 | def unpack(self, fmt, length): 35 | data = self.recv(length) 36 | if len(data) < length: 37 | raise Exception("XSocket.unpack: bad formatted stream") 38 | return struct.unpack(fmt, data) 39 | 40 | def pack(self, fmt, *args): 41 | data = struct.pack(fmt, *args) 42 | return self.sendall(data) 43 | 44 | def forward(self, dest): 45 | try: 46 | while True: 47 | data = self.recv(1024) 48 | if not data: 49 | break 50 | dest.sendall(data) 51 | finally: 52 | self.close() 53 | dest.close() 54 | 55 | 56 | class SocksServer(StreamServer): 57 | def handle(self, sock, addr): 58 | print 'connection from %s:%s' % addr 59 | 60 | src = XSocket(socket = sock) 61 | 62 | #socks5 negotiation step1: choose an authentication method 63 | ver, n_method = src.unpack('BB', 2) 64 | 65 | if ver != 0x05: 66 | src.pack('BB', 0x05, 0xff) 67 | return 68 | 69 | if n_method > 0: 70 | src.recv(n_method) 71 | 72 | src.pack('!BB', 0x05, 0x00) #0x00 means no authentication needed 73 | 74 | #socks5 negotiation step2: specify command and destination 75 | ver, cmd, rsv, atype = src.unpack('BBBB', 4) 76 | 77 | if cmd != 0x01: 78 | src.pack('BBBBIH', 0x05, 0x07, 0x00, 0x01, 0, 0) 79 | return 80 | 81 | if atype == 0x01: #ipv4 82 | host, port = src.unpack('!IH', 6) 83 | hostip = socket.inet_ntoa(struct.pack('!I', host)) 84 | elif atype == 0x03: #domain name 85 | length = src.unpack('B', 1)[0] 86 | hostname, port = src.unpack("!%dsH" % length, length + 2) 87 | hostip = gethostbyname(hostname) 88 | host = struct.unpack("!I", socket.inet_aton(hostip))[0] 89 | elif atype == 0x04: #ipv6: TODO 90 | src.pack('!BBBBIH', 0x05, 0x07, 0x00, 0x01, 0, 0) 91 | return 92 | else: 93 | src.pack('!BBBBIH', 0x05, 0x07, 0x00, 0x01, 0, 0) 94 | return 95 | 96 | try: 97 | dest = XSocket(addr = (hostip, port)) 98 | except IOError, ex: 99 | print "%s:%d" % addr, "failed to connect to %s:%d" % (hostip, port) 100 | src.pack('!BBBBIH', 0x05, 0x03, 0x00, 0x01, host, port) 101 | return 102 | 103 | src.pack('!BBBBIH', 0x05, 0x00, 0x00, 0x01, host, port) 104 | 105 | gevent.spawn(src.forward, dest) 106 | gevent.spawn(dest.forward, src) 107 | 108 | def close(self): 109 | sys.exit(0) 110 | 111 | @staticmethod 112 | def start_server(): 113 | global port 114 | server = SocksServer(('0.0.0.0', port)) 115 | gevent.signal(signal.SIGTERM, server.close) 116 | gevent.signal(signal.SIGINT, server.close) 117 | print "Server is listening on 0.0.0.0:%d" % port 118 | server.serve_forever() 119 | 120 | if __name__ == '__main__': 121 | import sys 122 | global port 123 | port = 7070 124 | if len(sys.argv) > 1: 125 | port = int(sys.argv[1]) 126 | SocksServer.start_server() 127 | -------------------------------------------------------------------------------- /socks5_test.py: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | #SOCKS Protocol Version 5: http://www.openssh.com/txt/rfc1928.txt 3 | 4 | #this file is used as single-time client or server. 5 | 6 | import socket 7 | 8 | #---- socks5 client ----# 9 | def client(): 10 | HOST = '127.0.0.1' 11 | PORT = 7070 12 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 | s.connect((HOST, PORT)) 14 | 15 | #ver=5, n_method=1, methods=[0] 16 | s.sendall("\x05\x01\x00") 17 | print repr(s.recv(1024)) 18 | 19 | #ver=5, cmd=1(connect), reserved=0 + atype=1(ip), host=127.0.0.1 + port=80 ;;p.s. ATYPE(03=host,04=ipv6) 20 | s.sendall("\x05\x01\x00" + "\x01\x7f\x00\x00\x01" + "\x00\x50") 21 | print repr(s.recv(1024)) 22 | 23 | #http request 24 | s.sendall("GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n") 25 | print s.recv(4096) 26 | 27 | #---- socks5 server ----# 28 | def server(): 29 | HOST = '0.0.0.0' 30 | PORT = 7070 31 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 32 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 33 | s.bind((HOST, PORT)) 34 | s.listen(1024) 35 | t, a = s.accept() 36 | print repr(t.recv(1024)) 37 | 38 | #ver=5, method=0(no authentication) 39 | t.send("\x05\x00") 40 | print repr(t.recv(1024)) 41 | 42 | #ver=5, Reply=0(succeeded), reserved=0, atype=1(ip), host=127.0.0.1 + port=80 43 | t.send("\x05\x00\x00" + "\x01\x7f\x00\x00\x01" + "\x00\x50") 44 | 45 | #http-request from client 46 | x = t.recv(4096) 47 | print x 48 | 49 | #http-response 50 | t.send("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nhello!") 51 | 52 | client() 53 | #server() 54 | -------------------------------------------------------------------------------- /ssocks5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2013 clowwindy 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | # @2013.08.22 by felix021 24 | # This file is modified fron local.py from shadowsocks 1.3.3 to act as a pure 25 | # socks5 proxy. 26 | # usage: 27 | # python ssocks5.py #listens on 7070 28 | # python ssocks5.py 1080 #listens on 1080 29 | 30 | from __future__ import with_statement 31 | import sys 32 | 33 | try: 34 | import gevent 35 | import gevent.monkey 36 | gevent.monkey.patch_all(dns=gevent.version_info[0] >= 1) 37 | except ImportError: 38 | gevent = None 39 | print >>sys.stderr, 'warning: gevent not found, using threading instead' 40 | 41 | import socket 42 | import select 43 | import SocketServer 44 | import struct 45 | import os 46 | import logging 47 | import getopt 48 | 49 | 50 | def send_all(sock, data): 51 | bytes_sent = 0 52 | while True: 53 | r = sock.send(data[bytes_sent:]) 54 | if r < 0: 55 | return r 56 | bytes_sent += r 57 | if bytes_sent == len(data): 58 | return bytes_sent 59 | 60 | 61 | class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 62 | allow_reuse_address = True 63 | 64 | 65 | class Socks5Server(SocketServer.StreamRequestHandler): 66 | def handle_tcp(self, sock, remote): 67 | try: 68 | fdset = [sock, remote] 69 | while True: 70 | r, w, e = select.select(fdset, [], []) 71 | if sock in r: 72 | data = sock.recv(4096) 73 | if len(data) <= 0: 74 | break 75 | result = send_all(remote, data) 76 | if result < len(data): 77 | raise Exception('failed to send all data') 78 | 79 | if remote in r: 80 | data = remote.recv(4096) 81 | if len(data) <= 0: 82 | break 83 | result = send_all(sock, data) 84 | if result < len(data): 85 | raise Exception('failed to send all data') 86 | finally: 87 | sock.close() 88 | remote.close() 89 | 90 | def handle(self): 91 | try: 92 | sock = self.connection 93 | sock.recv(262) 94 | sock.send("\x05\x00") 95 | data = self.rfile.read(4) or '\x00' * 4 96 | mode = ord(data[1]) 97 | if mode != 1: 98 | logging.warn('mode != 1') 99 | return 100 | 101 | addrtype = ord(data[3]) 102 | if addrtype == 1: 103 | addr_ip = self.rfile.read(4) 104 | addr = socket.inet_ntoa(addr_ip) 105 | elif addrtype == 3: 106 | addr_len = self.rfile.read(1) 107 | addr = self.rfile.read(ord(addr_len)) 108 | elif addrtype == 4: 109 | addr_ip = self.rfile.read(16) 110 | addr = socket.inet_ntop(socket.AF_INET6, addr_ip) 111 | else: 112 | logging.warn('addr_type not support') 113 | # not support 114 | return 115 | addr_port = self.rfile.read(2) 116 | port = struct.unpack('>H', addr_port) 117 | try: 118 | reply = "\x05\x00\x00\x01" 119 | reply += socket.inet_aton('0.0.0.0') + struct.pack(">H", 2222) 120 | self.wfile.write(reply) 121 | # reply immediately 122 | remote = socket.create_connection((addr, port[0])) 123 | logging.info('connecting %s:%d' % (addr, port[0])) 124 | except socket.error, e: 125 | logging.warn(e) 126 | return 127 | self.handle_tcp(sock, remote) 128 | except socket.error, e: 129 | logging.warn(e) 130 | 131 | 132 | def main(): 133 | global PORT, LOCAL, IPv6 134 | 135 | logging.basicConfig(level=logging.DEBUG, 136 | format='%(asctime)s %(levelname)-8s %(message)s', 137 | datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') 138 | 139 | # fix py2exe 140 | if hasattr(sys, "frozen") and sys.frozen in \ 141 | ("windows_exe", "console_exe"): 142 | p = os.path.dirname(os.path.abspath(sys.executable)) 143 | os.chdir(p) 144 | 145 | KEY = None 146 | METHOD = None 147 | LOCAL = '' 148 | IPv6 = False 149 | 150 | PORT = 7070 151 | if len(sys.argv) > 1: 152 | PORT = int(sys.argv[1]) 153 | 154 | try: 155 | if IPv6: 156 | ThreadingTCPServer.address_family = socket.AF_INET6 157 | server = ThreadingTCPServer((LOCAL, PORT), Socks5Server) 158 | logging.info("starting local at %s:%d" % tuple(server.server_address[:2])) 159 | server.serve_forever() 160 | except socket.error, e: 161 | logging.error(e) 162 | except KeyboardInterrupt: 163 | server.shutdown() 164 | sys.exit(0) 165 | 166 | if __name__ == '__main__': 167 | main() 168 | --------------------------------------------------------------------------------