Search code examples
pythontwisted

Error when transport.write() from thread in twisted


I'm trying to make this simple server script where multiple clients connect to it and are stored in the factory.connected_clients dictionary. I then want to show a menu so the user executing the server can select what client does he want to work with and what commands does he want to send them (like in a reverse shell). Here's the code:

class RshServer(protocol.Protocol):

    def __init__(self, factory, addr):
        self.addr = addr
        self.factory = factory

    def connectionMade(self):
        address = self.addr.host + ':' + str(self.addr.port)
        self.factory.connected_clients[address] = self
        print '[*] Connection made with ' + address

class RshFactory(Factory):

    def __init__(self):
        self.connected_clients = {}

    def buildProtocol(self, addr):
        return RshServer(self, addr)

def show_server_menu():
    print '1. Show connected clients'
    print '2. Open client interface'
    msg = raw_input('Select an option: ')
    return msg

def show_client_menu():
    print '1. Show all processes'
    msg = raw_input('Select an option: ')
    return msg

def server_interface():
    while 1:
        msg = show_server_menu()
        command = server_commands.get(msg, None)
        if command: command()

def client_interface():
    protocol_name = raw_input('Enter client address: ')
    protoc = rsh_factory.connected_clients.get(protocol_name, None)
    if protoc: 
        msg = show_client_menu()
        protoc.transport.write(msg) 


rsh_factory = RshFactory()

server_commands = {
    '1' : lambda: sys.stdout.write(str(rsh_factory.connected_clients)+'\n'),
    '2' : client_interface
}

reactor.listenTCP(8000, rsh_factory)
reactor.callInThread(server_interface)
reactor.run()

However, when I select a client and execute the client interface function I get this error:

Unhandled Error
Traceback (most recent call last):
  File "rsh_twisted_serv.py", line 58, in <module>
    reactor.run()
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1192, in run
    self.mainLoop()
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1204, in mainLoop
    self.doIteration(t)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/epollreactor.py", line 396, in doPoll
    log.callWithLogger(selectable, _drdw, selectable, fd, event)
--- <exception caught here> ---
  File "/usr/lib/python2.7/dist-packages/twisted/python/log.py", line 88, in callWithLogger
    return callWithContext({"system": lp}, func, *args, **kw)
  File "/usr/lib/python2.7/dist-packages/twisted/python/log.py", line 73, in callWithContext
    return context.call({ILogContext: newCtx}, func, *args, **kw)
  File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 118, in callWithContext
    return self.currentContext().callWithContext(ctx, func, *args, **kw)
  File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 81, in callWithContext
    return func(*args,**kw)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/posixbase.py", line 627, in _doReadOrWrite
    self._disconnectSelectable(selectable, why, inRead)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/posixbase.py", line 257, in _disconnectSelectable
    selectable.readConnectionLost(f)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/tcp.py", line 279, in readConnectionLost
    self.connectionLost(reason)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/tcp.py", line 293, in connectionLost
    abstract.FileDescriptor.connectionLost(self, reason)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/abstract.py", line 207, in connectionLost
    self.stopWriting()
  File "/usr/lib/python2.7/dist-packages/twisted/internet/abstract.py", line 429, in stopWriting
    self.reactor.removeWriter(self)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/epollreactor.py", line 344, in removeWriter
    EPOLLOUT, EPOLLIN)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/epollreactor.py", line 321, in _remove
    self._poller.unregister(fd)
exceptions.IOError: [Errno 2] No such file or directory

I believe this is error is due to the transport.write() being called from a non-reactor thread. Am I right? How should I solve this?

SOLUTION: as @Glyph stated, the problem was indeed that I was calling transport.write() from a non-reactor thread (more details in his answer). I used the solution he proposed by changing protoc.transport.write() to reactor.callFromThread(lambda: protoc.transport.write(msg))


Solution

  • I think you mean "called from a non-reactor thread". No code in Twisted is thread-safe unless explicitly specified. See the threading documentation for more information. Use reactor.callFromThread to invoke Twisted methods from your stdio-reading thread, or use StandardIO to tell Twisted to read from standard input instead.