Пример #1
0
 def startListening(self, addOld):
     assert self.i2pListeningSocket is None,'there is already one?!'
     
     if addOld:
         existingConns = [samId for samId in self.samIdToConnId.itervalues() if samId < 0]
     else:
         existingConns = None
     
     connId = self.i2pSockStatus.addConn(self.destId, 'tcpListen', 'out')
     self.i2pListeningSocket = SamTcpListeningSocket(self.i2pSockStatus, connId, existingConns)
     return connId
Пример #2
0
class SamTcpDestination(SamExtendedDestination):
    def __init__(self, destId, asyncSocketManager, realSockStatus, log, ip, port, sessionName, sessionDirection, sessionOptions,
                 i2pSockStatus, defaultOutMaxQueueSize, defaultInMaxQueueSize, defaultInRecvLimitThreshold):
        
        #i2p socket
        self.i2pSockStatus = i2pSockStatus
        self.i2pSockets = {}
        self.i2pListeningSocket = None
        self.defaultOutMaxQueueSize = defaultOutMaxQueueSize
        self.defaultInMaxQueueSize = defaultInMaxQueueSize
        self.defaultInRecvLimitThreshold = defaultInRecvLimitThreshold
        
        #sam
        self.nextSamOutId = 1
        self.nextSamInId = -1
        
        #mapper
        self.connIdToSamId = {}     #mapper from conn id to sam id for all active conns
        self.samIdToConnId = {}     #mapper from sam id to conn id for all active conns
        
        #other
        self.failedSockets = set()  #set of conn ids which are not active, meaning they already failed but were not removed
        self.waitingSockets = set() #set of conn ids of all outgoing conns which wait for session establishement to connect
                    
        SamExtendedDestination.__init__(self, destId, asyncSocketManager, realSockStatus, log, ip, port,
                                        sessionName, 'tcp', sessionDirection, sessionOptions)
    
    
    ##internal functions - destinations
                                        
    def _establishedDestination(self):
        #send connect messages for all sockets which were created in the meantime
        for connId in self.waitingSockets:
            self._connectI2PSocket(connId, self.i2pSockets[connId])
        self.waitingSockets.clear()
        SamExtendedDestination._establishedDestination(self)
            
    
    def _failDestination(self, reason):
        #fail sockets
        for connId in self.i2pSockets.keys():
            if connId not in self.failedSockets:
                self.i2pSockets[connId].errorEvent('SESSION_FAILED')
                
        assert len(self.connIdToSamId)==0 and len(self.samIdToConnId)==0, 'failed all conns but active conns left?!'
        assert len(self.waitingSockets)==0, 'failed but still waiting?!'
            
        #clear listening socket if any
        if self.i2pListeningSocket is not None:
            self.i2pListeningSocket.clear()
            
        #reset status
        self.nextSamOutId = 1
        self.nextSamInId = -1
        
        SamExtendedDestination._failDestination(self, reason)
        
        
    def _removeDestination(self):
        #fail sockets
        for connId in self.i2pSockets.keys():
            if not connId in self.failedSockets:
                self.i2pSockets[connId].errorEvent('SESSION_CLOSED')
        
        assert len(self.connIdToSamId)==0 and len(self.samIdToConnId)==0, 'failed all conns but active conns left?!'
        assert len(self.waitingSockets)==0, 'failed but still waiting?!'
        
        #remove sockets
        for i2pSocket in self.i2pSockets.values():
            i2pSocket.close(force=True)
            
        #listening socket
        if self.i2pListeningSocket is not None:
            self.i2pSockStatus.removeConn(self.i2pListeningSocket.fileno())
            self.i2pListeningSocket = None
            
        SamExtendedDestination._removeDestination(self)
        
        
    ##internal functions - sockets
    
    def _addOutgoingI2PSocket(self, remoteDest, inMaxQueueSize=None, outMaxQueueSize=None, inRecvLimitThreshold=None):
        #set defaults
        if inMaxQueueSize is None:
            inMaxQueueSize = self.defaultInMaxQueueSize
            
        if outMaxQueueSize is None:
            outMaxQueueSize = self.defaultOutMaxQueueSize
            
        if inRecvLimitThreshold is None:
            inRecvLimitThreshold = self.defaultInRecvLimitThreshold
            
        #add to status obj
        connId = self.i2pSockStatus.addConn(self.destId, 'tcpOut', 'out')
        
        #create socket object
        conn = SamTcpSocket(self._sendOverRealSocket, self._failI2PSocket, self._removeI2PSocket, self.i2pSockStatus, connId, None, 'out', remoteDest, 
                            inMaxQueueSize, outMaxQueueSize, inRecvLimitThreshold)
        self.i2pSockets[connId] = conn
        
        #connect
        if self.sessionEstablished:
            self._connectI2PSocket(connId, conn)
        else:
            self.waitingSockets.add(connId)
            
        return connId
            
    
    def _addIncommingI2PSocket(self, samId, remoteDest):
        #add to status obj
        connId = self.i2pSockStatus.addConn(self.destId, 'tcpIn', 'in')
        
        #increment samId counter
        assert self.nextSamInId >= samId , 'Wrong assumption about sam id?!'
        self.nextSamInId = samId - 1
        
        #add to mapper
        self.connIdToSamId[connId] = samId
        self.samIdToConnId[samId] = connId
        
        #create socket object
        conn = SamTcpSocket(self._sendOverRealSocket, self._failI2PSocket, self._removeI2PSocket, self.i2pSockStatus, connId, samId, 'in', remoteDest, 
                            self.defaultInMaxQueueSize, self.defaultOutMaxQueueSize, self.defaultInRecvLimitThreshold)
        self.i2pSockets[connId] = conn
        
        #connect event
        conn.connectEvent()
        
        #add to listening socket
        if self.i2pListeningSocket is not None:
            self.i2pListeningSocket.acceptEvent(connId, remoteDest)
            
            
    def _allocateSamId(self, connId):
        samId = self.nextSamOutId
        self.nextSamOutId += 1
        
        #add to mapper
        self.connIdToSamId[connId] = samId
        self.samIdToConnId[samId] = connId
        return samId
            
            
    def _connectI2PSocket(self, connId, i2pSocket):
        remoteDest = i2pSocket.getRemoteDestination()
        if len(remoteDest) == 516 and remoteDest.endswith('AAAA'):
            #full i2p destination
            samId = self._allocateSamId(connId)
            i2pSocket.connect(remoteDest, samId)
            
        else:
            #not a real destination but some kind of name
            self._addNameLookupQuery(connId, remoteDest, self._finishedNameLookup, funcArgs=[connId])
            
            
    def _finishedNameLookup(self, connId, success, msg):
        if success:
            samId = self._allocateSamId(connId)
            self.i2pSockets[connId].connect(msg, samId)
        else:
            self.i2pSockets[connId].errorEvent('FAILED_NAME_LOOKUP')
            
    
    def _failI2PSocket(self, connId):
        "called when a conn failed, either an already connected one or a connecting one"
        i2pSocket = self.i2pSockets[connId]
        samId = self.connIdToSamId.get(connId)
        
        if samId is None:
            #outgoing conn with name query, remove
            self._removeNameLookupQuery(connId)
        
        else:
            #active conn, remove from id mapper
            del self.connIdToSamId[connId]
            del self.samIdToConnId[samId]
        
        #set changes
        self.waitingSockets.discard(connId)
        self.failedSockets.add(connId)
        
    
    def _removeI2PSocket(self, connId):
        "called when close() was called for a socket"
        #remove from set
        self.failedSockets.remove(connId)
        
        #remove from socket dict
        del self.i2pSockets[connId]
        
        #remove from status obj
        self.i2pSockStatus.removeConn(connId)
    
    
    ##internal functions - messages
        
    def _handleCustomMessage(self, message):
        messageType = message['msgType']
        messageParas = message['msgParas']
        
        if messageType=='STREAM STATUS':
            #a outgoing sam tcp-socket connected ... or failed
            samId = int(messageParas['ID'])
            connId = self.samIdToConnId.get(samId)
            
            if connId is not None:
                #i2p socket still exists
                i2pSocket = self.i2pSockets[connId]
                if messageParas['RESULT'].upper() == 'OK':
                    #socket connected
                    i2pSocket.connectEvent()
                else:
                    #failed
                    i2pSocket.errorEvent(messageParas['RESULT'])
                    
            
        elif messageType=='STREAM CONNECTED':
            #got a new incomming tcp-like connection
            samId = int(messageParas['ID'])
            self._addIncommingI2PSocket(samId, messageParas['DESTINATION'])
            
            
        elif messageType=='STREAM SEND':
            #status info about a previous STREAM SEND command
            
            samId = int(messageParas['ID'])
            connId = self.samIdToConnId.get(samId)
                
            if connId is not None:
                #socket still exists
                i2pSocket = self.i2pSockets[connId]
                if messageParas['RESULT'].upper() == 'OK':
                    #data was send
                    i2pSocket.sendSucceededEvent()
                else:
                    #for some reason, the send failed - probably a bug, but a bug in this lib or in sam?
                    i2pSocket.sendFailedEvent()
                    
                if messageParas['STATE'].upper() == 'READY':
                    #the buffer of the sam bridge is not full, allowed to send more
                    i2pSocket.sendEvent()
                    
        
        elif messageType=='STREAM READY_TO_SEND':
            samId = int(messageParas['ID'])
            connId = self.samIdToConnId.get(samId)
                
            if connId is not None:
                #socket still exists
                self.i2pSockets[connId].sendEvent()
                
        
        elif messageType=='STREAM CLOSED':
            samId = int(messageParas['ID'])
            connId = self.samIdToConnId.get(samId)
                
            if connId is not None:
                #socket still exists
                if messageParas['RESULT'].upper() == 'OK':
                    #OK isn't a good error reason ...
                    messageParas['RESULT'] = 'CLOSED_BY_PEER'
                    
                self.i2pSockets[connId].errorEvent(messageParas['RESULT'])
                
        
        elif messageType=='STREAM RECEIVED':
            samId = int(messageParas['ID'])
            connId = self.samIdToConnId.get(samId, None)
                
            if connId is not None:
                #socket still exists
                self.i2pSockets[connId].recvEvent(''.join(message['Data']))
                
        else:
            if self.log is not None:
                self.log.error('Got unknown message "%s" with the following paras:\n%s', messageType, '\n'.join(tup[0]+': '+tup[1] for tup in messageParas.iteritems()))
        
        
    ##external functions - sockets - normal
    
    def connect(self, remoteDest, inMaxQueueSize=None, outMaxQueueSize=None, inRecvLimitThreshold=None):
        return self._addOutgoingI2PSocket(remoteDest, inMaxQueueSize, outMaxQueueSize, inRecvLimitThreshold)
    
    
    def send(self, connId, data):
        return self.i2pSockets[connId].send(data)
    
    
    def recv(self, connId, max, peekOnly):
        return self.i2pSockets[connId].recv(max, peekOnly)
    
    
    def close(self, connId, force):
        if connId in self.i2pSockets:
            self.i2pSockets[connId].close(force)
        
        
    ##external functions - sockets - listening
        
    def startListening(self, addOld):
        assert self.i2pListeningSocket is None,'there is already one?!'
        
        if addOld:
            existingConns = [samId for samId in self.samIdToConnId.itervalues() if samId < 0]
        else:
            existingConns = None
        
        connId = self.i2pSockStatus.addConn(self.destId, 'tcpListen', 'out')
        self.i2pListeningSocket = SamTcpListeningSocket(self.i2pSockStatus, connId, existingConns)
        return connId
    
    
    def accept(self, max):
        return self.i2pListeningSocket.accept(max)
    
    
    def stopListening(self, connId):
        assert self.i2pListeningSocket.fileno() == connId, 'connId doesn\'t match id of listening socket!'
        self.i2pSockStatus.removeConn(connId)
        self.i2pListeningSocket = None
        
        
    ##external functions - change settings
    
    def changeDefaultQueueSize(self, defaultInMaxQueueSize=None, defaultOutMaxQueueSize=None):
        if defaultInMaxQueueSize is not None:
            self.defaultInMaxQueueSize = defaultInMaxQueueSize
        if defaultOutMaxQueueSize is not None:
            self.defaultOutMaxQueueSize = defaultOutMaxQueueSize
            
    
    def changeDefaultInRecvLimitThreshold(self, defaultInRecvLimitThreshold):
        self.defaultInRecvLimitThreshold = self.defaultInRecvLimitThreshold
        
        
    ##external functions - get info
    
    def getI2PSocketRemoteDestination(self, connId):
        return self.i2pSockets[connId].getRemoteDestination()
    
    
    def getI2PSocketUsedInBufferSpace(self, connId):
        return self.i2pSockets[connId].getUsedInBufferSpace()
    
    
    def getI2PSocketFreeOutBufferSpace(self, connId):
        return self.i2pSockets[connId].getFreeOutBufferSpace()
    
    
    def getI2PSocketErrorReason(self, connId):
        return self.i2pSockets[connId].getErrorReason()