Esempio n. 1
0
    def _sipreceiver(self, stack, maxsize=16386):
        '''Handle the messages or connections on the given SIP stack's socket, and pass it to the stack
        so that stack can invoke appropriate callback on this object such as receivedRequest.'''
        sock = stack.sock

        def tcpreceiver(
                sock,
                remote):  # handle the messages on the given TCP connection.
            while True:
                data = yield multitask.recv(sock, maxsize)
                if _debug:
                    print '%r=>%r on type=%r\n%s' % (
                        remote, sock.getsockname(), sock.type, data)
                if data: stack.received(data, remote)

        while True:
            if sock.type == socket.SOCK_DGRAM:
                data, remote = yield multitask.recvfrom(sock, maxsize)
                if _debug:
                    print '%r=>%r on type=%r\n%s' % (
                        remote, sock.getsockname(), sock.type, data)
                if data: stack.received(data, remote)
            elif sock.type == socket.SOCK_STREAM:
                conn, remote = yield multitask.accept(sock)
                if conn:
                    self.conn[remote] = conn
                    multitask.add(tcpreceiver(conn, remote))
            else:
                raise ValueError, 'invalid socket type'
Esempio n. 2
0
    def _sipreceiver(self, stack, maxsize=16386):
        '''Handle the messages or connections on the given SIP stack's socket, and pass it to the stack
        so that stack can invoke appropriate callback on this object such as receivedRequest.'''
        sock = stack.sock

        def tcpreceiver(sock,
                        remote): # handle the messages on the given TCP connection.
            while True:
                data = yield multitask.recv(sock, maxsize)
                if _debug: print '%r=>%r on type=%r\n%s' % (
                    remote, sock.getsockname(), sock.type, data)
                if data: stack.received(data, remote)

        while True:
            if sock.type == socket.SOCK_DGRAM:
                data, remote = yield multitask.recvfrom(sock, maxsize)
                if _debug: print '%r=>%r on type=%r\n%s' % (
                    remote, sock.getsockname(), sock.type, data)
                if data: stack.received(data, remote)
            elif sock.type == socket.SOCK_STREAM:
                conn, remote = yield multitask.accept(sock)
                if conn:
                    self.conn[remote] = conn
                    multitask.add(tcpreceiver(conn, remote))
            else:
                raise ValueError, 'invalid socket type'
Esempio n. 3
0
 def _sipreceiver(self, stack, maxsize=16386):
     '''Handle the messages or connections on the given SIP stack's socket, and pass it to the stack so that stack can invoke 
     appropriate callback on this object such as receivedRequest.'''
     sock = stack.sock
     while True:
         if sock.type == socket.SOCK_DGRAM:
             data, remote = yield multitask.recvfrom(sock, maxsize)
             logger.debug('%r=>%r on type=%r\n%s', remote,
                          sock.getsockname(), stack.transport.type, data)
             if data:
                 try:
                     stack.received(data, remote)
                 except:
                     logger.exception('received')
         elif sock.type == socket.SOCK_STREAM:
             conn, remote = yield multitask.accept(sock)
             if conn:
                 logger.debug('%r=>%r connection type %r', remote,
                              conn.getsockname(), stack.transport.type)
                 if stack.transport.type in ('ws', 'wss'):
                     multitask.add(
                         self._wsreceiver(stack, conn, remote, maxsize))
                 else:
                     multitask.add(
                         self._tcpreceiver(stack, conn, remote, maxsize))
         else:
             raise ValueError, 'invalid socket type'
Esempio n. 4
0
 def receiveRTCP(self, sock):
     try:
         fd = sock.fileno()
         while True:
             data, remote = yield multitask.recvfrom(sock, self.maxsize)
             if self.app: self.app.receivedRTCP(data, remote, self.srcRTCP)
     except GeneratorExit: pass # terminated
     except: print 'receive RTCP exception', (sys and sys.exc_info())
     try: os.close(fd)
     except: pass
