# Written by Arno Bakker # see LICENSE.txt for license information import sys from threading import Event,Thread from traceback import print_exc from Tribler.Core.BitTornado.natpunch import UPnPWrapper, UPnPError DEBUG = False class UPnPThread(Thread): """ Thread to run the UPnP code. Moved out of main startup- sequence for performance. As you can see this thread won't exit until the client exits. This is due to a funky problem with UPnP mode 2. That uses Win32/COM API calls to find and talk to the UPnP-enabled firewall. This mechanism apparently requires all calls to be carried out by the same thread. This means we cannot let the final DeletePortMapping(port) (==UPnPWrapper.close(port)) be done by a different thread, and we have to make this one wait until client shutdown. Arno, 2006-11-12 """ def __init__(self,upnp_type,ext_ip,listen_port,error_func,got_ext_ip_func): Thread.__init__(self) self.setDaemon(True) self.setName( "UPnP"+self.getName() ) self.upnp_type = upnp_type self.locally_guessed_ext_ip = ext_ip self.listen_port = listen_port self.error_func = error_func self.got_ext_ip_func = got_ext_ip_func self.shutdownevent = Event() def run(self): if self.upnp_type > 0: self.upnp_wrap = UPnPWrapper.getInstance() self.upnp_wrap.register(self.locally_guessed_ext_ip) from Tribler.Core.Statistics.StatusReporter import get_reporter_instance reporter = get_reporter_instance() if self.upnp_wrap.test(self.upnp_type): reporter.add_event("UPnP", "Init'ed") try: shownerror=False # Get external IP address from firewall if self.upnp_type != 1: # Mode 1 doesn't support getting the IP address" ret = self.upnp_wrap.get_ext_ip() if ret == None: shownerror=True self.error_func(self.upnp_type,self.listen_port,0) else: self.got_ext_ip_func(ret) # Do open_port irrespective of whether get_ext_ip() # succeeds, UPnP mode 1 doesn't support get_ext_ip() # get_ext_ip() must be done first to ensure we have the # right IP ASAP. # Open TCP listen port on firewall ret = self.upnp_wrap.open(self.listen_port,iproto='TCP') if ret == False and not shownerror: self.error_func(self.upnp_type,self.listen_port,0) # Open UDP listen port on firewall ret = self.upnp_wrap.open(self.listen_port,iproto='UDP') if ret == False and not shownerror: self.error_func(self.upnp_type,self.listen_port,0,listenproto='UDP') reporter.add_event("UPnP", "UDP:%d" % ret) except UPnPError,e: self.error_func(self.upnp_type,self.listen_port,1,e) else: reporter.add_event("UPnP", "Init failed") if self.upnp_type != 3: self.error_func(self.upnp_type,self.listen_port,2) elif DEBUG: print >>sys.stderr,"upnp: thread: Initialization failed, but didn't report error because UPnP mode 3 is now enabled by default" # Now that the firewall is hopefully open, activate other services # here. For Buddycast we don't have an explicit notification that it # can go ahead. It will start 15 seconds after client startup, which # is assumed to be sufficient for UPnP to open the firewall. ## dmh.start_active() if self.upnp_type > 0: if DEBUG: print >>sys.stderr,"upnp: thread: Waiting till shutdown" self.shutdownevent.wait() # Don't write to sys.stderr, that sometimes doesn't seem to exist # any more?! Python garbage collection funkiness of module sys import? # The GUI is definitely gone, so don't use self.error_func() if DEBUG: print "upnp: thread: Shutting down, closing port on firewall" try: self.upnp_wrap.close(self.listen_port,iproto='TCP') self.upnp_wrap.close(self.listen_port,iproto='UDP') except Exception,e: print "upnp: thread: close port at shutdown threw",e print_exc() # End of UPnPThread def shutdown(self): self.shutdownevent.set()