def tcphandler(self, sock, addr, maxsize=16386, timeout=60): '''Handle an established TCP connection, and close it if no activity for timeout.''' remotes = [] try: buffer = '' # buffer of data while True: try: data = yield multitask.recv(sock, maxsize, timeout=timeout) except multitask.Timeout: break # no activity for the timeout if not data: continue buffer = buffer + data if len(buffer) < 2: continue # wait for length atleast size = struct.unpack('!H', buffer[:2]) if size>maxsize: buffer=''; print 'Network.tcphandler() something wrong, ignoring'; continue # TODO: something wrong happened, the protocol went out of sync? if len(buffer)<(2+size): continue # we need more data. data, buffer = buffer[2:2+size], buffer[2+size:] msg, remote = self.parse(data, addr, sock.type) if not msg: continue # TODO: handle non-p2p message if _debug: print self.name, 'tcp-received %s=>%s: %r'%(remote.hostport, self.nodetcp.hostport, msg) if remote not in self.tcpc: self.tcpc[remote] = sock # update the table, indicating we have a connection to this node. remotes.append(remote) # store the remote node so that we can clear tcpc on exit if 'ack' in msg: del msg['ack'] # just remove the ack attribute. No need to ack on tcp. 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. finally: toremove = map(lambda y: y[0], filter(lambda x: x[1] == sock, self.tcpc.items())) for node in toremove: del self.tcpc[node]
def _tcpreceiver(self, stack, sock, remote, maxsize=16386): # handle the messages on the given TCP connection. self.conn[remote] = sock pending = '' while True: data = yield multitask.recv(sock, maxsize) logger.debug('%r=>%r on type=%r\n%s', remote, sock.getsockname(), stack.transport.type, data) if data: pending += data while True: msg = pending index1, index2 = msg.find('\n\n'), msg.find('\n\r\n') if index2 > 0 and index1 > 0: if index1 < index2: index = index1 + 2 else: index = index2 + 3 elif index1 > 0: index = index1 + 2 elif index2 > 0: index = index2 + 3 else: logger.debug('no CRLF found'); break # no header part yet match = re.search(r'content-length\s*:\s*(\d+)\r?\n', msg.lower()) if not match: logger.debug('no content-length found'); break # no content length yet length = int(match.group(1)) if len(msg) < index+length: logger.debug('has more content %d < %d (%d+%d)', len(msg), index+length, index, length); break # pending further content. total, pending = msg[:index+length], msg[index+length:] try: stack.received(total, remote) except: logger.exception('receiving') else: # socket closed break try: del self.conn[remote] except: pass
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)
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)
def tcphandler(self, sock, addr, maxsize=16386, timeout=60): '''Handle an established TCP connection, and close it if no activity for timeout.''' remotes = [] try: buffer = '' # buffer of data while True: try: data = yield multitask.recv(sock, maxsize, timeout=timeout) except multitask.Timeout: break # no activity for the timeout if not data: continue buffer = buffer + data if len(buffer) < 2: continue # wait for length atleast size = struct.unpack('!H', buffer[:2]) if size > maxsize: buffer = '' print 'Network.tcphandler() something wrong, ignoring' continue # TODO: something wrong happened, the protocol went out of sync? if len(buffer) < (2 + size): continue # we need more data. data, buffer = buffer[2:2 + size], buffer[2 + size:] msg, remote = self.parse(data, addr, sock.type) if not msg: continue # TODO: handle non-p2p message if _debug: print self.name, 'tcp-received %s=>%s: %r' % ( remote.hostport, self.nodetcp.hostport, msg) if remote not in self.tcpc: self.tcpc[ remote] = sock # update the table, indicating we have a connection to this node. remotes.append( remote ) # store the remote node so that we can clear tcpc on exit if 'ack' in msg: del msg[ 'ack'] # just remove the ack attribute. No need to ack on tcp. 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. finally: toremove = map(lambda y: y[0], filter(lambda x: x[1] == sock, self.tcpc.items())) for node in toremove: del self.tcpc[node]
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
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
def _wsreceiver(self, stack, sock, remote, maxsize=16386): # handle the messages on the given TCP connection. handshake = False pending = '' while True: data = yield multitask.recv(sock, maxsize) logger.debug('%r=>%r on type=%r length %d', sock.getpeername(), sock.getsockname(), stack.transport.type, len(data)) if data: pending += data if not handshake: # do handshake first logger.debug('handshake\n%s', data) if pending.startswith('<policy-file-request/>'): logger.debug('received policy-file-request, responding for port %r'%(stack.transport.port,)) sock.sendall('''<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy><allow-access-from domain="*" to-ports="%d"/></cross-domain-policy>'''%(stack.transport.port,)) sock.close() break else: msg = pending index1, index2 = msg.find('\n\n'), msg.find('\n\r\n') if index2 > 0 and index1 > 0: if index1 < index2: index = index1 + 2 else: index = index2 + 3 elif index1 > 0: index = index1 + 2 elif index2 > 0: index = index2 + 3 else: logger.debug('no CRLF found'); break # no header part yet, wait for more match = re.search(r'content-length\s*:\s*(\d+)\r?\n', msg[:index].lower()) length = int(match.group(1)) if match else 0 if len(msg) < index+length: logger.debug('has more content %d < %d (%d+%d)', len(msg), index+length, index, length); break # pending further content. total, pending = pending[:index+length], pending[index+length:] try: lines = total.split('\n') headers = dict([(h.strip().lower(), value.strip()) for h,sep,value in [line.strip().partition(':') for line in lines[1:] if line.strip()]]) method, path, protocol = lines[0].split(' ') headers.update(method=method, path=path, protocol=protocol) if method != 'GET' or headers.get('upgrade', '').lower() != 'websocket' or headers.get('connection', '') != 'Upgrade' or headers.get('sec-websocket-protocol', '').lower() != 'sip': raise ValueError, 'Invalid WS request with invalid method, upgrade, connection or protocol' if int(headers.get('sec-websocket-version', '0')) < 13: # version is too old raise ValueError, 'Old or missing websocket version. Must be >= 13' stack.websocket = headers # store the handshake parameters key = headers.get('sec-websocket-key','') # key = 'dGhlIHNhbXBsZSBub25jZQ==' # used for testing, generates accept = s3pPLMBiTxaQ9kYGzzhZRbK+xOo= accept = base64.b64encode(hashlib.sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest()) response = '\r\n'.join([ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: %s'%(accept,), 'Sec-WebSocket-Protocol: sip']) + '\r\n\r\n' logger.debug('%r=>%r\n%s', sock.getsockname(), sock.getpeername(), response) sock.sendall(response) handshake = True self.conn[remote] = sock except: logger.exception('parsing websocket request') sock.close() break else: # process request while pending: m = pending opcode, length = ord(m[0]) & 0x0f, ord(m[1]) & 0x7f if opcode != 0x01: logger.warning('only text opcode is supported') if length == 126: length = struct.unpack('>H', m[2:4])[0] masks = m[4:8] offset = 8 elif length == 127: lengths = struct.unpack('>II', m[2:10]) length = lengths[0] * 2**32 + lengths[1] masks = m[10:14] offset = 14 else: masks = m[2:6] offset = 6 logger.debug('offset=%r length=%r', offset, length) if len(m) < offset + length: logger.debug('incomplete message') break decoded, encoded, pending = [], m[offset:offset+length], m[offset+length:] for i, ch in enumerate(encoded): decoded.append(chr(ord(ch) ^ ord(masks[i % 4]))) decoded = ''.join(decoded) logger.debug('websocket received\n%s', decoded) try: stack.received(decoded, remote) except: logger.exception('receiving') else: # socket closed break try: del self.conn[remote] except: pass