# Written by Arno Bakker, Diego Rabaioli # see LICENSE.txt for license information """ Communication layer between other instance or Web plugin e.g. for starting Downloads. """ # Protocol V1: Tribler 4.5.0: # - [4 byte length of cmd][cmd] # Protocol V2: SwarmPlugin # - [cmd]\r\n # # import sys import socket from traceback import print_exc from threading import Thread, Event from Tribler.Core.BitTornado.RawServer import RawServer DEBUG = False class Instance2InstanceServer(Thread): def __init__(self,i2iport,connhandler,timeout=300.0): Thread.__init__(self) self.setDaemon(True) self.setName('Instance2Instance'+self.getName()) self.i2iport = i2iport self.connhandler = connhandler self.i2idoneflag = Event() self.rawserver = RawServer(self.i2idoneflag, timeout/5.0, timeout, ipv6_enable = False, failfunc = self.rawserver_fatalerrorfunc, errorfunc = self.rawserver_nonfatalerrorfunc) self.rawserver.add_task(self.rawserver_keepalive,1) # Only accept local connections self.rawserver.bind(self.i2iport,bind=['127.0.0.1'],reuse=True) def rawserver_keepalive(self): """ Hack to prevent rawserver sleeping in select() for a long time, not processing any tasks on its queue at startup time Called by Instance2Instance thread """ self.rawserver.add_task(self.rawserver_keepalive,1) def shutdown(self): self.connhandler.shutdown() self.i2idoneflag.set() # # Following methods are called by Instance2Instance thread # def rawserver_fatalerrorfunc(self,e): """ Called by network thread """ if DEBUG: print >>sys.stderr,"i2is: RawServer fatal error func called",e print_exc() def rawserver_nonfatalerrorfunc(self,e): """ Called by network thread """ if DEBUG: print >>sys.stderr,"i2is: RawServer non fatal error func called",e print_exc() # Could log this somewhere, or phase it out def run(self): try: try: if DEBUG: print >>sys.stderr,"i2is: Ready to receive remote commands on",self.i2iport self.rawserver.listen_forever(self) except: print_exc() finally: self.rawserver.shutdown() def external_connection_made(self,s): try: self.connhandler.external_connection_made(s) except: print_exc() s.close() def connection_flushed(self,s): self.connhandler.connection_flushed(s) def connection_lost(self,s): if DEBUG: print >>sys.stderr,"i2is: connection_lost ------------------------------------------------" self.connhandler.connection_lost(s) def data_came_in(self, s, data): try: self.connhandler.data_came_in(s,data) except: print_exc() s.close() def add_task(self,func,t): self.rawserver.add_task(func,t) class InstanceConnectionHandler: def __init__(self,readlinecallback=None): self.readlinecallback = readlinecallback self.singsock2ic = {} # Maps Tribler/Core/BitTornado/SocketHandler.py:SingleSocket to InstanceConnection def set_readlinecallback(self,readlinecallback): self.readlinecallback = readlinecallback def external_connection_made(self,s): # Extra check in case bind() no work if DEBUG: print >>sys.stderr,"i2is: ich: ext_conn_made" peername = s.get_ip() if peername != "127.0.0.1": print >>sys.stderr,"i2is: ich: ext_conn_made: Refusing non-local connection from",peername s.close() ic = InstanceConnection(s,self,self.readlinecallback) self.singsock2ic[s] = ic def connection_flushed(self,s): pass def connection_lost(self,s): """ Called when peer closes connection and when we close the connection """ if DEBUG: print >>sys.stderr,"i2is: ich: connection_lost ------------------------------------------------" # Extra check in case bind() no work peername = s.get_ip() if peername != "127.0.0.1": print >>sys.stderr,"i2is: ich: connection_lost: Refusing non-local connection from",peername return del self.singsock2ic[s] def data_came_in(self,s, data): if DEBUG: print >>sys.stderr,"i2is: ich: data_came_in" ic = self.singsock2ic[s] try: ic.data_came_in(data) except: print_exc() def shutdown(self): for ic in self.singsock2ic.values(): ic.shutdown() class InstanceConnection: def __init__( self, singsock, connhandler, readlinecallback): self.singsock = singsock self.connhandler = connhandler self.readlinecallback = readlinecallback self.rflag = False self.remain = '' self.proto = 0 def data_came_in(self,data): """ Read \r\n ended lines from data and call readlinecallback(self,line) """ #if DEBUG: print >>sys.stderr,"i2is: ic: data_came_in",`data`,len(data),"proto",self.proto if self.proto == 0: if data[0] == '\x00': # Backwards compatibility: first 4 bytes are length, no \r\n self.proto = 1 else: self.proto = 2 if self.proto == 1: # hack self.remain += data idx = self.remain.find('START') if idx == -1: return else: data = self.remain[idx:]+'\r\n' self.remain = '' for i in range(0,len(data)): if not self.rflag: if data[i]=='\r': self.rflag = True else: if data[i] == '\n': cmd = self.remain+data[0:i+1] self.remain = data[i+1:] self.readlinecallback(self,cmd[:-2])# strip off \r\n if self.remain.endswith('\r\n'): cmd = self.remain self.remain = '' self.readlinecallback(self,cmd[:-2])# strip off \r\n return rflag = False self.remain = self.remain + data def write(self,data): if self.singsock is not None: self.singsock.write(data) def close(self): if self.singsock is not None: self.singsock.close() self.connhandler.connection_lost(self.singsock) self.singsock = None class Instance2InstanceClient: def __init__(self,port,cmd,param): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('127.0.0.1',port)) msg = cmd+' '+param+'\r\n' s.send(msg) s.close()