[Twisted-Python] Trying to proxy through multiple IPs

Andrew Bennetts andrew-twisted at puzzling.org
Tue Nov 4 05:04:43 EST 2008

Erik Wickstrom wrote:
> Hi all,
> I'm trying to write a proxy server that accepts connections on
> multiple ports, and depending on the port, using a different IP for
> the outgoing connection.  My code works except that adding additional
> reactor.listenTCP(...)s overwrite the IP of the previous listenTCPs.
> So the proxy accepts connections on all the desired ports, but only
> uses the last IP address for outgoing connections.
> The ip (bindIP) is somehow being overwritten.  Based on advice from
> IRC (exarkun --thanks!), I've tried a couple attempts at using
> closures to solve the problem, but non of my implementations have done
> the trick.

I doesn't look like you understand how to write closures in Python.  Consider
this snippet of your code:

> class ProxyFactory(http.HTTPFactory):
>     def class_factory(self, bindIP):
>         def closure(ip):
>             klass2 = ProxyRequest
>             setattr(klass2, 'bindIP', ip)
>             return klass2
>         klass = Proxy
>         setattr(klass, 'requestFactory', closure(bindIP))
>         return klass
>     def __init__(self, ip):
>         http.HTTPFactory.__init__(self)
>         self.ip = ip
>         #self.protocol = proxy.Proxy
>         self.protocol = self.class_factory(ip)

These lines are equivalent to the much simpler:

class ProxyFactory(http.HTTPFactory):

    def __init__(self, ip):
         self.protocol = Proxy
         self.protocol.requestFactory = ProxyRequest
         self.protocol.requestFactory.bindIP = ip

In particular, even though you define a function you call “closure”, because you
always invoke it immediately after defining it (and do nothing else with it) you
don't gain any difference in behaviour by making a function.

So the problem is you have just a single global requestFactory for all ProxyFactory's
(the ProxyRequest class), but you're mutating that as if it's not global.

The solution is to either,

  a) actually have a different requestFactory, or
  b) pass the bindIP to the ProxyRequest (the object that cares about it) some
     other way.

a) is a bit messy, even when done correctly.  The simpler way is b):

    class ProxyRequest(Request):

        protocols = {'http': ProxyClientFactory}
        ports = {'http': 80}
        def __init__(self, channel, queued, reactor=reactor):
            Request.__init__(self, channel, queued)
            self.reactor = reactor
            self.bindIP = self.channel.factory.ip

        # ...the rest of ProxyRequest as you had it...

    class Proxy(HTTPChannel):
        requestFactory = ProxyRequest
    class ProxyFactory(http.HTTPFactory):

        def __init__(self, ip):
             self.ip = ip


More information about the Twisted-Python mailing list