| 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. | #!/usr/bin/env python # hex-dump network proxy: run with "-h" to see usage information """usage: PROGRAM [options] <fromport:hostname:toport> <...> A port forwarding proxy server that hex-dumps traffic in both directions. Example: Listen for and forward connections on port 8080 to port 80 on www.google.com, hex-dumping the network traffic in both directions: $ PROGRAM 8080:www.google.com:80 Copyright (C) 2005-2007 Andrew Ellerton, mail: activestate-at->ellerton.net """ import sys, getopt, logging, re try: import twisted except ImportError: print "Please install the 'twisted' package from http://twistedmatrix.com/" sys.exit(1) from twisted.python import failure from twisted.internet import reactor, error, address, tcp from twisted.internet.protocol import Protocol, Factory, ClientFactory # -------------------------------------------------------------------------- # This GREAT hexdump function from Sebastian Keim's Python recipe at: # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/142812 # -------------------------------------------------------------------------- HEX_FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) def hexdump(prefix, src, length=16): N=0; result='' while src: s,src = src[:length],src[length:] hexa = ' '.join(["%02X"%ord(x) for x in s]) s = s.translate(HEX_FILTER) result += "%s %04X %-*s %s\n" % (prefix, N, length*3, hexa, s) N+=length return result # -------------------------------------------------------------------------- def transport_info(t): if isinstance(t, tcp.Client): return transport_info(t.getHost()) elif isinstance(t, address.IPv4Address): return "%s:%d" % (t.host, t.port) else: return repr(t) class HexdumpClientProtocol(Protocol): def __init__(self, factory): self.factory=factory def connectionMade(self): logger.debug("bridge connection open %s" % transport_info(self.transport)) self.factory.owner.clientName=transport_info(self.transport) self.writeCache() def write(self, buf): self.transport.write(buf) def dataReceived(self, recvd): self.factory.owner.write(recvd) def writeCache(self): while self.factory.writeCache: item = self.factory.writeCache.pop(0) self.write(item) class HexdumpClientFactory(ClientFactory): def __init__(self, owner): self.owner = owner self.writeCache = [] def startedConnecting(self, connector): logger.debug('connection opening...') def buildProtocol(self, addr): logger.debug('connecting to remote server %s' % transport_info(addr)) p = HexdumpClientProtocol(self) self.owner.dest = p return p def clientConnectionLost(self, connector, reason): if isinstance(reason, failure.Failure): if reason.type == twisted.internet.error.ConnectionDone: logger.info("remote server closed connection") else: logger.info("remote server connection lost, reason: %r" % reason) self.owner.close() def clientConnectionFailed(self, connector, reason): logger.debug("dest: connection failed: %r" % reason) self.owner.close() class HexdumpServerProtocol(Protocol): def __init__(self, serverFactory): self.factory=serverFactory self.clientFactory = HexdumpClientFactory(self) self.clientName=id(self) # this is repopulated later self.serverName=None def serverName(self): return "%s:%d" %(self.factory.remote_host,self.factory.remote_port) def connectionMade(self): self.serverName="%s:%d" %(self.factory.remote_host,self.factory.remote_port) logger.info("client %s opened connection -> server %s" % ( self.clientName, self.serverName)) # cxn to this server has opened. Open a port to the destination... reactor.connectTCP(self.factory.remote_host, self.factory.remote_port, self.clientFactory) def connectionLost(self, reason): logger.info("client %s closed connection" % self.clientName) # str(reason) if self.dest and self.dest.transport: self.dest.transport.loseConnection() def connectionFailed(self, reason): logger.debug("proxy connection failed: %s" % str(reason)) def dataReceived(self, recvd): logger.info("client %s -> server %s (%d bytes)\n%s" % ( self.clientName, self.serverName, len(recvd), hexdump('->', recvd))) if hasattr(self, "dest"): self.dest.write(recvd) else: logger.debug("caching data until remote connection is open") self.clientFactory.writeCache.append(recvd) def write(self, buf): logger.info("client %s <= server %s (%d bytes)\n%s" % ( self.clientName, self.serverName, len(buf), hexdump('<=', buf))) self.transport.write(buf) def close(self): self.dest = None self.transport.loseConnection() class HexdumpServerFactory(Factory): def __init__(self, listen_port, remote_host, remote_port, max_connections = None): self.listen_port = listen_port self.remote_host = remote_host self.remote_port = remote_port self.max_connections = max_connections self.numConnections = 0 def startFactory(self): logger.info("listening on %d -> %s:%d" % (self.listen_port, self.remote_host, self.remote_port)) def buildProtocol(self, addr): # could process max/num connections here return HexdumpServerProtocol(self) if __name__ == "__main__": import os.path __usage__ = __doc__.replace("PROGRAM", os.path.basename(sys.argv[0])) port_map_pattern=re.compile("(\d+):([\w\.\-]+):(\d+)") logger = logging.getLogger() num_factories=0 def die_usage(msg=""): sys.stderr.write("%s%s\n" % (__usage__, msg)) sys.exit(1) def add_factory(port_map_desc): match = port_map_pattern.match(port_map_desc) if not match: die_usage("malformed port map description: %s" % port_map_desc) listen_port = int(match.group(1)) remote_host = match.group(2) remote_port = int(match.group(3)) factory = HexdumpServerFactory(listen_port, remote_host, remote_port) reactor.listenTCP(factory.listen_port, factory) try: opts, args = getopt.getopt(sys.argv[1:], "hl:v", ["help", "log=", "verbose"]) except getopt.GetoptError, e: die_usage(str(e)) logname=None logger.setLevel(logging.INFO) for o, a in opts: if o in ("-h", "--help"): die_usage() if o in ("-v", "--verbose"): logger.setLevel(logging.DEBUG) if o in ("-l", "--log"): logname=a; print "log: [%s]" % logname if logname: handler=logging.FileHandler(logname) else: handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) for a in args: add_factory(a); num_factories+=1 if num_factories==0: die_usage("No proxy/port forwarding connections specified") logger.info("ready (Ctrl+C to stop)") reactor.run() logger.info("stopped") |