2 # Copyright (c) 2015-2016 The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Dummy Socks5 server for testing."""
7 import socket
, threading
, queue
10 logger
= logging
.getLogger("TestFramework.socks5")
12 ### Protocol constants
23 """Receive n bytes from a socket, or fail."""
28 raise IOError('Unexpected end of stream')
33 ### Implementation classes
34 class Socks5Configuration(object):
35 """Proxy configuration."""
37 self
.addr
= None # Bind address (must be set)
38 self
.af
= socket
.AF_INET
# Bind address family
39 self
.unauth
= False # Support unauthenticated
40 self
.auth
= False # Support authentication
42 class Socks5Command(object):
43 """Information about an incoming socks5 command."""
44 def __init__(self
, cmd
, atyp
, addr
, port
, username
, password
):
45 self
.cmd
= cmd
# Command (one of Command.*)
46 self
.atyp
= atyp
# Address type (one of AddressType.*)
47 self
.addr
= addr
# Address
48 self
.port
= port
# Port to connect to
49 self
.username
= username
50 self
.password
= password
52 return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self
.cmd
, self
.atyp
, self
.addr
, self
.port
, self
.username
, self
.password
)
54 class Socks5Connection(object):
55 def __init__(self
, serv
, conn
, peer
):
61 """Handle socks5 request according to RFC192."""
63 # Verify socks version
64 ver
= recvall(self
.conn
, 1)[0]
66 raise IOError('Invalid socks version %i' % ver
)
67 # Choose authentication method
68 nmethods
= recvall(self
.conn
, 1)[0]
69 methods
= bytearray(recvall(self
.conn
, nmethods
))
71 if 0x02 in methods
and self
.serv
.conf
.auth
:
72 method
= 0x02 # username/password
73 elif 0x00 in methods
and self
.serv
.conf
.unauth
:
74 method
= 0x00 # unauthenticated
76 raise IOError('No supported authentication method was offered')
78 self
.conn
.sendall(bytearray([0x05, method
]))
79 # Read authentication (optional)
83 ver
= recvall(self
.conn
, 1)[0]
85 raise IOError('Invalid auth packet version %i' % ver
)
86 ulen
= recvall(self
.conn
, 1)[0]
87 username
= str(recvall(self
.conn
, ulen
))
88 plen
= recvall(self
.conn
, 1)[0]
89 password
= str(recvall(self
.conn
, plen
))
90 # Send authentication response
91 self
.conn
.sendall(bytearray([0x01, 0x00]))
93 # Read connect request
94 ver
, cmd
, _
, atyp
= recvall(self
.conn
, 4)
96 raise IOError('Invalid socks version %i in connect request' % ver
)
97 if cmd
!= Command
.CONNECT
:
98 raise IOError('Unhandled command %i in connect request' % cmd
)
100 if atyp
== AddressType
.IPV4
:
101 addr
= recvall(self
.conn
, 4)
102 elif atyp
== AddressType
.DOMAINNAME
:
103 n
= recvall(self
.conn
, 1)[0]
104 addr
= recvall(self
.conn
, n
)
105 elif atyp
== AddressType
.IPV6
:
106 addr
= recvall(self
.conn
, 16)
108 raise IOError('Unknown address type %i' % atyp
)
109 port_hi
,port_lo
= recvall(self
.conn
, 2)
110 port
= (port_hi
<< 8) | port_lo
112 # Send dummy response
113 self
.conn
.sendall(bytearray([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
115 cmdin
= Socks5Command(cmd
, atyp
, addr
, port
, username
, password
)
116 self
.serv
.queue
.put(cmdin
)
117 logger
.info('Proxy: %s', cmdin
)
118 # Fall through to disconnect
119 except Exception as e
:
120 logger
.exception("socks5 request handling failed.")
121 self
.serv
.queue
.put(e
)
125 class Socks5Server(object):
126 def __init__(self
, conf
):
128 self
.s
= socket
.socket(conf
.af
)
129 self
.s
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
130 self
.s
.bind(conf
.addr
)
134 self
.queue
= queue
.Queue() # report connections and exceptions to client
138 (sockconn
, peer
) = self
.s
.accept()
140 conn
= Socks5Connection(self
, sockconn
, peer
)
141 thread
= threading
.Thread(None, conn
.handle
)
146 assert(not self
.running
)
148 self
.thread
= threading
.Thread(None, self
.run
)
149 self
.thread
.daemon
= True
154 # connect to self to end run loop
155 s
= socket
.socket(self
.conf
.af
)
156 s
.connect(self
.conf
.addr
)