def handle_channel_bind(self, message, addr): try: key = (self, addr) allocation = self.server.allocations[key] except KeyError: return self.error_response(message, 437, 'Allocation does not exist') if message.attributes['USERNAME'] != allocation.username: return self.error_response(message, 441, 'Wrong credentials') for attr in ['CHANNEL-NUMBER', 'XOR-PEER-ADDRESS']: if attr not in message.attributes: return self.error_response(message, 400, 'Missing %s attribute' % attr) channel = message.attributes['CHANNEL-NUMBER'] peer_address = message.attributes['XOR-PEER-ADDRESS'] if channel not in CHANNEL_RANGE: return self.error_response(message, 400, 'Channel number is outside valid range') if allocation.channel_to_peer.get(channel) not in [None, peer_address]: return self.error_response(message, 400, 'Channel is already bound to another peer') if allocation.peer_to_channel.get(peer_address) not in [None, channel]: return self.error_response(message, 400, 'Peer is already bound to another channel') # register channel allocation.channel_to_peer[channel] = peer_address allocation.peer_to_channel[peer_address] = channel # build response response = stun.Message( message_method=message.message_method, message_class=stun.Class.RESPONSE, transaction_id=message.transaction_id) return response
def test_peer_reflexive(self): connection = ice.Connection(ice_controlling=True) connection.remote_password = '******' connection.remote_username = '******' protocol = ProtocolMock() request = stun.Message(message_method=stun.Method.BINDING, message_class=stun.Class.REQUEST) request.attributes['PRIORITY'] = 456789 connection.check_incoming(request, ('2.3.4.5', 2345), protocol) self.assertIsNone(protocol.sent_message) # check we have discovered a peer-reflexive candidate self.assertEqual(len(connection.remote_candidates), 1) candidate = connection.remote_candidates[0] self.assertEqual(candidate.component, 1) self.assertEqual(candidate.transport, 'udp') self.assertEqual(candidate.priority, 456789) self.assertEqual(candidate.host, '2.3.4.5') self.assertEqual(candidate.port, 2345) self.assertEqual(candidate.type, 'prflx') self.assertEqual(candidate.generation, None) # check a new pair was formed self.assertEqual(len(connection._check_list), 1) pair = connection._check_list[0] self.assertEqual(pair.protocol, protocol) self.assertEqual(pair.remote_candidate, candidate) # check a triggered check was scheduled self.assertIsNotNone(pair.handle) protocol.response_addr = ('2.3.4.5', 2345) protocol.response_message = 'bad' run(pair.handle)
def handle_refresh(self, message, addr): try: key = (self, addr) allocation = self.server.allocations[key] except KeyError: return self.error_response(message, 437, "Allocation does not exist") if message.attributes["USERNAME"] != allocation.username: return self.error_response(message, 441, "Wrong credentials") if "LIFETIME" not in message.attributes: return self.error_response(message, 400, "Missing LIFETIME attribute") # refresh allocation lifetime = min(message.attributes["LIFETIME"], self.server.maximum_lifetime) if lifetime: logger.info("Allocation refreshed %s", allocation.relayed_address) allocation.expiry = time.time() + lifetime else: logger.info("Allocation deleted %s", allocation.relayed_address) del self.server.allocations[key] # build response response = stun.Message( message_method=message.message_method, message_class=stun.Class.RESPONSE, transaction_id=message.transaction_id, ) response.attributes["LIFETIME"] = lifetime return response
def handle_binding(self, message, addr): response = stun.Message( message_method=message.message_method, message_class=stun.Class.RESPONSE, transaction_id=message.transaction_id) response.attributes['XOR-MAPPED-ADDRESS'] = addr return response
def test_timeout(self): class DummyProtocol: def send_stun(self, message, address): pass request = stun.Message(message_method=stun.Method.BINDING, message_class=stun.Class.REQUEST) transaction = stun.Transaction(request, ('127.0.0.1', 1234), DummyProtocol()) # timeout with self.assertRaises(exceptions.TransactionTimeout): run(transaction.run()) # receive response after timeout response = stun.Message(message_method=stun.Method.BINDING, message_class=stun.Class.RESPONSE) transaction.response_received(response, ('127.0.0.1', 1234))
def test_transaction_failed(self): response = stun.Message( message_method=stun.Method.BINDING, message_class=stun.Class.RESPONSE ) response.attributes["ERROR-CODE"] = (487, "Role Conflict") exc = exceptions.TransactionFailed(response) self.assertEqual(str(exc), "STUN transaction failed (487 - Role Conflict)")
def error_response(self, request, code, message): """ Build an error response for the given request. """ response = stun.Message(message_method=request.message_method, message_class=stun.Class.ERROR, transaction_id=request.transaction_id) response.attributes['ERROR-CODE'] = (code, message) return response
def test_request_with_invalid_method(self): connection = ice.Connection(ice_controlling=True) protocol = ProtocolMock() request = stun.Message( message_method=stun.Method.ALLOCATE, message_class=stun.Class.REQUEST) connection.request_received(request, ('2.3.4.5', 2345), protocol, bytes(request)) self.assertIsNotNone(protocol.sent_message) self.assertEqual(protocol.sent_message.message_method, stun.Method.ALLOCATE) self.assertEqual(protocol.sent_message.message_class, stun.Class.ERROR) self.assertEqual(protocol.sent_message.attributes['ERROR-CODE'], (400, 'Bad Request'))
async def stunTest(protocol: StunProtocol, server_ip: Tuple[str, int], change_ip: bool, change_port: bool) -> Tuple[bool, Tuple[str, int]]: cmd = stun.Message(message_method=stun.Method.BINDING, message_class=stun.Class.REQUEST) if change_ip: cmd.attributes["CHANGE-REQUEST"] = 0x4 if change_port: cmd.attributes["CHANGE-REQUEST"] = 0x2 try: ack, _ = await protocol.request(cmd, server_ip, retransmissions=3) if ack: return True, ack.attributes["XOR-MAPPED-ADDRESS"] except Exception as e: print("[ERROR] %s" % str(e)) return False, None
async def handle_allocate(self, message, addr, integrity_key): key = (self, addr) if key in self.server.allocations: response = self.error_response(message, 437, "Allocation already exists") elif "REQUESTED-TRANSPORT" not in message.attributes: response = self.error_response( message, 400, "Missing REQUESTED-TRANSPORT attribute") elif message.attributes["REQUESTED-TRANSPORT"] != UDP_TRANSPORT: response = self.error_response(message, 442, "Unsupported transport protocol") else: lifetime = message.attributes.get("LIFETIME", self.server.default_lifetime) lifetime = min(lifetime, self.server.maximum_lifetime) # create allocation loop = asyncio.get_event_loop() _, allocation = await loop.create_datagram_endpoint( lambda: Allocation( client_address=addr, client_protocol=self, expiry=time.time() + lifetime, username=message.attributes["USERNAME"], ), local_addr=("127.0.0.1", 0), ) self.server.allocations[key] = allocation logger.info("Allocation created %s", allocation.relayed_address) # build response response = stun.Message( message_method=message.message_method, message_class=stun.Class.RESPONSE, transaction_id=message.transaction_id, ) response.attributes["LIFETIME"] = lifetime response.attributes["XOR-MAPPED-ADDRESS"] = addr response.attributes[ "XOR-RELAYED-ADDRESS"] = allocation.relayed_address # send response response.add_message_integrity(integrity_key) response.add_fingerprint() self.send_stun(response, addr)
def datagram_received(self, data, addr): # demultiplex channel data if len(data) >= 4 and (data[0]) & 0xc0 == 0x40: channel, length = struct.unpack('!HH', data[0:4]) assert len(data) >= length + 4 # echo test if data[4:] == b'ping': response = b'pong' self.transport.sendto( struct.pack('!HH', channel, len(response)) + response, addr) # send back some junk too self.transport.sendto(b'\x00\x00', addr) return try: message = stun.parse_message(data) except ValueError: return assert message.message_class == stun.Class.REQUEST if 'USERNAME' not in message.attributes: response = stun.Message(message_method=message.message_method, message_class=stun.Class.ERROR, transaction_id=message.transaction_id) response.attributes['ERROR-CODE'] = (401, 'Unauthorized') response.attributes['NONCE'] = random_string(16).encode('ascii') response.attributes['REALM'] = self.realm self.transport.sendto(bytes(response), addr) return # check credentials username = message.attributes['USERNAME'] password = self.users[username] integrity_key = hashlib.md5(':'.join([username, self.realm, password ]).encode('utf8')).digest() try: stun.parse_message(data, integrity_key=integrity_key) except ValueError: return if message.message_method == stun.Method.ALLOCATE: response = stun.Message(message_method=message.message_method, message_class=stun.Class.RESPONSE, transaction_id=message.transaction_id) response.attributes['LIFETIME'] = message.attributes['LIFETIME'] response.attributes['XOR-MAPPED-ADDRESS'] = addr response.attributes['XOR-RELAYED-ADDRESS'] = ('1.2.3.4', 1234) response.add_message_integrity(integrity_key) response.add_fingerprint() self.transport.sendto(bytes(response), addr) elif message.message_method == stun.Method.REFRESH: response = stun.Message(message_method=message.message_method, message_class=stun.Class.RESPONSE, transaction_id=message.transaction_id) response.attributes['LIFETIME'] = message.attributes['LIFETIME'] response.add_message_integrity(integrity_key) response.add_fingerprint() self.transport.sendto(bytes(response), addr) elif message.message_method == stun.Method.CHANNEL_BIND: response = stun.Message(message_method=message.message_method, message_class=stun.Class.RESPONSE, transaction_id=message.transaction_id) response.add_message_integrity(integrity_key) response.add_fingerprint() self.transport.sendto(bytes(response), addr)