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

566 lines
18 KiB
Java

/**
*
* Copyright 2003-2005 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.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.IQ;
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.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* RTPBridge IQ Stanza used to request and retrieve a RTPBridge Candidates that can be used for a Jingle Media Transmission between two parties that are behind NAT.
* This Jingle Bridge has all the needed information to establish a full UDP Channel (Send and Receive) between two parties.
* <i>This transport method should be used only if other transport methods are not allowed. Or if you want a more reliable transport.</i>
*
* High Level Usage Example:
*
* RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, sessionID);
*
* @author Thiago Camargo
*/
public class RTPBridge extends IQ {
private static final Logger LOGGER = Logger.getLogger(RTPBridge.class.getName());
private String sid;
private String pass;
private String ip;
private String name;
private int portA = -1;
private int portB = -1;
private String hostA;
private String hostB;
private BridgeAction bridgeAction = BridgeAction.create;
private enum BridgeAction {
create, change, publicip
}
/**
* Element name of the stanza extension.
*/
public static final String NAME = "rtpbridge";
/**
* Element name of the stanza extension.
*/
public static final String ELEMENT_NAME = "rtpbridge";
/**
* Namespace of the stanza extension.
*/
public static final String NAMESPACE = "http://www.jivesoftware.com/protocol/rtpbridge";
static {
ProviderManager.addIQProvider(NAME, NAMESPACE, new Provider());
}
/**
* Creates a RTPBridge Instance with defined Session ID.
*
* @param sid TODO javadoc me please
*/
public RTPBridge(String sid) {
this();
this.sid = sid;
}
/**
* Creates a RTPBridge Instance with defined Session ID.
*
* @param action TODO javadoc me please
*/
public RTPBridge(BridgeAction action) {
this();
this.bridgeAction = action;
}
/**
* Creates a RTPBridge Instance with defined Session ID.
*
* @param sid TODO javadoc me please
* @param bridgeAction TODO javadoc me please
*/
public RTPBridge(String sid, BridgeAction bridgeAction) {
this();
this.sid = sid;
this.bridgeAction = bridgeAction;
}
/**
* Creates a RTPBridge Stanza without Session ID.
*/
public RTPBridge() {
super(ELEMENT_NAME, NAMESPACE);
}
/**
* Get the attributes string.
*
* @return the attributes.
*/
public String getAttributes() {
StringBuilder str = new StringBuilder();
if (getSid() != null)
str.append(" sid='").append(getSid()).append('\'');
if (getPass() != null)
str.append(" pass='").append(getPass()).append('\'');
if (getPortA() != -1)
str.append(" porta='").append(getPortA()).append('\'');
if (getPortB() != -1)
str.append(" portb='").append(getPortB()).append('\'');
if (getHostA() != null)
str.append(" hosta='").append(getHostA()).append('\'');
if (getHostB() != null)
str.append(" hostb='").append(getHostB()).append('\'');
return str.toString();
}
/**
* Get the Session ID of the Stanza (usually same as Jingle Session ID).
*
* @return the session ID
*/
public String getSid() {
return sid;
}
/**
* Set the Session ID of the Stanza (usually same as Jingle Session ID).
*
* @param sid TODO javadoc me please
*/
public void setSid(String sid) {
this.sid = sid;
}
/**
* Get the Host A IP Address.
*
* @return the Host A IP Address
*/
public String getHostA() {
return hostA;
}
/**
* Set the Host A IP Address.
*
* @param hostA TODO javadoc me please
*/
public void setHostA(String hostA) {
this.hostA = hostA;
}
/**
* Get the Host B IP Address.
*
* @return the Host B IP Address
*/
public String getHostB() {
return hostB;
}
/**
* Set the Host B IP Address.
*
* @param hostB TODO javadoc me please
*/
public void setHostB(String hostB) {
this.hostB = hostB;
}
/**
* Get Side A receive port.
*
* @return the side A receive port
*/
public int getPortA() {
return portA;
}
/**
* Set Side A receive port.
*
* @param portA TODO javadoc me please
*/
public void setPortA(int portA) {
this.portA = portA;
}
/**
* Get Side B receive port.
*
* @return the side B receive port
*/
public int getPortB() {
return portB;
}
/**
* Set Side B receive port.
*
* @param portB TODO javadoc me please
*/
public void setPortB(int portB) {
this.portB = portB;
}
/**
* Get the RTP Bridge IP.
*
* @return the RTP Bridge IP
*/
public String getIp() {
return ip;
}
/**
* Set the RTP Bridge IP.
*
* @param ip TODO javadoc me please
*/
public void setIp(String ip) {
this.ip = ip;
}
/**
* Get the RTP Agent Pass.
*
* @return the RTP Agent Pass
*/
public String getPass() {
return pass;
}
/**
* Set the RTP Agent Pass.
*
* @param pass TODO javadoc me please
*/
public void setPass(String pass) {
this.pass = pass;
}
/**
* Get the name of the Candidate.
*
* @return the name of the Candidate
*/
public String getName() {
return name;
}
/**
* Set the name of the Candidate.
*
* @param name TODO javadoc me please
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the Child Element XML of the Packet
*
* @return the Child Element XML of the Packet
*/
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder str) {
str.attribute("sid", sid);
str.rightAngleBracket();
if (bridgeAction.equals(BridgeAction.create))
str.append("<candidate/>");
else if (bridgeAction.equals(BridgeAction.change))
str.append("<relay ").append(getAttributes()).append(" />");
else
str.append("<publicip ").append(getAttributes()).append(" />");
return str;
}
/**
* IQProvider for RTP Bridge packets.
* Parse receive RTPBridge stanza to a RTPBridge instance
*
* @author Thiago Rocha
*/
public static class Provider extends IQProvider<RTPBridge> {
@Override
public RTPBridge parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws XmlPullParserException,
IOException {
boolean done = false;
XmlPullParser.Event eventType;
if (!parser.getNamespace().equals(RTPBridge.NAMESPACE))
// TODO: Should be SmackParseException.
throw new IOException("Not a RTP Bridge packet");
RTPBridge iq = new RTPBridge();
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("sid"))
iq.setSid(parser.getAttributeValue(i));
}
// Start processing sub-elements
while (!done) {
eventType = parser.next();
if (eventType == XmlPullParser.Event.START_ELEMENT) {
String elementName = parser.getName();
if (elementName.equals("candidate")) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("ip"))
iq.setIp(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("pass"))
iq.setPass(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("name"))
iq.setName(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("porta"))
iq.setPortA(Integer.parseInt(parser.getAttributeValue(i)));
else if (parser.getAttributeName(i).equals("portb"))
iq.setPortB(Integer.parseInt(parser.getAttributeValue(i)));
}
}
else if (elementName.equals("publicip")) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("ip"))
iq.setIp(parser.getAttributeValue(i));
}
}
}
else if (eventType == XmlPullParser.Event.END_ELEMENT) {
if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) {
done = true;
}
}
}
return iq;
}
}
/**
* Get a new RTPBridge Candidate from the server.
* If a error occurs or the server don't support RTPBridge Service, null is returned.
*
* @param connection TODO javadoc me please
* @param sessionID TODO javadoc me please
* @return the new RTPBridge
* @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 RTPBridge getRTPBridge(XMPPConnection connection, String sessionID) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
if (!connection.isConnected()) {
return null;
}
RTPBridge rtpPacket = new RTPBridge(sessionID);
DomainBareJid jid;
try {
jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain());
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
rtpPacket.setTo(jid);
RTPBridge response = connection.sendIqRequestAndWaitForResponse(rtpPacket);
return response;
}
/**
* Check if the server support RTPBridge Service.
*
* @param connection TODO javadoc me please
* @return true if the server supports the RTPBridge service
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public static boolean serviceAvailable(XMPPConnection connection) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
if (!connection.isConnected()) {
return false;
}
LOGGER.fine("Service listing");
ServiceDiscoveryManager disco = ServiceDiscoveryManager
.getInstanceFor(connection);
// DiscoverItems items = disco.discoverItems(connection.getXMPPServiceDomain());
// Iterator iter = items.getItems();
// while (iter.hasNext()) {
// DiscoverItems.Item item = (DiscoverItems.Item) iter.next();
// if (item.getEntityID().startsWith("rtpbridge.")) {
// return true;
// }
// }
DiscoverInfo discoInfo = disco.discoverInfo(connection.getXMPPServiceDomain());
for (DiscoverInfo.Identity identity : discoInfo.getIdentities()) {
if (identity.getName() != null && identity.getName().startsWith("rtpbridge")) {
return true;
}
}
return false;
}
/**
* Check if the server support RTPBridge Service.
*
* @param connection TODO javadoc me please
* @param sessionID the session id.
* @param pass the password.
* @param proxyCandidate the proxy candidate.
* @param localCandidate the local candidate.
* @return the RTPBridge
* @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 RTPBridge relaySession(XMPPConnection connection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
if (!connection.isConnected()) {
return null;
}
RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change);
DomainBareJid jid;
try {
jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain());
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
rtpPacket.setTo(jid);
rtpPacket.setType(Type.set);
rtpPacket.setPass(pass);
rtpPacket.setPortA(localCandidate.getPort());
rtpPacket.setPortB(proxyCandidate.getPort());
rtpPacket.setHostA(localCandidate.getIp());
rtpPacket.setHostB(proxyCandidate.getIp());
// LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
RTPBridge response = connection.sendIqRequestAndWaitForResponse(rtpPacket);
return response;
}
/**
* Get Public Address from the Server.
*
* @param xmppConnection TODO javadoc me please
* @return public IP String or null if not found
* @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 String getPublicIP(XMPPConnection xmppConnection) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
if (!xmppConnection.isConnected()) {
return null;
}
RTPBridge rtpPacket = new RTPBridge(RTPBridge.BridgeAction.publicip);
DomainBareJid jid;
try {
jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + xmppConnection.getXMPPServiceDomain());
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
rtpPacket.setTo(jid);
rtpPacket.setType(Type.set);
// LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
RTPBridge response = xmppConnection.sendIqRequestAndWaitForResponse(rtpPacket);
if (response == null) return null;
if (response.getIp() == null || response.getIp().equals("")) return null;
Enumeration<NetworkInterface> ifaces = null;
try {
ifaces = NetworkInterface.getNetworkInterfaces();
}
catch (SocketException e) {
LOGGER.log(Level.WARNING, "exception", e);
}
while (ifaces != null && ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = iaddresses.nextElement();
if (!iaddress.isLoopbackAddress())
if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0)
return null;
}
}
return response.getIp();
}
}