Esempio n. 5
0
 def receiveRTCP(self, sock):
     try:
         fd = sock.fileno()
         while True:
             data, remote = yield multitask.recvfrom(sock, self.maxsize)
             if self.app: self.app.receivedRTCP(data, remote, self.srcRTCP)
     except GeneratorExit: pass # terminated
     except: print 'receive RTCP exception', (sys and sys.exc_info())
     try: os.close(fd)
     except: pass
Esempio n. 6
0
 def relayreceiver(sock, fivetuple):
     while True: # start the main listening loop of the udp server
         try:
             data, remote = (yield multitask.recvfrom(sock, maxsize)) # receive a packet
             if data:
                 if _debug: print 'server().recvfrom() from', remote
                 multitask.add(datahandler(sock1, data, remote))
         except: # some other socket error, probably sock1 is closed.
             break
     if _debug: print 'server() exiting'
Esempio n. 7
0
 def waitonsock(sock):
     global pending
     try:
         while True:
             yield multitask.recvfrom(sock, 10)
             for s in pending:
                 multitask.add(execute(s))
             pending[:] = []
     except StopIteration:
         raise
     except:
         print 'waitonsock', sys.exc_info(), traceback.print_exc()
Esempio n. 8
0
 def waitonsock(sock):
     global pending
     try:
         while True: 
             yield multitask.recvfrom(sock, 10)
             for s in pending:
                 multitask.add(execute(s))
             pending[:] = []
     except StopIteration:
         raise
     except:
         print 'waitonsock', sys.exc_info(), traceback.print_exc()
Esempio n. 9
0
 def udpreceiver(self, maxsize=16386, timeout=None, interval=30):
     '''A UDP receiver task which also performs network ack.'''
     while True:
         data, addr = yield multitask.recvfrom(self.udp, maxsize, timeout=timeout)
         msg, remote = self.parse(data, addr, self.udp.type)
         if not msg: continue # ignore invalid messages. TODO: handle non-p2p message
         if _debug and msg.name[:4] != 'Hash': print self.name, 'udp-received %s=>%s: %r'%(remote.hostport, self.node.hostport, msg)
         if 'ack' in msg and msg.name != 'Ack:Indication': # the remote requires an ack. send one.
             del msg['ack'] # remove the ack
             ack = dht.Message(name='Ack:Indication', hash=H(data))    # hash of original received packet
             yield self.send(msg=ack, node=remote) # send the NetworkAck message
         msg['remote'] = remote # put remote as an attribute in msg before putting on queue.
         yield self.put(msg)    # put the parsed msg so that other interested party may get it.
Esempio n. 10
0
    def _listener(self):
        '''Listen for transport messages on the signaling socket. The default maximum 
		packet size to receive is 1500 bytes. The interval argument specifies how
		often should the sock be checked for close, default is 180 s.
		This is a generator function and should be invoked as multitask.add(u._listener()).'''
        self.app.status.append('Listener Generator Initiated')
        try:
            while self.sock and self._stack:
                try:
                    data, remote = (yield
                                    multitask.recvfrom(self.sock,
                                                       self.max_size,
                                                       timeout=self.interval))
                    logger.debug('received[%d] from %s\n%s' %
                                 (len(data), remote, data))
                    if self.state != self.FUZZING and self.state != self.FUZZING_COMPETED:
                        self._stack.received(data, remote)
                    else:
                        m = rfc3261_IPv6.Message()
                        try:
                            if self.crash_fuzz == self.WAIT_CRSH_CHK and self.crash_porbe == self.CRASH_PROBE_REC:
                                # If response to fuzz is received
                                # m._parse(data)
                                self.app.fuzzResponse[
                                    self.fuzz_index] = data.split(
                                        '\n', 1)[0].replace('\r', '')
                                self.crash_porbe = self.FUZZ_RECV
                            elif self.crash_fuzz == self.WAIT_CRSH_CHK and self.crash_porbe == self.CRASH_PROBE_SENT:
                                # If response to probe received
                                self._stack.received(data, remote)
                                self.app.probeResponse[
                                    self.fuzz_index] = data.split(
                                        '\n', 1)[0].replace('\r', '')
                        except ValueError, E:  # TODO: send 400 response to non-ACK request
                            logger.debug('Error in received message:', E)
                            logger.debug(traceback.print_exc())
                except multitask.Timeout:
                    if self.state == self.FUZZING and self.crash_detect:
                        print(
                            bcolors.FAIL + "Crash detected!" + bcolors.ENDC +
                            " It seems that we found a server crash. Server is not responding. If this is a false-positive you can use --fuzz-crash-no-stop option to prevent the app stop at crash detection."
                        )
                        self.app.printResults()
                        self.app.stop()
        except GeneratorExit:
            pass
        except:
            print 'User._listener exception', (sys and sys.exc_info() or None)
            traceback.print_exc()
            raise
        logger.debug('terminating User._listener()')
