You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

socks5.py 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. """Dummy Socks5 server for testing."""
  6. import socket, threading, queue
  7. import traceback, sys
  8. import logging
  9. logger = logging.getLogger("TestFramework.socks5")
  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. """Handle socks5 request according to RFC192."""
  54. try:
  55. # Verify socks version
  56. ver = recvall(self.conn, 1)[0]
  57. if ver != 0x05:
  58. raise IOError('Invalid socks version %i' % ver)
  59. # Choose authentication method
  60. nmethods = recvall(self.conn, 1)[0]
  61. methods = bytearray(recvall(self.conn, nmethods))
  62. method = None
  63. if 0x02 in methods and self.serv.conf.auth:
  64. method = 0x02 # username/password
  65. elif 0x00 in methods and self.serv.conf.unauth:
  66. method = 0x00 # unauthenticated
  67. if method is None:
  68. raise IOError('No supported authentication method was offered')
  69. # Send response
  70. self.conn.sendall(bytearray([0x05, method]))
  71. # Read authentication (optional)
  72. username = None
  73. password = None
  74. if method == 0x02:
  75. ver = recvall(self.conn, 1)[0]
  76. if ver != 0x01:
  77. raise IOError('Invalid auth packet version %i' % ver)
  78. ulen = recvall(self.conn, 1)[0]
  79. username = str(recvall(self.conn, ulen))
  80. plen = recvall(self.conn, 1)[0]
  81. password = str(recvall(self.conn, plen))
  82. # Send authentication response
  83. self.conn.sendall(bytearray([0x01, 0x00]))
  84. # Read connect request
  85. (ver,cmd,rsv,atyp) = recvall(self.conn, 4)
  86. if ver != 0x05:
  87. raise IOError('Invalid socks version %i in connect request' % ver)
  88. if cmd != Command.CONNECT:
  89. raise IOError('Unhandled command %i in connect request' % cmd)
  90. if atyp == AddressType.IPV4:
  91. addr = recvall(self.conn, 4)
  92. elif atyp == AddressType.DOMAINNAME:
  93. n = recvall(self.conn, 1)[0]
  94. addr = recvall(self.conn, n)
  95. elif atyp == AddressType.IPV6:
  96. addr = recvall(self.conn, 16)
  97. else:
  98. raise IOError('Unknown address type %i' % atyp)
  99. port_hi,port_lo = recvall(self.conn, 2)
  100. port = (port_hi << 8) | port_lo
  101. # Send dummy response
  102. self.conn.sendall(bytearray([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
  103. cmdin = Socks5Command(cmd, atyp, addr, port, username, password)
  104. self.serv.queue.put(cmdin)
  105. logger.info('Proxy: %s', cmdin)
  106. # Fall through to disconnect
  107. except Exception as e:
  108. logger.exception("socks5 request handling failed.")
  109. self.serv.queue.put(e)
  110. finally:
  111. self.conn.close()
  112. class Socks5Server(object):
  113. def __init__(self, conf):
  114. self.conf = conf
  115. self.s = socket.socket(conf.af)
  116. self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  117. self.s.bind(conf.addr)
  118. self.s.listen(5)
  119. self.running = False
  120. self.thread = None
  121. self.queue = queue.Queue() # report connections and exceptions to client
  122. def run(self):
  123. while self.running:
  124. (sockconn, peer) = self.s.accept()
  125. if self.running:
  126. conn = Socks5Connection(self, sockconn, peer)
  127. thread = threading.Thread(None, conn.handle)
  128. thread.daemon = True
  129. thread.start()
  130. def start(self):
  131. assert(not self.running)
  132. self.running = True
  133. self.thread = threading.Thread(None, self.run)
  134. self.thread.daemon = True
  135. self.thread.start()
  136. def stop(self):
  137. self.running = False
  138. # connect to self to end run loop
  139. s = socket.socket(self.conf.af)
  140. s.connect(self.conf.addr)
  141. s.close()
  142. self.thread.join()