/** * Gets the <tt>TransportAddress</tt> specified in the XOR-MAPPED-ADDRESS attribute of a specific * <tt>Response</tt>. * * @param response the <tt>Response</tt> from which the XOR-MAPPED-ADDRESS attribute is to be * retrieved and its <tt>TransportAddress</tt> value is to be returned * @return the <tt>TransportAddress</tt> specified in the XOR-MAPPED-ADDRESS attribute of * <tt>response</tt> */ protected TransportAddress getMappedAddress(Response response) { Attribute attribute = response.getAttribute(Attribute.XOR_MAPPED_ADDRESS); if (attribute instanceof XorMappedAddressAttribute) { return ((XorMappedAddressAttribute) attribute).getAddress(response.getTransactionID()); } // old STUN servers (RFC3489) send MAPPED-ADDRESS address attribute = response.getAttribute(Attribute.MAPPED_ADDRESS); if (attribute instanceof MappedAddressAttribute) { return ((MappedAddressAttribute) attribute).getAddress(); } else return null; }
private void allocateAddress(TransportAddress turnServerAddress) throws StunException, IOException { logger.info("Requesting address allocation at {}", serverAddress); Response response = sendBlockingStunRequest(MessageFactory.createAllocateRequest(UDP, false)); byte[] transactionID = response.getTransactionID(); relayedAddress = ((XorRelayedAddressAttribute) response.getAttribute(XOR_RELAYED_ADDRESS)) .getAddress(transactionID); mappedAddress = ((XorMappedAddressAttribute) response.getAttribute(XOR_MAPPED_ADDRESS)) .getAddress(transactionID); logger.info("Relayed address: {}, mapped address: {}", relayedAddress, mappedAddress); refreshTask = scheduleRefresh(refreshInterval); }
@NotNull private Response sendBlockingStunRequest(Request request) { try { CompletableFuture<Response> responseFuture = new CompletableFuture<>(); stunStack.sendRequest( request, serverAddress, localAddress, blockingResponseCollector(responseFuture)); Response response = responseFuture.get(); if (response.isErrorResponse()) { logger.warn( "STUN error: {}", ((ErrorCodeAttribute) response.getAttribute(ERROR_CODE)).getReasonPhrase()); } return response; } catch (IOException | InterruptedException | ExecutionException e) { throw new RuntimeException(e); } }
private void bind(TransportAddress address, int channelNumber) { if (peerAddressToChannel.containsKey(address)) { return; } logger.info("Binding '{}' to channel '{}'", address, channelNumber); Response response = sendBlockingStunRequest( MessageFactory.createChannelBindRequest( (char) channelNumber, address, TransactionID.createNewTransactionID().getBytes())); if (response.isSuccessResponse()) { logger.debug("Bound '{}' to channel '{}'", address, channelNumber); peerAddressToChannel.put(address, (char) channelNumber); channelToPeerAddress.put((char) channelNumber, address); } else { logger.warn("Binding for '{}' to channel '{}' failed", address, channelNumber); } }
/** * Notifies this <tt>StunCandidateHarvest</tt> that a specific STUN <tt>Response</tt> has been * received and it challenges a specific STUN <tt>Request</tt> for a long-term credential (as the * short-term credential mechanism does not utilize challenging). * * @param response the STUN <tt>Response</tt> which has been received * @param request the STUN <tt>Request</tt> to which <tt>response</tt> responds and which it * challenges for a long-term credential * @return <tt>true</tt> if the challenge has been processed and this * <tt>StunCandidateHarvest</tt> is to continue processing STUN <tt>Response</tt>s; otherwise, * <tt>false</tt> * @param transactionID the <tt>TransactionID</tt> of <tt>response</tt> and <tt>request</tt> * because <tt>response</tt> and <tt>request</tt> only have it as a <tt>byte</tt> array and * <tt>TransactionID</tt> is required for the <tt>applicationData</tt> property value * @throws StunException if anything goes wrong while processing the challenge */ private boolean processChallenge(Response response, Request request, TransactionID transactionID) throws StunException { boolean retried = false; if (response.getAttributeCount() > 0) { /* * The response SHOULD NOT contain a USERNAME or * MESSAGE-INTEGRITY attribute. */ char[] excludedResponseAttributeTypes = new char[] {Attribute.USERNAME, Attribute.MESSAGE_INTEGRITY}; boolean challenge = true; for (char excludedResponseAttributeType : excludedResponseAttributeTypes) { if (response.containsAttribute(excludedResponseAttributeType)) { challenge = false; break; } } if (challenge) { // This response MUST include a REALM value. RealmAttribute realmAttribute = (RealmAttribute) response.getAttribute(Attribute.REALM); if (realmAttribute == null) challenge = false; else { // The response MUST include a NONCE. NonceAttribute nonceAttribute = (NonceAttribute) response.getAttribute(Attribute.NONCE); if (nonceAttribute == null) challenge = false; else { retried = processChallenge( realmAttribute.getRealm(), nonceAttribute.getNonce(), request, transactionID); } } } } return retried; }
/** * Completes the harvesting of <tt>Candidate</tt>s for {@link #hostCandidate}. Notifies {@link * #harvester} about the completion of the harvesting of <tt>Candidate</tt> for * <tt>hostCandidate</tt> performed by this <tt>StunCandidateHarvest</tt>. * * @param request the <tt>Request</tt> sent by this <tt>StunCandidateHarvest</tt> with which the * harvesting of <tt>Candidate</tt>s for <tt>hostCandidate</tt> has completed * @param response the <tt>Response</tt> received by this <tt>StunCandidateHarvest</tt>, if any, * with which the harvesting of <tt>Candidate</tt>s for <tt>hostCandidate</tt> has completed * @return <tt>true</tt> if the harvesting of <tt>Candidate</tt>s for <tt>hostCandidate</tt> * performed by this <tt>StunCandidateHarvest</tt> has completed; otherwise, <tt>false</tt> */ protected boolean completedResolvingCandidate(Request request, Response response) { if (!completedResolvingCandidate) { completedResolvingCandidate = true; try { if (((response == null) || !response.isSuccessResponse()) && (longTermCredentialSession != null)) { harvester .getStunStack() .getCredentialsManager() .unregisterAuthority(longTermCredentialSession); longTermCredentialSession = null; } } finally { harvester.completedResolvingCandidate(this); } } return completedResolvingCandidate; }
/** * Notifies this <tt>ResponseCollector</tt> that a STUN response described by the specified * <tt>StunResponseEvent</tt> has been received. * * @param event the <tt>StunResponseEvent</tt> which describes the received STUN response * @see ResponseCollector#processResponse(StunResponseEvent) */ @Override public void processResponse(StunResponseEvent event) { TransactionID transactionID = event.getTransactionID(); logger.finest("Received a message: tranid= " + transactionID); logger.finest("localCand= " + hostCandidate); /* * Clean up for the purposes of the workaround which determines the STUN * Request to which a STUN Response responds. */ synchronized (requests) { requests.remove(transactionID); } // At long last, do start handling the received STUN Response. Response response = event.getResponse(); Request request = event.getRequest(); boolean completedResolvingCandidate = true; try { if (response.isSuccessResponse()) { // Authentication and Message-Integrity Mechanisms if (request.containsAttribute(Attribute.MESSAGE_INTEGRITY)) { MessageIntegrityAttribute messageIntegrityAttribute = (MessageIntegrityAttribute) response.getAttribute(Attribute.MESSAGE_INTEGRITY); /* * RFC 5389: If MESSAGE-INTEGRITY was absent, the response * MUST be discarded, as if it was never received. */ if (messageIntegrityAttribute == null) return; UsernameAttribute usernameAttribute = (UsernameAttribute) request.getAttribute(Attribute.USERNAME); /* * For a request or indication message, the agent MUST * include the USERNAME and MESSAGE-INTEGRITY attributes in * the message. */ if (usernameAttribute == null) return; if (!harvester .getStunStack() .validateMessageIntegrity( messageIntegrityAttribute, LongTermCredential.toString(usernameAttribute.getUsername()), !request.containsAttribute(Attribute.REALM) && !request.containsAttribute(Attribute.NONCE), event.getRawMessage())) return; } processSuccess(response, request, transactionID); } else { ErrorCodeAttribute errorCodeAttr = (ErrorCodeAttribute) response.getAttribute(Attribute.ERROR_CODE); if ((errorCodeAttr != null) && (errorCodeAttr.getErrorClass() == 4)) { try { switch (errorCodeAttr.getErrorNumber()) { case 1: // 401 Unauthorized if (processUnauthorized(response, request, transactionID)) completedResolvingCandidate = false; break; case 38: // 438 Stale Nonce if (processStaleNonce(response, request, transactionID)) completedResolvingCandidate = false; break; } } catch (StunException sex) { completedResolvingCandidate = true; } } if (completedResolvingCandidate && processErrorOrFailure(response, request, transactionID)) completedResolvingCandidate = false; } } finally { if (completedResolvingCandidate) completedResolvingCandidate(request, response); } }