Repository: jitsi/turnserver Branch: master Commit: 3f4cbd6bfa43 Files: 54 Total size: 288.1 KB Directory structure: gitextract_bn5f4f2j/ ├── .gitignore ├── LICENSE ├── README.md ├── build.xml ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── org/ │ │ └── jitsi/ │ │ └── turnserver/ │ │ ├── IndicationListener.java │ │ ├── TurnException.java │ │ ├── TurnStackProperties.java │ │ ├── collectors/ │ │ │ ├── AllocationResponseCollector.java │ │ │ ├── ChannelBindResponseCollector.java │ │ │ ├── ConnectResponseCollector.java │ │ │ ├── ConnectionBindResponseCollector.java │ │ │ ├── CreatePermissionResponseCollector.java │ │ │ └── RefreshResponseCollector.java │ │ ├── listeners/ │ │ │ ├── AllocationRequestListener.java │ │ │ ├── BindingRequestListener.java │ │ │ ├── ChannelBindRequestListener.java │ │ │ ├── ConnectRequestListener.java │ │ │ ├── ConnectionAttemptIndicationListener.java │ │ │ ├── ConnectionBindRequestListener.java │ │ │ ├── CreatePermissionRequestListener.java │ │ │ ├── DataIndicationListener.java │ │ │ ├── PeerTcpConnectEventListner.java │ │ │ ├── RefreshRequestListener.java │ │ │ └── SendIndicationListener.java │ │ ├── socket/ │ │ │ ├── IceTcpEventizedServerSockerWrapper.java │ │ │ ├── TcpConnectEvent.java │ │ │ ├── TcpConnectEventGenerator.java │ │ │ └── TcpConnectEventListener.java │ │ ├── stack/ │ │ │ ├── Allocation.java │ │ │ ├── ChannelBind.java │ │ │ ├── FiveTuple.java │ │ │ ├── Permission.java │ │ │ ├── ServerChannelDataEventHandler.java │ │ │ ├── ServerPeerUdpEventHandler.java │ │ │ ├── TurnClientTransaction.java │ │ │ ├── TurnServer.java │ │ │ ├── TurnServerTransaction.java │ │ │ └── TurnStack.java │ │ └── turnClient/ │ │ ├── ClientChannelDataEventHandler.java │ │ ├── InteractiveUdpPeer.java │ │ ├── StunClient.java │ │ ├── TcpPeer.java │ │ ├── TurnAllocationClient.java │ │ ├── TurnClient.java │ │ └── TurnTcpAllocationClient.java │ └── resources/ │ ├── TurnServer.propertes │ ├── accounts.txt │ └── logging.properties └── test/ ├── java/ │ └── org/ │ └── jitsi/ │ └── turnserver/ │ ├── client/ │ │ └── ClientTest.java │ └── stack/ │ └── TurnServerTestSuite.java └── resources/ ├── TurnServer.propertes ├── accounts.txt └── logging.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea bin .project .classpath .settings target/ ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ turnserver ========== Welcome to the TurnServer project - open-source TURN server implementation. The TURN protocol allows a client to obtain IP addresses and ports from a relay server. It is most useful for endpoints located behind restrictive NATs or firewalls that wish to receive media from another peer. The TurnServer project aims to be fully compliant with the TURN and STUN Request For Comments (respectively RFC 5766 and RFC 5389). It also supports RFC6156 namely TURN-IPV6 (relay between IPv4-IPv6, IPv6-IPv4 and IPv6-IPv6 addresses) and RFC6062 namely TURN-TCP (relay data with TCP). TurnServer is freely available under the terms of the Apache Public License 2.0. ================================================ FILE: build.xml ================================================ ================================================ FILE: pom.xml ================================================ 4.0.0 org.jitsi jitsi-universe 1.0-SNAPSHOT ../jitsi-universe/pom.xml turnserver 1.0-SNAPSHOT jar turnserver https://github.com/jitsi/turnserver org.jitsi ice4j javax.sdp jain-sdp org.bitlet weupnp junit junit test org.apache.maven.plugins maven-surefire-plugin **/*TestSuite.* jitsi-maven-repository-releases default Jitsi Maven Repository (Releases) true false https://github.com/jitsi/jitsi-maven-repository/raw/master/releases/ jitsi-maven-repository-snapshots default Jitsi Maven Repository (Snapshots) false true https://github.com/jitsi/jitsi-maven-repository/raw/master/snapshots/ ================================================ FILE: src/main/java/org/jitsi/turnserver/IndicationListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver; import org.ice4j.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.stack.*; /** * Abstract class for Indication Listener. * * @author Aakash Garg */ public abstract class IndicationListener implements MessageEventHandler { /** * The turnStack for this instance. */ private final TurnStack turnStack; /** * Represents if the listener is started or not. */ private boolean started = false; /** * Represents the localAddress associated with the listener. */ private TransportAddress localAddress; /** * The turnStack to call. * @param turnStack */ public IndicationListener(TurnStack turnStack) { this.turnStack = turnStack; } /** * Checks if the message is an Indication message. If yes it then finds the * five tuple and corresponding allocation and calls the handleIndication. */ @Override public void handleMessageEvent(StunMessageEvent evt) { Message message = evt.getMessage(); if(Message.isIndicationType(message.getMessageType())) { Indication ind = (Indication) message; TransportAddress clientAddress = evt.getRemoteAddress(); TransportAddress serverAddress = evt.getLocalAddress(); Transport transport = serverAddress.getTransport(); FiveTuple fiveTuple = new FiveTuple(clientAddress, serverAddress, transport); Allocation alloc = turnStack.getServerAllocation(fiveTuple); this.handleIndication(ind,alloc); } } /** * Sets the turnStack for this class. * @return */ public TurnStack getTurnStack() { return this.turnStack; } /** * Sets the localAddress. * * @param localAddress * the localAddress to listen on. */ public void setLocalAddress(TransportAddress localAddress) { this.localAddress = localAddress; } /** * Handles the Indication message. * It must be implemented by the subclass. * * @param ind * the Indication to handle. * @param alloc * the corresponding to the request. */ abstract public void handleIndication(Indication ind, Allocation alloc); /** * Starts this IndicationListener. If it is not currently * running, does nothing. */ public void start() { if (!started) { turnStack.addIndicationListener(localAddress, this); started = true; } } /** * Stops this IndicationListenerr. A stopped * IndicationListenerr can be restarted by calling * {@link #start()} on it. */ public void stop() { turnStack.removeIndicationListener(localAddress, this); started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/TurnException.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver; import org.ice4j.StunException; /** * @author Aakash Garg * */ public class TurnException extends StunException { /** * */ private static final long serialVersionUID = -8004612606830162094L; /** * */ public TurnException() { } /** * @param id */ public TurnException(int id) { super(id); } /** * @param message */ public TurnException(String message) { super(message); } /** * @param id * @param message */ public TurnException(int id, String message) { super(id, message); } /** * @param id * @param message * @param cause */ public TurnException(int id, String message, Throwable cause) { super(id, message, cause); } /** * @param message * @param cause */ public TurnException(String message, Throwable cause) { super(message, cause); } /** * @param cause */ public TurnException(Throwable cause) { super(cause); } } ================================================ FILE: src/main/java/org/jitsi/turnserver/TurnStackProperties.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver; import java.util.logging.*; import org.ice4j.*; /** * The class contains a number of property names and their default values that * we use to configure the behavior of the Turnserver stack. * * @author Aakash Garg. */ public class TurnStackProperties extends StackProperties { /** * Our class logger. */ private static final Logger logger = Logger.getLogger(TurnStackProperties.class.getName()); public static final String TURNSERVER_UDP_PORT = "org.jitsi.turnserver.udp_port"; public static final int DEFAULT_TURNSERVER_UDP_PORT = 3478; public static final String TURNSERVER_MIN_PORT = "org.jitsi.turnserver.min_port"; public static final int DEFAULT_TURNSERVER_MIN_PORT = 49152; public static final String TURNSERVER_MAX_PORT = "org.jitsi.turnserver.max_port"; public static final int DEFAULT_TURNSERVER_MAX_PORT = 65535; public static final String ALLOCATION_LIFETIME = "org.jitsi.turnserver.allocation_lifetime"; public static final int DEFAULT_ALLOCATION_LIFETIME = 10 * 60 * 1000; public static final String MAX_ALLOCATIONS = "org.jitsi.turnserver.max_allocations"; public static final int DEFAULT_MAX_ALLOCATIONS = 50; public static final String CHANNELBIND_LIFETIME = "org.jitsi.turnserver.channelbind_lifetime"; public static final int DEFAULT_CHANNELBIND_LIFETIME = 10 * 60 * 1000; public static final String MAX_PERMISSIONS_PER_ALLOCATION = "org.jitsi.turnserver.max_permissions_per_allocation"; public static final int DEFAULT_MAX_PERMISSIONS_PER_ALLOCATION = 10; public static final String PERMISSION_LIFETIME = "org.jitsi.turnserver.permission_lifetime"; public static final int DEFAULT_PERMISSION_LIFETIME = 300 * 1000; public static final String MAX_CHANNELBINDS_PER_ALLOCATION = "org.jitsi.turnserver.max_channelbinds_per_allocation"; public static final int DEFAULT_MAX_CHANNELBINDS_PER_ALLOCATION = 10; public static final String REALM = "org.jitsi.turnserver.realm"; public static final String DEFAULT_REALM = "org.jitsi.turnserver"; public static final String DEFAULT_ACCOUNTS_FILE = ""; public static final String ACCOUNTS_FILE = ""; } ================================================ FILE: src/main/java/org/jitsi/turnserver/collectors/AllocationResponseCollector.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.collectors; import java.io.IOException; import java.util.logging.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.stack.TurnStack; /** * The class that would be handling to incoming Allocation responses. * * @author Aakash Garg */ public class AllocationResponseCollector implements ResponseCollector { /** * The Logger used by the AllocationresponseCollector * class and its instances for logging output. */ private static final Logger logger = Logger .getLogger(AllocationResponseCollector.class.getName()); private final StunStack stunStack; /** * Creates a new AllocationresponseCollector * * @param turnStack */ public AllocationResponseCollector(StunStack stunStack) { this.stunStack = stunStack; } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processResponse(org.ice4j.StunResponseEvent) */ @Override public void processResponse(StunResponseEvent evt) { if (logger.isLoggable(Level.FINER)) { logger.finer("Received response " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.ALLOCATE_ERROR_RESPONSE) { ErrorCodeAttribute errorCodeAttribute = (ErrorCodeAttribute) message.getAttribute(Attribute.ERROR_CODE); NonceAttribute nonceAttr = (NonceAttribute) message .getAttribute(Attribute.NONCE); // System.out.println("Nonce : "+new Nonce(nonceAttr.getNonce())); Request request = MessageFactory.createAllocateRequest(); TransactionID tran = TransactionID.createNewTransactionID(); try { request.setTransactionID(tran.getBytes()); } catch (StunException e1) { System.err.println("Unable to set tran ID."); } request.putAttribute(nonceAttr); String username = "JitsiGsocStudent"; UsernameAttribute usernameAttr = AttributeFactory .createUsernameAttribute(username+":"); /* byte[] key = this.stunStack.getCredentialsManager().getLocalKey( username); System.out.println("Username found " + (this.stunStack.getCredentialsManager() .checkLocalUserName(username))); System.out.println("User " + username + " found : " + TurnStack.toHexString(key)); byte[] messageB = request.encode(stunStack); */ MessageIntegrityAttribute msgInt = AttributeFactory .createMessageIntegrityAttribute(username); RequestedTransportAttribute reqTrans = AttributeFactory .createRequestedTransportAttribute( RequestedTransportAttribute.UDP); try{ // msgInt.encode(stunStack, messageB, 0, messageB.length); }catch(Exception e) { e.printStackTrace(); } request.putAttribute(reqTrans); request.putAttribute(usernameAttr); request.putAttribute(msgInt); try { this.stunStack.sendRequest(request, evt.getRemoteAddress(), evt.getLocalAddress(), this); } catch (Exception e) { e.printStackTrace(); // System.err.println(e.getMessage()); } if(errorCodeAttribute != null) { System.out.println("Error Code : " + (int) errorCodeAttribute.getErrorCode()); } switch (errorCodeAttribute.getErrorCode()) { case ErrorCodeAttribute.BAD_REQUEST: // code for bad response error break; case ErrorCodeAttribute.UNAUTHORIZED: // code for unauthorised error code break; case ErrorCodeAttribute.FORBIDDEN: // code for forbidden error code break; case ErrorCodeAttribute.UNKNOWN_ATTRIBUTE: // code for Unknown Attribute error code break; case ErrorCodeAttribute.ALLOCATION_MISMATCH: // code for Allocation mismatch Error break; case ErrorCodeAttribute.STALE_NONCE: // code for Stale Nonce error code break; case ErrorCodeAttribute.WRONG_CREDENTIALS: // code for wrong credentials error code break; case ErrorCodeAttribute.UNSUPPORTED_TRANSPORT_PROTOCOL: // code for unsupported transport protocol break; case ErrorCodeAttribute.ALLOCATION_QUOTA_REACHED: // code for allocation quota reached break; case ErrorCodeAttribute.INSUFFICIENT_CAPACITY: // code for insufficient capacity break; } } else if (message.getMessageType() == Message.ALLOCATE_RESPONSE) { System.out.println("Allocate Sucess Response."); // code for doing processing of Allocation success response } else { return; } } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processTimeout(org.ice4j.StunTimeoutEvent) */ @Override public void processTimeout(StunTimeoutEvent event) { } } ================================================ FILE: src/main/java/org/jitsi/turnserver/collectors/ChannelBindResponseCollector.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.collectors; import java.util.logging.Level; import java.util.logging.Logger; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; /** * The class that would be handling and responding to incoming ChannelBind * response. * * @author Aakash Garg */ public class ChannelBindResponseCollector implements ResponseCollector { /** * The Logger used by the ChannelBindresponseCollector * class and its instances for logging output. */ private static final Logger logger = Logger .getLogger(ChannelBindResponseCollector.class.getName()); private final StunStack stunStack; /** * Creates a new ChannelBindresponseCollector * * @param turnStack */ public ChannelBindResponseCollector(StunStack stunStack) { this.stunStack = stunStack; } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processResponse(org.ice4j.StunResponseEvent) */ @Override public void processResponse(StunResponseEvent evt) { if (logger.isLoggable(Level.FINER)) { logger.finer("Received response " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.CHANNELBIND_ERROR_RESPONSE) { ErrorCodeAttribute errorCodeAttribute = (ErrorCodeAttribute) message.getAttribute(Attribute.ERROR_CODE); switch (errorCodeAttribute.getErrorCode()) { case ErrorCodeAttribute.BAD_REQUEST: // code for bad response error break; case ErrorCodeAttribute.INSUFFICIENT_CAPACITY: // logic for processing insufficient capacity error code break; } } else if (message.getMessageType() == Message.CHANNELBIND_RESPONSE) { // logic for processing the success response. } else { return; } } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processTimeout(org.ice4j.StunTimeoutEvent) */ @Override public void processTimeout(StunTimeoutEvent event) { // TODO Auto-generated method stub } } ================================================ FILE: src/main/java/org/jitsi/turnserver/collectors/ConnectResponseCollector.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.collectors; import java.util.logging.Level; import java.util.logging.Logger; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; /** * The class that would be handling and responding to incoming Connect response. * * @author Aakash Garg */ public class ConnectResponseCollector implements ResponseCollector { /** * The Logger used by the ConnectresponseCollector class * and its instances for logging output. */ private static final Logger logger = Logger .getLogger(ConnectResponseCollector.class.getName()); private final StunStack stunStack; /** * Creates a new ConnectresponseCollector * * @param turnStack */ public ConnectResponseCollector(StunStack stunStack) { this.stunStack = stunStack; } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processResponse(org.ice4j.StunResponseEvent) */ @Override public void processResponse(StunResponseEvent evt) { if (logger.isLoggable(Level.FINER)) { logger.finer("Received response " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.CONNECT_ERROR_RESPONSE) { ErrorCodeAttribute errorCodeAttribute = (ErrorCodeAttribute) message.getAttribute(Attribute.ERROR_CODE); switch (errorCodeAttribute.getErrorCode()) { case ErrorCodeAttribute.ALLOCATION_MISMATCH: // code for bad response error break; case ErrorCodeAttribute.CONNECTION_ALREADY_EXISTS: // code for processing connection already exists error break; case ErrorCodeAttribute.FORBIDDEN: // code for processing forbidden error code break; case ErrorCodeAttribute.CONNECTION_TIMEOUT_OR_FAILURE: // code for processing connection timeout or failure error code. break; } } else if (message.getMessageType() == Message.CONNECT_RESPONSE) { // code for doing processing of Connect success response } else { return; } } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processTimeout(org.ice4j.StunTimeoutEvent) */ @Override public void processTimeout(StunTimeoutEvent event) { // TODO Auto-generated method stub } } ================================================ FILE: src/main/java/org/jitsi/turnserver/collectors/ConnectionBindResponseCollector.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.collectors; import java.util.logging.Level; import java.util.logging.Logger; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; /** * The class that would be handling and responding to incoming ConnectionBind * response. * * @author Aakash Garg */ public class ConnectionBindResponseCollector implements ResponseCollector { /** * The Logger used by the ConnectionBindresponseCollector * class and its instances for logging output. */ private static final Logger logger = Logger .getLogger(ConnectionBindResponseCollector.class.getName()); private final StunStack stunStack; /** * Creates a new ConnectionBindresponseCollector * * @param turnStack */ public ConnectionBindResponseCollector(StunStack stunStack) { this.stunStack = stunStack; } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processResponse(org.ice4j.StunResponseEvent) */ @Override public void processResponse(StunResponseEvent evt) { if (logger.isLoggable(Level.FINER)) { logger.finer("Received response " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.ALLOCATE_ERROR_RESPONSE) { ErrorCodeAttribute errorCodeAttribute = (ErrorCodeAttribute) message.getAttribute(Attribute.ERROR_CODE); switch (errorCodeAttribute.getErrorCode()) { case ErrorCodeAttribute.BAD_REQUEST: // code for bad response error break; } } else if (message.getMessageType() == Message.ALLOCATE_RESPONSE) { // code for doing processing of ConnectionBind success response } else { return; } } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processTimeout(org.ice4j.StunTimeoutEvent) */ @Override public void processTimeout(StunTimeoutEvent event) { // TODO Auto-generated method stub } } ================================================ FILE: src/main/java/org/jitsi/turnserver/collectors/CreatePermissionResponseCollector.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.collectors; import java.util.logging.Level; import java.util.logging.Logger; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; /** * The class that would be handling and responding to incoming CreatePermission * response. * * @author Aakash Garg */ public class CreatePermissionResponseCollector implements ResponseCollector { /** * The Logger used by the * CreatePermissionResponseCollector class and its instances for * logging output. */ private static final Logger logger = Logger .getLogger(CreatePermissionResponseCollector.class.getName()); private final StunStack stunStack; /** * Creates a new CreatePermissionResponseCollector * * @param turnStack */ public CreatePermissionResponseCollector(StunStack stunStack) { this.stunStack = stunStack; } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processResponse(org.ice4j.StunResponseEvent) */ @Override public void processResponse(StunResponseEvent evt) { if (logger.isLoggable(Level.FINER)) { logger.finer("Received response " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.ALLOCATE_ERROR_RESPONSE) { ErrorCodeAttribute errorCodeAttribute = (ErrorCodeAttribute) message.getAttribute(Attribute.ERROR_CODE); switch (errorCodeAttribute.getErrorCode()) { case ErrorCodeAttribute.BAD_REQUEST: // logic for processing bad request error break; case ErrorCodeAttribute.INSUFFICIENT_CAPACITY: // logic for processing insufficient capacity error break; case ErrorCodeAttribute.FORBIDDEN: // logic for processing forbidden error code break; default: logger.finer("error : Received error response with error code " + errorCodeAttribute.getErrorCode() + evt); } } else { return; } } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processTimeout(org.ice4j.StunTimeoutEvent) */ @Override public void processTimeout(StunTimeoutEvent event) { // TODO Auto-generated method stub } } ================================================ FILE: src/main/java/org/jitsi/turnserver/collectors/RefreshResponseCollector.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.collectors; import java.util.logging.*; import org.ice4j.*; import org.ice4j.message.*; import org.ice4j.stack.*; /** * The class that would be handling and responding to incoming Refresh * responses. * * @author Aakash Garg */ public class RefreshResponseCollector implements ResponseCollector { /** * The Logger used by the RefreshresponseCollector class * and its instances for logging output. */ private static final Logger logger = Logger .getLogger(RefreshResponseCollector.class.getName()); private final StunStack stunStack; /** * Creates a new RefreshResponseCollector * * @param turnStack */ public RefreshResponseCollector(StunStack stunStack) { this.stunStack = stunStack; } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processResponse(org.ice4j.StunResponseEvent) */ @Override public void processResponse(StunResponseEvent evt) { if (logger.isLoggable(Level.FINER)) { logger.finer("Received response " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.REFRESH_ERROR_RESPONSE) { // delete allocation } else if (message.getMessageType() == Message.REFRESH_RESPONSE) { // update allocation } else { return; } } /* * (non-Javadoc) * * @see * org.ice4j.ResponseCollector#processTimeout(org.ice4j.StunTimeoutEvent) */ @Override public void processTimeout(StunTimeoutEvent event) { } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/AllocationRequestListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.util.logging.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.stack.*; /** * The class that would be handling and responding to incoming Allocation * requests that are Allocation and sends a success or error response. * * @author Aakash Garg */ public class AllocationRequestListener implements RequestListener { /** * The Logger used by the AllocationRequestListener class * and its instances for logging output. */ private static final Logger logger = Logger .getLogger(AllocationRequestListener.class.getName()); private final TurnStack turnStack; /** * The indicator which determines whether this * AllocationrequestListener is currently started. */ private boolean started = false; /** * Creates a new AllocationRequestListener * * @param turnStack */ public AllocationRequestListener(StunStack stunStack) { if (stunStack instanceof TurnStack) { this.turnStack = (TurnStack) stunStack; } else { throw new IllegalArgumentException("This is not a TurnStack!"); } } @Override public void processRequest(StunMessageEvent evt) throws IllegalArgumentException { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); } Message message = evt.getMessage(); if (message.getMessageType() == Message.ALLOCATE_REQUEST) { logger.finest("Received a Allocation Request from " + evt.getRemoteAddress()); Response response = null; RequestedTransportAttribute requestedTransportAttribute = (RequestedTransportAttribute) message .getAttribute(Attribute.REQUESTED_TRANSPORT); DontFragmentAttribute dontFragmentAttribute = (DontFragmentAttribute) message .getAttribute(Attribute.DONT_FRAGMENT); ReservationTokenAttribute reservationTokenAttribute = (ReservationTokenAttribute) message .getAttribute(Attribute.RESERVATION_TOKEN); LifetimeAttribute lifetimeAttribute = (LifetimeAttribute) message.getAttribute(Attribute.LIFETIME); EvenPortAttribute evenPort = (EvenPortAttribute) message.getAttribute(Attribute.EVEN_PORT); if (lifetimeAttribute == null) { lifetimeAttribute = AttributeFactory .createLifetimeAttribute( (int) (Allocation.DEFAULT_LIFETIME / 1000)); } EvenPortAttribute evenPortAttribute = (EvenPortAttribute) message.getAttribute(Attribute.EVEN_PORT); TransportAddress clientAddress = evt.getRemoteAddress(); TransportAddress serverAddress = evt.getLocalAddress(); Transport transport = serverAddress.getTransport(); FiveTuple fiveTuple = new FiveTuple(clientAddress, serverAddress, transport); Character errorCode = null; if(!this.turnStack.canHaveMoreAllocations()) { errorCode = ErrorCodeAttribute.ALLOCATION_QUOTA_REACHED; } else if (requestedTransportAttribute == null) { errorCode = ErrorCodeAttribute.BAD_REQUEST; } else if (requestedTransportAttribute.getRequestedTransport() == RequestedTransportAttribute.TCP) { if (!this.turnStack.isTCPAllowed()) errorCode = ErrorCodeAttribute.UNSUPPORTED_TRANSPORT_PROTOCOL; else if (reservationTokenAttribute != null) { logger.finest("error : reservation token found in TCP message."); errorCode = ErrorCodeAttribute.UNSUPPORTED_TRANSPORT_PROTOCOL; } else if (evenPort != null) { logger.finest("error : even port found in TCP message."); errorCode = ErrorCodeAttribute.UNSUPPORTED_TRANSPORT_PROTOCOL; } else if (dontFragmentAttribute != null) { logger.finest("error : dont fragment found in TCP message."); errorCode = ErrorCodeAttribute.UNSUPPORTED_TRANSPORT_PROTOCOL; } } else if (requestedTransportAttribute.getRequestedTransport() == RequestedTransportAttribute.UDP && !this.turnStack.isUDPAllowed()) { errorCode = ErrorCodeAttribute.UNSUPPORTED_TRANSPORT_PROTOCOL; logger.finest("UDP not alllowed on Allocation Requests."); } else if (reservationTokenAttribute != null && evenPortAttribute != null) { errorCode = ErrorCodeAttribute.BAD_REQUEST; logger .finest("Both reservation Token and Even PortAttribute are found in Allocation request."); } if (turnStack.getServerAllocation(fiveTuple)!=null) { errorCode = ErrorCodeAttribute.ALLOCATION_MISMATCH; logger.finest("Allocation not found for the "+fiveTuple); } // do other checks here if (errorCode == null) { if(evenPortAttribute==null) { evenPortAttribute = AttributeFactory.createEvenPortAttribute(false); } TransportAddress relayAddress = turnStack.getNewRelayAddress( evenPortAttribute.isRFlag(), serverAddress.getTransport()); /* logger.finest("Added a new Relay Address "+relayAddress); System.out.println("Added a new Relay Address "+relayAddress +" for client "+evt.getRemoteAddress()); */ Allocation allocation = null; synchronized(this) { allocation = new Allocation(relayAddress, fiveTuple, lifetimeAttribute.getLifetime()); this.turnStack.addNewServerAllocation(allocation); // System.out.println("Added a new allocation."); } logger.finest("Added a new Allocation with relay address :" + allocation.getRelayAddress()+" for client " +evt.getRemoteAddress()); response = MessageFactory.createAllocationResponse( (Request) message, allocation.getFiveTuple().getClientTransportAddress(), allocation.getRelayAddress(), (int) allocation.getLifetime()); XorRelayedAddressAttribute relayedXorAddress = AttributeFactory.createXorRelayedAddressAttribute( allocation.getRelayAddress(), evt.getTransactionID().getBytes()); response.putAttribute(relayedXorAddress); LifetimeAttribute lifetime = AttributeFactory.createLifetimeAttribute( (int)(allocation.getLifetime())); response.putAttribute(lifetime); XorMappedAddressAttribute clientXorAddress = AttributeFactory.createXorMappedAddressAttribute( clientAddress, evt.getTransactionID().getBytes()); response.putAttribute(clientXorAddress); if(evenPort!=null) { // TODO : logic for process and creating Reservation Token. byte[] token = {7,7,7,7}; ReservationTokenAttribute reservationToken = AttributeFactory.createReservationTokenAttribute( token); response.putAttribute(reservationToken); if(evenPort.isRFlag()) { TransportAddress relayAddess = allocation.getRelayAddress(); TransportAddress nextAddress = new TransportAddress( relayAddress.getAddress(), relayAddress.getPort()+1, relayAddress.getTransport()); boolean isReserved = this.turnStack.reservePort(nextAddress); if(isReserved) { logger.log( Level.FINEST, nextAddress+" reserved by "+fiveTuple); } else { logger.log( Level.FINEST, nextAddress+" not reserved by "+fiveTuple); } } } } else { System.err.println("Error Code " + (int)errorCode + " on Allocation Request"); logger.finest("Error Code " + (int)errorCode + " on Allocation Request"); response = MessageFactory.createAllocationErrorResponse(errorCode); } try { logger .fine("Trying to send response to " + evt.getRemoteAddress() + " from " + evt.getLocalAddress()); turnStack.sendResponse( evt.getTransactionID().getBytes(), response, evt.getLocalAddress(), evt.getRemoteAddress()); logger.finest("Response sent."); } catch (Exception e) { System.err.println("Failed to send response"); logger.log( Level.INFO, "Failed to send " + response + " through " + evt.getLocalAddress(), e); // try to trigger a 500 response although if this one failed, throw new RuntimeException("Failed to send a response", e); } } else { return; } } /** * Starts this AllocationRequestListener. If it is not currently * running, does nothing. */ public void start() { if (!started) { turnStack.addRequestListener(this); started = true; } } /** * Stops this AllocationRequestListenerr. A stopped * AllocationRequestListenerr can be restarted by calling * {@link #start()} on it. */ public void stop() { turnStack.removeRequestListener(this); started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/BindingRequestListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.util.logging.*; import org.ice4j.*; import org.ice4j.message.*; import org.ice4j.stack.*; /** * The class that would be handling and responding to incoming requests that are * validated and sends a SUCCESS response * * @author Aakash Garg */ public class BindingRequestListener implements RequestListener { /** * The Logger used by the BindingRequestListener class and * its instances for logging output. */ private static final Logger logger = Logger .getLogger(BindingRequestListener.class.getName()); private final StunStack stunStack; /** * The indicator which determines whether this * ValidatedrequestListener is currently started. */ private boolean started = false; /** * Creates a new BindingRequestListener * * @param turnStack */ public BindingRequestListener(StunStack stunStack) { this.stunStack = stunStack; } @Override public void processRequest(StunMessageEvent evt) throws IllegalArgumentException { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); } Message message = evt.getMessage(); if (message.getMessageType() == Message.BINDING_REQUEST) { logger.finest("Received a Binding Request from " + evt.getRemoteAddress()); TransportAddress mappedAddress = evt.getRemoteAddress(); // Response response = // MessageFactory.createBindingResponse(request,mappedAddress); TransportAddress sourceAddress = evt.getLocalAddress(); TransportAddress changedAddress = new TransportAddress("stunserver.org", 3489, Transport.UDP); Response response = MessageFactory.create3489BindingResponse( mappedAddress, sourceAddress, changedAddress); try { stunStack.sendResponse( evt.getTransactionID().getBytes(), response, evt.getLocalAddress(), evt.getRemoteAddress()); logger.finest("Binding Response Sent."); } catch (Exception e) { logger.log( Level.INFO, "Failed to send " + response + " through " + evt.getLocalAddress(), e); // try to trigger a 500 response although if this one failed, throw new RuntimeException("Failed to send a response", e); } } } /** * Starts this BindingRequestListener. If it is not currently * running, does nothing. */ public void start() { if (!started) { stunStack.addRequestListener(this); started = true; } } /** * Stops this ValidatedRequestListenerr. A stopped * ValidatedRequestListenerr can be restarted by calling * {@link #start()} on it. */ public void stop() { stunStack.removeRequestListener(this); started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/ChannelBindRequestListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.util.logging.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.stack.*; /** * The class that would be handling and responding to incoming ChannelBind * requests that are validated and sends a success or error response * * @author Aakash Garg */ public class ChannelBindRequestListener implements RequestListener { /** * The Logger used by the ChannelBindRequestListener class * and its instances for logging output. */ private static final Logger logger = Logger .getLogger(ChannelBindRequestListener.class.getName()); private final TurnStack turnStack; /** * The indicator which determines whether this * ChannelBindrequestListener is currently started. */ private boolean started = false; /** * Creates a new ChannelBindRequestListener * * @param turnStack */ public ChannelBindRequestListener(StunStack turnStack) { if (turnStack instanceof TurnStack) { this.turnStack = (TurnStack) turnStack; } else { throw new IllegalArgumentException("This is not a TurnStack!"); } } @Override public void processRequest(StunMessageEvent evt) throws IllegalArgumentException { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); // logger.finer("Received request " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.CHANNELBIND_REQUEST) { logger.finest("Received Channel Bind request "); logger.finest("Event tran : "+evt.getTransactionID()); Response response = null; TransportAddress clientAddress = evt.getRemoteAddress(); TransportAddress serverAddress = evt.getLocalAddress(); Transport transport = serverAddress.getTransport(); FiveTuple fiveTuple = new FiveTuple(clientAddress, serverAddress, transport); Allocation allocation = this.turnStack.getServerAllocation(fiveTuple); ChannelNumberAttribute channelNo = (ChannelNumberAttribute) message.getAttribute( Attribute.CHANNEL_NUMBER); XorPeerAddressAttribute xorPeerAddress = (XorPeerAddressAttribute) message .getAttribute(Attribute.XOR_PEER_ADDRESS); if(xorPeerAddress!=null) { xorPeerAddress.setAddress(xorPeerAddress.getAddress(), evt.getTransactionID().getBytes()); } logger.finest("Adding ChannelBind : " +(int)(channelNo.getChannelNumber()) +", "+xorPeerAddress.getAddress()); ChannelBind channelBind = new ChannelBind( xorPeerAddress.getAddress(), channelNo.getChannelNumber()); Character errorCode = null; if(channelNo==null || xorPeerAddress==null) { errorCode = ErrorCodeAttribute.BAD_REQUEST; } else if(!ChannelNumberAttribute.isValidRange( channelNo.getChannelNumber())) { errorCode = ErrorCodeAttribute.BAD_REQUEST; } else if (allocation == null || allocation.isBadChannelRequest(channelBind)) { errorCode = ErrorCodeAttribute.BAD_REQUEST; } else if(!TurnStack.isIPAllowed(xorPeerAddress.getAddress())) { errorCode = ErrorCodeAttribute.FORBIDDEN; } else if(!allocation.canHaveMoreChannels()) { errorCode = ErrorCodeAttribute.INSUFFICIENT_CAPACITY; } if(errorCode != null) { logger.finest("Creating ChannelBindError response : " +(int)errorCode); response = MessageFactory.createChannelBindErrorResponse(errorCode); } else { logger.finest("Creating ChannelBind sucess response"); try { logger.finer("Adding ChannelBind : "+channelBind); allocation.addChannelBind(channelBind); } catch(IllegalArgumentException ex) { logger.log(Level.FINEST,ex.getMessage()); } response = MessageFactory.createChannelBindResponse(); } try { turnStack.sendResponse(evt.getTransactionID().getBytes(), response, evt.getLocalAddress(), evt.getRemoteAddress()); } catch (Exception e) { logger.log(Level.INFO, "Failed to send " + response + " through " + evt.getLocalAddress(), e); // try to trigger a 500 response although if this one failed, throw new RuntimeException("Failed to send a response", e); } } else { return; } } /** * Starts this ChannelBindRequestListener. If it is not currently * running, does nothing. */ public void start() { if (!started) { turnStack.addRequestListener(this); started = true; } } /** * Stops this ChannelBindRequestListenerr. A stopped * ChannelBindRequestListenerr can be restarted by calling * {@link #start()} on it. */ public void stop() { turnStack.removeRequestListener(this); started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/ConnectRequestListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; import org.ice4j.StunMessageEvent; import org.ice4j.Transport; import org.ice4j.TransportAddress; import org.ice4j.attribute.Attribute; import org.ice4j.attribute.AttributeFactory; import org.ice4j.attribute.ConnectionIdAttribute; import org.ice4j.attribute.ErrorCodeAttribute; import org.ice4j.attribute.XorPeerAddressAttribute; import org.ice4j.message.Message; import org.ice4j.message.MessageFactory; import org.ice4j.message.Response; import org.ice4j.socket.IceTcpSocketWrapper; import org.ice4j.stack.RequestListener; import org.ice4j.stack.StunStack; import org.jitsi.turnserver.stack.Allocation; import org.jitsi.turnserver.stack.FiveTuple; import org.jitsi.turnserver.stack.TurnStack; /** * The class that would be handling and responding to incoming Connect requests * that are validated and sends a success or error response * * @author Aakash Garg */ public class ConnectRequestListener implements RequestListener { /** * The Logger used by the ConnectRequestListener class and * its instances for logging output. */ private static final Logger logger = Logger .getLogger(ConnectRequestListener.class.getName()); private final TurnStack turnStack; /** * The indicator which determines whether this * ValidatedrequestListener is currently started. */ private boolean started = false; /** * Creates a new ConnectRequestListener * * @param turnStack */ public ConnectRequestListener(StunStack stunStack) { this.turnStack = (TurnStack) stunStack; } @Override public void processRequest(StunMessageEvent evt) throws IllegalArgumentException { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); } Message message = evt.getMessage(); if (message.getMessageType() == Message.CONNECT_REQUEST) { logger.finer("Received Connect request " + evt); Character errorCode = null; XorPeerAddressAttribute peerAddress = null; FiveTuple fiveTuple = null; Response response = null; ConnectionIdAttribute connectionId = null; if (!message.containsAttribute(Attribute.XOR_PEER_ADDRESS)) { errorCode = ErrorCodeAttribute.BAD_REQUEST; } else { peerAddress = (XorPeerAddressAttribute) message .getAttribute(Attribute.XOR_PEER_ADDRESS); peerAddress.setAddress( peerAddress.getAddress(), evt.getTransactionID().getBytes()); logger.finest("Peer Address requested : " + peerAddress.getAddress()); TransportAddress clientAddress = evt.getRemoteAddress(); TransportAddress serverAddress = evt.getLocalAddress(); Transport transport = evt.getLocalAddress().getTransport(); fiveTuple = new FiveTuple(clientAddress, serverAddress, transport); Allocation allocation = this.turnStack.getServerAllocation(fiveTuple); if (allocation == null) { errorCode = ErrorCodeAttribute.ALLOCATION_MISMATCH; } else if(!allocation.isPermitted(peerAddress.getAddress())){ errorCode = ErrorCodeAttribute.FORBIDDEN; } else { // code for processing the connect request. connectionId = AttributeFactory.createConnectionIdAttribute(); logger.finest("Created ConnectionID : " + connectionId.getConnectionIdValue()); try { Socket socket = new Socket(peerAddress.getAddress().getAddress(), peerAddress.getAddress().getPort()); socket.setSoTimeout(30*1000); IceTcpSocketWrapper iceSocket = new IceTcpSocketWrapper(socket); this.turnStack.addSocket(iceSocket); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } /** * TODO * Create a new TCP connection to the peer from relay * address. * wait for at-least 30 seconds. * If it fails then send 447 error code. **/ this.turnStack.addUnAcknowlededConnectionId( connectionId.getConnectionIdValue(), peerAddress.getAddress(), allocation); logger.finest("Creating Connect Success Response."); response = MessageFactory.createConnectResponse(connectionId .getConnectionIdValue()); } } if(errorCode != null){ response = MessageFactory.createConnectErrorResponse(errorCode); logger.finest("error Code : "+(int)errorCode+ " on ConnectRequest"); } try { logger.finest("Sending Connect Response"); turnStack.sendResponse( evt.getTransactionID().getBytes(), response, evt.getLocalAddress(), evt.getRemoteAddress()); } catch (Exception e) { System.err.println("Failed to send response"); logger.log( Level.INFO, "Failed to send " + response + " through " + evt.getLocalAddress(), e); // try to trigger a 500 response although if this one failed, throw new RuntimeException("Failed to send a response", e); } } else { return; } } /** * Starts this ConnectRequestListener. If it is not currently * running, does nothing. */ public void start() { if (!started) { turnStack.addRequestListener(this); started = true; } } /** * Stops this ConnectRequestListenerr. A stopped * ConnectRequestListenerr can be restarted by calling * {@link #start()} on it. */ public void stop() { turnStack.removeRequestListener(this); started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/ConnectionAttemptIndicationListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.io.*; import java.net.*; import java.util.logging.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.socket.*; import org.ice4j.stack.*; import org.ice4j.stunclient.*; import org.jitsi.turnserver.*; import org.jitsi.turnserver.stack.*; /** * Class to handle events when a Connection Attempt Indication is received on * CLient Side. * * @author Aakash Garg * */ public class ConnectionAttemptIndicationListener extends IndicationListener { /** * The Logger used by the DataIndicationListener class and * its instances for logging output. */ private static final Logger logger = Logger .getLogger(ConnectionAttemptIndicationListener.class.getName()); /** * The request sender to use to send request to Turn server. */ private BlockingRequestSender requestSender; /** * Constructor to create a ConnectionAttemptIndicationListener with * specified turnStack to use the requestSender will be null and a new * {@link BlockingRequestSender} will be created with new TCP socket to send * request to server. * * @param turnStack the turnStack to use for processing. */ public ConnectionAttemptIndicationListener(TurnStack turnStack) { super(turnStack); } /** * Constructor to create a ConnectionAttemptIndicationListener with * specified turnStack to use. * * @param turnStack the turnStack to use for processing. * @param requestSender the requestSender to use to send request to server. */ public ConnectionAttemptIndicationListener(TurnStack turnStack, BlockingRequestSender requestSender) { super(turnStack); this.requestSender = requestSender; } /** * The method to handle the incoming ConnectionAttempt Indications from Turn * Server. */ @Override public void handleIndication(Indication ind, Allocation alloc) { if (ind.getMessageType() == Message.CONNECTION_ATTEMPT_INDICATION) { logger.finest("Received a connection attempt indication."); byte[] tranId = ind.getTransactionID(); ConnectionIdAttribute connectionId = (ConnectionIdAttribute) ind .getAttribute(Attribute.CONNECTION_ID); XorPeerAddressAttribute peerAddress = (XorPeerAddressAttribute) ind .getAttribute(Attribute.XOR_PEER_ADDRESS); peerAddress.setAddress( peerAddress.getAddress(), tranId); logger .finest("Received a Connection Attempt Indication with connectionId-" + connectionId.getConnectionIdValue() + ", for peerAddress-" + peerAddress.getAddress()); Socket socket; try { socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 3478); IceTcpSocketWrapper sockWrapper = new IceTcpSocketWrapper(socket); this.getTurnStack().addSocket( sockWrapper); TransportAddress localAddr = new TransportAddress(sockWrapper.getLocalAddress(), sockWrapper.getLocalPort(), Transport.TCP); logger.finest("New Local TCP socket chosen - " + localAddr); TransportAddress serverAddress = new TransportAddress(InetAddress.getLocalHost(), 3478, Transport.TCP); StunMessageEvent evt = null; try { Request connectionBindRequest = MessageFactory.createConnectionBindRequest(connectionId .getConnectionIdValue()); TransactionID tranID = TransactionID.createNewTransactionID(); connectionBindRequest.setTransactionID(tranID.getBytes()); if (this.requestSender == null) { logger.finest("Setting RequestSender"); this.requestSender = new BlockingRequestSender(this.getTurnStack(), localAddr); } logger.finest("Sending ConnectionBind Request to " + serverAddress); evt = requestSender.sendRequestAndWaitForResponse( connectionBindRequest, serverAddress); if (evt != null) { Message msg = evt.getMessage(); if (msg.getMessageType() == Message.CONNECTION_BIND_SUCCESS_RESPONSE) { logger .finest("Received a ConnectionBind Sucess Response."); String myMessage = "Aakash Garg"; RawMessage rawMessage = RawMessage.build(myMessage.getBytes(), myMessage.length(), serverAddress, localAddr); try { logger .finest("--------------- Thread will now sleep for 20 sec."); Thread.sleep(20 * 1000); } catch (InterruptedException e) { System.err.println("Unable to stop thread"); } logger .finest(">>>>>>>>>>>> Sending a Test message : "); byte[] data = myMessage.getBytes(); for (int i = 0; i < data.length; i++) { System.out.print(String.format( "%02X, ", data[i])); } this.getTurnStack().sendUdpMessage( rawMessage, serverAddress, requestSender.getLocalAddress()); } else { logger .finest("Received a ConnectionBind error Response - " + msg.getAttribute(Attribute.ERROR_CODE)); } } else { System.err .println("No response received to ConnectionBind request"); } } catch (StunException e) { logger.finest("Unable to send ConnectionBind request"); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/ConnectionBindRequestListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.io.*; import java.util.logging.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.stack.*; /** * The class that would be handling and responding to incoming ConnectionBind * requests that are validated and sends a success or error response * * @author Aakash Garg */ public class ConnectionBindRequestListener implements RequestListener { /** * The Logger used by the ConnectionBindRequestListener * class and its instances for logging output. */ private static final Logger logger = Logger .getLogger(ConnectionBindRequestListener.class.getName()); private final TurnStack turnStack; /** * The indicator which determines whether this * ConnectionBindrequestListener is currently started. */ private boolean started = false; /** * Creates a new ConnectionBindRequestListener * * @param turnStack */ public ConnectionBindRequestListener(StunStack stunStack) { this.turnStack = (TurnStack)stunStack; } @Override public void processRequest(StunMessageEvent evt) throws IllegalArgumentException { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); } Message message = evt.getMessage(); if (message.getMessageType() == Message.CONNECTION_BIND_REQUEST) { Response response = null; Character errorCode = null; TransportAddress clientAddress = evt.getRemoteAddress(); TransportAddress serverAddress = evt.getLocalAddress(); Transport transport = evt.getLocalAddress().getTransport(); logger.finer("Received ConnectBind request " + evt + ", from " + clientAddress + ", at " + serverAddress + " over " + transport); ConnectionIdAttribute connectionId = null; if (transport != Transport.TCP) { errorCode = ErrorCodeAttribute.BAD_REQUEST; logger.finest("Transport is not TCP."); } else if (!message.containsAttribute(Attribute.CONNECTION_ID)) { errorCode = ErrorCodeAttribute.BAD_REQUEST; logger.finest("ConnectionID not found"); } else { connectionId = (ConnectionIdAttribute) message .getAttribute(Attribute.CONNECTION_ID); logger.finest("Requested ConnectionId - " + connectionId.getConnectionIdValue()); if (!this.turnStack.isUnacknowledged(connectionId .getConnectionIdValue())) { errorCode = ErrorCodeAttribute.BAD_REQUEST; logger.finest("ConnectionId-" + connectionId.getConnectionIdValue() + " not present."); } } if (errorCode != null) { logger .finest("Creating Connection Bind Error Response, errorCode:" + (int) errorCode); response = MessageFactory .createConnectionBindErrorResponse( ErrorCodeAttribute.BAD_REQUEST); } else { // processing logic. FiveTuple clientDataConnTuple = new FiveTuple(clientAddress, serverAddress, transport); this.turnStack.acknowledgeConnectionId( connectionId.getConnectionIdValue(), clientDataConnTuple); logger.finest("Creating ConnectionBind Success Response"); response = MessageFactory.createConnectionBindResponse(); } try { logger.finest("Sending ConnectionBind Response to " + clientAddress + " through " + serverAddress); this.turnStack.sendResponse( evt.getTransactionID().getBytes(), response, serverAddress, clientAddress); } catch (StunException e) { logger.finest("Unable to send ConnectionBind Response to " + clientAddress + " through " + serverAddress); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { return; } } /** * Starts this ConnectionBindRequestListener. If it is not * currently running, does nothing. */ public void start() { if (!started) { turnStack.addRequestListener(this); started = true; } } /** * Stops this ConnectionBindRequestListenerr. A stopped * ConnectionBindRequestListenerr can be restarted by calling * {@link #start()} on it. */ public void stop() { turnStack.removeRequestListener(this); started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/CreatePermissionRequestListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.util.logging.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.stack.*; /** * The class that would be handling and responding to incoming CreatePermission * requests that are CreatePermission and sends a success or error response * * @author Aakash Garg */ public class CreatePermissionRequestListener implements RequestListener { /** * The Logger used by the CreatePermissionRequestListener * class and its instances for logging output. */ private static final Logger logger = Logger .getLogger(CreatePermissionRequestListener.class.getName()); private final TurnStack turnStack; /** * The indicator which determines whether this * CreatePermissionrequestListener is currently started. */ private boolean started = false; /** * Creates a new CreatePermissionRequestListener * * @param turnStack */ public CreatePermissionRequestListener(StunStack turnStack) { if (turnStack instanceof TurnStack) { this.turnStack = (TurnStack) turnStack; } else { throw new IllegalArgumentException("This is not a TurnStack!"); } } /* * (non-Javadoc) * * @see * org.ice4j.stack.RequestListener#processRequest(org.ice4j.StunMessageEvent * ) */ @Override public void processRequest(StunMessageEvent evt) throws IllegalArgumentException { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); // logger.finer("Received request " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.CREATEPERMISSION_REQUEST) { logger.finest("Received create permission request "); logger.finest("Event tran : "+evt.getTransactionID()); XorPeerAddressAttribute xorPeerAddressAttribute = (XorPeerAddressAttribute) message .getAttribute(Attribute.XOR_PEER_ADDRESS); if(xorPeerAddressAttribute!=null) { xorPeerAddressAttribute.setAddress( xorPeerAddressAttribute.getAddress(), evt.getTransactionID().getBytes()); } // we should get multiple xor peer address attributes here. LifetimeAttribute lifetimeAttribute = (LifetimeAttribute) message.getAttribute(Attribute.LIFETIME); Response response = null; TransportAddress clientAddress = evt.getRemoteAddress(); TransportAddress serverAddress = evt.getLocalAddress(); Transport transport = serverAddress.getTransport(); FiveTuple fiveTuple = new FiveTuple(clientAddress, serverAddress, transport); Allocation allocation = this.turnStack.getServerAllocation(fiveTuple); Character errorCode = null; if (xorPeerAddressAttribute == null || allocation==null) { errorCode = ErrorCodeAttribute.BAD_REQUEST; } else if (!TurnStack.isIPAllowed( xorPeerAddressAttribute.getAddress())) { logger.finest("Peer Address requested " + xorPeerAddressAttribute.getAddress() +" "+TurnStack.isIPAllowed( xorPeerAddressAttribute.getAddress())); errorCode = ErrorCodeAttribute.FORBIDDEN; } else if (!allocation.canHaveMorePermisions()) { errorCode = ErrorCodeAttribute.INSUFFICIENT_CAPACITY; } if(errorCode!=null) { logger.finest("Creating error response : "+(int)errorCode); response = MessageFactory.createCreatePermissionErrorResponse( errorCode); } else { logger.finest("Creating success response."); TransportAddress peerAddress = xorPeerAddressAttribute.getAddress(); Permission permission = null; if(lifetimeAttribute!=null) { permission = new Permission(peerAddress, lifetimeAttribute.getLifetime()); } else { permission = new Permission(peerAddress); } logger.finest("Peer Address requested " + xorPeerAddressAttribute.getAddress() +" "+TurnStack.isIPAllowed( xorPeerAddressAttribute.getAddress())); allocation.addNewPermission(permission); logger.finest("Added permission to allocation."); response = MessageFactory.createCreatePermissionResponse(); } try { turnStack.sendResponse(evt.getTransactionID().getBytes(), response, evt.getLocalAddress(), evt.getRemoteAddress()); } catch (Exception e) { logger.log(Level.INFO, "Failed to send " + response + " through " + evt.getLocalAddress(), e); // try to trigger a 500 response although if this one failed, throw new RuntimeException("Failed to send a response", e); } } else { return; } } /** * Starts this CreatePermissionRequestListener. If it is not * currently running, does nothing. */ public void start() { if (!started) { turnStack.addRequestListener(this); started = true; } } /** * Stops this CreatePermissionRequestListenerr. A stopped * CreatePermissionRequestListenerr can be restarted by calling * {@link #start()} on it. */ public void stop() { turnStack.removeRequestListener(this); started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/DataIndicationListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.io.*; import java.util.logging.Logger; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.jitsi.turnserver.*; import org.jitsi.turnserver.stack.*; /** * Class to handle the incoming Data indications. * * @author Aakash Garg * */ public class DataIndicationListener extends IndicationListener { /** * The Logger used by the DataIndicationListener * class and its instances for logging output. */ private static final Logger logger = Logger .getLogger(DataIndicationListener.class.getName()); /** * parametrised constructor. * * @param turnStack * the turnStack to set for this class. */ public DataIndicationListener(TurnStack turnStack) { super(turnStack); } /** * Handles the incoming data indication. * * @param ind * the indication to handle. * @param alloc * the allocation associated with message. */ @Override public void handleIndication(Indication ind, Allocation alloc) { if (ind.getMessageType() == Message.DATA_INDICATION) { logger.finest("Received a Data Indication message."); byte[] tran = ind.getTransactionID(); XorPeerAddressAttribute xorPeerAddress = (XorPeerAddressAttribute) ind .getAttribute(Attribute.XOR_PEER_ADDRESS); xorPeerAddress.setAddress(xorPeerAddress.getAddress(), tran); DataAttribute data = (DataAttribute) ind .getAttribute(Attribute.DATA); TransportAddress peerAddr = xorPeerAddress.getAddress(); try { String line = new String(data.getData(), "UTF-8"); // System.out.println(line); logger.finest("Data Indiaction message from " + peerAddr + " is " + line); /* System.out.println("Received a Data indiction from " + peerAddr + ", message : " + line); */ } catch (UnsupportedEncodingException e) { System.err.println("Unable to convert to String!"); } } } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/PeerTcpConnectEventListner.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.util.logging.Level; import java.util.logging.Logger; import org.ice4j.StunException; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.socket.*; import org.jitsi.turnserver.stack.*; /** * Class to handle events when Peer tries to establish a TCP connection to a * Server Socket (generally Relay Address). * * @author Aakash Garg * */ public class PeerTcpConnectEventListner implements TcpConnectEventListener { /** * The Logger used by the FiveTuple class and its * instances for logging output. */ private static final Logger logger = Logger .getLogger(PeerTcpConnectEventListner.class.getName()); private final TurnStack turnStack; public PeerTcpConnectEventListner(TurnStack turnStack) { this.turnStack = turnStack; } @Override public void onConnect(TcpConnectEvent event) { logger.setLevel(Level.FINEST); logger.finest("Received a connect event src:" + event.getLocalAdress() + ", dest:" + event.getRemoteAdress()); Allocation allocation = this.turnStack.getServerAllocation(event.getLocalAdress()); if (allocation == null) { logger.finest("Allocation not found for relay : " + event.getLocalAdress()); } else if (allocation.isPermitted(event.getRemoteAdress())) { try { ConnectionIdAttribute connectionId = AttributeFactory.createConnectionIdAttribute(); logger.finest("Created ConnectionId - " + connectionId.getConnectionIdValue() + " for client " + allocation.getClientAddress()); TransactionID tranID = TransactionID.createNewTransactionID(); Indication connectionAttemptIndication = MessageFactory.createConnectionAttemptIndication( connectionId.getConnectionIdValue(), event.getRemoteAdress(), tranID.getBytes()); this.turnStack.addUnAcknowlededConnectionId( connectionId.getConnectionIdValue(), event.getRemoteAdress(), allocation); logger.finest("Sending Connection Attempt Indication."); this.turnStack.sendIndication( connectionAttemptIndication, allocation.getClientAddress(), allocation.getServerAddress()); } catch (StunException e) { logger .finest("Unable to send Connection Attempt Indiacation to " + allocation.getClientAddress()); } } else { logger.finest("permission not installed for - " + event.getRemoteAdress()); } // this.turnStack.add } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/RefreshRequestListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.util.logging.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.stack.*; /** * The class that would be handling and responding to incoming Refresh requests * that are validated and sends a success or error response * * @author Aakash Garg */ public class RefreshRequestListener implements RequestListener { /** * The Logger used by the RefreshRequestListener class and * its instances for logging output. */ private static final Logger logger = Logger .getLogger(RefreshRequestListener.class.getName()); private final TurnStack turnStack; /** * The indicator which determines whether this * RefreshRequestListener is currently started. */ private boolean started = false; /** * Creates a new RefreshRequestListener * * @param turnStack */ public RefreshRequestListener(StunStack turnStack) { if (turnStack instanceof TurnStack) { this.turnStack = (TurnStack) turnStack; } else { throw new IllegalArgumentException("This is not a TurnStack!"); } } /** * (non-Javadoc) * * @see org.ice4j.stack.RequestListener#processRequest(org.ice4j.StunMessageEvent) */ @Override public void processRequest(StunMessageEvent evt) throws IllegalArgumentException { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); // logger.finer("Received request " + evt); } Message message = evt.getMessage(); if (message.getMessageType() == Message.REFRESH_REQUEST) { logger.finer("Received refresh request " + evt); LifetimeAttribute lifetimeAttribute = (LifetimeAttribute) message.getAttribute(Attribute.LIFETIME); Response response = null; TransportAddress clientAddress = evt.getRemoteAddress(); TransportAddress serverAddress = evt.getLocalAddress(); Transport transport = serverAddress.getTransport(); FiveTuple fiveTuple = new FiveTuple(clientAddress, serverAddress, transport); Allocation allocation = this.turnStack.getServerAllocation(fiveTuple); if (allocation != null) { if (lifetimeAttribute != null) { logger.finest("Refreshing allocation with relay addr " + allocation.getRelayAddress() + " with lifetime " + lifetimeAttribute.getLifetime()); allocation.refresh(lifetimeAttribute.getLifetime()); response = MessageFactory.createRefreshResponse( (int) allocation.getLifetime()); } else { logger.finest("Refreshing allocation with relay addr " + allocation.getRelayAddress() + " with default lifetime"); allocation.refresh(); response = MessageFactory.createRefreshResponse( (int) allocation.getLifetime()); } } else { logger.finest("Allocation mismatch error"); response = MessageFactory.createRefreshErrorResponse( ErrorCodeAttribute.ALLOCATION_MISMATCH); } try { turnStack.sendResponse(evt.getTransactionID().getBytes(), response, evt.getLocalAddress(), evt.getRemoteAddress()); } catch (Exception e) { logger.log(Level.INFO, "Failed to send " + response + " through " + evt.getLocalAddress(), e); // try to trigger a 500 response although if this one failed, throw new RuntimeException("Failed to send a response", e); } } else { return; } } /** * Starts this RefreshRequestListener. If it is not currently * running, does nothing. */ public void start() { if (!started) { turnStack.addRequestListener(this); started = true; } } /** * Stops this RefreshRequestListenerr. A stopped * ValidatedRequestListenerr can be restarted by calling * {@link #start()} on it. */ public void stop() { turnStack.removeRequestListener(this); started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/listeners/SendIndicationListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.listeners; import java.util.logging.Logger; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.*; import org.jitsi.turnserver.stack.*; /** * Class to handle the incoming Send indications. * * @author Aakash Garg * */ public class SendIndicationListener extends IndicationListener { /** * The Logger used by the SendIndicationListener * class and its instances for logging output. */ private static final Logger logger = Logger .getLogger(SendIndicationListener.class.getName()); /** * parametrised constructor. * * @param turnStack * the turnStack to set for this class. */ public SendIndicationListener(TurnStack turnStack) { super(turnStack); } /** * Handles the incoming send indication. * * @param ind * the indication to handle. * @param alloc * the allocation associated with message. */ @Override public void handleIndication(Indication ind, Allocation alloc) { if(ind.getMessageType()==Message.SEND_INDICATION) { logger.finest("Received a Send Indication message."); byte[] tran = ind.getTransactionID(); XorPeerAddressAttribute xorPeerAddress = (XorPeerAddressAttribute) ind .getAttribute(Attribute.XOR_PEER_ADDRESS); xorPeerAddress.setAddress(xorPeerAddress.getAddress(), tran); DataAttribute data = (DataAttribute) ind.getAttribute(Attribute.DATA); TransportAddress peerAddr = xorPeerAddress.getAddress(); if(alloc!=null && alloc.isPermitted(peerAddr)) { RawMessage udpMessage = RawMessage.build(data.getData(), data.getDataLength(), peerAddr, alloc.getRelayAddress()); try { this.getTurnStack().sendUdpMessage(udpMessage, peerAddr, alloc.getRelayAddress()); logger.finest("Sent SendIndiaction to " + peerAddr + " from " + alloc.getRelayAddress()); } catch (StunException e) { System.err.println("Unable to send message."); } } // else silently ignore the indication. } } } ================================================ FILE: src/main/java/org/jitsi/turnserver/socket/IceTcpEventizedServerSockerWrapper.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.socket; import java.io.IOException; import java.net.*; import java.util.*; import java.util.logging.*; import org.ice4j.*; import org.ice4j.ice.*; import org.ice4j.socket.*; /** * Class for eventized TCP server Socket where event is when someone tries to * connect to the given server Socket of the class. * * @author Aakash Garg * */ public class IceTcpEventizedServerSockerWrapper extends IceSocketWrapper implements TcpConnectEventGenerator { /** * The Logger used by the LocalCandidate class and its * instances for logging output. */ private static Logger logger = Logger .getLogger(IceTcpEventizedServerSockerWrapper.class.getName()); /** * Thread that will wait new connections. */ private Thread acceptThread = null; /** * The wrapped TCP ServerSocket. */ private final ServerSocket serverSocket; /** * If the socket is still listening. */ private boolean isRun = false; private TcpConnectEventListener listener; /** * STUN stack. */ private final Component component; /** * List of TCP client sockets. */ private final List sockets = new ArrayList(); public IceTcpEventizedServerSockerWrapper(ServerSocket serverSocket, Component component) { this.serverSocket = serverSocket; this.component = component; acceptThread = new ThreadAccept(); acceptThread.start(); } /** * {@inheritDoc} */ @Override public void send(DatagramPacket p) throws IOException { System.err.println("Send called in IceTcpServerSocketWrapper."); /* Do nothing for the moment */ } /** * {@inheritDoc} */ @Override public void receive(DatagramPacket p) throws IOException { System.err.println("Receive called in IceTcpServerSocketWrapper."); /* Do nothing for the moment */ } /** * {@inheritDoc} */ @Override public void close() { try { isRun = false; serverSocket.close(); for (Socket s : sockets) { s.close(); } } catch (IOException e) { } } /** * {@inheritDoc} */ @Override public InetAddress getLocalAddress() { return serverSocket.getInetAddress(); } /** * {@inheritDoc} */ @Override public int getLocalPort() { return serverSocket.getLocalPort(); } /** * {@inheritDoc} */ @Override public SocketAddress getLocalSocketAddress() { return serverSocket.getLocalSocketAddress(); } /** * {@inheritDoc} */ @Override public Socket getTCPSocket() { if (sockets.size() > 0) { return sockets.get(0); } return null; } /** * {@inheritDoc} */ @Override public DatagramSocket getUDPSocket() { return null; } @Override public void setEventListener(TcpConnectEventListener listener) { this.listener = listener; } @Override public void removeEventListener() { this.listener = null; } @Override public void fireConnectEvent(TcpConnectEvent event) { if (this.listener != null) this.listener.onConnect(event); else logger.finest("Listener not registered"); } /** * Thread that will wait for new TCP connections. * */ private class ThreadAccept extends Thread { /** * Thread entry point. */ @Override public void run() { isRun = true; while (isRun) { try { Socket tcpSocket = serverSocket.accept(); if (tcpSocket != null) { MultiplexingSocket multiplexingSocket = new MultiplexingSocket(tcpSocket); component.getParentStream().getParentAgent() .getStunStack().addSocket( new IceTcpSocketWrapper(multiplexingSocket)); sockets.add(multiplexingSocket); String[] serverIpPort = serverSocket.getLocalSocketAddress().toString() .replaceAll( "/", "").split( ":"); TransportAddress localAddr = new TransportAddress(InetAddress.getLocalHost(), Integer.parseInt(serverIpPort[1]), Transport.TCP); String[] remoteIpPort = tcpSocket.getRemoteSocketAddress().toString() .replaceAll( "/", "").split( ":"); TransportAddress remoteAddr = new TransportAddress(remoteIpPort[0], Integer.parseInt(remoteIpPort[1]), Transport.TCP); logger.finest("Connection Request from "+remoteAddr+" to "+localAddr); TcpConnectEvent event = new TcpConnectEvent(localAddr, remoteAddr); IceTcpEventizedServerSockerWrapper.this .fireConnectEvent(event); } } catch (IOException e) { logger.info("Failed to accept TCP socket " + e); } } } } } ================================================ FILE: src/main/java/org/jitsi/turnserver/socket/TcpConnectEvent.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.socket; import org.ice4j.TransportAddress; /** * Represents the TCP connect event on the Server Socket. * * @author Aakash Garg * */ public class TcpConnectEvent { private final TransportAddress localAdress; private final TransportAddress remoteAdress; /** * @param localAdress * @param remoteAdress */ public TcpConnectEvent(TransportAddress localAdress, TransportAddress remoteAdress) { this.localAdress = localAdress; this.remoteAdress = remoteAdress; } public TransportAddress getLocalAdress() { return localAdress; } public TransportAddress getRemoteAdress() { return remoteAdress; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/socket/TcpConnectEventGenerator.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.socket; /** * Represents the source of generating the TCP connect events. * * @author Aakash Garg * */ public interface TcpConnectEventGenerator { public void setEventListener(TcpConnectEventListener listener); public void removeEventListener(); public void fireConnectEvent(TcpConnectEvent event); } ================================================ FILE: src/main/java/org/jitsi/turnserver/socket/TcpConnectEventListener.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.socket; /** * Represents the TCP Connect Event Listener. * * @author Aakash Garg. * */ public interface TcpConnectEventListener { public void onConnect(TcpConnectEvent event); } ================================================ FILE: src/main/java/org/jitsi/turnserver/stack/Allocation.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.stack; import java.util.HashMap; import java.util.Iterator; import java.util.logging.Logger; import org.ice4j.Transport; import org.ice4j.TransportAddress; /** * This class is an implementation of Allocations in TURN server. * * @author Aakash Garg * */ public class Allocation { /** * Our class logger. */ private static final Logger logger = Logger.getLogger(Allocation.class .getName()); /** * represents the relay address associated with this Allocation. */ private final TransportAddress relayAddress; /** * Represents the FiveTuple associated with this Allocation. */ private final FiveTuple fiveTuple; /** * Represents the username associated with this Allocation. */ private final String username; /** * represents the password associated with this Allocation. */ private final String password; /** * The time in milliseconds when the Allocation will expire. */ private long expirationTime = -1; /** * Determines whether or not the Allocation has expired. */ private boolean expired = false; /** * The default lifetime allowed for a Allocation. */ public static final long DEFAULT_LIFETIME = 10 * 60 * 1000; /** * The max lifetime allowed for a Allocation. */ public static final long MAX_LIFETIME = 60 * 60 * 1000; /** * The maximum no of Permissions per Allocation. */ public static final int MAX_PERMISSIONS = 10; /** * The Maximum no of ChannelBinds per Allocation. */ public static final int MAX_CHANNELBIND = 10; /** * The Thread which expires the Permissions of this * Allocation and removes them from {@link #permissions}. */ private Thread permissionExpireThread; /** * Represents the permissions associated with peerAddress IP installed for * this Allocation. */ private final HashMap permissions = new HashMap(); /** * The Thread which expires the ChannelBinds of this * Allocation and removes them from {@link #channelBindings}. */ private Thread channelBindExpireThread; /** * Represents the Channel Bindings associated with this Allocation. */ private final HashMap channelBindings = new HashMap(); /** * Contains the mapping of peerAdress of ChannelBinds to Channelno. This is * used to check the peerAddress while creating new Permissions and * ChannelBinds. */ private final HashMap peerToChannelMap = new HashMap(); /** * Maps one-to-one from ConnecionID to Data Connection. */ private final HashMap connIdToDataConnMap = new HashMap(); /** * Maps one-to-one from ConnecionID to Peer TCP Connection. */ private final HashMap connIdToPeerConnMap = new HashMap(); /** * Constructor to instantiate an Allocation without a username and password. * * @param relayAddress the realyAddress associated with this Allocation. * @param fiveTuple the fiveTuple associated with this Allocation. */ public Allocation( TransportAddress relayAddress, FiveTuple fiveTuple) { this(relayAddress, fiveTuple, null, null); } /** * Constructor to instantiate an Allocation without a username and password * with the lifetime value. * * @param relayAddress the realyAddress associated with this Allocation. * @param fiveTuple the fiveTuple associated with this Allocation. * @param lifetime the lifetime for this Allocation. */ public Allocation( TransportAddress relayAddress, FiveTuple fiveTuple, long lifetime) { this(relayAddress, fiveTuple, null, null, lifetime); } /** * Constructor to instantiate an Allocation with given relayAddress, * fiveTuple, username, passowrd and with default lifetime value. * * @param relayAddress the realyAddress associated with this Allocation. * @param fiveTuple the fiveTuple associated with this Allocation. * @param username the username associated with this Allocation. * @param password the password associated with this Allocation. */ public Allocation( TransportAddress relayAddress, FiveTuple fiveTuple, String username, String password) { this(relayAddress, fiveTuple, username, password, Allocation.DEFAULT_LIFETIME); } /** * Constructor to instantiate an Allocation with given relayAddress, * fiveTuple, username, passowrd and with default lifetime value. * * @param relayAddress the realyAddress associated with this Allocation. * @param fiveTuple the fiveTuple associated with this Allocation. * @param username the username associated with this Allocation. * @param password the password associated with this Allocation. * @param lifetime the lifetime for this allocation. */ public Allocation( TransportAddress relayAddress, FiveTuple fiveTuple, String username, String password, long lifetime) { this.relayAddress = relayAddress; this.fiveTuple = fiveTuple; this.username = username; this.password = password; this.setLifetime(lifetime); } /** * returns the fiveTuple associated with this Allocation. */ public FiveTuple getFiveTuple() { return this.fiveTuple; } /** * Returns the relayAddress associated with this Allocation. */ public TransportAddress getRelayAddress() { return this.relayAddress; } /** * Returns the clientAddress associated with this Allocation. * The client address who instianted this allocation. */ public TransportAddress getClientAddress() { return this.getFiveTuple().getClientTransportAddress(); } /** * Returns the serverAddress associated with this Allocation. * The serverAddress on which this allocation request is received. */ public TransportAddress getServerAddress() { return this.getFiveTuple().getServerTransportAddress(); } /** * Returns the Client Data Connection corresponding to Connection Id for * which ConnectionBind Request has been received. * * @param connectionId the ConnectionId for which Client Data Connection is * to be returned. * @return Client Data Connection if exists else null. */ public FiveTuple getDataConnection(int connectionId){ return this.connIdToDataConnMap.get(connectionId); } /** * Returns the Peer TCP Data Connection corresponding to Connection Id for * which ConnectionBind Request has been received. * * @param connectionId the ConnectionId for which Peer TCP Data Connection is * to be returned. * @return Peer TCP Data Connection if exists else null. */ public FiveTuple getPeerTCPConnection(int connectionId){ return this.connIdToPeerConnMap.get(connectionId); } /** * Adds the Connection Id with corresponding Client Data Connection to for * which ConnectionBind Request has been received. * * @param connectionId the ConnectionId. * @param clientDataConn Client Data Connection to corresponding * ConnectionId. */ public void addDataConnection(int connectionId, FiveTuple clientDataConn) { this.connIdToDataConnMap.put( connectionId, clientDataConn); } /** * Adds the Connection Id corresponding to Peer TCP Data Connection for * which ConnectionBind Request has been received. * * @param connectionId the ConnectionId. * @param peerDataConn Peer TCP Data Connection to corresponding * ConnectionId. */ public void addPeerTCPConnection(int connectionId, FiveTuple peerDataConn) { this.connIdToPeerConnMap.put(connectionId,peerDataConn); } /** * Removes the Client Data Connection with corresponding to Connection Id. * * @param connectionId the ConnectionId corresponding to Client Data * Connection. */ public void removeDataConnection(int connectionId) { this.connIdToDataConnMap.remove(connectionId); } /** * Removes the Peer TCP Data Connection corresponding to Connection Id. * * @param connectionId the ConnectionId corresponding to Client Data * Connection. */ public void removePeerTCPConnection(int connectionId) { this.connIdToPeerConnMap.remove(connectionId); } /** * Returns the lifetime associated with this Allocation. If the allocation * is expired it returns 0. */ public long getLifetime() { if (!isExpired()) { return (this.expirationTime - System.currentTimeMillis()); } else { return 0; } } /** * Sets the time to expire in milli-seconds for this allocation. Max * lifetime can be Allocation.MAX_LIFEIME. * * @param lifetime the lifetime for this Allocation. */ public void setLifetime(long lifetime) { synchronized (this) { this.expirationTime = System.currentTimeMillis() + Math.min(lifetime * 1000, Allocation.MAX_LIFETIME); } } /** * Refreshes the allocation with the DEFAULT_LIFETIME value. */ public void refresh() { this.setLifetime(Allocation.DEFAULT_LIFETIME); } /** * refreshes the allocation with given lifetime value. * * @param lifetime the required lifetime of allocation. */ public void refresh(int lifetime) { this.setLifetime(lifetime); } /** * Start the Allocation. This launches the countdown to the moment the * Allocation would expire. */ public synchronized void start() { synchronized (this) { if (expirationTime == -1) { expired = false; expirationTime = DEFAULT_LIFETIME + System.currentTimeMillis(); } else { throw new IllegalStateException( "Allocation has already been started!"); } } } /** * Determines whether this Allocation is expired now. * * @return true if this Allocation is expired now; * otherwise, false */ public boolean isExpired() { return isExpired(System.currentTimeMillis()); } /** * Expires the Allocation. Once this method is called the Allocation is * considered terminated. */ public synchronized void expire() { expired = true; /* * TurnStack has a background Thread running with the purpose of * removing expired Allocations. */ } /** * Determines whether this Allocation will be expired at a specific * point in time. * * @param now the time in milliseconds at which the expired state * of this Allocation is to be returned * @return true if this Allocation will be expired at the * specified point in time; otherwise, false */ public synchronized boolean isExpired(long now) { if (expirationTime == -1) return false; else if (expirationTime < now) return true; else return expired; } /** * Adds a new Permission for this Allocation. * * @param peerIP the peer IP address foe which to create this permission to * be added to this allocation. */ public void addNewPermission(TransportAddress peerIP) { TransportAddress peerIp = new TransportAddress(peerIP.getAddress(), 0, Transport.UDP); Permission permission = new Permission(peerIP); this.addNewPermission(permission); } /** * Adds a new Permission for this Allocation. * * @param permission the permission to be added to this allocation. */ public void addNewPermission(Permission permission) { TransportAddress peerAddr = new TransportAddress(permission.getIpAddress().getAddress(), 0, Transport.UDP); if (this.permissions.containsKey(peerAddr)) { this.permissions.get(permission.getIpAddress()).refresh(); } else if (!this.canHaveMorePermisions()) { return; } else { this.permissions.put( permission.getIpAddress(), permission); maybeStartPermissionExpireThread(); } } /** * Binds a new Channel to this Allocation. * If an existing ChannelBind is found it is refreshed * else a new ChannelBind and permission is added. * * @param channelBind the channelBind to be added to this allocation. * @throws IllegalArgumentException if the channelNo of the channelBind to * be added is already occupied. */ public void addChannelBind(ChannelBind channelBind) { TransportAddress peerAddr = new TransportAddress( channelBind.getPeerAddress().getAddress(), 0, Transport.UDP); if (isBadChannelRequest(channelBind)) { throw new IllegalArgumentException("400: BAD REQUEST"); } else if(!channelBindings.containsKey(channelBind.getChannelNo()) && !peerToChannelMap.containsKey(channelBind.getPeerAddress())) { synchronized(this.channelBindings) { this.channelBindings.put( channelBind.getChannelNo(), channelBind); } synchronized(this.peerToChannelMap) { this.peerToChannelMap.put( channelBind.getPeerAddress(), channelBind.getChannelNo()); } } else { synchronized(this.channelBindings) { this.channelBindings.get(channelBind.getChannelNo()).refresh(); } } this.addNewPermission(peerAddr); maybeStartChannelBindExpireThread(); } /** * Determines whether the ChannelBind request is a BAD request or not. * A request is BAD when the same client sends a ChannelBind Request and * channel no or peerAddress coincides with existing channel bindings. * A request is not bad if the channel no and peerAddress in the ChannelBind * request are same as that in current mapping. * * @param channelBind the channelBind request to validate. * @return true if request is a BAD request. */ public boolean isBadChannelRequest(ChannelBind channelBind) { boolean hasChannelNo = this.channelBindings.containsKey(channelBind.getChannelNo()); boolean hasPeerAddr = this.peerToChannelMap.containsKey(channelBind.getPeerAddress()); if(hasChannelNo && hasPeerAddr) { if (this.channelBindings.get( channelBind.getChannelNo()).equals( channelBind.getPeerAddress())) { return false; } } else if(!hasChannelNo && !hasPeerAddr) { return false; } return true; } /** * Removes the channelBind associated with this channlNo from this * allocation. * * @param channelNo the channelNo for which the ChannelBind to delete. * @return the ChannnelBindingf associated with this channelNo. */ public ChannelBind removeChannelBind(char channelNo) { ChannelBind channelBind = null; synchronized (this.channelBindings) { channelBind = this.channelBindings.remove(channelNo); } return channelBind; } /** * Checks if the Permission is installed for the peerAddress. The port value * is ignored. * * @param peerAddress * the peerAddress for which to check permission. * @return true if permission is installed for peerAddress else false. */ public boolean isPermitted(TransportAddress peerAddress) { peerAddress = new TransportAddress(peerAddress.getAddress(), 0, peerAddress.getTransport()); if (this.permissions.containsKey(peerAddress)) { return true; } return false; } /** * Checks if the specified channel no is binded to this allocation. * * @param channelNo * the channel number to check. * @return true if the specified channel no. is installed for this * allocation. */ public boolean containsChannel(char channelNo) { return this.channelBindings.containsKey(channelNo); } /** * Gets the channelNO for the specified peerAddress. * @param peerAddress the peerAddress for which to get the channel. * @return channelNo is channelNo is found, else 0x1000. */ public char getChannel(TransportAddress peerAddress) { char val = 0x1000; if(this.peerToChannelMap.containsKey(peerAddress)) { return this.peerToChannelMap.get(peerAddress); } return val; } /** * Gets the peerAddress associated with specified channelNo. * * @param channelNo * the channel no for which to get the peerAddress. * @return peerAddress the peerAddress associated with the channelNo in this * allocation. */ public TransportAddress getPeerAddr(char channelNo) { ChannelBind cb = this.channelBindings.get(channelNo); if(cb!=null) { return cb.getPeerAddress(); } return null; } /** * Determines if more permissions can be added to this allocation. * * @return true if no of permissions are less than maximum allowed * permissions per Allocation. */ public boolean canHaveMorePermisions() { return (this.permissions.size() < MAX_PERMISSIONS); } /** * Determines if more channels can be added to this allocation. * * @return true if no of channels are less than maximum allowed channels per * Allocation. */ public boolean canHaveMoreChannels() { return (this.channelBindings.size() < MAX_CHANNELBIND); } /** * Initialises and starts {@link #channelBindExpireThread} if necessary. */ public void maybeStartChannelBindExpireThread() { synchronized (channelBindings) { if (!channelBindings.isEmpty() && (channelBindExpireThread == null)) { Thread t = new Thread() { @Override public void run() { runInAllocationChannelBindExpireThread(); } }; t.setDaemon(true); t.setName(getClass().getName() + ".channelBindExpireThread"); boolean started = false; channelBindExpireThread = t; try { t.start(); started = true; } finally { if (!started && (channelBindExpireThread == t)) channelBindExpireThread = null; } } } } /** * Runs in {@link #channelBindExpireThread} and expires the * ChannelBinds of this Allocation and removes them from * {@link #channelBindingings}. */ private void runInAllocationChannelBindExpireThread() { try { long idleStartTime = -1; do { synchronized (channelBindings) { try { channelBindings.wait(ChannelBind.MAX_LIFETIME); } catch (InterruptedException ie) { } /* * Is the current Thread still designated to expire the * ChannelBinds of this Allocation? */ if (Thread.currentThread() != channelBindExpireThread) break; long now = System.currentTimeMillis(); /* * Has the current Thread been idle long enough to merit * disposing of it? */ if (channelBindings.isEmpty()) { if (idleStartTime == -1) idleStartTime = now; else if (now - idleStartTime > 60 * 1000) break; } else { // Expire the ChannelBinds of this Allocation. idleStartTime = -1; for (Iterator i = channelBindings.values().iterator(); i.hasNext();) { ChannelBind channelBind = i.next(); if (channelBind == null) { i.remove(); } else if (channelBind.isExpired(now)) { logger.finer("ChannelBind " + channelBind + " expired"); i.remove(); this.peerToChannelMap.remove( channelBind.getPeerAddress()); channelBind.expire(); } } } } } while (true); } finally { synchronized (channelBindings) { if (channelBindExpireThread == Thread.currentThread()) channelBindExpireThread = null; /* * If channelBindExpireThread dies unexpectedly and yet it is * still necessary, resurrect it. */ if (channelBindExpireThread == null) maybeStartChannelBindExpireThread(); } } } /** * Initialises and starts {@link #permissionExpireThread} if necessary. */ public void maybeStartPermissionExpireThread() { synchronized (permissions) { if (!permissions.isEmpty() && (permissionExpireThread == null)) { Thread t = new Thread() { @Override public void run() { runInAllocationPermissionExpireThread(); } }; t.setDaemon(true); t.setName(getClass().getName() + ".permissionExpireThread"); boolean started = false; permissionExpireThread = t; try { t.start(); started = true; } finally { if (!started && (permissionExpireThread == t)) permissionExpireThread = null; } } } } /** * Runs in {@link #PermissionExpireThread} and expires the * Permissions of this Allocation and removes them from * {@link #permissions}. */ private void runInAllocationPermissionExpireThread() { try { long idleStartTime = -1; do { synchronized (permissions) { try { permissions.wait(Permission.MAX_LIFETIME); } catch (InterruptedException ie) { } /* * Is the current Thread still designated to expire the * Permissions of this Allocation? */ if (Thread.currentThread() != permissionExpireThread) break; long now = System.currentTimeMillis(); /* * Has the current Thread been idle long enough to merit * disposing of it? */ if (permissions.isEmpty()) { if (idleStartTime == -1) idleStartTime = now; else if (now - idleStartTime > 60 * 1000) break; } else { // Expire the Permissions of this Allocation. idleStartTime = -1; for (Iterator i = permissions.values().iterator(); i.hasNext();) { Permission permission = i.next(); if (permission == null) { i.remove(); } else if (permission.isExpired(now)) { logger.finer("Permission " + permission + " expired"); i.remove(); permission.expire(); } } } } } while (true); } finally { synchronized (permissions) { if (permissionExpireThread == Thread.currentThread()) permissionExpireThread = null; /* * If permissionExpireThread dies unexpectedly and yet it is * still necessary, resurrect it. */ if (permissionExpireThread == null) maybeStartPermissionExpireThread(); } } } @Override public int hashCode() { return this.fiveTuple.hashCode(); } /** * Since an Allocation is uniquely identified by its relay address or five * tuple hence we only compare these members. */ @Override public boolean equals(Object o) { if (!(o instanceof Allocation)) { return false; } Allocation allocation = (Allocation) o; if (!this.fiveTuple.equals(allocation.fiveTuple)) { return false; } if (!this.relayAddress.equals(allocation.relayAddress)) { return false; } return true; } @Override public String toString() { return this.getRelayAddress().toString(); } } ================================================ FILE: src/main/java/org/jitsi/turnserver/stack/ChannelBind.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.stack; import java.util.logging.Logger; import org.ice4j.*; /** * This class is an implementation of ChannelBind in TURN protocol. * * @author Aakash Garg * */ public class ChannelBind { /** * Our class logger. */ private static final Logger logger = Logger.getLogger(ChannelBind.class .getName()); /** * The maximum lifetime allowed for a ChannelBind (10 min). */ public static final long MAX_LIFETIME = 10 * 60 * 1000; /** * The IP address and port of the peer for which to create ChannelBind. */ private final TransportAddress peerAddress; /** * Represents the channel no of the ChannelBind. */ private final char channelNo; /** * The time in milliseconds when the ChannelBinding will expire. */ private long expirationTime = -1; /** * Determines whether or not the ChannelBinding has expired. */ private boolean expired = true; /** * Creates a new ChannelBind object with MAX_LIFETIME as default lifetime * value. * * @param peerAddress contains the peer IP address with port no and * transport protocol to be assigned. * @param channelNo the channelNo of ChannelBind. */ public ChannelBind( TransportAddress peerAddress, char channelNo) { this(peerAddress, channelNo, ChannelBind.MAX_LIFETIME); } /** * Creates a new ChannelBind object with given lifetime value. * * @param peerAddress contains the peer IP address and transport protocol to * be assigned. The port value is ignored. * @param channelNo the channelNo of the ChannelBind request. * @param lifetime the lifetime of ChannelBind. */ public ChannelBind( TransportAddress peerAddress, char channelNo, long lifetime) { this.peerAddress = peerAddress; if (channelNo < 0x4000) throw new IllegalArgumentException("Illegal value of channel no"); this.channelNo = channelNo; this.setLifetime(lifetime); } /** * Creates a new ChannelBind object with given lifetime value and UDP as * default protocol. * * @param peerAddress contains the peer IP address and port no in String * format. * @param channelNo the channelNo of the ChannelBind. * @param lifetime the lifetime of ChannelBind. */ public ChannelBind( String peerAddress, char channelNo, long lifetime) { this(new TransportAddress(peerAddress, 0, Transport.UDP), channelNo, lifetime); } /** * @return the peerAddress as a String. */ public String getPeerAddressString() { return this.getPeerAddress().getHostAddress(); } /** * Returns the Peer Address associated with this ChannelBind. */ public TransportAddress getPeerAddress() { return peerAddress; } /** * returns the channelNo associated with this ChannelBind. */ public char getChannelNo() { return channelNo; } /** * Returns the lifetime associated with this ChannelBind. * If the ChannelBind is expired it returns 0. */ public long getLifetime() { if(!isExpired()) { return (this.expirationTime-System.currentTimeMillis()); } else { return 0; } } /** * Sets the time to expire in milliseconds for this ChannelBind. * Max lifetime can be ChannelBind.MAX_LIFEIME. * * @param lifetime the lifetime for this ChannelBind. */ public void setLifetime(long lifetime) { synchronized(this) { this.expirationTime = System.currentTimeMillis() + Math.min(lifetime*1000, ChannelBind.MAX_LIFETIME); } } /** * Refreshes the ChannelBind with the MAX_LIFETIME value. */ public void refresh() { this.setLifetime(ChannelBind.MAX_LIFETIME); } /** * refreshes the ChannelBind with given lifetime value. * @param lifetime the required lifetime of ChannelBind. */ public void refresh(int lifetime) { this.setLifetime(lifetime); } /** * Start the ChannelBind. This launches the countdown to the moment the * ChannelBind would expire. */ public synchronized void start() { synchronized(this) { if (expirationTime == -1) { expired = false; expirationTime = MAX_LIFETIME + System.currentTimeMillis(); } else { throw new IllegalStateException( "ChannelBind has already been started!"); } } } /** * Determines whether this ChannelBind is expired now. * * @return true if this ChannelBind is expired * now; otherwise, false */ public boolean isExpired() { return isExpired(System.currentTimeMillis()); } /** * Expires the ChannelBind. Once this method is called the ChannelBind is * considered terminated. */ public synchronized void expire() { expired = true; /* * Allocation has a background Thread running with the purpose of * removing expired ChannelBinds. */ } /** * Determines whether this ChannelBind will be expired at * a specific point in time. * * @param now the time in milliseconds at which the expired state * of this ChannelBind is to be returned * @return true if this ChannelBind will be * expired at the specified point in time; otherwise, false */ public synchronized boolean isExpired(long now) { if (expirationTime == -1) return false; else if (expirationTime < now) return true; else return expired; } @Override public int hashCode() { return channelNo + peerAddress.hashCode(); } @Override public boolean equals(Object o) { if(!(o instanceof ChannelBind)) { return false; } ChannelBind c = (ChannelBind) o; if(c.getChannelNo() == this.channelNo && c.getPeerAddress().equals(this.peerAddress)) { return true; } return false; } @Override public String toString() { return "ChannelBind [" + (peerAddress != null ? "peerAddress=" + peerAddress + ", " : "") + "channelNo=" + (int)channelNo + "]"; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/stack/FiveTuple.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.stack; import java.util.logging.*; import org.ice4j.*; /** * The class would represent the FiveTuple object of TURN protocol. The hashCode * function should be unique if possible since it will be used to uniquely find * the allocation object. * * @author Aakash Garg */ public class FiveTuple { /** * The Logger used by the FiveTuple class and its * instances for logging output. */ private static final Logger logger = Logger.getLogger(FiveTuple.class .getName()); /** * Represents the Client's Transport Address. */ protected TransportAddress clientTransportAddress; /** * Represents the Server's Transport Address. */ protected TransportAddress serverTransportAddress; /** * Represents the Transport Protocol. */ protected Transport transport = Transport.UDP; /** * Creates a new Five tuple Object with given arguments. * * @param clientTransportAddress The client's Address to be set * @param serverTransportAddress The server's Address to be set * @param transport The transport protocol of the client server connection. */ public FiveTuple(TransportAddress clientAddress, TransportAddress serverAddress, Transport transport) { this.clientTransportAddress = clientAddress; this.serverTransportAddress = serverAddress; this.transport = transport; } /** * @return the clientTransportAddress or null if the the Client's Address * has not been set. */ public TransportAddress getClientTransportAddress() { return clientTransportAddress; } /** * @return client's port number as +ve int value or -1 if the the Client's * Address has not been set. */ public int getClientPortNo() { if (this.clientTransportAddress != null) { return this.clientTransportAddress.getPort(); } return -1; } /** * @return client Host Address in String or null if the the Client's Address * has not been set. */ public String getClientHostAddress() { if (this.clientTransportAddress != null) { return this.clientTransportAddress.getHostAddress(); } else { return null; } } /** * @param clientTransportAddress The client's IP Address to be set. */ public void setClientTransportAddress(TransportAddress clientAddress) { this.clientTransportAddress = clientAddress; } /** * @return the serverTransportAddress or null if the the Server's Address * has not been set. */ public TransportAddress getServerTransportAddress() { return serverTransportAddress; } /** * @return server's port number as +ve int value or -1 if the the Server's * Address has not been set. */ public int getServerPortNo() { if (this.serverTransportAddress != null) { return this.serverTransportAddress.getPort(); } else { return -1; } } /** * @return server Host Address in String or null if the the Server's Address * has not been set. */ public String getServerHostAddress() { if (this.serverTransportAddress != null) { return this.serverTransportAddress.getHostAddress(); } else { return null; } } /** * @param serverTransportAddress The Server's Address to be set. */ public void setServerTransportAddress(TransportAddress serverAddress) { this.serverTransportAddress = serverAddress; } /** * @return the transport protocol used for client server connection. */ public Transport getTransport() { return transport; } /** * @param transport the transport to set. */ public void setTransport(Transport transport) { this.transport = transport; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((clientTransportAddress == null) ? 0 : clientTransportAddress.hashCode()); result = prime * result + ((serverTransportAddress == null) ? 0 : serverTransportAddress.hashCode()); result = prime * result + ((transport == null) ? 0 : transport.hashCode()); return result; } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof FiveTuple)) { return false; } FiveTuple other = (FiveTuple) obj; if (clientTransportAddress == null) { if (other.clientTransportAddress != null) { return false; } } else if (!clientTransportAddress.equals(other.clientTransportAddress)) { return false; } if (serverTransportAddress == null) { if (other.serverTransportAddress != null) { return false; } } else if (!serverTransportAddress.equals(other.serverTransportAddress)) { return false; } if (transport != other.transport) { return false; } return true; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "FiveTuple [" + (getClientTransportAddress() != null ? "getClientAddress()=" + getClientTransportAddress() + ", " : "") + (getServerTransportAddress() != null ? "getServerAddress()=" + getServerTransportAddress() + ", " : "") + (getTransport() != null ? "getTransport()=" + getTransport() + ", " : "") + "hashCode()=" + hashCode() + "]"; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/stack/Permission.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.stack; import org.ice4j.*; /** * This class is an implementation of Permissions in TURN protocol. * * @author Aakash Garg * */ public class Permission { /** * The maximum lifetime allowed for a Permission. */ public static final long MAX_LIFETIME = 300 * 1000; /** * The IP address of the peer for which to create Permission. */ private TransportAddress ipAddress; /** * The time in milliseconds when the Permission will expire. */ private long expirationTime = -1; /** * Determines whether or not the Permission has expired. */ private boolean expired = false; /** * @param ipAddress contains the peer IP address and transport protocol to * be assigned. The port value is ignored. */ public Permission(TransportAddress ipAddress) { this.setIpAddress(ipAddress); this.setLifetime(Permission.MAX_LIFETIME); } /** * @param ipAddress contains the peer IP address and transport protocol to * be assigned. The port value is ignored. * @param lifetime the lifetime of permission. */ public Permission(TransportAddress ipAddress, long lifetime) { this.setIpAddress(ipAddress); this.setLifetime(lifetime); } /** * @param ipAddress contains the peer IP address in String format. * @param lifetime the lifetime of permission. */ public Permission(String ipAddress, long lifetime) { this.setIpAddress(ipAddress); this.setLifetime(lifetime); } /** * @return the ipAddress of the Permission as a TransportAddress. */ public TransportAddress getIpAddress() { return ipAddress; } /** * @return the ipAddress as a String. */ public String getIpAddressString() { return this.getIpAddress().getHostAddress(); } /** * @param ipAddress the ipAddress of the peer for which to create * Permission. */ public void setIpAddress(TransportAddress ipAddress) { this.ipAddress = new TransportAddress(ipAddress.getHostAddress(), 0, ipAddress.getTransport()); } /** * @param ipAddress the ipAddress as String of the peer for which to create * Permission. */ public void setIpAddress(String ipAddress) { this.ipAddress = new TransportAddress(ipAddress, 0, Transport.UDP); } /** * Returns the lifetime associated with this Permission. * If the Permission is expired it returns 0. */ public long getLifetime() { if(!isExpired()) { return (this.expirationTime-System.currentTimeMillis()); } else { return 0; } } /** * Sets the time to expire in milli-seconds for this Permission. * Max lifetime can be Permission.MAX_LIFEIME. * * @param lifetime the lifetime for this Permission. */ public void setLifetime(long lifetime) { synchronized(this) { this.expirationTime = System.currentTimeMillis() + Math.min(lifetime*1000, Permission.MAX_LIFETIME); } } /** * Refreshes the permission with the MAX_LIFETIME value. */ public void refresh() { this.setLifetime(Permission.MAX_LIFETIME); } /** * refreshes the permission with given lifetime value. * @param lifetime the required lifetime of permission. */ public void refresh(int lifetime) { this.setLifetime(lifetime); } /** * Start the Permission. This launches the countdown to the moment the * Permission would expire. */ public synchronized void start() { synchronized(this) { if (expirationTime == -1) { expired = false; expirationTime = MAX_LIFETIME + System.currentTimeMillis(); } else { throw new IllegalStateException( "Permission has already been started!"); } } } /** * Determines whether this Permission is expired now. * * @return true if this Permission is expired * now; otherwise, false */ public boolean isExpired() { return isExpired(System.currentTimeMillis()); } /** * Expires the Permission. Once this method is called the Permission is * considered terminated. */ public synchronized void expire() { expired = true; /* * TurnStack has a background Thread running with the purpose of * removing expired Permissions. */ } /** * Determines whether this Permission will be expired at * a specific point in time. * * @param now the time in milliseconds at which the expired state * of this Permission is to be returned * @return true if this Permission will be * expired at the specified point in time; otherwise, false */ public synchronized boolean isExpired(long now) { if (expirationTime == -1) return false; else if (expirationTime < now) return true; else return expired; } /* * The permission is uniquely identified by its IP address, so hashCode is * calculated on the IP address only. */ @Override public int hashCode() { return ipAddress.getHostAddress().hashCode(); } /* * Two Permissions are equal if their associated IP address, lifetime and * transport protocol are same. */ @Override public boolean equals(Object obj) { if (!(obj instanceof Permission)) { return false; } Permission other = (Permission) obj; if (ipAddress == null) { if (other.ipAddress != null) { return false; } } else if (ipAddress.getHostAddress().compareTo( other.ipAddress.getHostAddress()) != 0) { return false; } if (expirationTime != other.expirationTime) { return false; } return true; } @Override public String toString() { return "Permission [" + (ipAddress != null ? "ipAddress=" + ipAddress : "") + "]"; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/stack/ServerChannelDataEventHandler.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.stack; import java.util.Arrays; import java.util.logging.* ; import org.ice4j.*; import org.ice4j.message.*; import org.ice4j.stack.*; /** * Class to handle incoming ChannelData messages coming from Client to Server. * It first finds if there is a ChannelBind installed for the peer. * If yes it then sends the UDP message to peer. * If no it then silently ignores the message. * * @author Aakash Garg */ public class ServerChannelDataEventHandler implements ChannelDataEventHandler { /** * The Logger used by the * ServerChannelDataEventHandler class and its instances for * logging output. */ private static final Logger logger = Logger .getLogger(ServerChannelDataEventHandler.class.getName()); /** * The turnStack to call. */ private TurnStack turnStack; /** * Default Constructor. */ public ServerChannelDataEventHandler() { } /** * Parametrized constructor. * @param turnStack the turnStack to set for this class. */ public ServerChannelDataEventHandler(StunStack turnStack) { if (turnStack instanceof TurnStack) { this.turnStack = (TurnStack) turnStack; } else { throw new IllegalArgumentException("This is not a TurnStack!"); } } /** * Sets the TurnStack for this class. * @param turnStack the turnStack to set for this class. */ public void setTurnStack(TurnStack turnStack) { this.turnStack = turnStack; } /** * Handles the ChannelDataMessageEvent. * @param evt the ChannelDataMessageEvent to handle/process. */ @Override public void handleMessageEvent(ChannelDataMessageEvent evt) { if(!logger.isLoggable(Level.FINER)){ logger.setLevel(Level.FINER); } ChannelData channelData = evt.getChannelDataMessage(); char channelNo = channelData.getChannelNumber(); byte[] data = channelData.getData(); logger.finer("Received a ChannelData message for " + (int)channelNo + " , message : " + Arrays.toString(data)); TransportAddress clientAddress = evt.getRemoteAddress(); TransportAddress serverAddress = evt.getLocalAddress(); Transport transport = Transport.UDP; FiveTuple fiveTuple = new FiveTuple(clientAddress, serverAddress, transport); Allocation allocation = this.turnStack.getServerAllocation(fiveTuple); if(allocation==null) { logger.finer("allocation not found."); } else if(!allocation.containsChannel(channelNo)) { logger.finer("ChannelNo " + (int) channelNo + " not found in Allocation!"); return; } TransportAddress destAddr = allocation.getPeerAddr(channelNo); if(destAddr != null) { RawMessage message = RawMessage.build(data, data.length, destAddr, allocation.getClientAddress()); try { logger.finer("Dispatching a UDP message to " + destAddr + ", data: " + Arrays.toString(message.getBytes())); this.turnStack.sendUdpMessage(message, destAddr, allocation.getRelayAddress()); } catch (StunException e) { logger.finer(e.getMessage()); } } else { logger.finer("Peer address not found for channel " + (int) channelNo); } } } ================================================ FILE: src/main/java/org/jitsi/turnserver/stack/ServerPeerUdpEventHandler.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.stack; import java.util.*; import java.util.logging.*; import org.ice4j.*; import org.ice4j.message.*; import org.ice4j.stack.*; /** * Class to handle UDP messages coming from Peer. The class first checks if * there is a non-expired ChannelBind for the peer if yes it then sends a * ChannelData message to Client. If no it then finds if there is a non-expired * permission if yes then it sends a DataIndicatio to Client. All the mesages * sent to client here are from the address on which the allocation request was * received or the serverAddress of fiveTuple of corresponding Allocation. * * @author Aakash Garg */ public class ServerPeerUdpEventHandler implements PeerUdpMessageEventHandler { /** * The Logger used by the PeerUdpMessageEventHandler class * and its instances for logging output. */ private static final Logger logger = Logger .getLogger(ServerPeerUdpEventHandler.class.getName()); /** * The turnStack to call. */ private TurnStack turnStack; /** * Default constructor. */ public ServerPeerUdpEventHandler() { } /** * Parametrized constructor. * * @param turnStack the turnStack to set for this class. */ public ServerPeerUdpEventHandler(StunStack turnStack) { if (turnStack instanceof TurnStack) { this.turnStack = (TurnStack) turnStack; } else { throw new IllegalArgumentException("This is not a TurnStack!"); } } public void setTurnStack(TurnStack turnStack) { this.turnStack = turnStack; } /** * Handles the PeerUdpMessageEvent. * * @param evt the PeerUdpMessageEvent to handle/process. */ @Override public void handleMessageEvent(PeerUdpMessageEvent evt) { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); logger.finer("Received Peer UdP message message " + evt); } byte[] data = evt.getBytes(); TransportAddress localAddress = evt.getLocalAddress(); TransportAddress remoteAddress = evt.getRemoteAddress(); logger.finest("Received a UDP message on: " + localAddress + ", data: " + byteArrayToHex(data)); Allocation allocation = this.turnStack.getServerAllocation(localAddress); if (remoteAddress.getTransport() == Transport.TCP) { FiveTuple fiveTuple = new FiveTuple(remoteAddress, localAddress, Transport.TCP); logger.finest("Send message request from "+fiveTuple); if (allocation == null) // came from client { // get client allocation logger.finest("Message came from TCP Client"); int connectionId = this.turnStack.getConnectionIdForDataConn(fiveTuple); logger.finest("Connection Id extracted for "+fiveTuple+" is "+connectionId); allocation = this.turnStack.getAllocationFromConnectionId(connectionId); logger.finest("Allocation extracted is "+allocation+" for client-"+fiveTuple); FiveTuple peerTuple = allocation.getPeerTCPConnection(connectionId); TransportAddress peerAddress = peerTuple.getClientTransportAddress(); TransportAddress relayAddress = peerTuple.getServerTransportAddress(); RawMessage rawMessage = RawMessage.build(data, data.length, peerAddress, relayAddress); try { logger.finest("Relaying data to peer-" + peerAddress + " from " + remoteAddress+" data-"); this.turnStack.sendUdpMessage( rawMessage, peerAddress, relayAddress); } catch (StunException e) { System.err.println("Unable to relay message to peer-" + peerAddress + " from client-" + remoteAddress + " message-" + Arrays.toString(data)); } } else { // else came from peer logger.finest("Message came from TCP peer."); int connectionId = this.turnStack.getConnectionIdForPeer(fiveTuple); if (!allocation.isPermitted(remoteAddress)) { logger.finest("No permission installed for peer-"+remoteAddress); return; } else { TransportAddress dataConn = allocation.getDataConnection( connectionId).getClientTransportAddress(); if (dataConn != null) { RawMessage rawMessage = RawMessage.build(data, data.length, dataConn, allocation.getServerAddress()); try { logger.finest("Relaying data to client-" + dataConn + " from peer-" + remoteAddress); this.turnStack.sendUdpMessage( rawMessage, dataConn, allocation.getServerAddress()); } catch (StunException e) { System.err .println("Unable to relay message to client-" + dataConn + " from peer-" + remoteAddress + " message-" + Arrays.toString(data)); } }else{ logger.finest("No data connection found for peer-" + remoteAddress); } } } } else if (allocation != null && allocation.getChannel(remoteAddress) != 0x1000) { char channelNo = allocation.getChannel(remoteAddress); ChannelData channelData = new ChannelData(); channelData.setChannelNumber(channelNo); channelData.setData(data); try { logger.finest("Sending a ChannelData message " + channelData + " from " + allocation.getServerAddress() + " to " + allocation.getClientAddress()); this.turnStack.sendChannelData( channelData, allocation.getClientAddress(), allocation.getServerAddress()); } catch (StunException ex) { logger.finer(ex.getMessage()); } } else if (allocation != null && allocation.isPermitted(remoteAddress)) { TransactionID tranID = TransactionID.createNewTransactionID(); Indication dataInd = MessageFactory.createDataIndication( remoteAddress, data, tranID.getBytes()); try { logger.finest("Sending a ChannelData message " + dataInd + " from " + allocation.getServerAddress() + " to " + allocation.getClientAddress()); this.turnStack.sendIndication( dataInd, allocation.getClientAddress(), allocation.getServerAddress()); } catch (StunException e) { logger.finer(e.getMessage()); } }else{ logger .finest("unable to find allocation and the message is not on TCP."); } } private String byteArrayToHex(byte[] data){ String arrayToHex= ""; for(int i=0; iLogger used by the turnStack class and its * instances for logging output. */ private static final Logger logger = Logger.getLogger(TurnStack.class.getName()); /** * The maximum no of Allocations per TurnStack. */ public static final int MAX_ALLOCATIONS = 500; /** * To track the portNo used. */ // private static int nextPortNo = 49152; private static int nextPortNo = 15000; /** * Represents the Allocations stored for Server Side. */ private final HashMap serverAllocations = new HashMap(); /** * Contains the mapping of relayAddress to Allocation. */ private final HashMap serverRelayAllocationMap = new HashMap(); /** * RelayAddress reserved by server. */ private final HashSet reservedAddress = new HashSet(); /** * Represents the Allocations stored for Client Side. */ private final HashMap clientAllocations = new HashMap(); /** * Maps one-to-one from Data Connection to Connection Id. */ private final HashMap dataConnToConnIdMap = new HashMap(); /** * Maps one-to-one from Peer TCP Connection to Connection Id. */ private final HashMap peerConnToConnIdMap = new HashMap(); /** * Maps many-to-one from Connection Id to Allocation for where * ConnectionBind Request has been received for Connection ID . */ private final HashMap connIdToAllocMap = new HashMap(); /** * Contains unAcknowledged Connection Id. Every element will expire after * min of 30 sec. */ private final HashSet unAcknowledgedConnId = new HashSet(); /** * The Thread which expires the TurnServerAllocations of * this TurnStack and removes them from {@link #serverAllocations} * . */ private Thread serverAllocationExpireThread; /** * Indicates that if the don't fragment is support or not. */ private static final boolean dontFragmentSupported = false; /** * Component variable. */ private Component component; /** * Boolean to allow or disallow TCP messages. Default is allowed. */ private boolean tcpAllowed = true; /** * Boolean to allow or disallow UDP messages. Default is allowed. */ private boolean udpAllowed = true; /** * Default Constructor. Initializes the NetAccessManager and */ public TurnStack() { super(); initCredentials(); } /** * Parameterized constructor for TurnStack. * * @param peerUdpMessageEventHandler * the PeerUdpMessageEventHandler for this turnStack. * @param channelDataEventHandler * the ChannelDataEventHandler for this turnStack. */ public TurnStack(PeerUdpMessageEventHandler peerUdpMessageEventHandler, ChannelDataEventHandler channelDataEventHandler) { super(peerUdpMessageEventHandler,channelDataEventHandler); initCredentials(); } /** * Called to notify this provider for an incoming message. method overridden * to modify the logic of the Turn Stack. * * @param ev the event object that contains the new message. */ @Override public void handleMessageEvent(StunMessageEvent ev) { Message msg = ev.getMessage(); logger.finest("Received an Event."+ev.getTransactionID()); if (!TurnStack.isTurnMessage(msg)) { logger.finest("Ignored a non-TURN message!"); return; } else { removeUsernameIntegrityFromBinding(ev.getMessage()); super.handleMessageEvent(ev); return; } /* logger.setLevel(Level.FINEST); if (logger.isLoggable(Level.FINEST)) { logger.finest("Received a message on " + ev.getLocalAddress() + " of type:" + (int) msg.getMessageType()); } // request if (msg instanceof Request) { TransactionID serverTid = ev.getTransactionID(); logger.finer("parsing request : "+serverTid); TurnServerTransaction sTran = (TurnServerTransaction) getServerTransaction(serverTid); if (sTran != null) { // requests from this transaction have already been seen // retransmit the response if there was any logger.finest("found an existing transaction"); try { sTran.retransmitResponse(); logger.finest("Response retransmitted"); } catch (Exception ex) { // we couldn't really do anything here .. apart from logging logger.log( Level.WARNING, "Failed to retransmit a Turn response", ex); } if (!Boolean .getBoolean(StackProperties.PROPAGATE_RECEIVED_RETRANSMISSIONS)) { return; } } else { logger.finest("existing transaction not found"); sTran = new TurnServerTransaction(this, serverTid, ev.getLocalAddress(), ev.getRemoteAddress()); // if there is an OOM error here, it will lead to // NetAccessManager.handleFatalError that will stop the // MessageProcessor thread and restart it that will lead again // to an OOM error and so on... So stop here right now try { sTran.start(); } catch (OutOfMemoryError t) { logger.info("Turn transaction thread start failed:" + t); return; } startNewServerTransactionThread( serverTid, sTran); } // validate attributes that need validation. try { // validateRequestAttributes(ev); } catch (Exception exc) { // validation failed. log get lost. logger.log( Level.FINE, "Failed to validate msg: " + ev, exc); return; } try { fireMessageEventFormEventDispatcher(ev); } catch (Throwable t) { Response error; logger.log( Level.INFO, "Received an invalid request.", t); Throwable cause = t.getCause(); if (((t instanceof StunException) && ((StunException) t) .getID() == StunException.TRANSACTION_ALREADY_ANSWERED) || ((cause instanceof StunException) && ((StunException) cause) .getID() == StunException.TRANSACTION_ALREADY_ANSWERED)) { // do not try to send an error response since we will // get another TRANSACTION_ALREADY_ANSWERED return; } if (t instanceof IllegalArgumentException) { error = MessageFactory.createBindingErrorResponse( ErrorCodeAttribute.BAD_REQUEST, t.getMessage()); } else { error = MessageFactory.createBindingErrorResponse( ErrorCodeAttribute.SERVER_ERROR, "Oops! Something went wrong on our side :("); } try { sendResponse( serverTid.getBytes(), error, ev.getLocalAddress(), ev.getRemoteAddress()); } catch (Exception exc) { logger.log( Level.FINE, "Couldn't send a server error response", exc); } } } // response else if (msg instanceof Response) { logger.finer("Parsing response"); TransactionID tid = ev.getTransactionID(); StunClientTransaction tran = removeTransactionFromClientTransactions(tid); if (tran != null) { tran.handleResponse(ev); } else { // do nothing - just drop the phantom response. logger .fine("Dropped response - no matching client tran found for" + " tid " + tid + "\n"); } } // indication else if (msg instanceof Indication) { logger.finer("Dispatching a Indication."); fireMessageEventFormEventDispatcher(ev); } */ } /** * Method to know if the Don't fragment is supported. * * @return true if supported else false. */ public static boolean isDontfragmentsupported() { return dontFragmentSupported; } /** * Returns the Allocation with the specified fiveTuple or * null if no such Allocation exists. * * @param fiveTuple the fiveTuple of the Allocation we are looking for. * * @return the {@link Allocation} we are looking for. */ public Allocation getServerAllocation(FiveTuple fiveTuple) { Allocation allocation = null; synchronized (this.serverAllocations) { allocation = this.serverAllocations.get(fiveTuple); } /* * If a Allocation is expired, do not return it. It will be * removed from serverAllocations soon. */ if ((allocation != null) && allocation.isExpired()) allocation = null; return allocation; } /** * Returns the Allocation with the specified fiveTuple or * null if no such Allocation exists. * * @param fiveTuple the fiveTuple of the Allocation we are looking for. * * @return the {@link Allocation} we are looking for. */ public Allocation getClientAllocation(FiveTuple fiveTuple) { Allocation allocation; synchronized (clientAllocations) { allocation = clientAllocations.get(fiveTuple); } /* * If a Allocation is expired, do not return it. It will be * removed from serverAllocations soon. */ if ((allocation != null) && allocation.isExpired()) allocation = null; return allocation; } /** * Determines if more allocations can be added to this TurnStack. * * @return true if no of allocations are less than maximum allowed * allocations per TurnStack. */ public boolean canHaveMoreAllocations() { return (this.serverAllocations.size() < MAX_ALLOCATIONS); } /** * Adds a new server allocation to this TurnStack. * * @param allocation the allocation to be added to this TurnStack. */ public synchronized void addNewServerAllocation(Allocation allocation) { synchronized(this.serverAllocations) { this.serverAllocations.put(allocation.getFiveTuple(), allocation); IceSocketWrapper sock; if(true) { // check if meanwhile other thread has put the same allocation. try { logger.finer("Adding a new Socket for : " + allocation.getRelayAddress()); if(allocation.getRelayAddress().getTransport()==Transport.UDP) { sock = new IceUdpSocketWrapper( new SafeCloseDatagramSocket( allocation.getRelayAddress())); } else { IceTcpEventizedServerSockerWrapper mySock2 = new IceTcpEventizedServerSockerWrapper( new ServerSocket(allocation.getRelayAddress() .getPort()), this.getComponent()); PeerTcpConnectEventListner listener = new PeerTcpConnectEventListner(this); mySock2.setEventListener(listener); sock = mySock2; /* sock = new IceTcpServerSocketWrapper(new ServerSocket(allocation .getRelayAddress().getPort()),this.getComponent()); */ } this.addSocket(sock); logger.finer("Added a new Socket for : " + allocation.getRelayAddress()); try { allocation.start(); } catch(Exception e) { } } catch (SocketException e) { logger.finer("Error obtained : "+e.getMessage()); logger.log(Level.FINEST, "Error! Cannot add new socket from TurnStack at " +"addNewServerAllocation "); logger.log(Level.FINEST, e.getMessage()); // allocation.expire(); } catch (UnknownHostException e) { System.err.println("Unable to add TCP relay Address for : " + allocation.getRelayAddress()); } catch (IOException e) { e.printStackTrace(); } } this.serverRelayAllocationMap.put( allocation.getRelayAddress(), allocation); maybeStartServerAllocationExpireThread(); } } /** * Gets the allocation corresponding to the relay address. * @param relayAddress the relayAddress for which to find allocation. * @return the Allocation corresponding to relayAddress. */ public Allocation getServerAllocation(TransportAddress relayAddress) { return this.serverRelayAllocationMap.get(relayAddress); } /** * Function to check if given IP is allowed for peer address.s * @param peerAddr * @return */ public static boolean isIPAllowed(TransportAddress peerAddr) { String ip = peerAddr.getHostAddress(); int portNo = peerAddr.getPort(); //TODO : logic for validating the invalid IP address. return true; } /** * Reserves a port for future use for Reservation-token. * * @param reserAddr the address to be reserved. * @return false if it is already reserved, else true. */ public boolean reservePort(TransportAddress reserAddr) { if(this.reservedAddress.contains(reserAddr)) { return false; } else { this.reservedAddress.add(reserAddr); return true; } } /** * Function to get new Relay address. * TODO : It has to be replaced with jitsi api. * * @param evenCompulsary * @return a new RelayAddress */ public TransportAddress getNewRelayAddress(boolean evenCompulsary, Transport transport) { InetAddress ipAddress = null; try { ipAddress = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } TransportAddress possibleAddr = new TransportAddress(ipAddress, nextPortNo++, transport); int diff = evenCompulsary ? 2 : 1; nextPortNo += (evenCompulsary && (nextPortNo%2)==0) ? 0 : 1; while(this.reservedAddress.contains(possibleAddr) && nextPortNo < 65535) { nextPortNo += diff; possibleAddr = new TransportAddress(ipAddress, nextPortNo++, Transport.UDP); } return possibleAddr; } /** * Adds a new ConnectionId for the specified peerAddress and for the * specified allocation. * * @param connectionId the connectionId created. * @param peerAddress the peerAddress who initiated the TCP connection on * the relay address of the Allocation. * @param allocation the allocation corresponding to the relay address on * which the connect request is received. */ public void addUnAcknowlededConnectionId(int connectionId, TransportAddress peerAddress, Allocation allocation) { FiveTuple peerTuple = new FiveTuple(peerAddress,allocation.getRelayAddress(), Transport.TCP); this.unAcknowledgedConnId.add(connectionId); this.peerConnToConnIdMap.put( peerTuple, connectionId); this.connIdToAllocMap.put( connectionId, allocation); allocation.addPeerTCPConnection( connectionId, peerTuple); logger.finest("Adding connectionId-" + connectionId + " for peerTuple-" + peerTuple + " at allocation-" + allocation); } /** * Acknowledges the ConnectionID associated with the specified client data * connection. * * @param connectionId the connectionId associated with the data connection. * @param clientDataConnectionTuple the fiveTuple of the data connection. */ public void acknowledgeConnectionId(int connectionId, FiveTuple clientDataConnectionTuple) { if (!this.unAcknowledgedConnId.contains(connectionId)) { throw new IllegalArgumentException("No such connectionId:" + connectionId + " exists"); } else { this.unAcknowledgedConnId.remove(connectionId); this.dataConnToConnIdMap.put( clientDataConnectionTuple, connectionId); Allocation allocation = this.connIdToAllocMap.get(connectionId); allocation.addDataConnection( connectionId, clientDataConnectionTuple); logger.finest("Acknowledging connectiodId-" + connectionId + " for client data conn-" + clientDataConnectionTuple); } } /** * Determines if the given ConnectionId is acknowledged or not. * @param connectionID the connectionId to check. * @return true if the specified connectionID is acknowledged, else false. */ public boolean isUnacknowledged(int connectionID){ return this.unAcknowledgedConnId.contains(connectionID); } /** * Returns the Connection associated with the specified peerFiveTuple. * * @param peerFiveTuple the peerFiveTuple for which to get the ConnectionID. * @return connectionID associated with the specified peerFiveTuple. */ public int getConnectionIdForPeer(FiveTuple peerFiveTuple) { return this.peerConnToConnIdMap.get(peerFiveTuple); } /** * Returns the ConnectionID associated with the specified * * @param dataConnTuple the five tuple of the data connection. * @return the connectionId associated with the given data connection if * exists. */ public int getConnectionIdForDataConn(FiveTuple dataConnTuple) { return this.dataConnToConnIdMap.get(dataConnTuple); } /** * Initialises and starts {@link #serverAllocationExpireThread} if * necessary. */ public void maybeStartServerAllocationExpireThread() { synchronized (serverAllocations) { if (!serverAllocations.isEmpty() && (serverAllocationExpireThread == null)) { Thread t = new Thread() { @Override public void run() { runInServerAllocationExpireThread(); } }; t.setDaemon(true); t.setName(getClass().getName() + ".serverAllocationExpireThread"); boolean started = false; serverAllocationExpireThread = t; try { t.start(); started = true; } finally { if (!started && (serverAllocationExpireThread == t)) serverAllocationExpireThread = null; } } } } /** * Runs in {@link #serverAllocationExpireThread} and expires the * Allocations of this TurnStack and removes * them from {@link #serverAllocations}. */ private void runInServerAllocationExpireThread() { try { long idleStartTime = -1; do { synchronized (serverAllocations) { try { serverAllocations.wait(Allocation.DEFAULT_LIFETIME); } catch (InterruptedException ie) { } /* * Is the current Thread still designated to expire the * Allocations of this TurnStack? */ if (Thread.currentThread() != serverAllocationExpireThread) break; long now = System.currentTimeMillis(); /* * Has the current Thread been idle long enough to merit * disposing of it? */ if (serverAllocations.isEmpty()) { if (idleStartTime == -1) idleStartTime = now; else if (now - idleStartTime > 60 * 1000) break; } else { // Expire the Allocations of this TurnStack. idleStartTime = -1; for (Iterator i = serverAllocations.values().iterator(); i.hasNext();) { Allocation allocation = i.next(); if (allocation == null) { i.remove(); } else if (allocation.isExpired(now)) { logger.finer("allocation "+allocation+" expired"); i.remove(); allocation.expire(); } } } } } while (true); } finally { synchronized (serverAllocations) { if (serverAllocationExpireThread == Thread.currentThread()) serverAllocationExpireThread = null; /* * If serverAllocationExpireThread dies unexpectedly and yet it * is still necessary, resurrect it. */ if (serverAllocationExpireThread == null) maybeStartServerAllocationExpireThread(); } } } /** * Method to check if the given message method is of Turn method. * * @param message * @return true if message is of Turn method else false. */ public static boolean isTurnMessage(Message message) { char method = message.getMessageType(); method = (char) (method & 0xfeef); // ignore the class logger.finest("method extracted from " + (int) message.getMessageType() + " is : " + (int) method); boolean isTurnMessage = false; switch (method) { // Turn Specific Methods case Message.TURN_METHOD_ALLOCATE: case Message.TURN_METHOD_CHANNELBIND: case Message.TURN_METHOD_CREATEPERMISSION: case Message.TURN_METHOD_DATA: case Message.TURN_METHOD_REFRESH: case Message.TURN_METHOD_SEND: // Turn TCP support Methods case Message.TURN_METHOD_CONNECT: case Message.TURN_METHOD_CONNECTION_BIND: case Message.TURN_METHOD_CONNECTION_ATTEMPT: case Message.STUN_METHOD_BINDING: isTurnMessage = true; break; default: isTurnMessage = false; } return isTurnMessage; } /** * Removes the username and Message Integrity attribute form Binding * messages only. * * @param msg the Binding message from which the attribute is to be removed. */ private void removeUsernameIntegrityFromBinding(Message msg) { if((msg.getMessageType() & 0xfeef) != Message.STUN_METHOD_BINDING) { return; } if(msg.containsAttribute(Attribute.USERNAME)) { msg.removeAttribute(Attribute.USERNAME); } if(msg.containsAttribute(Attribute.MESSAGE_INTEGRITY)) { msg.removeAttribute(Attribute.MESSAGE_INTEGRITY); } } /** * Initializes the turnstack with the registered users with username and their * corresponding key. */ public void initCredentials() { String fileName = TurnStackProperties.DEFAULT_ACCOUNTS_FILE; FileReader fr; try { fr = new FileReader(fileName); BufferedReader br = new BufferedReader(fr); CredentialsManager cm = this.getCredentialsManager(); String line = null; while((line = br.readLine())!=null) { String[] tok = line.split(":"); LongTermCredential ltc = new LongTermCredential( tok[0].getBytes("UTF-8"), tok[1].getBytes("UTF-8")); System.out.println("Adding - " + new String(ltc.getUsername()) + ":" + new String(ltc.getPassword())); // TODO replace with REALM instead of DEFAULT_REALM. LongTermCredentialSession ltcs = new LongTermCredentialSession( ltc, TurnStackProperties.DEFAULT_REALM.getBytes("UTF-8")); cm.registerAuthority(ltcs); } fr.close(); br.close(); } catch (FileNotFoundException fnfe) { logger.finest("File not found."); }catch(IOException ioe){ logger.finest("Unable to read file."); } } /** * Gets the component as RTP with TCP as transport also agent's stunStack as * this TurnStack. * * @return component. */ public Component getComponent() { if (this.component == null) { Agent agent = new Agent(); agent.setStunStack(this); IceMediaStream stream = IceMediaStream.build(agent, "Turn Server"); this.component = Component.build(Component.RTP, stream); } return this.component; } /** * Determines if the UDP messages are allowed in TURN server. * * @return true if UDP is allowed else false. */ public boolean isUDPAllowed() { return this.udpAllowed; } /** * Sets the udpAllowed variable to enable disable UDP messages. * * @param udpAllowed the boolean value to allow or disallow UDP messages. */ public void setUDPAllowed(boolean udpAllowed) { this.udpAllowed = udpAllowed; } /** * Determines if the TCP messages are allowed in TURN server. * * @return true if TCP is allowed else false. */ public boolean isTCPAllowed() { return this.tcpAllowed; } /** * Sets the tcpAllowed variable to enable disable TCP messages. * * @param tcpAllowed the boolean value to allow or disallow TCP messages. */ public void setTCPAllowed(boolean tcpAllowed) { this.tcpAllowed = tcpAllowed; } /** * Gets the allocation for the specified connectionID no. * * @param connectionId the connectionID for which to get the allocation. * @return Allocation corresponding to specified connectionID or nul if not * found. */ public Allocation getAllocationFromConnectionId(int connectionId) { return this.connIdToAllocMap.get(connectionId); } } ================================================ FILE: src/main/java/org/jitsi/turnserver/turnClient/ClientChannelDataEventHandler.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.turnClient; import java.io.*; import java.util.logging.* ; import org.ice4j.*; import org.ice4j.message.*; import org.ice4j.stack.*; import org.jitsi.turnserver.listeners.*; import org.jitsi.turnserver.stack.*; /** * Handles the incoming ChannelData message for Client from Server. * * @author Aakash Garg * */ public class ClientChannelDataEventHandler implements ChannelDataEventHandler { /** * The Logger used by the * ServerChannelDataEventHandler class and its instances for * logging output. */ private static final Logger logger = Logger .getLogger(ClientChannelDataEventHandler.class.getName()); /** * The turnStack to call. */ private TurnStack turnStack; /** * Default constructor. */ public ClientChannelDataEventHandler() { } /** * parametrised contructor. * * @param turnStack * the turnStack for this class. */ public ClientChannelDataEventHandler(StunStack turnStack) { if (turnStack instanceof TurnStack) { this.turnStack = (TurnStack) turnStack; } else { throw new IllegalArgumentException("This is not a TurnStack!"); } } /** * Sets the turnStack for this class. * * @param turnStack * the turnStack to set for this class. */ public void setTurnStack(TurnStack turnStack) { this.turnStack = turnStack; } /** * Handles the ChannelDataMessageEvent. * * @param evt * the ChannelDataMessageEvent to handle/process. */ @Override public void handleMessageEvent(ChannelDataMessageEvent evt) { if (logger.isLoggable(Level.FINER)) { logger.setLevel(Level.FINEST); logger.finer("Received ChannelData message " + evt); } ChannelData channelData = evt.getChannelDataMessage(); char channelNo = channelData.getChannelNumber(); byte[] data = channelData.getData(); try { String line = new String(data,"UTF-8"); System.out.println(line); } catch (UnsupportedEncodingException e) { System.err.println("Unable to get back String."); } /** System.out.println("Received a ChannelData message for " + (int) channelNo + " , message : " + Arrays.toString(data)); **/ } } ================================================ FILE: src/main/java/org/jitsi/turnserver/turnClient/InteractiveUdpPeer.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.turnClient; import java.io.*; import java.net.*; import org.ice4j.*; /** * An interactive UDP peer client to send messages. * @author Aakash Garg * */ public class InteractiveUdpPeer { private static DatagramSocket sock; private static int serverPort = 15000; // private static int serverPort = 49152; private static int clientPort = 11000; /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { String[] temp = {InetAddress.getLocalHost().toString(),""+clientPort}; if(args.length == 0) { args = temp; } TransportAddress serverAddr = null; TransportAddress clientAddr = null; if(args.length==2) { serverAddr = new TransportAddress(args[0], Integer.parseInt(args[1]),Transport.UDP); clientAddr = new TransportAddress(InetAddress.getLocalHost(), 12000,Transport.UDP); } else if(args.length==4) { serverAddr = new TransportAddress(args[0], Integer.parseInt(args[1]),Transport.UDP); serverPort = Integer.parseInt(args[1]); clientAddr = new TransportAddress(args[2], Integer.parseInt(args[3]), Transport.UDP); clientPort = Integer.parseInt(args[3]); } else { throw new IllegalArgumentException("Please enter valid arguments."); } sock = new DatagramSocket(clientPort,InetAddress.getLocalHost()); /* Thread recThread = getRecThread(); recThread.start(); */ /* FileReader fr = new FileReader( "D:\\Eclipse\\turnserver\\samoleInputFile.txt"); */ //BufferedReader br = new BufferedReader(fr); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = null; System.out.println("Start typing message."); while((line = br.readLine())!=null) { byte[] data = line.getBytes(); DatagramPacket pkt = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), serverPort); sock.send(pkt); System.out.println("Sent : "+line); byte[] receiveData = new byte[1024]; DatagramPacket recPkt = new DatagramPacket(receiveData, receiveData.length); sock.receive(recPkt); System.out.println("Recv : " + getString(recPkt)); } } public static Thread getRecThread() { Runnable recRun = new Runnable(){ @Override public void run() { byte[] receiveData = new byte[1024]; DatagramPacket recPkt = new DatagramPacket(receiveData, receiveData.length); System.out .println("Waiting for receiving data on " + sock.getLocalAddress().toString() + ":" + sock.getLocalPort()); try { sock.receive(recPkt); System.out .println("Received Data : " + getString(recPkt)); } catch (IOException e) { System.err.println(e.getMessage()); } } }; Thread thread = new Thread(recRun); return thread; } public static String getString(DatagramPacket recPkt) { byte[] data = recPkt.getData(); int len = recPkt.getLength(); byte[] recData = new byte[len]; for(int i=0; i * Usage from command prompt
* * java org.ice4j.stunclient.StunCient stunserver.org stunPort
* (Displays the network configuration and port mapping using stunserver.org as * stunserver and stunPort as server's stun port
* * java org.ice4j.stunclient.StunCient N
* (chooses server from array of { "stunserver.org", "stun.softjoys.com", * "stun.voiparound.com", "stun.voipbuster.com", "stun.voipstunt.com", * "stun.voxgratia.org", "stun.ekiga.net", "stun.ideasip.com", "stun.iptel.org", * "stun.rixtelecom.se" }) * * @author Aakash * */ public class StunClient { /** * @param args */ public static StunDiscoveryReport getReport(String... args) throws Exception { TransportAddress localAddr = null; TransportAddress serverAddr = null; Transport protocol = Transport.UDP; int serverUdpPort = 3478; String[] server = { "stunserver.org", "stun.softjoys.com", "stun.voiparound.com", "stun.voipbuster.com", "stun.voipstunt.com", "stun.voxgratia.org", "stun.ekiga.net", "stun.ideasip.com", "stun.iptel.org", "stun.rixtelecom.se" }; if (args.length == 4) { // uses args[0] and args[1] as server name and port and args[2] and // args[3] as client ip and port localAddr = new TransportAddress(args[2], Integer.valueOf( args[3]).intValue(), protocol); serverUdpPort = Integer.valueOf( args[1]).intValue(); serverAddr = new TransportAddress(args[0], serverUdpPort, protocol); } else if (args.length == 2) { // uses args as server name and port localAddr = new TransportAddress(InetAddress.getLocalHost(), 5678, protocol); System.out.println("Sending request on "+args[0]+":"+args[1]); serverAddr = new TransportAddress(args[0], Integer.valueOf( args[1]).intValue(), protocol); } else if (args.length == 1) { // chooses a server indexed by N localAddr = new TransportAddress(InetAddress.getLocalHost(), 5678, protocol); serverAddr = new TransportAddress(server[Integer.parseInt(args[0])], serverUdpPort, protocol); // serverAddr = new // TransportAddress(InetAddress.getLocalHost(),serverUdpPort,protocol); System.out.println("Stun Server - " + server[Integer.parseInt(args[0])] + ":" + serverUdpPort); } else { // runs server running on same computer localAddr = new TransportAddress(InetAddress.getLocalHost(), 5678, protocol); // serverAddr = new TransportAddress( // server[0],serverUdpPort,protocol); serverAddr = new TransportAddress(InetAddress.getLocalHost(), serverUdpPort, protocol); } NetworkConfigurationDiscoveryProcess addressDiscovery = new NetworkConfigurationDiscoveryProcess(new StunStack(), localAddr, serverAddr); addressDiscovery.start(); StunDiscoveryReport report = addressDiscovery.determineAddress(); addressDiscovery.shutDown(); return report; } public static void main(String... args) throws Exception { StunDiscoveryReport report = StunClient.getReport(args); System.out.println(report); } } ================================================ FILE: src/main/java/org/jitsi/turnserver/turnClient/TcpPeer.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.turnClient; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.Socket; import org.ice4j.socket.IceTcpSocketWrapper; public class TcpPeer { public static void main(String... args) throws IOException, InterruptedException { System.out.println("sending request to server...."); Socket client = new Socket(InetAddress.getLocalHost(), 15000); System.out.println("successfully conneted from " + client.getLocalPort()); IceTcpSocketWrapper mySock = new IceTcpSocketWrapper(client); byte[] data = new byte[1500]; DatagramPacket p = new DatagramPacket(data, 0); System.out.println("Waiting for packet."); mySock.receive(p); System.out.println("packet received"); data = p.getData(); for (int i = 0; i < p.getLength(); i++) { System.out.print(String.format("%02X, ", data[i])); } System.out.println(); String returnMessage = "Paras"; p = new DatagramPacket(returnMessage.getBytes(), returnMessage.length()); System.out.println("Sending return message-" + byteArrayToHex(p.getData())); mySock.send(p); System.out.println("Message Sent."); System.out.println("Thread going to sleep"); Thread.sleep(100000); } private static String byteArrayToHex(byte[] data) { String arrayToHex = ""; for (int i = 0; i < data.length; i++) { arrayToHex += String.format("%02X, ", data[i]); } return arrayToHex; } public static void main2(String[] args) { String returnMessage = "Paras"; DatagramPacket p = new DatagramPacket(returnMessage.getBytes(), returnMessage.length()); System.out.println("Sending return message-" + byteArrayToHex(p.getData())); System.out.println(p.getLength() + ", " + p.getOffset()); } } ================================================ FILE: src/main/java/org/jitsi/turnserver/turnClient/TurnAllocationClient.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.turnClient; import java.io.*; import java.net.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.socket.*; import org.ice4j.stack.*; import org.ice4j.stunclient.*; import org.jitsi.turnserver.collectors.AllocationResponseCollector; import org.jitsi.turnserver.listeners.*; import org.jitsi.turnserver.stack.*; /** * Class to run Allocation Client. * * @author Aakash Garg * */ public class TurnAllocationClient { private static BlockingRequestSender requestSender; private static IceUdpSocketWrapper sock; private static TurnStack turnStack; private static TransportAddress localAddress; private static TransportAddress serverAddress; private static boolean started; /** * The instance that should be notified when an incoming UDP message has * been processed and ready for delivery */ private PeerUdpMessageEventHandler peerUdpMessageEventHandler; /** * The instance that should be notified when an incoming ChannelData message * has been processed and ready for delivery */ private ChannelDataEventHandler channelDataEventHandler; /** * @param args * @throws IOException * @throws StunException * @throws InterruptedException */ public static void main(String[] args) throws IOException, StunException, InterruptedException { String[] temp = {InetAddress.getLocalHost().getHostAddress(),"3478"}; // String[] temp = {"176.31.40.85","3478"}; args = temp; Transport protocol = Transport.UDP; // uses args as server name and port localAddress = new TransportAddress(InetAddress.getLocalHost(), 5678, protocol); serverAddress = new TransportAddress(args[0], Integer.valueOf( args[1]).intValue(), protocol); System.out.println("Client adress : "+localAddress); System.out.println("Server adress : "+serverAddress); start(); StunMessageEvent evt = null; evt = sendAllocationRequest(localAddress,serverAddress); evt = sendCreatePermissionRequest(9999); evt = sendCreatePermissionRequest(9999); evt = sendCreatePermissionRequest(11000); TransportAddress peerAddr = new TransportAddress(InetAddress.getLocalHost(), 11000, protocol); evt = sendChannelBindRequest((char) 0x4000,peerAddr); sendChannelDataMessage(); System.out.println("Starting interactive communication."); doInteractiveComm(); /* System.out.println("Thread will now sleep."); Thread.sleep(600*1000); */ shutDown(); } public static StunMessageEvent sendAllocationRequest( TransportAddress localAddr, TransportAddress serverAddress) throws IOException { Request request = MessageFactory.createAllocateRequest(); RequestedTransportAttribute requestedTransportAttribute = AttributeFactory.createRequestedTransportAttribute( RequestedTransportAttribute.UDP); request.putAttribute(requestedTransportAttribute); StunMessageEvent evt = null; try { AllocationResponseCollector allocResCollec = new AllocationResponseCollector(turnStack); /* turnStack.sendRequest(request, serverAddress, localAddress, allocResCollec); */ evt = requestSender.sendRequestAndWaitForResponse( request, serverAddress); allocResCollec.processResponse((StunResponseEvent) evt); } catch (Exception ex) { //this shouldn't happen since we are the ones that created the //request ex.printStackTrace(); System.out.println("Internal Error. Failed to encode a message"); return null; } if(evt != null) System.out.println("Allocation TEST res=" +(int)(evt.getMessage().getMessageType()) +" - "+ evt.getRemoteAddress().getHostAddress()); else System.out.println("NO RESPONSE received to Allocation TEST."); return evt; } public static StunMessageEvent sendCreatePermissionRequest(int peerPort) throws IOException, StunException { System.out.println(); TransportAddress peerAddr = new TransportAddress( serverAddress.getAddress(), peerPort, Transport.UDP); TransactionID tran = TransactionID.createNewTransactionID(); System.out.println("Create request for : "+peerAddr); Request request = MessageFactory.createCreatePermissionRequest( peerAddr, tran.getBytes()); StunMessageEvent evt = null; System.out.println("Permission tran : "+tran); try { evt = requestSender.sendRequestAndWaitForResponse( request, serverAddress,tran); } catch (StunException ex) { //this shouldn't happen since we are the ones that created the //request System.out.println("Internal Error. Failed to encode a message"); return null; } if(evt != null) System.out.println("Permission TEST res=" +(int)(evt.getMessage().getMessageType()) +" - "+ evt.getRemoteAddress().getHostAddress()); else System.out.println("NO RESPONSE received to Permission TEST."); return evt; } public static StunMessageEvent sendChannelBindRequest( char channelNo, TransportAddress peerAddress) throws IOException, StunException { System.out.println(); System.out.println("ChannelBind request for : "+peerAddress +" on "+(int)channelNo); TransactionID tran = TransactionID.createNewTransactionID(); Request request = MessageFactory.createChannelBindRequest( channelNo, peerAddress, tran.getBytes()); char cNo = ((ChannelNumberAttribute) (request .getAttribute(Attribute.CHANNEL_NUMBER))).getChannelNumber(); TransportAddress pAddr = ((XorPeerAddressAttribute) (request .getAttribute(Attribute.XOR_PEER_ADDRESS))).getAddress(); XorMappedAddressAttribute mappedAddr = AttributeFactory.createXorMappedAddressAttribute( localAddress, tran.getBytes()); //mappedAddr.setAddress(mappedAddr.getAddress(), tran.getBytes()); System.out.println(">"+mappedAddr.getAddress()); request.putAttribute(mappedAddr); System.out.println("input mappedAddress : "+mappedAddr.getAddress()); XorMappedAddressAttribute retMapAddr = (XorMappedAddressAttribute) (request .getAttribute(Attribute.XOR_MAPPED_ADDRESS)); TransportAddress mAddr = (retMapAddr).getAddress(); System.out.println("output mappedAddress : "+mAddr.getHostAddress()); System.out.println("Retrived ChannelBind request is : "+pAddr +" on "+(int)cNo); StunMessageEvent evt = null; System.out.println("ChannelBind tran : "+tran); try { evt = requestSender.sendRequestAndWaitForResponse( request, serverAddress,tran); } catch (StunException ex) { //this shouldn't happen since we are the ones that created the //request System.out.println("Internal Error. Failed to encode a message"); return null; } if(evt != null) System.out.println("ChannelBind TEST res=" +evt.getRemoteAddress().toString() +" - "+ evt.getRemoteAddress().getHostAddress()); else System.out.println("NO RESPONSE received to ChannelBind TEST."); return evt; } public static void sendChannelDataMessage() throws StunException, IOException { byte[] message = {0xa,0xb}; ChannelData channelData = new ChannelData(); channelData.setChannelNumber((char)0x4000); channelData.setData(message); turnStack.sendChannelData(channelData, serverAddress, localAddress); System.out.println("ChannelData message sent."); } /** * Puts the discoverer into an operational state. * @throws IOException if we fail to bind. * @throws StunException if the stun4j stack fails start for some reason. */ public static void start() throws IOException, StunException { ClientChannelDataEventHandler channelDataHandler = new ClientChannelDataEventHandler(); turnStack = new TurnStack(null, channelDataHandler); channelDataHandler.setTurnStack(turnStack); sock = new IceUdpSocketWrapper( new SafeCloseDatagramSocket(localAddress)); turnStack.addSocket(sock); DataIndicationListener dataIndListener = new DataIndicationListener(turnStack); dataIndListener.setLocalAddress(localAddress); dataIndListener.start(); requestSender = new BlockingRequestSender(turnStack, localAddress); started = true; } public static void doInteractiveComm() throws IOException, StunException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Started interaction start typing message"); String line = br.readLine(); System.out.println("My first message : "+line); while(line!=null) { byte[] data = line.getBytes(); TransactionID tran = TransactionID.createNewTransactionID(); TransportAddress peerAddress = new TransportAddress( InetAddress.getLocalHost(), 11000, Transport.UDP); Indication ind = MessageFactory.createSendIndication(peerAddress, data, tran.getBytes()); System.out.println("Trying to send message to server"); turnStack.sendIndication(ind, serverAddress, localAddress); System.out.println("message sent"); System.out.println("Type a new message : "); line = br.readLine(); } } /** * Shuts down the underlying stack and prepares the object for garbage * collection. */ public static void shutDown() { turnStack.removeSocket(localAddress); sock.close(); sock = null; localAddress = null; requestSender = null; started = false; } } ================================================ FILE: src/main/java/org/jitsi/turnserver/turnClient/TurnClient.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.turnClient; import java.net.InetAddress; import org.ice4j.Transport; import org.ice4j.TransportAddress; import org.ice4j.stack.StunStack; import org.ice4j.stunclient.NetworkConfigurationDiscoveryProcess; import org.ice4j.stunclient.StunDiscoveryReport; /** * The class would be acting as a TurnClient. The client runs on UDP initiates * displays network configuration
* Usage from command prompt
* * java org.ice4j.Turnclient.TurnCient Turnserver.org TurnPort
* (Displays the network configuration and port mapping using Turnserver.org as * Turnserver and TurnPort as server's Turn port
* * java org.ice4j.Turnclient.TurnCient N
* (chooses server from array of { "Turnserver.org", "Turn.softjoys.com", * "Turn.voiparound.com", "Turn.voipbuster.com", "Turn.voipTurnt.com", * "Turn.voxgratia.org", "Turn.ekiga.net", "Turn.ideasip.com", "Turn.iptel.org", * "Turn.rixtelecom.se" }) * * @author Aakash * */ public class TurnClient { /** * @param args */ public static StunDiscoveryReport getReport(String... args) throws Exception { TransportAddress localAddr = null; TransportAddress serverAddr = null; Transport protocol = Transport.UDP; int serverUdpPort = 3478; String[] server = { "stunserver.org", "stun.softjoys.com", "stun.voiparound.com", "stun.voipbuster.com", "stun.voipstunt.com", "stun.voxgratia.org", "stun.ekiga.net", "stun.ideasip.com", "stun.iptel.org", "stun.rixtelecom.se" }; String[] temp = {"120.57.226.103","26768"}; String[] temp2 = {"127.0.0.1","3478"}; args = temp2; if (args.length == 4) { // uses args[0] and args[1] as server name and port and args[2] and // args[3] as client ip and port localAddr = new TransportAddress(args[2], Integer.valueOf( args[3]).intValue(), protocol); serverUdpPort = Integer.valueOf( args[1]).intValue(); serverAddr = new TransportAddress(args[0], serverUdpPort, protocol); } else if (args.length == 2) { // uses args as server name and port localAddr = new TransportAddress(InetAddress.getLocalHost(), 5678, protocol); serverAddr = new TransportAddress(args[0], Integer.valueOf( args[1]).intValue(), protocol); } else if (args.length == 1) { // chooses a server indexed by N localAddr = new TransportAddress(InetAddress.getLocalHost(), 5678, protocol); serverAddr = new TransportAddress(server[Integer.parseInt(args[0])], serverUdpPort, protocol); // serverAddr = new // TransportAddress(InetAddress.getLocalHost(),serverUdpPort,protocol); System.out.println("Stun Server - " + server[Integer.parseInt(args[0])] + ":" + serverUdpPort); } else { // runs server running on same computer localAddr = new TransportAddress(InetAddress.getLocalHost(), 5678, protocol); // serverAddr = new TransportAddress( // server[0],serverUdpPort,protocol); serverAddr = new TransportAddress(InetAddress.getLocalHost(), serverUdpPort, protocol); } NetworkConfigurationDiscoveryProcess addressDiscovery = new NetworkConfigurationDiscoveryProcess(new StunStack(), localAddr, serverAddr); addressDiscovery.start(); StunDiscoveryReport report = addressDiscovery.determineAddress(); addressDiscovery.shutDown(); return report; } public static void main(String... args) throws Exception { StunDiscoveryReport report = TurnClient.getReport(args); System.out.println(report); } } ================================================ FILE: src/main/java/org/jitsi/turnserver/turnClient/TurnTcpAllocationClient.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.turnClient; import java.io.*; import java.net.*; import org.ice4j.*; import org.ice4j.attribute.*; import org.ice4j.message.*; import org.ice4j.socket.*; import org.ice4j.stack.*; import org.ice4j.stunclient.*; import org.jitsi.turnserver.collectors.*; import org.jitsi.turnserver.listeners.*; import org.jitsi.turnserver.stack.*; /** * Class to run Allocation Client over TCP. * * @author Aakash Garg * */ public class TurnTcpAllocationClient { private static BlockingRequestSender requestSender; private static IceTcpSocketWrapper sock; private static TurnStack turnStack; private static TransportAddress localAddress; private static TransportAddress serverAddress; private static boolean started; private static Socket tcpSocketToServer = null; /** * The instance that should be notified when an incoming TCP message has * been processed and ready for delivery */ private PeerUdpMessageEventHandler peerUdpMessageEventHandler; /** * Puts the discoverer into an operational state. * * @throws IOException if we fail to bind. * @throws StunException if the stun4j stack fails start for some reason. */ public static void start(Transport protocol) throws IOException, StunException { sock = new IceTcpSocketWrapper(tcpSocketToServer); System.out.println("Adding an new TCP connection to : " + serverAddress.getHostAddress()); localAddress = new TransportAddress(InetAddress.getLocalHost(), tcpSocketToServer.getLocalPort(), protocol); System.out.println("Client adress : " + localAddress); System.out.println("Server adress : " + serverAddress); ClientChannelDataEventHandler channelDataHandler = new ClientChannelDataEventHandler(); turnStack = new TurnStack(null, channelDataHandler); channelDataHandler.setTurnStack(turnStack); turnStack.addSocket(sock); requestSender = new BlockingRequestSender(turnStack, localAddress); ConnectionAttemptIndicationListener connectionAttemptIndicationListener = new ConnectionAttemptIndicationListener(turnStack/*,requestSender*/); connectionAttemptIndicationListener.setLocalAddress(localAddress); connectionAttemptIndicationListener.start(); started = true; } /** * @param args * @throws IOException * @throws StunException * @throws InterruptedException */ public static void main(String[] args) throws IOException, StunException, InterruptedException { String[] temp = { InetAddress.getLocalHost().toString(), "3478" }; args = temp; Transport protocol = Transport.TCP; // uses args as server name and port serverAddress = new TransportAddress(InetAddress.getLocalHost(), Integer.valueOf( args[1]).intValue(), protocol); tcpSocketToServer = new Socket(serverAddress.getHostAddress(), 3478); System.out.println("Local port chosen : " + tcpSocketToServer.getLocalPort()); start(protocol); StunMessageEvent evt = null; evt = sendAllocationRequest( localAddress, serverAddress); evt = sendCreatePermissionRequest(9999); // evt = sendCreatePermissionRequest(9999); // evt = sendCreatePermissionRequest(11000); // evt = sendConnectRequest(9999); TransportAddress peerAddr = new TransportAddress(InetAddress.getLocalHost(), 11000, protocol); Thread.sleep(600 * 1000); shutDown(); } public static StunMessageEvent sendAllocationRequest( TransportAddress localAddr, TransportAddress serverAddress) throws IOException { Request request = MessageFactory.createAllocateRequest(); RequestedTransportAttribute requestedTransportAttribute = AttributeFactory .createRequestedTransportAttribute(RequestedTransportAttribute.TCP); request.putAttribute(requestedTransportAttribute); System.out.println("Message type : " + (int) request.getMessageType()); StunMessageEvent evt = null; try { AllocationResponseCollector allocResCollec = new AllocationResponseCollector(turnStack); /* * turnStack.sendRequest(request, serverAddress, localAddress, * allocResCollec); */ evt = requestSender.sendRequestAndWaitForResponse( request, serverAddress); allocResCollec.processResponse((StunResponseEvent) evt); } catch (Exception ex) { // this shouldn't happen since we are the ones that created the // request ex.printStackTrace(); System.out.println("Internal Error. Failed to encode a message"); return null; } if (evt != null) System.out.println("Allocation TEST res=" + (int) (evt.getMessage().getMessageType()) + " - " + evt.getRemoteAddress().getHostAddress()); else System.out.println("NO RESPONSE received to Allocation TEST."); return evt; } public static StunMessageEvent sendCreatePermissionRequest(int peerPort) throws IOException, StunException { System.out.println(); TransportAddress peerAddr = new TransportAddress(serverAddress.getAddress(), peerPort, Transport.TCP); TransactionID tran = TransactionID.createNewTransactionID(); System.out.println("Create request for : " + peerAddr); Request request = MessageFactory.createCreatePermissionRequest( peerAddr, tran.getBytes()); StunMessageEvent evt = null; System.out.println("Permission tran : " + tran); try { evt = requestSender.sendRequestAndWaitForResponse( request, serverAddress, tran); } catch (StunException ex) { // this shouldn't happen since we are the ones that created the // request System.out.println("Internal Error. Failed to encode a message"); return null; } if (evt != null) System.out.println("Permission TEST res=" + (int) (evt.getMessage().getMessageType()) + " - " + evt.getRemoteAddress().getHostAddress()); else System.out.println("NO RESPONSE received to Permission TEST."); return evt; } public static StunMessageEvent sendConnectRequest(int peerPort) throws IOException, StunException { System.out.println(); TransportAddress peerAddr = new TransportAddress(serverAddress.getAddress(), peerPort, Transport.TCP); TransactionID tran = TransactionID.createNewTransactionID(); System.out.println("Connect request for : " + peerAddr); Request request = MessageFactory.createConnectRequest( peerAddr, tran.getBytes()); request.setTransactionID(tran.getBytes()); StunMessageEvent evt = null; System.out.println("Connect Req tran : " + tran); try { evt = requestSender.sendRequestAndWaitForResponse( request, serverAddress, tran); } catch (StunException ex) { // this shouldn't happen since we are the ones that created the // request System.out.println("Internal Error. Failed to encode a message"); return null; } if (evt != null) System.out.println("Connect request TEST res=" + (int) (evt.getMessage().getMessageType()) + " - " + evt.getRemoteAddress().getHostAddress()); else System.out.println("NO RESPONSE received to Connect Request TEST."); return evt; } public static void doInteractiveComm() throws IOException, StunException { System.out.println("---->Interactve Communication started<---------"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = null; while ((line = br.readLine()) != null) { byte[] data = line.getBytes(); TransactionID tran = TransactionID.createNewTransactionID(); TransportAddress peerAddress = new TransportAddress(InetAddress.getLocalHost(), 11000, Transport.TCP); Indication ind = MessageFactory.createSendIndication( peerAddress, data, tran.getBytes()); turnStack.sendIndication( ind, serverAddress, localAddress); } } /** * Shuts down the underlying stack and prepares the object for garbage * collection. */ public static void shutDown() { turnStack.removeSocket(localAddress); sock.close(); sock = null; localAddress = null; requestSender = null; started = false; } } ================================================ FILE: src/main/resources/TurnServer.propertes ================================================ org.jitsi.turnserver.udp_port = 3478; org.jitsi.turnserver.max_port = 65535; org.jitsi.turnserver.min_port = 49152; org.jitsi.turnserver.unpriv_user = ; org.jitsi.turnserver.max_client = ; org.jitsi.turnserver.max_relay_per_username = ; org.jitsi.turnserver.allocation_lifetime = ; org.jitsi.turnserver.nonce_key = 79897BC1042DF24AAE2FACFF9E2A6093; org.jitsi.turnserver.ca_file = ; org.jitsi.turnserver.cert_file = ; org.jitsi.turnserver.private_key_file = ; org.jitsi.turnserver.realm = ; org.jitsi.turnserver.bandwidth_per_allocation = ; org.jitsi.turnserver.restricted_bandwidth = ; org.jitsi.turnserver.account_method = ; org.jitsi.turnserver.account_file = ; ================================================ FILE: src/main/resources/accounts.txt ================================================ JitsiGsocStudent:8156CE4E8499609C34C641E2C2904171 JitsiGsocMentor:654714090BC4348274203EE55EEFBB26 ================================================ FILE: src/main/resources/logging.properties ================================================ ############################################################ # Default Logging Configuration File # # You can use a different file by specifying a filename # with the java.util.logging.config.file system property. # For example java -Djava.util.logging.config.file=myfile ############################################################ ############################################################ # Global properties ############################################################ # "handlers" specifies a comma separated list of log Handler # classes. These handlers will be installed during VM startup. # Note that these classes must be on the system classpath. # By default we only configure a ConsoleHandler, which will only # show messages at the INFO and above levels. handlers= java.util.logging.ConsoleHandler # To also add the FileHandler, use the following line instead. #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # Default global logging level. # This specifies which kinds of events are logged across # all loggers. For any given facility this global level # can be overriden by a facility specific level # Note that the ConsoleHandler also has a separate level # setting to limit messages printed to the console. .level= ALL ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ # default file output is in user's home directory. # Note that the level for the console handler may be modified by the application. # Also note that you won't see any logs with a Level Lower than this one. java.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = org.ice4j.util.Ice4jLogFormatter ############################################################ # Facility specific properties. # Provides extra control for each logger. ############################################################ # Things coming from ice4j org.ice4j.level = INFO test.level = INFO #org.ice4j.ice.checks.levell = FINEST #org.ice4j.ice.ConnectivityCheckClient.level = FINER ================================================ FILE: src/test/java/org/jitsi/turnserver/client/ClientTest.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.client; import static org.junit.Assert.*; import java.io.*; import java.net.*; import java.util.*; import java.util.concurrent.*; import org.jitsi.turnserver.*; import org.jitsi.turnserver.stack.*; import org.junit.*; import org.ice4j.*; import org.ice4j.ice.*; import org.ice4j.ice.harvest.*; import org.ice4j.socket.*; import org.ice4j.stack.*; import org.ice4j.stunclient.*; /** * This tests clients against a local turnserver. * * @author Paul Gregoire */ public class ClientTest { private static ExecutorService executor; // holder for reference to the running turnserver private static Future future; private static TurnServer turnServer; private static TransportAddress serverAddr; // goggle stun for testing purposes TransportAddress googAddr = new TransportAddress("stun.l.google.com", 19302, Transport.UDP); @Before public void setUp() throws Exception { // instance an executor executor = Executors.newCachedThreadPool(); // create an address for binding serverAddr = new TransportAddress(getLocalAddress(), 3478, Transport.UDP); // instance a turnserver turnServer = new TurnServer(serverAddr); // start the server future = executor.submit(new Runnable() { public void run() { try { turnServer.start(); } catch (TurnException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } do { try { Thread.sleep(1000L); } catch (InterruptedException e) { // e.printStackTrace(); } } while (turnServer.isStarted()); } }); } @After public void tearDown() throws Exception { // stop the server turnServer.shutDown(); // cancel the future future.cancel(true); } @Test public void testUDPClient() throws Exception { // create a client address for binding TransportAddress clientAddr = new TransportAddress(getLocalAddress(), 5678, Transport.UDP); // create a discover process NetworkConfigurationDiscoveryProcess addressDiscovery = new NetworkConfigurationDiscoveryProcess(new StunStack(), clientAddr, serverAddr); // start discovery process addressDiscovery.start(); // pull the report StunDiscoveryReport report = addressDiscovery.determineAddress(); System.out.println(report); assertNotNull(report.getPublicAddress()); // stop discovery process addressDiscovery.shutDown(); } @Test public void testIceMediaStream() throws Exception { // instance a control-side Future ctrl = executor.submit(new ControlClient()); // instance an end point / subscriber Future end = executor.submit(new EndpointClient()); // sleep this thread Thread.sleep(3000L); // kill the clients ctrl.cancel(true); end.cancel(true); } /** * Utility method to provide a local IP address for current interfaces. * * @return local address or default of 127.0.0.1 */ private String getLocalAddress() { Enumeration ifaces; try { ifaces = NetworkInterface.getNetworkInterfaces(); while (ifaces.hasMoreElements()) { NetworkInterface iface = (NetworkInterface) ifaces.nextElement(); Enumeration iaddresses = iface.getInetAddresses(); while (iaddresses.hasMoreElements()) { InetAddress iaddress = (InetAddress) iaddresses.nextElement(); if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress() && !iaddress.isSiteLocalAddress()) { String hostAddr = iaddress.getHostAddress(); return hostAddr != null ? hostAddr : iaddress .getHostName(); } } } String localhostAddr = InetAddress.getLocalHost().getHostAddress(); return localhostAddr != null ? localhostAddr : InetAddress .getLocalHost().getHostName(); } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } return "127.0.0.1"; } class ControlClient implements Runnable { public void run() { // instance an ICE agent Agent agent = new Agent(); agent.setControlling(true); agent.setTrickling(true); String user = agent.getLocalUfrag(); String passwd = agent.getLocalPassword(); System.out.printf("Control - user: %s passwd: %s\n", user, passwd); // add havesters // agent.addCandidateHarvester(new // StunCandidateHarvester(googAddr)); agent.addCandidateHarvester(new TurnCandidateHarvester(serverAddr)); System.out.printf("Agent state: %s\n", agent.getState()); // add a stream IceMediaStream stream = agent.getStream("junit"); assertNull(stream); stream = agent.createMediaStream("junit"); assertNotNull(stream); // create a data component Component rtp = null; try { rtp = agent.createComponent(stream, Transport.UDP, 6000, 6000, 6016); } catch (Exception e) { e.printStackTrace(); } for (CandidateHarvester harvester : agent.getHarvesters()) { System.out.printf("Harvesting for rtp via %s\n", harvester); try { Collection candidates = harvester.harvest(rtp); if (candidates.isEmpty()) { System.out.println("No candidates found"); continue; } System.out.printf("Harvested candidates: %s\n", candidates); for (LocalCandidate candidate : candidates) { rtp.addLocalCandidate(candidate); } break; } catch (Exception e) { e.printStackTrace(); } } // start connection establishment agent.startConnectivityEstablishment(); IceProcessingState state; while ((state = agent.getState()) != IceProcessingState.TERMINATED) { System.out.printf( "Control connectivity establishment in process, state: %s\n", state); try { if (state == IceProcessingState.FAILED) { throw new Exception( "ICE connectivity failed"); } // sleep until ICE terminates or fails Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); agent.free(); break; } } // get candidates CandidatePair rtpPair = agent.getStream("junit").getComponent(Component.RTP) .getSelectedPair(); System.out.printf("Control IP: %s\n", rtpPair.getRemoteCandidate() .getTransportAddress()); IceUdpSocketWrapper wrapper = new IceUdpSocketWrapper(rtpPair.getDatagramSocket()); // wrapper.send(packet); } } class EndpointClient implements Runnable { public void run() { // instance an ICE agent Agent agent = new Agent(); agent.setControlling(false); agent.setTrickling(true); String user = agent.getLocalUfrag(); String passwd = agent.getLocalPassword(); System.out.printf("Endpoint - user: %s passwd: %s\n", user, passwd); // add havesters // agent.addCandidateHarvester(new // StunCandidateHarvester(googAddr)); agent.addCandidateHarvester(new TurnCandidateHarvester(serverAddr)); System.out.printf("Agent state: %s\n", agent.getState()); // add a stream IceMediaStream stream = agent.getStream("junit"); assertNull(stream); stream = agent.createMediaStream("junit"); assertNotNull(stream); // create a data component Component rtp = null; try { rtp = agent.createComponent(stream, Transport.UDP, 7000, 7000, 7016); } catch (Exception e) { e.printStackTrace(); } for (CandidateHarvester harvester : agent.getHarvesters()) { System.out.printf("Harvesting for rtp via %s\n", harvester); try { Collection candidates = harvester.harvest(rtp); if (candidates.isEmpty()) { System.out.println("No candidates found"); continue; } System.out.printf("Harvested candidates: %s\n", candidates); for (LocalCandidate candidate : candidates) { rtp.addLocalCandidate(candidate); } break; } catch (Exception e) { e.printStackTrace(); } } // start connection establishment agent.startConnectivityEstablishment(); IceProcessingState state; while ((state = agent.getState()) != IceProcessingState.TERMINATED) { System.out.printf( "Endpoint connectivity establishment in process, state: %s\n", state); try { if (state == IceProcessingState.FAILED) { throw new Exception( "ICE connectivity failed"); } // sleep until ICE terminates or fails Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); agent.free(); break; } } // get candidates CandidatePair rtpPair = agent.getStream("junit").getComponent(Component.RTP) .getSelectedPair(); System.out.printf("Endpoint IP: %s\n", rtpPair.getRemoteCandidate() .getTransportAddress()); IceUdpSocketWrapper wrapper = new IceUdpSocketWrapper(rtpPair.getDatagramSocket()); // wrapper.receive(packet); } } } ================================================ FILE: src/test/java/org/jitsi/turnserver/stack/TurnServerTestSuite.java ================================================ /* * TurnServer, the OpenSource Java Solution for TURN protocol. Maintained by the * Jitsi community (http://jitsi.org). * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jitsi.turnserver.stack; import org.jitsi.turnserver.client.*; import org.junit.runner.*; import org.junit.runners.*; /** * Contains the suite of tests in the turnserver project. * * @author Paul Gregoire */ @RunWith(Suite.class) @Suite.SuiteClasses({ ClientTest.class }) public class TurnServerTestSuite { } ================================================ FILE: src/test/resources/TurnServer.propertes ================================================ org.jitsi.turnserver.udp_port = 3478; org.jitsi.turnserver.max_port = 65535; org.jitsi.turnserver.min_port = 49152; org.jitsi.turnserver.unpriv_user = ; org.jitsi.turnserver.max_client = ; org.jitsi.turnserver.max_relay_per_username = ; org.jitsi.turnserver.allocation_lifetime = ; org.jitsi.turnserver.nonce_key = 79897BC1042DF24AAE2FACFF9E2A6093; org.jitsi.turnserver.ca_file = ; org.jitsi.turnserver.cert_file = ; org.jitsi.turnserver.private_key_file = ; org.jitsi.turnserver.realm = ; org.jitsi.turnserver.bandwidth_per_allocation = ; org.jitsi.turnserver.restricted_bandwidth = ; org.jitsi.turnserver.account_method = ; org.jitsi.turnserver.account_file = ; ================================================ FILE: src/test/resources/accounts.txt ================================================ JitsiGsocStudent:8156CE4E8499609C34C641E2C2904171 JitsiGsocMentor:654714090BC4348274203EE55EEFBB26 ================================================ FILE: src/test/resources/logging.properties ================================================ ############################################################ # Default Logging Configuration File # # You can use a different file by specifying a filename # with the java.util.logging.config.file system property. # For example java -Djava.util.logging.config.file=myfile ############################################################ ############################################################ # Global properties ############################################################ # "handlers" specifies a comma separated list of log Handler # classes. These handlers will be installed during VM startup. # Note that these classes must be on the system classpath. # By default we only configure a ConsoleHandler, which will only # show messages at the INFO and above levels. handlers= java.util.logging.ConsoleHandler # To also add the FileHandler, use the following line instead. #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # Default global logging level. # This specifies which kinds of events are logged across # all loggers. For any given facility this global level # can be overriden by a facility specific level # Note that the ConsoleHandler also has a separate level # setting to limit messages printed to the console. .level= ALL ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ # default file output is in user's home directory. # Note that the level for the console handler may be modified by the application. # Also note that you won't see any logs with a Level Lower than this one. java.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = org.ice4j.util.Ice4jLogFormatter ############################################################ # Facility specific properties. # Provides extra control for each logger. ############################################################ # Things coming from ice4j org.ice4j.level = FINEST test.level = FINEST