I've a webserver that is going to answer requests at a remote socks5 proxy.
I want the webserver to connect to the proxy, send the bind command, and listen to a connection.
How can I set up a http.server
to bind and listen on a remote port?
I have tried to override server_bind
without success:
class TestHTTPServer ( http.server.HTTPServer ):
def __init__ ( self, server_address, RequestHandlerClass, proxy_host=None, proxy_port=None, bind_and_activate=True ):
if ':' in (str)(server_address[0]):
self.address_family = socket.AF_INET6
else:
self.address_family = socket.AF_INET
self.proxy_host = proxy_host
self.proxy_port = proxy_port
super().__init__ ( server_address, RequestHandlerClass, bind_and_activate )
def serve_forever ( self, poll_interval = 0.5 ):
self.socket.settimeout ( poll_interval )
while not stop_unittest_event.is_set ():
try:
self.handle_request ()
except OSError:
pass
def server_bind ( self ):
if self.proxy_host is None or self.proxy_port is None:
super().server_bind ()
else:
self.socket = socks.socksocket()
self.socket.set_proxy(socks.SOCKS5, self.server_address[0], self.server_address[1])
self.socket.bind(self.server_address)
It seems as set_proxy
only handles client requests (not servers). How can this code be changed to listen on a socks5 port?
It is something an FTP-server does in active mode.
class TestHTTPServerServer ( http.server.HTTPServer ):
def __init__ ( self, server_address, RequestHandlerClass, bind_and_activate=True, **kwargs ):
self.proxy_args = kwargs.copy ()
if bool ( self.proxy_args ) is False : # standard way of initalization
if ':' in (str)(server_address[0]):
self.address_family = socket.AF_INET6
else:
self.address_family = socket.AF_INET
self.allow_reuse_address = True
super().__init__ ( server_address, RequestHandlerClass, bind_and_activate )
else:
self.proxy_args = kwargs.copy ()
self.RequestHandlerClass = RequestHandlerClass
self.socket = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
self.server_socket = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
self.server_address = ( "0.0.0.0", 0 )
def serve_forever ( self, poll_interval = 0.5 ):
if bool ( self.proxy_args ) is False:
self.socket.settimeout ( poll_interval )
while not test_unittests_stop_event.is_set ():
try:
self.handle_request ()
except OSError:
pass
else:
d = {}
while True:
if ':' in (str)(self.proxy_args [ "proxy_host" ] ):
self.proxy_socket = socket.socket ( socket.AF_INET6, socket.SOCK_STREAM )
else:
self.proxy_socket = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
self.proxy_socket.setsockopt ( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
self.proxy_socket.settimeout ( 10 )
self.proxy_socket.connect ( ( self.proxy_args [ "proxy_host" ], self.proxy_args [ "proxy_port" ] ) )
if "username" in self.proxy_args and "password" in self.proxy_args:
d [ "username" ] = self.proxy_args [ "username" ]
d [ "password" ] = self.proxy_args [ "password" ]
server_socks5_client_send_greet ( self.proxy_socket, d )
server_socks5_client_recv_choice ( self.proxy_socket, d )
server_socks5_client_send_authorization_request ( self.proxy_socket, d )
server_socks5_client_recv_authorization_response ( self.proxy_socket, d )
d [ "cmd" ] = 2 # bind
if "bind.ip.version" in d:
d [ "ip.version" ] = d [ "bind.ip.version" ]
else:
d [ "ip.version" ] = 4
if "bind.ip" in d:
d [ "ip" ] = d [ "bind.ip" ]
else:
d [ "ip" ] = "127.0.0.1"
if "bind.port" in d:
d [ "port" ] = d [ "bind.port" ]
else:
d [ "port" ] = 0
# print ( d )
server_socks5_client_send_connect_request ( self.proxy_socket, d )
server_socks5_client_recv_connect_response ( self.proxy_socket, d )
if "bind.ip" in d:
self.bind_host = d [ "bind.ip" ]
if "bind.domain" in d:
self.bind_host = d [ "bind.domain" ]
if "bind.port" in d:
self.bind_port = d [ "bind.port" ]
print ( f"SOCKS5 Proxy listening on {self.bind_host}:{self.bind_port}" )
self.proxy_socket.settimeout ( 18 )
rlist, _, _ = select.select ( [ self.proxy_socket, ], [], [] )
if self.proxy_socket in rlist:
l = {}
server_socks5_client_recv_connect_response ( self.proxy_socket, l )
address_0 = None
address_1 = None
if "bind.ip" in l:
address_0 = l [ "bind.ip" ]
if "bind.domain" in l:
address_0 = l [ "bind.domain" ]
if "bind.port" in l:
address_1 = l [ "bind.port" ]
client_address = ( address_0, address_1 )
print ( f"Accepted connection from {address_0}:{address_1}" )
self.RequestHandlerClass ( self.proxy_socket, client_address, self)
self.shutdown_request ( self.proxy_socket )
def server_socks5_client_send_greet ( s : socket.socket, d : dict ):
# Client greeting VER NAUTH AUTH
# Byte count 1 1 variable
# VER
# SOCKS version (0x05)
# NAUTH
# Number of authentication methods supported, uint8
# AUTH
# Authentication methods, 1 byte per method supported
# The authentication methods supported are numbered as follows:
# 0x00: No authentication
# 0x01: GSSAPI (RFC 1961)
# 0x02: Username/password (RFC 1929)
# 0x03–0x7F: methods assigned by IANA[17]
# 0x03: Challenge–Handshake Authentication Protocol
# 0x04: Unassigned
# 0x05: Challenge–Response Authentication Method
# 0x06: Secure Sockets Layer
# 0x07: NDS Authentication
# 0x08: Multi-Authentication Framework
# 0x09: JSON Parameter Block
# 0x0A–0x7F: Unassigned
# 0x80–0xFE: methods reserved for private use
methods = [ 0x00 ]
if "username" in d and "password" in d:
methods.append ( 0x02 )
transmit = struct.pack ( "BB", 0x05, len ( methods ) )
transmit += bytes ( methods )
s.sendall ( transmit )
def server_socks5_client_recv_choice ( s : socket.socket, d : dict ):
# Server choice
# VER CAUTH
# Byte count 1 1
# VER
# SOCKS version (0x05)
# CAUTH
# chosen authentication method, or 0xFF if no acceptable methods were offered
version, cauth = struct.unpack ( "!BB", s.recv ( 2 ) )
if version != 0x5:
raise SocksChainException ( "Unsupported socks version", version )
if cauth == 0xff:
raise AuthenticationChainException ( "No accepted AUTH type" )
d [ "cauth" ] = cauth
def server_socks5_client_send_authorization_request ( s : socket.socket, d : dict ):
# Client authentication request, 0x02
# VER IDLEN ID PWLEN PW
# Byte count 1 1 (1–255) 1 (1–255)
# VER
# 0x01 for current version of username/password authentication
# IDLEN, ID
# Username length, uint8; username as bytestring
# PWLEN, PW
# Password length, uint8; password as bytestring
if d [ "cauth" ] == 0x00:
return
if d [ "cauth" ] != 0x02:
raise AuthenticationChainException ( "No accepted AUTH type" )
if not "username" in d or not "password" in d:
raise AuthenticationChainException ( "No AUTH without username and password not accepted" )
transmit = b'\x01' # username/password authentication
transmit += int.to_bytes ( len ( d [ "username" ] ) )
transmit += str.encode ( d [ "username" ] )
transmit += int.to_bytes ( len ( d [ "password" ] ) )
transmit += str.encode ( d [ "password" ] )
s.sendall ( transmit )
def server_socks5_client_recv_authorization_response ( s : socket.socket, d : dict ):
# Server response, 0x02
# VER STATUS
# Byte count 1 1
# VER
# 0x01 for current version of username/password authentication
# STATUS
# 0x00 success, otherwise failure, connection must be closed
if d [ "cauth" ] == 0x00:
return d
version, status = struct.unpack ( "!BB", s.recv ( 2 ) )
if version != 0x01:
raise SocksChainException ( "Wrong socks username and password", version )
if status != 0x00:
raise AuthenticationChainException ( "Access denied", json.dumps ( d ) )
def server_socks5_client_send_connect_request ( s : socket.socket, d : dict ):
# Client connection request
# VER CMD RSV DSTADDR DSTPORT
# Byte Count 1 1 1 Variable 2
# VER
# SOCKS version (0x05)
# CMD
# command code:
# 0x01: establish a TCP/IP stream connection
# 0x02: establish a TCP/IP port binding
# 0x03: associate a UDP port
# RSV
# reserved, must be 0x00
# DSTADDR
# SOCKS5 address TYPE ADDR
# Byte Count 1 variable
# TYPE
# type of the address. One of:
# 0x01: IPv4 address
# 0x03: Domain name
# 0x04: IPv6 address
# DSTPORT
# port number in a network byte order
# print ( sys._getframe().f_lineno, d, file=sys.stderr )
transmit = b'\x05' # SOCKS version 5
transmit += d [ "cmd" ].to_bytes ()
transmit += b'\x00' # Reserved
if "ip.version" in d and d [ "ip.version" ] == 4: # IPv4
transmit += b'\x01'
transmit += socket.inet_pton ( socket.AF_INET, d [ "ip" ] )
elif "ip.version" in d and d [ "ip.version" ] == 6: # IPv6
transmit += b'\x04'
transmit += socket.inet_pton ( socket.AF_INET6, d [ "ip" ] )
elif "domain" in d: # Domain name
transmit += b'\x03'
transmit += len ( d [ "domain" ] ).to_bytes ( 1, byteorder='big' ) # Length of domain
transmit += d [ "domain" ].encode ( 'utf-8' )
transmit += struct.pack('!H', d [ "port" ] ) # Port number
s.sendall ( transmit )
def server_socks5_client_recv_connect_response ( s : socket.socket, d : dict ):
# Response packet from server
# VER STATUS RSV BNDADDR BNDPORT
# Byte Count 1 1 1 variable 2
# VER
# SOCKS version (0x05)
# STATUS
# status code:
# 0x00: request granted
# 0x01: general failure
# 0x02: connection not allowed by ruleset
# 0x03: network unreachable
# 0x04: host unreachable
# 0x05: connection refused by destination host
# 0x06: TTL expired
# 0x07: command not supported / protocol error
# 0x08: address type not supported
# RSV
# reserved, must be 0x00
# BNDADDR
# SOCKS5 address TYPE ADDR
# Byte Count 1 variable
# TYPE
# type of the address. One of:
# 0x01: IPv4 address
# 0x03: Domain name
# 0x04: IPv6 address
# BNDPORT
# server bound port number in a network byte order
version, status, reserved, address_type = struct.unpack("!BBBB", s.recv(4))
# Check for successful response
if version != 0x05:
raise SocksChainException ( "Unsupported socks version", version )
if status != 0x00:
raise SocksChainException ( "Socket chain error", status )
# Parse the remaining response based on the address type
if address_type == 0x01: # IPv4
d [ "bind.ip" ] = socket.inet_ntoa ( s.recv ( 4 ) )
d [ "bind.ip.version" ] = 4
d [ "bind.port" ] = struct.unpack ( "!H", s.recv ( 2 ) )[ 0 ]
elif address_type == 0x03: # Domain name
domain_len = struct.unpack("!B", s.recv(1))[0]
d [ "bind.domain" ] = s.recv ( domain_len ).decode ()
d [ "bind.port" ] = struct.unpack ( "!H", s.recv ( 2 ) )[ 0 ]
elif address_type == 0x04: # IPv6
d [ "bind.ip" ] = socket.inet_ntop ( socket.AF_INET6, s.recv ( 16 ) )
d [ "bind.ip.version" ] = 6
d [ "bind.port" ] = struct.unpack ( "!H", s.recv ( 2 ) )[ 0 ]
else:
raise Exception("Unknown address type")
Start http-proxy-server:
test_http_servers_listen_addresses = [
{ "proxy_host": '127.0.0.1', 'proxy_port': 1084 },
{ "proxy_host": '::1', 'proxy_port': 1076, "username": "username2", "password": "password5" },
]
for i in test_http_servers_listen_addresses:
httpd = TestHTTPServerServer ( None, TestHTTPServerHandler, **i )
server_thread = threading.Thread ( target=test_run_http_server, args = ( httpd, ) )
server_thread.start ()
Tested with gost.