PageRenderTime 57ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

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

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