Esempio n. 11
0
	def _listener(self):
		'''Listen for transport messages on the signaling socket. The interval argument specifies how
		often should the sock be checked for close, default is 180 s.
		This is a generator function and should be invoked as multitask.add(u._listener()).'''
		self.app.status.append('Listener Generator Initiated')
		try:
			while self.sock and self._stack:
				try:
					data, remote = (yield multitask.recvfrom(self.sock, self.max_size, timeout=self.interval))
					logger.debug('received[%d] from %s\n%s'%(len(data),remote,data))
					self._stack.received(data, remote)
				except multitask.Timeout: pass
		except GeneratorExit: pass
		except: print 'User._listener exception', (sys and sys.exc_info() or None); traceback.print_exc(); raise
		logger.debug('terminating User._listener()')
Esempio n. 12
0
 def mcastreceiver(self, maxsize=1500, timeout=None, interval=30):
     while True:
         if self.mcast is not None:
             data, addr = yield multitask.recvfrom(self.mcast, maxsize, timeout=timeout)
             msg, remote = self.parse(data, addr, self.mcast.type)
             if not msg: print 'ignoring empty msg'; continue # ignore invalid message. TODO: handle non-p2p message
             if remote == self.node: 
                 if _debug: print 'ignoring our own multicast packet'
                 continue
             if _debug: print self.name, 'mcast-received %s=>%s: %r'%(remote.hostport, self.nodemcast.hostport, msg)
             if 'ack' in msg: del msg['ack'] # just remove ack, but don't send an ack for multicast
             msg['remote'] = remote
             msg['multicast'] = True # so that application knows that this is received on multicast
             yield self.put(msg) 
         else:
             yield dht.randomsleep(interval)
Esempio n. 13
0
 def _sipreceiver(self, stack, maxsize=16386):
     '''Handle the messages or connections on the given SIP stack's socket, and pass it to the stack so that stack can invoke 
     appropriate callback on this object such as receivedRequest.'''
     sock = stack.sock
     while True:
         if sock.type == socket.SOCK_DGRAM:
             data, remote = yield multitask.recvfrom(sock, maxsize)
             logger.debug('%r=>%r on type=%r\n%s', remote, sock.getsockname(), stack.transport.type, data)
             if data: 
                 try: stack.received(data, remote)
                 except: logger.exception('received')
         elif sock.type == socket.SOCK_STREAM:
             conn, remote = yield multitask.accept(sock)
             if conn:
                 logger.debug('%r=>%r connection type %r', remote, conn.getsockname(), stack.transport.type)
                 if stack.transport.type in ('ws', 'wss'):
                     multitask.add(self._wsreceiver(stack, conn, remote, maxsize))
                 else:
                     multitask.add(self._tcpreceiver(stack, conn, remote, maxsize))
         else: raise ValueError, 'invalid socket type'
