Advertisement
linuxlizard

USB / TCP Proxy

May 28th, 2014
433
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.05 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. # Use the Python libusb wrapper to talk to Marvell printer Embedded Web Server
  4. # over USB.
  5. #
  6. # Work in progress. A bit messy. Partial HTTP proxy implemented.
  7. #
  8. # https://pypi.python.org/pypi/libusb1
  9. # https://github.com/vpelletier/python-libusb1
  10. #
  11. # davep 23-Apr-2014
  12.  
  13. import os
  14. import logging
  15. import select
  16. import Queue
  17. import socket
  18. import time
  19. from httplib import HTTPResponse
  20. from StringIO import StringIO
  21. import libusb1
  22. import usb1
  23. import pickle
  24.  
  25. import hexdump
  26.  
  27. nlog = logging.getLogger(__name__)
  28.  
  29. logfilename = "ewstraffic.dat"
  30.  
  31. # prn1's EWS
  32. prn1 = {
  33.     "vid" : 0x3f0,
  34.     "pid" : 0x5617,
  35.     "interface" : 1,
  36.     "endpoint": 2
  37. }
  38.  
  39. prn2 = {
  40.     "vid": 0x1286,
  41.     "pid": 0x0100,
  42.     "interface":1,
  43.     "endpoint":2
  44. }
  45.  
  46. # TODO get the vid/pid/iface/endpoint from cmdline
  47. #dev = prn1
  48. dev = prn2
  49.  
  50. tcp_port = 8080
  51.  
  52. # USB timeouts are in milliseconds
  53. SECONDS=1000
  54.  
  55. queue_timeout_seconds = 2
  56.  
  57. # big enough?
  58. tcp_sock_recv_size = 65536
  59.  
  60. # Big enough? Depends on the firmware I guess. We risk overflows if too small.
  61. bulk_read_buffer_size = 65535
  62. #bulk_read_buffer_size = 64*1024*10
  63.  
  64. class TCPSocketClosed(Exception):
  65.     def __init__(self,sock):
  66.         Exception.__init__(self)
  67.         self.sock = sock
  68.  
  69. class USBClosed(Exception):
  70.     def __init__(self,interface):
  71.         Exception.__init__(self)
  72.         self.interface = interface
  73.  
  74. def on_transfer_complete(transfer):
  75.     nlog.info("on_transfer_complete() status={0} endpoint={1:#x}".format(
  76.                 transfer.getStatus(), transfer.getEndpoint() ))
  77.  
  78.     # user data should be a queue to send the transfer
  79.     queue = transfer.getUserData()
  80.     nlog.debug("on_transfer_complete() put transfer into queue")
  81.     queue.put(transfer,queue_timeout_seconds)
  82.  
  83. def on_hotplug(ctx,dev):
  84.     nlog.info("on_hotplug()")
  85.  
  86.     # from the usb1.py source:
  87.     #    Callback must return whether it must be unregistered (any true value
  88.     #    to be unregistered, any false value to be kept registered).
  89.     return False
  90.  
  91. def make_server_socket():
  92.     sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  93.     sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  94.     sock.bind(('',tcp_port))
  95.     sock.listen(0)
  96.     return sock
  97.  
  98. def read_tcp_write_to_usb(sock,usb_handle,transfer_queue):
  99.     # Read from TCP.
  100.     # Write to USB.
  101.     # That's about it.
  102.     nlog.debug("sock.recv()")
  103.     buf = sock.recv(tcp_sock_recv_size)
  104.     nlog.debug("conn.recv() buflen={0}".format(len(buf)))
  105.     if len(buf)==0:
  106.         nlog.debug("sock={0} closed".format(sock))
  107.         raise TCPSocketClosed(sock)
  108.  
  109.     nlog.debug(hexdump.dump(buf,16))
  110.  
  111.     with open(logfilename,"ab") as outfile:
  112.         outfile.write("@@@ read data socket={0} at {1}".format(sock.fileno(),time.ctime()))
  113.         outfile.write(buf)
  114.  
  115.     # send the data to USB
  116.     write_transfer = usb_handle.getTransfer()
  117.     write_transfer.setBulk( dev["endpoint"],
  118.                             buf,
  119.                             on_transfer_complete,
  120.                             transfer_queue,
  121.                             2*SECONDS)
  122.     retcode = write_transfer.submit()
  123.     nlog.debug("write submit() returned {0}".format(retcode))
  124.  
  125. def clean_transfer_queue( transfer_queue ) :
  126.     # This funcion called at interrupt and program exit.  Clean/free any
  127.     # outstanding transfers that might be hanging around in our transfer_queue.
  128.     while not transfer_queue.empty():
  129.         transfer = transfer_queue.get()
  130.         transfer_type = transfer.getType()
  131.         transfer_endpoint = transfer.getEndpoint()
  132.         nlog.debug("transfer={0} type={1} endpoint={2:#x}".format(transfer,transfer_type,transfer_endpoint))
  133.  
  134.         transfer.close()
  135.         del transfer
  136.  
  137. # http://pythonwise.blogspot.com/2010/02/parse-http-response.html
  138. class FakeSocket(StringIO):
  139.     def makefile(self, *args, **kw):
  140.         return self
  141.  
  142. def parse_http(buf):
  143.     if len(buf)==0 :
  144.         # ignore
  145.         return
  146.  
  147. #    with open("buf.pkl","wb") as outfile:
  148. #        pickle.dump(buf,outfile)
  149.  
  150.     # http://pythonwise.blogspot.com/2010/02/parse-http-response.html
  151.     fakesock = FakeSocket(buf)
  152.     response = HTTPResponse(fakesock)
  153.     response.begin()
  154.  
  155.     nlog.debug("HTTP headers={0}".format(response.getheaders()))
  156.  
  157.     content_length = response.getheader('content-length')
  158.  
  159.     headers,body = buf.split("\r\n\r\n",1)
  160.     nlog.debug("len(body)={0} content-length={1}".format(len(body),content_length))
  161.  
  162. def poll_transfer_queue( transfer_queue, conn ) :
  163.     # When the USBTransfer callback is called, the finished transfer structure
  164.     # is put into a queue. This function will read those structures from the
  165.     # queue.
  166.     #
  167.     # Read transfers are written to the TCP socket.
  168.     # Write transfers are freed.
  169.  
  170.     nlog.debug("queue status empty={0} full={1}".format(
  171.                     transfer_queue.empty(), transfer_queue.full() ) )
  172.  
  173.     # handle USBTransfer objects from the callback
  174.     while not transfer_queue.empty():
  175.         transfer = transfer_queue.get()
  176.         transfer_type = transfer.getType()
  177.         transfer_endpoint = transfer.getEndpoint()
  178.         nlog.debug("transfer={0} type={1} endpoint={2:#x}".format(transfer,transfer_type,transfer_endpoint))
  179.  
  180.         if transfer_type==libusb1.LIBUSB_TRANSFER_TYPE_BULK:
  181.             nlog.debug("transfer_type=BULK")
  182.             if transfer_endpoint & libusb1.LIBUSB_ENDPOINT_IN :
  183.                 nlog.debug("transfer_endpoint=BULK_IN")
  184.                 # read data read
  185.                 full_buffer = transfer.getBuffer()
  186.                 buflen = transfer.getActualLength()
  187.                 nlog.debug("BULK_IN buflen={0}".format(buflen))
  188.  
  189.                 if buflen==0:
  190.                     transfer.close()
  191.                     del transfer
  192.                     raise USBClosed(transfer_endpoint)
  193.                    
  194.                 buf = full_buffer[:buflen]
  195.  
  196.                 nlog.debug(hexdump.dump(buf,16))
  197.  
  198.                 # parse the HTTP to find the connection boundaries so we can
  199.                 # multiplex multiple TCP sockets through the same USB
  200.                 # connection
  201.                 parse_http(buf)
  202.  
  203.                 # write to TCP socket
  204.                 if conn:
  205.                     conn.send(buf)
  206.                     nlog.debug("wrote usb data to socket {0}".format(conn.fileno()))
  207.  
  208.                 # save to our logfile
  209.                 with open(logfilename,"ab") as outfile:
  210.                     outfile.write("@@@ bulk in at {0}".format(time.ctime()))
  211.                     outfile.write(buf)
  212.  
  213.                 # resubmit the read (we use the same single read transfer
  214.                 # repeatedly)
  215.                 # TODO might need multiple read transfers outstanding at some
  216.                 # point
  217.                 retcode = transfer.submit()
  218.                 nlog.debug("read re-submit() returned {0}".format(retcode))
  219.  
  220.             else:
  221.                 # write transfer completed; release it
  222.                 # (tiny optimization would be to save this and re-use it for
  223.                 # the next write)
  224.                 nlog.debug("transfer_endpoint=BULK_OUT")
  225.                 transfer.close()
  226.                 del transfer
  227.         else:
  228.             assert 0, transfer_type
  229.  
  230. def main():
  231.     sock = make_server_socket()
  232.     nlog.info("tcp socket={0} fd={1}".format(sock,sock.fileno()))
  233.  
  234.     ctx = usb1.USBContext()
  235.     ctx.setDebug(4)
  236.  
  237.     prn = None
  238.     dlist = ctx.getDeviceList( skip_on_error=True)
  239.     for d in dlist :
  240.         vid,pid = d.getVendorID(),d.getProductID()
  241.         nlog.debug("{0:#06x} {1:#06x}".format(vid,pid))
  242.  
  243.         if vid==dev["vid"]:
  244.             prn = d
  245.  
  246.     nlog.info("serial_num={0}".format(prn.getSerialNumber()))
  247.  
  248.     handle = prn.open()
  249.  
  250.     kernel_active = handle.kernelDriverActive(dev["interface"])
  251.     nlog.debug("kernelDriverActive() returned {0}".format(kernel_active))
  252.  
  253.     nlog.info("claim interface={0}".format(dev["interface"]))
  254.     handle.claimInterface(dev["interface"])
  255.  
  256.     kernel_active = handle.kernelDriverActive(dev["interface"])
  257.     nlog.debug("kernelDriverActive() returned {0}".format(kernel_active))
  258.  
  259.     http_get = "GET / HTTP/1.1\r\n\r\n"
  260.  
  261.     # synchronous transfer to test interface
  262.     if 0:
  263.         retval = handle.bulkWrite(dev["endpoint"],http_get)
  264.         nlog.debug("bulkWrite returned {0} ".format(retval))
  265.  
  266.         buf = handle.bulkRead(dev["endpoint"],bulk_read_buffer_size,2*SECONDS)
  267.         nlog.debug("bulkRead return buf len={0}".format(len(buf)))
  268.         with open(logfilename,"ab") as outfile:
  269.             outfile.write("@@@ bulk in at {0}".format(time.ctime()))
  270.             outfile.write(buf)
  271.  
  272.     # Use async transfers. The transfer callback will send transfer
  273.     # structures back to main loop via this queue.
  274.     transfer_queue = Queue.Queue()
  275.  
  276.     # send an asynchronous write to test interface
  277.     write_transfer = handle.getTransfer()
  278. #    transfer.setBulk(libusb1.LIBUSB_ENDPOINT_OUT,http_get,on_transfer_complete,evt,2*SECONDS)
  279.     write_transfer.setBulk(dev["endpoint"],http_get,on_transfer_complete,transfer_queue,2*SECONDS)
  280.     retcode = write_transfer.submit()
  281.     nlog.debug("write submit() returned {0}".format(retcode))
  282.  
  283.     # we will re-use this transfer for all reads
  284.     # TODO might need multiple read transfers outstanding at some point
  285.     read_transfer = handle.getTransfer()
  286.     read_transfer.setBulk( dev["endpoint"]|libusb1.LIBUSB_ENDPOINT_IN,
  287.                             bulk_read_buffer_size,
  288.                             on_transfer_complete,
  289.                             transfer_queue,
  290.                             0 )
  291.     read_transfer.submit()
  292.     nlog.debug("read submit() returned {0}".format(retcode))
  293.  
  294.     # add the USB fds to the poll()
  295.     usbfds = ctx.getPollFDList()
  296.     poller = select.poll()
  297.     for fd,events in usbfds:
  298.         nlog.debug("fd={0} event={1:#x}".format(fd,events))
  299.         poller.register( fd, events )
  300.  
  301.     # add the listening TCP socket
  302.     poller.register(sock,select.POLLIN
  303.                         |select.POLLOUT
  304.                         |select.POLLPRI
  305.                         |select.POLLERR
  306.                         |select.POLLHUP
  307.                         |select.POLLNVAL)
  308.  
  309.     conn = None
  310.     connfd = -1
  311.  
  312.     # TODO catch signal, cleanly exit this loop
  313.     quit = False
  314.     while not quit:
  315.         timeout = ctx.getNextTimeout()
  316.         if not timeout:
  317.             # If libusb doesn't want a timeout, do I need a timeout?
  318.             # If there are any outstanding transfers, need to peek at the queue
  319.             # again soon. There is a race condition (maybe?) between the events
  320.             # happening and the callback pushing the data into the queue.
  321. #            if not transfer_queue.empty() :
  322. #                timeout = 1*SECONDS
  323.             pass
  324.  
  325.         nlog.debug("sleeping for {0} on poll()".format(timeout))
  326.  
  327.         # Stay a while and listen.
  328.         try :
  329.             evt = poller.poll(timeout)
  330.         except KeyboardInterrupt:
  331.             nlog.info("quitting on keyboard interrupt")
  332.             quit = True
  333.             break
  334.  
  335.         nlog.debug("awake! evt={0}".format(evt))
  336.  
  337.         if not evt:
  338.             # timeout
  339.             nlog.debug("timeout; handle events")
  340.             ctx.handleEventsTimeout(1)
  341.  
  342.         for fd,events in evt :
  343.             nlog.debug("fd={0} event={1:#x} sockfd={2} connfd={3}".format(fd,events,sock.fileno(),connfd))
  344.             if fd==sock.fileno():
  345.                 # new TCP connection on our listen socket
  346.                 if events & select.POLLIN:
  347.                     if conn is None:
  348.                         (conn,addr) = sock.accept()
  349.                         connfd = conn.fileno()
  350.                         nlog.debug("sock.accept() conn={0} connfd={1} addr={2}".format(conn,connfd,addr))
  351.                         poller.register(conn,select.POLLIN
  352.                                             |0 # select.POLLOUT
  353.                                             |select.POLLPRI
  354.                                             |select.POLLERR
  355.                                             |select.POLLHUP
  356.                                             |select.POLLNVAL)
  357.                     else :
  358.                         # I can't find any way to block Python from
  359.                         # allowing a second connection. (No, listen(1)
  360.                         # doesn't work.)  USB will probably get upset if I
  361.                         # mix HTTP from two separate connections. For now,
  362.                         # slam the socket closed. Webpage is missing some
  363.                         # elements. >.<
  364.                         #
  365.                         # TODO find a way to allow 2+ connections but force
  366.                         # them serialized through USB.
  367.                         nlog.error("client attempted second socket open; slam door in face")
  368.                         sock.accept()[0].close()
  369.                 else:
  370.                     # not sure if this can happen but put a bucket of glass
  371.                     # here to break its fall in case it does
  372.                     nlog.debug("wtf?")
  373.                     assert 0
  374.  
  375.             elif fd==connfd:
  376.                 # read data from TCP socket, write back to USB
  377.                 if events & select.POLLIN :
  378.                     try:
  379.                         read_tcp_write_to_usb(conn,handle,transfer_queue)
  380.                     except TCPSocketClosed:
  381.                         poller.unregister(conn)
  382.                         conn.close()
  383.                         conn = None
  384.                         connfd = -1
  385.                 else:
  386.                     # How can this happen? Not sure. Catch it if it does.
  387.                     assert 0,events
  388.             else :
  389.                 # assume a USB event
  390.                 nlog.debug("ctx.handleEvents()")
  391.                 ctx.handleEvents()
  392.  
  393.         # end for evt
  394.  
  395.         # check the USB transfers from our USB read/write callback
  396.         try :
  397.             poll_transfer_queue(transfer_queue,conn)
  398.         except USBClosed:
  399.             quit = True
  400.             break
  401.  
  402.     # end while(1)
  403.  
  404.     # From the docs:
  405.     # Note: cancellation happens asynchronously, so you must wait for
  406.     #   LIBUSB_TRANSFER_CANCELLED.
  407.     nlog.debug("read_transfer isSubmitted={0}".format(read_transfer.isSubmitted()))
  408.  
  409.     clean_transfer_queue(transfer_queue)
  410.     nlog.info("canceling read_transfer")
  411.     if read_transfer.isSubmitted():
  412.         read_transfer.cancel()
  413.         while 1:
  414.             nlog.info("waiting for read transfer to cancel...")
  415.             ctx.handleEventsTimeout(1)
  416.             transfer = None
  417.             try:
  418.                 transfer = transfer_queue.get(True,1)
  419.             except Queue.Empty:
  420.                 pass
  421.             if transfer:
  422.                 nlog.debug("transfer={0} type={1} endpoint={2:#x}".format(
  423.                             transfer,
  424.                             transfer.getType(),
  425.                             transfer.getEndpoint()))
  426.                 transfer.close()
  427.                 del transfer
  428.                 break
  429.     clean_transfer_queue(transfer_queue)
  430.  
  431.     if conn:
  432.         poller.unregister(conn)
  433.         conn.close()
  434.     poller.unregister(sock)
  435.     sock.close()
  436.  
  437.     nlog.debug("release interface {0}".format(dev["interface"]))
  438.     handle.releaseInterface(dev["interface"])
  439.  
  440.     nlog.debug("close usb handle")
  441.     handle.close()
  442.  
  443.     # will this wake up the polling thread?
  444.     ctx.exit()
  445.  
  446.     sock.close()
  447.  
  448. if __name__=='__main__':
  449. #    fmt = "%(message)s"
  450.     fmt = "%(filename)s %(lineno)d %(name)s %(message)s"
  451. #    logging.basicConfig( level=logging.INFO, format=fmt )
  452.     logging.basicConfig( level=logging.DEBUG, format=fmt )
  453.  
  454.     # log to file and stderr/stdout
  455.     # http://stackoverflow.com/questions/13733552/logger-configuration-to-log-to-file-and-print-to-stdout
  456.     formatter = logging.Formatter("%(asctime)s:%(name)s:%(levelname)s:%(message)s")
  457.  
  458.     # log to file, too (useful during dev)
  459.     filehandler = logging.FileHandler("ewsproxy.log")
  460.     filehandler.setLevel(logging.DEBUG)
  461.     filehandler.setFormatter(formatter)
  462.     nlog.addHandler(filehandler)
  463.  
  464.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement