SOCKS5 is a widely used proxy protocol for facilitating secure and efficient communication between clients and servers.
Compared to SOCKS4, SOCKS5 has the following additional capabilities:
- Authentication,
- IPv6,
- Domain name resolution (DNS),
- UDP association.
HTTP proxy is even more limited. It is exclusively used for HTTP(S) traffic.
Note: none of the proxy technologies is encrypted by default and most are not even capable of encryption. Proxying a connection is a way to evade censorship and evade geofencing (block by source IP).
- Why not just using a private VPN instead of the proposed private Proxy?
- Because, as long as encryption is not required, a SOCKS5 proxy has a much smaller overhead than a VPN and a single-core VM or CT is capable of top up its network interfaces with minimum latency. Using a proxy is much easier to associate a single application (e.g. a secondary browser) to use it instead of the whole operating system or network-based routing.
Note: my Internet speed was the bottleneck during the speed test.
It is worthy to mention that multiple sources on the internet offer frequently updated lists of open proxy servers [Link].
Proxies in general can be used as cache for static web content to reduce data traffic in metered or impaired links [Link], but is is not the scope of this post.
The following codes make possible to hop (proxy) on another server to gain access to the internet hiding the real source of the request.
GOLANG – A high performance proxy written in Go using a third party module [Link]:
Create a new file called mail.go
and add the following content in it:
package main import ( "fmt" "syscall" "github.com/things-go/go-socks5" ) func main() { if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &syscall.Rlimit{Max: 65536, Cur: 65536}); err != nil { fmt.Println("Error setting rlimit:", err) } server := socks5.NewServer() if err := server.ListenAndServe("tcp", ":1080"); err != nil { panic(err) } }
go run main.go
Note: the syscall
was used to increase the limit of open file descriptors (connections) allowed by the system to the user. It could also be accomplished via console using ulimit -n 65536
.
PYTHON – This example requires username and password for authentication. It is a fork of the code available at [Link].
import select import socket import struct from socketserver import ForkingTCPServer, TCPServer, StreamRequestHandler class ThreadingTCPServer(ForkingTCPServer, TCPServer): pass class SocksProxy(StreamRequestHandler): username = 'username' password = 'password' def handle(self): # greeting header - read and unpack 2 bytes from a client header = self.connection.recv(2) version, nmethods = struct.unpack("!BB", header) # socks 5 assert version == 5 assert nmethods >= 0 # get available methods methods = self.get_available_methods(nmethods) # accept only USERNAME/PASSWORD auth if 2 not in set(methods): # close connection self.server.close_request(self.request) return # send welcome message self.connection.sendall(struct.pack("!BB", 5, 2)) if not self.verify_credentials(): return # parsing the request version, cmd, _, address_type = struct.unpack("!BBBB", self.connection.recv(4)) assert version == 5 if address_type == 1: # IPv4 address = socket.inet_ntoa(self.connection.recv(4)) elif address_type == 3: # DNS try: domain_length = self.connection.recv(1)[0] address = self.connection.recv(domain_length) address = socket.gethostbyname(address) except: address = '0.0.0.0' port = struct.unpack('!H', self.connection.recv(2))[0] # replying to the request try: if cmd == 1: remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM) remote.connect((address, port)) bind_address = remote.getsockname() else: self.server.close_request(self.request) addr = struct.unpack("!I", socket.inet_aton(bind_address[0]))[0] port = bind_address[1] reply = struct.pack("!BBBBIH", 5, 0, 0, 1, addr, port) except Exception as err: reply = self.generate_failed_reply(address_type, 5) self.connection.sendall(reply) # establish data exchange if reply[1] == 0 and cmd == 1: self.exchange_loop(self.connection, remote) self.server.close_request(self.request) def get_available_methods(self, n): methods = [] for i in range(n): methods.append(ord(self.connection.recv(1))) return methods def verify_credentials(self): pice_of_data = self.connection.recv(1) if len(pice_of_data) == 0: return version = ord(pice_of_data) assert version == 1 username_len = ord(self.connection.recv(1)) username = self.connection.recv(username_len).decode('utf-8') password_len = ord(self.connection.recv(1)) password = self.connection.recv(password_len).decode('utf-8') # change the following flag to True to do not check credentials accept_any_creds = False if (username == self.username and password == self.password) or accept_any_creds: response = struct.pack("!BB", version, 0) self.connection.sendall(response) return True response = struct.pack("!BB", version, 0xFF) self.connection.sendall(response) self.server.close_request(self.request) return False def generate_failed_reply(self, address_type, error_number): return struct.pack("!BBBBIH", 5, error_number, 0, address_type, 0, 0) def exchange_loop(self, client, remote): while True: try: # wait until client or remote is available for read r, w, e = select.select([client, remote], [], []) if client in r: data = client.recv(4096) if remote.send(data) <= 0: break if remote in r: data = remote.recv(4096) if client.send(data) <= 0: break except KeyboardInterrupt: self.server.close_request(self.request) exit() except Exception as err: print(err) if __name__ == '__main__': with ThreadingTCPServer(('0.0.0.0', 1080), SocksProxy) as server: try: server.serve_forever() except: pass
BONUS
The following PHP code, that can be dropped on an existent web server can provide a hop for single
<?php if ($_SERVER['REQUEST_METHOD'] == 'GET') { $url = $_GET['url']; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, getallheaders()); $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header = substr($response, 0, $header_size); $body = substr($response, $header_size); curl_close($ch); foreach (explode("\r\n", $header) as $header_line) { header($header_line); } echo $body; } else { die('Invalid request method'); } ?>
It requires the PHP cURL module to be installed:
sudo apt-get install php-curl -y sudo systemctl restart apache2
Utilisation (the file size will be limited to the PHP allowed memory size on the server configuration):
curl "http://200.200.200.200/path/proxy.php?url=https://7-zip.org/a/7z2201-x64.exe" > 7zip.exe