Esempio n. 14
0
 def udpreceiver(self, maxsize=16386, timeout=None, interval=30):
     '''A UDP receiver task which also performs network ack.'''
     while True:
         data, addr = yield multitask.recvfrom(self.udp,
                                               maxsize,
                                               timeout=timeout)
         msg, remote = self.parse(data, addr, self.udp.type)
         if not msg:
             continue  # ignore invalid messages. TODO: handle non-p2p message
         if _debug and msg.name[:4] != 'Hash':
             print self.name, 'udp-received %s=>%s: %r' % (
                 remote.hostport, self.node.hostport, msg)
         if 'ack' in msg and msg.name != 'Ack:Indication':  # the remote requires an ack. send one.
             del msg['ack']  # remove the ack
             ack = dht.Message(
                 name='Ack:Indication',
                 hash=H(data))  # hash of original received packet
             yield self.send(msg=ack,
                             node=remote)  # send the NetworkAck message
         msg['remote'] = remote  # put remote as an attribute in msg before putting on queue.
         yield self.put(
             msg
         )  # put the parsed msg so that other interested party may get it.
Esempio n. 15
0
 def mcastreceiver(self, maxsize=1500, timeout=None, interval=30):
     while True:
         if self.mcast is not None:
             data, addr = yield multitask.recvfrom(self.mcast,
                                                   maxsize,
                                                   timeout=timeout)
             msg, remote = self.parse(data, addr, self.mcast.type)
             if not msg:
                 print 'ignoring empty msg'
                 continue  # ignore invalid message. TODO: handle non-p2p message
             if remote == self.node:
                 if _debug: print 'ignoring our own multicast packet'
                 continue
             if _debug:
                 print self.name, 'mcast-received %s=>%s: %r' % (
                     remote.hostport, self.nodemcast.hostport, msg)
             if 'ack' in msg:
                 del msg[
                     'ack']  # just remove ack, but don't send an ack for multicast
             msg['remote'] = remote
             msg['multicast'] = True  # so that application knows that this is received on multicast
             yield self.put(msg)
         else:
             yield dht.randomsleep(interval)
Esempio n. 16
0
	def _listener(self):
		'''Listen for transport messages on the signaling socket. The default maximum 
		packet size to receive is 1500 bytes. The interval argument specifies how
		often should the sock be checked for close, default is 180 s.
		This is a generator function and should be invoked as multitask.add(u._listener()).'''
		self.app.status.append('Listener Generator Initiated')
		try:
			while self.sock and self._stack:
				try:
					data, remote = (yield multitask.recvfrom(self.sock, self.max_size, timeout=self.interval))
					logger.debug('received[%d] from %s\n%s'%(len(data),remote,data))
					if self.state != self.FUZZING and self.state != self.FUZZING_COMPETED:
						self._stack.received(data, remote)
					else:
						m = rfc3261_IPv6.Message()
						try:
							if self.crash_fuzz==self.WAIT_CRSH_CHK and self.crash_porbe==self.CRASH_PROBE_REC:
								# If response to fuzz is received
								# m._parse(data)
								self.app.fuzzResponse[self.fuzz_index] = data.split('\n', 1)[0].replace('\r','')
								self.crash_porbe=self.FUZZ_RECV
							elif self.crash_fuzz==self.WAIT_CRSH_CHK and self.crash_porbe==self.CRASH_PROBE_SENT:
								# If response to probe received
								self._stack.received(data, remote)
								self.app.probeResponse[self.fuzz_index] = data.split('\n', 1)[0].replace('\r','')
						except ValueError, E: # TODO: send 400 response to non-ACK request
							logger.debug('Error in received message:', E)
							logger.debug(traceback.print_exc())
				except multitask.Timeout:
					if self.state == self.FUZZING and self.crash_detect:
						print (bcolors.FAIL+"Crash detected!"+bcolors.ENDC +" It seems that we found a server crash. Server is not responding. If this is a false-positive you can use --fuzz-crash-no-stop option to prevent the app stop at crash detection.")
						self.app.printResults()
						self.app.stop()
		except GeneratorExit: pass
		except: print 'User._listener exception', (sys and sys.exc_info() or None); traceback.print_exc(); raise
		logger.debug('terminating User._listener()')
