├── LICENSE ├── README.md └── server.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Artem Golubin 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 all 13 | 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # socks5 2 | A toy socks 5 server written in Python 3 | 4 | 5 | https://rushter.com/blog/python-socks-server/ -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import select 3 | import socket 4 | import struct 5 | from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler 6 | 7 | logging.basicConfig(level=logging.DEBUG) 8 | 9 | SOCKS_VERSION = 5 10 | SOCKS_ADDR = '127.0.0.1' 11 | SOCKS_PORT = 1080 12 | SOCKS_USER = 'username' 13 | SOCKS_PASS = 'password' 14 | 15 | class ThreadingTCPServer(ThreadingMixIn, TCPServer): 16 | pass 17 | 18 | 19 | class SocksProxy(StreamRequestHandler): 20 | username = SOCKS_USER 21 | password = SOCKS_PASS 22 | 23 | def handle(self): 24 | logging.info('Accepting connection from %s:%s' % self.client_address) 25 | 26 | # greeting header 27 | # read and unpack 2 bytes from a client 28 | header = self.connection.recv(2) 29 | version, nmethods = struct.unpack("!BB", header) 30 | 31 | # socks 5 32 | assert version == SOCKS_VERSION 33 | assert nmethods > 0 34 | 35 | # get available methods 36 | methods = self.get_available_methods(nmethods) 37 | 38 | # accept only USERNAME/PASSWORD auth 39 | if 2 not in set(methods): 40 | # close connection 41 | self.server.close_request(self.request) 42 | return 43 | 44 | # send welcome message 45 | self.connection.sendall(struct.pack("!BB", SOCKS_VERSION, 2)) 46 | 47 | if not self.verify_credentials(): 48 | return 49 | 50 | # request 51 | version, cmd, _, address_type = struct.unpack("!BBBB", self.connection.recv(4)) 52 | assert version == SOCKS_VERSION 53 | 54 | if address_type == 1: # IPv4 55 | inet_type = socket.AF_INET 56 | address = socket.inet_ntop(inet_type, self.connection.recv(4)) 57 | elif address_type == 3: # Domain name 58 | domain_length = self.connection.recv(1)[0] 59 | address = self.connection.recv(domain_length) 60 | address = socket.gethostbyname(address) 61 | elif address_type == 4: # IPv6 62 | inet_type = socket.AF_INET6 63 | address = socket.inet_ntop(inet_type, self.connection.recv(16)) 64 | port = struct.unpack('!H', self.connection.recv(2))[0] 65 | 66 | # reply 67 | try: 68 | if cmd == 1: # CONNECT 69 | remote = socket.socket(inet_type, socket.SOCK_STREAM) 70 | remote.connect((address, port)) 71 | bind_address = remote.getsockname() 72 | logging.info('Connected to %s %s' % (address, port)) 73 | else: 74 | self.server.close_request(self.request) 75 | 76 | addr = struct.unpack("!I", socket.inet_aton(bind_address[0]))[0] 77 | port = bind_address[1] 78 | reply = struct.pack("!BBBBIH", SOCKS_VERSION, 0, 0, 1, 79 | addr, port) 80 | 81 | except Exception as err: 82 | logging.error(err) 83 | # return connection refused error 84 | reply = self.generate_failed_reply(address_type, 5) 85 | 86 | self.connection.sendall(reply) 87 | 88 | # establish data exchange 89 | if reply[1] == 0 and cmd == 1: 90 | self.exchange_loop(self.connection, remote) 91 | 92 | self.server.close_request(self.request) 93 | 94 | def get_available_methods(self, n): 95 | methods = [] 96 | for i in range(n): 97 | methods.append(ord(self.connection.recv(1))) 98 | return methods 99 | 100 | def verify_credentials(self): 101 | version = ord(self.connection.recv(1)) 102 | assert version == 1 103 | 104 | username_len = ord(self.connection.recv(1)) 105 | username = self.connection.recv(username_len).decode('utf-8') 106 | 107 | password_len = ord(self.connection.recv(1)) 108 | password = self.connection.recv(password_len).decode('utf-8') 109 | 110 | if username == self.username and password == self.password: 111 | # success, status = 0 112 | response = struct.pack("!BB", version, 0) 113 | self.connection.sendall(response) 114 | return True 115 | 116 | # failure, status != 0 117 | response = struct.pack("!BB", version, 0xFF) 118 | self.connection.sendall(response) 119 | self.server.close_request(self.request) 120 | return False 121 | 122 | def generate_failed_reply(self, address_type, error_number): 123 | return struct.pack("!BBBBIH", SOCKS_VERSION, error_number, 0, address_type, 0, 0) 124 | 125 | def exchange_loop(self, client, remote): 126 | 127 | while True: 128 | 129 | # wait until client or remote is available for read 130 | r, w, e = select.select([client, remote], [], []) 131 | 132 | if client in r: 133 | data = client.recv(4096) 134 | if remote.send(data) <= 0: 135 | break 136 | 137 | if remote in r: 138 | data = remote.recv(4096) 139 | if client.send(data) <= 0: 140 | break 141 | 142 | 143 | if __name__ == '__main__': 144 | with ThreadingTCPServer((SOCKS_ADDR, SOCKS_PORT), SocksProxy) as server: 145 | server.serve_forever() 146 | --------------------------------------------------------------------------------