/qa/rpc-tests/test_framework/socks5.py

https://github.com/energicryptocurrency/energi · Python · 164 lines · 119 code · 17 blank · 28 comment · 21 complexity · 9959fc8d6dbd4c70914e1c0abbe44158 MD5 · raw file

  1. #!/usr/bin/env python3
  2. # Copyright (c) 2015-2018 The Energi 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. # Copyright (c) 2015-2016 The Bitcoin Core developers
  6. # Distributed under the MIT software license, see the accompanying
  7. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
  8. '''
  9. Dummy Socks5 server for testing.
  10. '''
  11. import socket, threading, queue
  12. import traceback, sys
  13. ### Protocol constants
  14. class Command:
  15. CONNECT = 0x01
  16. class AddressType:
  17. IPV4 = 0x01
  18. DOMAINNAME = 0x03
  19. IPV6 = 0x04
  20. ### Utility functions
  21. def recvall(s, n):
  22. '''Receive n bytes from a socket, or fail'''
  23. rv = bytearray()
  24. while n > 0:
  25. d = s.recv(n)
  26. if not d:
  27. raise IOError('Unexpected end of stream')
  28. rv.extend(d)
  29. n -= len(d)
  30. return rv
  31. ### Implementation classes
  32. class Socks5Configuration(object):
  33. '''Proxy configuration'''
  34. def __init__(self):
  35. self.addr = None # Bind address (must be set)
  36. self.af = socket.AF_INET # Bind address family
  37. self.unauth = False # Support unauthenticated
  38. self.auth = False # Support authentication
  39. class Socks5Command(object):
  40. '''Information about an incoming socks5 command'''
  41. def __init__(self, cmd, atyp, addr, port, username, password):
  42. self.cmd = cmd # Command (one of Command.*)
  43. self.atyp = atyp # Address type (one of AddressType.*)
  44. self.addr = addr # Address
  45. self.port = port # Port to connect to
  46. self.username = username
  47. self.password = password
  48. def __repr__(self):
  49. return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self.cmd, self.atyp, self.addr, self.port, self.username, self.password)
  50. class Socks5Connection(object):
  51. def __init__(self, serv, conn, peer):
  52. self.serv = serv
  53. self.conn = conn
  54. self.peer = peer
  55. def handle(self):
  56. '''
  57. Handle socks5 request according to RFC1928
  58. '''
  59. try:
  60. # Verify socks version
  61. ver = recvall(self.conn, 1)[0]
  62. if ver != 0x05:
  63. raise IOError('Invalid socks version %i' % ver)
  64. # Choose authentication method
  65. nmethods = recvall(self.conn, 1)[0]
  66. methods = bytearray(recvall(self.conn, nmethods))
  67. method = None
  68. if 0x02 in methods and self.serv.conf.auth:
  69. method = 0x02 # username/password
  70. elif 0x00 in methods and self.serv.conf.unauth:
  71. method = 0x00 # unauthenticated
  72. if method is None:
  73. raise IOError('No supported authentication method was offered')
  74. # Send response
  75. self.conn.sendall(bytearray([0x05, method]))
  76. # Read authentication (optional)
  77. username = None
  78. password = None
  79. if method == 0x02:
  80. ver = recvall(self.conn, 1)[0]
  81. if ver != 0x01:
  82. raise IOError('Invalid auth packet version %i' % ver)
  83. ulen = recvall(self.conn, 1)[0]
  84. username = str(recvall(self.conn, ulen))
  85. plen = recvall(self.conn, 1)[0]
  86. password = str(recvall(self.conn, plen))
  87. # Send authentication response
  88. self.conn.sendall(bytearray([0x01, 0x00]))
  89. # Read connect request
  90. (ver,cmd,rsv,atyp) = recvall(self.conn, 4)
  91. if ver != 0x05:
  92. raise IOError('Invalid socks version %i in connect request' % ver)
  93. if cmd != Command.CONNECT:
  94. raise IOError('Unhandled command %i in connect request' % cmd)
  95. if atyp == AddressType.IPV4:
  96. addr = recvall(self.conn, 4)
  97. elif atyp == AddressType.DOMAINNAME:
  98. n = recvall(self.conn, 1)[0]
  99. addr = recvall(self.conn, n)
  100. elif atyp == AddressType.IPV6:
  101. addr = recvall(self.conn, 16)
  102. else:
  103. raise IOError('Unknown address type %i' % atyp)
  104. port_hi,port_lo = recvall(self.conn, 2)
  105. port = (port_hi << 8) | port_lo
  106. # Send dummy response
  107. self.conn.sendall(bytearray([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
  108. cmdin = Socks5Command(cmd, atyp, addr, port, username, password)
  109. self.serv.queue.put(cmdin)
  110. print('Proxy: ', cmdin)
  111. # Fall through to disconnect
  112. except Exception as e:
  113. traceback.print_exc(file=sys.stderr)
  114. self.serv.queue.put(e)
  115. finally:
  116. self.conn.close()
  117. class Socks5Server(object):
  118. def __init__(self, conf):
  119. self.conf = conf
  120. self.s = socket.socket(conf.af)
  121. self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  122. self.s.bind(conf.addr)
  123. self.s.listen(5)
  124. self.running = False
  125. self.thread = None
  126. self.queue = queue.Queue() # report connections and exceptions to client
  127. def run(self):
  128. while self.running:
  129. (sockconn, peer) = self.s.accept()
  130. if self.running:
  131. conn = Socks5Connection(self, sockconn, peer)
  132. thread = threading.Thread(None, conn.handle)
  133. thread.daemon = True
  134. thread.start()
  135. def start(self):
  136. assert(not self.running)
  137. self.running = True
  138. self.thread = threading.Thread(None, self.run)
  139. self.thread.daemon = True
  140. self.thread.start()
  141. def stop(self):
  142. self.running = False
  143. # connect to self to end run loop
  144. s = socket.socket(self.conf.af)
  145. s.connect(self.conf.addr)
  146. s.close()
  147. self.thread.join()