Esempio n. 17
0
    def _listener(self):
        '''Listen for transport messages on the signaling socket. The interval argument specifies how
		often should the sock be checked for close, default is 180 s.
		This is a generator function and should be invoked as multitask.add(u._listener()).'''
        self.app.status.append('Listener Generator Initiated')
        try:
            while self.sock and self._stack:
                try:
                    data, remote = (yield
                                    multitask.recvfrom(self.sock,
                                                       self.max_size,
                                                       timeout=self.interval))
                    logger.debug('received[%d] from %s\n%s' %
                                 (len(data), remote, data))
                    self._stack.received(data, remote)
                except multitask.Timeout:
                    pass
        except GeneratorExit:
            pass
        except:
            print 'User._listener exception', (sys and sys.exc_info() or None)
            traceback.print_exc()
            raise
        logger.debug('terminating User._listener()')
Esempio n. 18
0
def server(sock1, **kwargs):
    '''A simple server implementation to test the code or to use in real deployment.
    The application should start the server as multitask.add(server(sock)).
    
    The caller should make sure that the sock1 argument is a UDP or TCP socket
    which is already bound. Additionally, sock2, sock3, and sock4 can be supplied
    as keyword arguments and represent the socket to use for change-IP, change-port
    and change IP+port commands respectively. Other keyword arguments are as follows:
      timeout: optional acivity timeout (second) if relay is activated, defaults to 180.
      external: optional external (ip, port) of the socket in case it is behind
        a full-cone NAT and still acts as a (relay) server.
      handler: optional function that gets invoked as handler(sock, remote, data) for 
        non-STUN data, and allows the application to demultiplex other types of data.
      maxsize: optional maximum size of packet to handle, defaults to 1500.'''
        
    sock2, sock3, sock4 = kwargs.get('sock2', None), kwargs.get('sock3', None), kwargs.get('sock4', None)
    addr1 = getlocaladdr(sock1)    
    addr4 = sock4 and getlocaladdr(sock4) or None
    timeout = kwargs.get('timeout', 180) # three minutes
    external = kwargs.get('external', addr1)
    handler = kwargs.get('handler', None)
    maxsize = kwargs.get('maxsize', 1500)
    
    tcp = (sock1.type == socket.SOCK_STREAM) # whether the server is on tcp or udp.
    binding = dict()  # allocated relay bindings if any
    
    def respond(sock, data, remote):
        if sock.type == socket.SOCK_STREAM:
            yield multitask.send(sock, data)
        else:
            yield multitask.sendto(sock, data, remote)
    
    def bindingRequest(sock, m, remote): # Serve a binding request of STUN
        res = Message()
        res.method, res.type, res.tid = Message.BINDING, Message.RESPONSE, m.tid
        mapped = Attribute(Attribute.MAPPED_ADDRESS) # mapped-address attribute
        mapped.address = (sock.family, addr1[0], addr1[1])
        res.attrs.append(mapped)
        if Attribute.CHANGE_REQUEST not in m: # send from same address:port
            if addr4: # add the other address attribute
                other = Attribute(Attribute.OTHER_ADDRESS)
                other.address = (sock4.family, addr4[0], addr4[1])
                res.attrs.append(other)
        else:
            change = m[Attribute.CHANGE_REQUEST]
            sock = change.value == '\x00\x00\x00\x06' and sock4 or change.value == '\x00\x00\x00\x02' and sock3 or change.value == '\x00\x00\x00\x04' and sock2 or None
        if sock:
            yield respond(sock, str(res), remote)
        raise StopIteration()

    def allocateRequest(sock, m, remote): # serve the allocate request of TURN
        fivetuple = (sock.type, getlocaladdr(sock), remote)
        lifetime = timeout
        if Attribute.LIFETIME in m:
            lt = struct.unpack('!L', m[Attribute.LIFETIME].value)
            if lt < lifetime: lifetime = lt
        if fivetuple in binding: # already found
            newsock = binding[fivetuple]
            if lifetime == 0: # terminate the binding
                del binding[fivetuple]
                del binding[newsock]
        else:
            if lifetime > 0: # allocate, otherwise it is already missing.
                newsock = socket.socket(sock.family, sock.type)
                newsock.bind(('0.0.0.0', 0)) # bind to any
                binding[newsock] = fivetuple
                binding[fivetuple] = newsock
            
        res = Message()
        res.method, res.type, res.tid = m.method, Message.RESPONSE, m.tid
        mapped = Attribute(Attribute.MAPPED_ADDRESS) # mapped-address attribute
        mapped.address = (newsock.family, (external, newsock and newsock.getsockname()[1] or 0))
        res.attrs.append(mapped)
        res.attrs.append(Attribute(Attribute.LIFETIME, struct.pack('!L', lifetime)))
        
        if lifetime == 0 and newsock: # close any previous listening function
            newsock.close() # this should trigger close of functions
        else:
            if sock.type == socket.SOCK_STREAM:
                multitask.add(relayaccepter(newsock, fivetuple))
            else:
                multitask.add(relayreceiver(newsock, fivetuple))
                
        yield respond(sock, str(res), remote)

    def relaytcpreceiver(sock, fivetuple):
        pass

    def relayaccepter(sock, fivetuple):
        sock.listen(5) # accept queue
        while True: # start the main listening loop of the tcp server
            try:
                conn, remote = (yield multitask.accept(sock))
                if conn:
                    if _debug: print 'relayaccepter().accept() from', remote
                    sock.close() # close the original listening socket -- no more connections
                    binding[fivetuple] = conn # update the binding
                    del binding[sock]
                    binding[conn] = fivetuple
                    multitask.add(relaytcpreceiver(conn, fivetuple, remote))
                    break
            except: # some other socket error, probably sock is closed.
                break
        if _debug: print 'relaytcpaccepter() exiting'
        
    def relayreceiver(sock, fivetuple):
        while True: # start the main listening loop of the udp server
            try:
                data, remote = (yield multitask.recvfrom(sock, maxsize)) # receive a packet
                if data:
                    if _debug: print 'server().recvfrom() from', remote
                    multitask.add(datahandler(sock1, data, remote))
            except: # some other socket error, probably sock1 is closed.
                break
        if _debug: print 'server() exiting'
        
    def sendRequest(sock, m, remote): # serve the send request of TURN
        fivetuple = (sock.type, getlocaladdr(sock), remote)
        try:
            if fivetuple not in binding: # not found
                raise ValueError, 'no turn binding found'
            newsock = binding[fivetuple]
            destaddr = Attribute.DESTINATION_ADDRESS in m  and m[Attribute.DESTINATION_ADDRESS].address[1:] or None
            data     = Attribute.DATA in m and m[Attribute.DATA] or None
            if sock.type == socket.SOCK_STREAM:
                try: 
                    remote = newsock.getpeername()
                except: 
                    remote = None
                if not remote: 
                    newsock.connect(destaddr)
                    remote = destaddr
                yield multitask.send(newsock, data)
            else:
                yield multitask.sendto(newsock, data, destaddr)
            # TODO: we don't lock to destaddr. This is a security risk.
            result = True
        except:
            if _debug: print 'sendRequest() exception', sys.exc_info()
            result = False
        res = Message()
        res.method, res.type, res.tid = m.method, (result and Message.RESPONSE or Message.ERROR), m.tid
        if not result:
            error = Attribute(Attribute.ERROR_CODE)
            error.error = (400, 'cannot send request') # TODO: be more explicit.
            res.attrs.append(error)
        yield respond(sock, str(res), remote)
    
    def datahandler(sock, data, remote): #handle a new data from given remote (ip, port)
        try: 
            m = Message(data) # parse the message
            func = m.type == Message.REQUEST and ( \
                    m.method == Message.BINDING and bindingRequest \
                    or m.method == Message.ALLOCATE and allocateRequest \
                    or m.method == Message.SEND and sendRequest \
                    ) or None 
            if func:
                yield func(sock, m, remote)
            else:
                raise ValueError, 'unhandled request or message'
        except StopIteration:
            if _debug: print 'datahandler: stop iteration'
            raise
        except: # parsing error or unhandled message
            if _debug: print 'datahandler() exception', sys.exc_info()
            if handler: 
                handler(sock, remote, data) # invoke the application's handler.
        
    def tcpreceiver(sock, remote): # handle a new incoming TCP connection
        while True:
            data = (yield multitask.recv(sock, maxsize))
            if not data: break # socket closed
            type, length, magic = struct.unpack('!HHL', data[:8])
            valid = (type & 0xC000 == 0) and magic == Message.MAGIC and length<=(maxsize-8) # valid
            if valid: 
                yield datahandler(sock, data, remote)
                if _debug: print 'tcpreceiver() finished data handler'
            else: 
                handler(sock, data, remote)

    if tcp: sock1.listen(5) # create the listen queue

    if _debug: print 'server listening on', addr1
    while True: # start the main listening loop of the server
        try: tcp = (sock1.type == socket.SOCK_STREAM)
        except: break # probably a bad file descriptor because sock1 is closed.
        try:
            if tcp:
                conn, remote = (yield multitask.accept(sock1, timeout=5))
                if conn:
                    if _debug: print 'server().accept() from', remote 
                    multitask.add(tcpreceiver(conn, remote))
            else:
                data, remote = (yield multitask.recvfrom(sock1, maxsize, timeout=5)) # receive a packet
                if data:
                    if _debug: print 'server().recvfrom() from', remote
                    multitask.add(datahandler(sock1, data, remote))
        except multitask.Timeout:
            continue
        except: # some other socket error, probably sock1 is closed.
            break
    if _debug: print 'server() exiting'
