Smack/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/nat/STUN.java

276 lines
8.9 KiB
Java

/**
*
* Copyright 2003-2006 Jive Software.
*
* 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.jivesoftware.smackx.jingleold.nat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.packet.SimpleIQ;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.provider.IqProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* STUN IQ Stanza used to request and retrieve a STUN server and port to make p2p connections easier. STUN is usually used by Jingle Media Transmission between two parties that are behind NAT.
*
* High Level Usage Example:
*
* STUN stun = STUN.getSTUNServer(connection);
*
* @author Thiago Camargo
*/
public class STUN extends SimpleIQ {
private static final Logger LOGGER = Logger.getLogger(STUN.class.getName());
private final List<StunServerAddress> servers = new ArrayList<>();
private String publicIp = null;
/**
* Element name of the stanza extension.
*/
public static final String DOMAIN = "stun";
/**
* Element name of the stanza extension.
*/
public static final String ELEMENT_NAME = "query";
/**
* Namespace of the stanza extension.
*/
public static final String NAMESPACE = "google:jingleinfo";
static {
ProviderManager.addIQProvider(ELEMENT_NAME, NAMESPACE, new STUN.Provider());
}
/**
* Creates a STUN IQ.
*/
public STUN() {
super(ELEMENT_NAME, NAMESPACE);
}
/**
* Get a list of STUN Servers recommended by the Server.
*
* @return the list of STUN servers
*/
public List<StunServerAddress> getServers() {
return servers;
}
/**
* Get Public Ip returned from the XMPP server.
*
* @return the public IP
*/
public String getPublicIp() {
return publicIp;
}
/**
* Set Public Ip returned from the XMPP server
*
* @param publicIp TODO javadoc me please
*/
private void setPublicIp(String publicIp) {
this.publicIp = publicIp;
}
/**
* IQProvider for RTP Bridge packets.
* Parse receive RTPBridge stanza to a RTPBridge instance
*
* @author Thiago Rocha
*/
public static class Provider extends IqProvider<STUN> {
@Override
public STUN parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment)
throws XmlPullParserException,
IOException {
boolean done = false;
XmlPullParser.Event eventType;
String elementName;
if (!parser.getNamespace().equals(NAMESPACE))
throw new IOException("Not a STUN packet");
STUN iq = new STUN();
// Start processing sub-elements
while (!done) {
eventType = parser.next();
elementName = parser.getName();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
if (elementName.equals("server")) {
String host = null;
String port = null;
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("host"))
host = parser.getAttributeValue(i);
else if (parser.getAttributeName(i).equals("udp"))
port = parser.getAttributeValue(i);
}
if (host != null && port != null)
iq.servers.add(new StunServerAddress(host, port));
}
else if (elementName.equals("publicip")) {
String host = null;
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("ip"))
host = parser.getAttributeValue(i);
}
if (host != null && !host.equals(""))
iq.setPublicIp(host);
}
}
else if (eventType == XmlPullParser.Event.END_ELEMENT) {
if (parser.getName().equals(ELEMENT_NAME)) {
done = true;
}
}
}
return iq;
}
}
/**
* Get a new STUN Server Address and port from the server.
* If a error occurs or the server don't support STUN Service, null is returned.
*
* @param connection TODO javadoc me please
* @return the STUN server address
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
*/
public static STUN getSTUNServer(XMPPConnection connection) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
if (!connection.isConnected()) {
return null;
}
STUN stunPacket = new STUN();
DomainBareJid jid;
try {
jid = JidCreate.domainBareFrom(DOMAIN + "." + connection.getXMPPServiceDomain());
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
stunPacket.setTo(jid);
STUN response = connection.sendIqRequestAndWaitForResponse(stunPacket);
return response;
}
/**
* Check if the server support STUN Service.
*
* @param connection the connection
* @return true if the server support STUN
* @throws SmackException if Smack detected an exceptional situation.
* @throws XMPPException if an XMPP protocol error was received.
* @throws InterruptedException if the calling thread was interrupted.
*/
public static boolean serviceAvailable(XMPPConnection connection) throws XMPPException, SmackException, InterruptedException {
if (!connection.isConnected()) {
return false;
}
LOGGER.fine("Service listing");
ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(connection);
DiscoverItems items = disco.discoverItems(connection.getXMPPServiceDomain());
for (DiscoverItems.Item item : items.getItems()) {
DiscoverInfo info = disco.discoverInfo(item.getEntityID());
for (DiscoverInfo.Identity identity : info.getIdentities()) {
if (identity.getCategory().equals("proxy") && identity.getType().equals("stun"))
if (info.containsFeature(NAMESPACE))
return true;
}
LOGGER.fine(item.getName() + "-" + info.getType());
}
return false;
}
/**
* Provides easy abstract to store STUN Server Addresses and Ports.
*/
public static class StunServerAddress {
private String server;
private String port;
public StunServerAddress(String server, String port) {
this.server = server;
this.port = port;
}
/**
* Get the Host Address.
*
* @return the host address
*/
public String getServer() {
return server;
}
/**
* Get the Server Port.
*
* @return the server port
*/
public String getPort() {
return port;
}
}
}