/** * $RCSfile: RTPBridge.java,v $ * $Revision: 1.1 $ * $Date: 2007/07/02 17:41:07 $ * * Copyright 2003-2005 Jive Software. * * All rights reserved. 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.jingle.nat; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import java.util.Iterator; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.PacketCollector; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.provider.IQProvider; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smackx.ServiceDiscoveryManager; import org.jivesoftware.smackx.jingle.SmackLogger; import org.jivesoftware.smackx.packet.DiscoverInfo; import org.xmlpull.v1.XmlPullParser; /** * RTPBridge IQ Packet 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> * <p/> * High Level Usage Example: * <p/> * RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, sessionID); * * @author Thiago Camargo */ public class RTPBridge extends IQ { private enum BridgeAction { create, change, publicip } /** * IQProvider for RTP Bridge packets. Parse receive RTPBridge packet to a * RTPBridge instance * * @author Thiago Rocha */ public static class Provider implements IQProvider { public Provider() { super(); } @Override public IQ parseIQ(XmlPullParser parser) throws Exception { boolean done = false; int eventType; String elementName; String namespace; if (!parser.getNamespace().equals(RTPBridge.NAMESPACE)) { throw new Exception("Not a RTP Bridge packet"); } final 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(); elementName = parser.getName(); namespace = parser.getNamespace(); if (eventType == XmlPullParser.START_TAG) { 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")) { // String p = parser.getAttributeName(0); final int x = parser.getAttributeCount(); for (int i = 0; i < parser.getAttributeCount(); i++) { if (parser.getAttributeName(i).equals("ip")) { iq.setIp(parser.getAttributeValue(i)); } } } } else if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) { done = true; } } } return iq; } } private static final SmackLogger LOGGER = SmackLogger .getLogger(RTPBridge.class); 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; /** * Element name of the packet extension. */ public static final String NAME = "rtpbridge"; /** * Element name of the packet extension. */ public static final String ELEMENT_NAME = "rtpbridge"; /** * Namespace of the packet extension. */ public static final String NAMESPACE = "http://www.jivesoftware.com/protocol/rtpbridge"; static { ProviderManager.getInstance().addIQProvider(NAME, NAMESPACE, new Provider()); } /** * Get Public Address from the Server. * * @param xmppConnection * @return public IP String or null if not found */ public static String getPublicIP(Connection xmppConnection) { if (!xmppConnection.isConnected()) { return null; } final RTPBridge rtpPacket = new RTPBridge( RTPBridge.BridgeAction.publicip); rtpPacket.setTo(RTPBridge.NAME + "." + xmppConnection.getServiceName()); rtpPacket.setType(Type.SET); // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + // candidate.getPort()); final PacketCollector collector = xmppConnection .createPacketCollector(new PacketIDFilter(rtpPacket .getPacketID())); xmppConnection.sendPacket(rtpPacket); final RTPBridge response = (RTPBridge) collector .nextResult(SmackConfiguration.getPacketReplyTimeout()); // Cancel the collector. collector.cancel(); if (response == null) { return null; } if (response.getIp() == null || response.getIp().equals("")) { return null; } Enumeration ifaces = null; try { ifaces = NetworkInterface.getNetworkInterfaces(); } catch (final SocketException e) { e.printStackTrace(); } while (ifaces != null && ifaces.hasMoreElements()) { final NetworkInterface iface = (NetworkInterface) ifaces .nextElement(); final Enumeration iaddresses = iface.getInetAddresses(); while (iaddresses.hasMoreElements()) { final InetAddress iaddress = (InetAddress) iaddresses .nextElement(); if (!iaddress.isLoopbackAddress()) { if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0) { return null; } } } } return response.getIp(); } /** * 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 * @param sessionID * @return */ public static RTPBridge getRTPBridge(Connection connection, String sessionID) { if (!connection.isConnected()) { return null; } final RTPBridge rtpPacket = new RTPBridge(sessionID); rtpPacket.setTo(RTPBridge.NAME + "." + connection.getServiceName()); final PacketCollector collector = connection .createPacketCollector(new PacketIDFilter(rtpPacket .getPacketID())); connection.sendPacket(rtpPacket); final RTPBridge response = (RTPBridge) collector .nextResult(SmackConfiguration.getPacketReplyTimeout()); // Cancel the collector. collector.cancel(); return response; } /** * Check if the server support RTPBridge Service. * * @param connection * @return */ public static RTPBridge relaySession(Connection connection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) { if (!connection.isConnected()) { return null; } final RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change); rtpPacket.setTo(RTPBridge.NAME + "." + connection.getServiceName()); 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()); final PacketCollector collector = connection .createPacketCollector(new PacketIDFilter(rtpPacket .getPacketID())); connection.sendPacket(rtpPacket); final RTPBridge response = (RTPBridge) collector .nextResult(SmackConfiguration.getPacketReplyTimeout()); // Cancel the collector. collector.cancel(); return response; } /** * Check if the server support RTPBridge Service. * * @param connection * @return */ public static boolean serviceAvailable(Connection connection) { if (!connection.isConnected()) { return false; } LOGGER.debug("Service listing"); final ServiceDiscoveryManager disco = ServiceDiscoveryManager .getInstanceFor(connection); try { // DiscoverItems items = // disco.discoverItems(connection.getServiceName()); // Iterator iter = items.getItems(); // while (iter.hasNext()) { // DiscoverItems.Item item = (DiscoverItems.Item) iter.next(); // if (item.getEntityID().startsWith("rtpbridge.")) { // return true; // } // } final DiscoverInfo discoInfo = disco.discoverInfo(connection .getServiceName()); final Iterator iter = discoInfo.getIdentities(); while (iter.hasNext()) { final DiscoverInfo.Identity identity = (DiscoverInfo.Identity) iter .next(); if ((identity.getName() != null) && (identity.getName().startsWith("rtpbridge"))) { return true; } } } catch (final XMPPException e) { e.printStackTrace(); } return false; } /** * Creates a RTPBridge Packet without Session ID */ public RTPBridge() { } /** * Creates a RTPBridge Instance with defined Session ID * * @param action */ public RTPBridge(BridgeAction action) { bridgeAction = action; } /** * Creates a RTPBridge Instance with defined Session ID * * @param sid */ public RTPBridge(String sid) { this.sid = sid; } /** * Creates a RTPBridge Instance with defined Session ID * * @param sid * @param bridgeAction */ public RTPBridge(String sid, BridgeAction bridgeAction) { this.sid = sid; this.bridgeAction = bridgeAction; } /** * Get the attributes string */ public String getAttributes() { final 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 Child Element XML of the Packet * * @return */ @Override public String getChildElementXML() { final StringBuilder str = new StringBuilder(); str.append("<" + ELEMENT_NAME + " xmlns='" + NAMESPACE + "' sid='") .append(sid).append("'>"); 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(" />"); } str.append("</" + ELEMENT_NAME + ">"); return str.toString(); } /** * Get the Host A IP Address * * @return */ public String getHostA() { return hostA; } /** * Get the Host B IP Address * * @return */ public String getHostB() { return hostB; } /** * Get the RTP Bridge IP * * @return */ public String getIp() { return ip; } /** * Get the name of the Candidate * * @return */ public String getName() { return name; } /** * Get the RTP Agent Pass * * @return */ public String getPass() { return pass; } /** * Get Side A receive port * * @return */ public int getPortA() { return portA; } /** * Get Side B receive port * * @return */ public int getPortB() { return portB; } /** * Get the Session ID of the Packet (usually same as Jingle Session ID) * * @return */ public String getSid() { return sid; } /** * Set the Host A IP Address * * @param hostA */ public void setHostA(String hostA) { this.hostA = hostA; } /** * Set the Host B IP Address * * @param hostB */ public void setHostB(String hostB) { this.hostB = hostB; } /** * Set the RTP Bridge IP * * @param ip */ public void setIp(String ip) { this.ip = ip; } /** * Set the name of the Candidate * * @param name */ public void setName(String name) { this.name = name; } /** * Set the RTP Agent Pass * * @param pass */ public void setPass(String pass) { this.pass = pass; } /** * Set Side A receive port * * @param portA */ public void setPortA(int portA) { this.portA = portA; } /** * Set Side B receive port * * @param portB */ public void setPortB(int portB) { this.portB = portB; } /** * Set the Session ID of the Packet (usually same as Jingle Session ID) * * @param sid */ public void setSid(String sid) { this.sid = sid; } }