Esempio n. 19
0
def request(sock, server=None, **kwargs):
    '''Send a STUN client request with retransmissions and return the response.
    This is a generator function, and can be called as
        response, external = yield request(sock, ('stun.iptel.org', 3478))

    It raises ValueError in case of failure and multitask.Timeout in case of timeout
    or failure to connect TCP or invalid response received. For TCP, the sock remains
    connected after successful return or exception from this function.
    
    Arguments are as follows:
        sock: the socket to use for sending request and receiving response.
        server: optional server (ip, port), defaults to defaultServers[0]. For TCP if sock
          is already connected, then server argument is ignored.
        method: optional STUN method, defaults to Message.BINDING.
        tid: optional transaction id, by default generates a new.
        attrs: optional attributes, by default empty list [].
        rto: optional RTO, defaults to 0.1 for UDP and 9.3 for TCP.
        retry: optional retry count, defaults to 7 for UDP and 1 for TCP.
        maxsize: optional maximum packet size, defaults to 1500. 
        handler: optional handler function, that receives any message that was received
          but not handled by the request method. 
    The handler argument allows demultiplexing other types of received messages on the 
    same socket. Note that raising an exception is not good, because we still want to 
    wait for response instead of exiting. The handler is invoked as 
    handler(sock, remote, data) where data is raw data string and remote is usually 
    server (ip, port). If no handler is specified, then invalid data raises a ValueError.
    '''
    
    server = server or defaultServers[0] # use first server if missing
    handler = kwargs.get('handler', None)
    maxsize = kwargs.get('maxsize', 1500)
    
    m = Message()
    m.method = kwargs.get('method', Message.BINDING)
    m.type = Message.REQUEST
    m.tid = kwargs.get('tid', urandom(12))
    m.attrs = kwargs.get('attrs', [])
    mstr = str(m) # formatted message bytes to send
    
    if len(mstr) >= maxsize: raise ValueError, 'Cannot send packet of length>%d'%(maxsize)
    
    if sock.type == socket.SOCK_STREAM:
        remote = None
        try: remote = sock.getpeername()
        except: pass
        if not remote: 
            try: 
                sock.connect(server)
                remote = server # connect if not already connected.
            except: 
                raise multitask.Timeout() # can't connect, then raise a timeout error.
        tcp, rto, retry = True, kwargs.get('rto', 9.3), kwargs.get('retry', 1)
    else:
        tcp, rto, retry = False, kwargs.get('rto', 0.100), kwargs.get('retry', 7) 
    
    while retry>0:
        retry = retry - 1
        if _debug: print 'sending STUN request method=%d, len=%d, remaining-retry=%d'%(m.method, len(mstr), retry)
        if tcp: 
            yield multitask.send(sock, mstr) # send the request
        else: 
            yield multitask.sendto(sock, mstr, server)
        try:
            if tcp: # receiving a TCP packet is complicated. remote is already set
                data = (yield multitask.recv(sock, maxsize, timeout=rto))
                if not data: break
                if _debug: print 'request() received data'
                type, length, magic = struct.unpack('!HHL', data[:8])
                if type & 0xC000 != 0 or magic != Message.MAGIC:
                    raise ValueError, 'invalid STUN response from server type=0x%x, magic=0x%x'%(type, magic)
                if length > (maxsize-8):
                    raise ValueError, 'very large response length[%d]>%d'%(length+8, maxsize)
            else: # receive a UDP datagram
                data, remote = (yield multitask.recvfrom(sock, maxsize, timeout=rto))
                
            if data:
                try:
                    response = Message(data) # parse the message if any
                    if _debug: print 'received STUN message method=%d, type=%d'%(response.method, response.type)
                except:
                    if _debug: print 'received invalid STUN message len=%d'%(len(response))
                    if handler: 
                        handler(sock, remote, data) # allow app to demultiplex
                        continue # retry next
                    else:
                        raise ValueError, 'Invalid response from server'
                    
                if response.tid != m.tid: 
                    if _debug: print 'The tid does not match. ignoring'
                    if handler: handler(sock, remote, data)
                    continue # probably a old response, don't raise exception.
                
                external = None
                for attr in response.attrs:
                    if not attr.optional and attr.type not in Attribute.knownTypes:
                        raise ValueError, 'Attribute 0x%04x not understood in response'%attr.type
                if response.type == Message.RESPONSE: # success response
                    for attr in response.attrs:
                        if m.method == Message.BINDING:
                            if attr.type == Attribute.XOR_MAPPED_ADDRESS:
                                external = attr.xorAddress # (family, ip, port)
                            elif attr.type == Attribute.MAPPED_ADDRESS: # for backward compatibility with RFC 3489
                                external = attr.address 
                elif response.type == Message.ERROR: # error response
                    error = None
                    for attr in response.attrs:
                        if attrs.type == Attribute.ERROR_CODE:
                            error = attrs.error  # (code, reason)
                            break
                    raise ValueError, 'Request failed with error %r'%error
                if external:
                    external = external[1:] # ignore the address family
                    raise StopIteration(response, external) # result to the caller
                # TODO: else do we continue or raise an error?
        except multitask.Timeout:
            rto = rto * 2 # double the rto
        except StopIteration:
            if _debug: print 'request() returning external=' + str(external)
            raise
        except: # any other exception, fall back to Timeout exception
            if _debug: print 'Some ValueError exception', sys.exc_info()
            break
        
    raise multitask.Timeout  # no response after all retransmissions