001/** 002 * 003 * Copyright 2003-2006 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.jingleold.nat; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.logging.Logger; 023 024import org.jivesoftware.smack.SmackException; 025import org.jivesoftware.smack.SmackException.NotConnectedException; 026import org.jivesoftware.smack.StanzaCollector; 027import org.jivesoftware.smack.XMPPConnection; 028import org.jivesoftware.smack.XMPPException; 029import org.jivesoftware.smack.packet.SimpleIQ; 030import org.jivesoftware.smack.packet.XmlEnvironment; 031import org.jivesoftware.smack.provider.IQProvider; 032import org.jivesoftware.smack.provider.ProviderManager; 033import org.jivesoftware.smack.xml.XmlPullParser; 034import org.jivesoftware.smack.xml.XmlPullParserException; 035 036import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 037import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 038import org.jivesoftware.smackx.disco.packet.DiscoverItems; 039 040import org.jxmpp.jid.DomainBareJid; 041import org.jxmpp.jid.impl.JidCreate; 042import org.jxmpp.stringprep.XmppStringprepException; 043 044/** 045 * 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. 046 * 047 * High Level Usage Example: 048 * 049 * STUN stun = STUN.getSTUNServer(connection); 050 * 051 * @author Thiago Camargo 052 */ 053public class STUN extends SimpleIQ { 054 055 private static final Logger LOGGER = Logger.getLogger(STUN.class.getName()); 056 057 private final List<StunServerAddress> servers = new ArrayList<>(); 058 059 private String publicIp = null; 060 061 /** 062 * Element name of the stanza extension. 063 */ 064 public static final String DOMAIN = "stun"; 065 066 /** 067 * Element name of the stanza extension. 068 */ 069 public static final String ELEMENT_NAME = "query"; 070 071 /** 072 * Namespace of the stanza extension. 073 */ 074 public static final String NAMESPACE = "google:jingleinfo"; 075 076 static { 077 ProviderManager.addIQProvider(ELEMENT_NAME, NAMESPACE, new STUN.Provider()); 078 } 079 080 /** 081 * Creates a STUN IQ. 082 */ 083 public STUN() { 084 super(ELEMENT_NAME, NAMESPACE); 085 } 086 087 /** 088 * Get a list of STUN Servers recommended by the Server. 089 * 090 * @return the list of STUN servers 091 */ 092 public List<StunServerAddress> getServers() { 093 return servers; 094 } 095 096 /** 097 * Get Public Ip returned from the XMPP server. 098 * 099 * @return the public IP 100 */ 101 public String getPublicIp() { 102 return publicIp; 103 } 104 105 /** 106 * Set Public Ip returned from the XMPP server 107 * 108 * @param publicIp TODO javadoc me please 109 */ 110 private void setPublicIp(String publicIp) { 111 this.publicIp = publicIp; 112 } 113 114 /** 115 * IQProvider for RTP Bridge packets. 116 * Parse receive RTPBridge stanza to a RTPBridge instance 117 * 118 * @author Thiago Rocha 119 */ 120 public static class Provider extends IQProvider<STUN> { 121 122 @Override 123 public STUN parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) 124 throws XmlPullParserException, 125 IOException { 126 127 boolean done = false; 128 129 XmlPullParser.Event eventType; 130 String elementName; 131 132 if (!parser.getNamespace().equals(NAMESPACE)) 133 throw new IOException("Not a STUN packet"); 134 135 STUN iq = new STUN(); 136 137 // Start processing sub-elements 138 while (!done) { 139 eventType = parser.next(); 140 elementName = parser.getName(); 141 142 if (eventType == XmlPullParser.Event.START_ELEMENT) { 143 if (elementName.equals("server")) { 144 String host = null; 145 String port = null; 146 for (int i = 0; i < parser.getAttributeCount(); i++) { 147 if (parser.getAttributeName(i).equals("host")) 148 host = parser.getAttributeValue(i); 149 else if (parser.getAttributeName(i).equals("udp")) 150 port = parser.getAttributeValue(i); 151 } 152 if (host != null && port != null) 153 iq.servers.add(new StunServerAddress(host, port)); 154 } 155 else if (elementName.equals("publicip")) { 156 String host = null; 157 for (int i = 0; i < parser.getAttributeCount(); i++) { 158 if (parser.getAttributeName(i).equals("ip")) 159 host = parser.getAttributeValue(i); 160 } 161 if (host != null && !host.equals("")) 162 iq.setPublicIp(host); 163 } 164 } 165 else if (eventType == XmlPullParser.Event.END_ELEMENT) { 166 if (parser.getName().equals(ELEMENT_NAME)) { 167 done = true; 168 } 169 } 170 } 171 return iq; 172 } 173 } 174 175 /** 176 * Get a new STUN Server Address and port from the server. 177 * If a error occurs or the server don't support STUN Service, null is returned. 178 * 179 * @param connection TODO javadoc me please 180 * @return the STUN server address 181 * @throws NotConnectedException if the XMPP connection is not connected. 182 * @throws InterruptedException if the calling thread was interrupted. 183 */ 184 public static STUN getSTUNServer(XMPPConnection connection) throws NotConnectedException, InterruptedException { 185 186 if (!connection.isConnected()) { 187 return null; 188 } 189 190 STUN stunPacket = new STUN(); 191 DomainBareJid jid; 192 try { 193 jid = JidCreate.domainBareFrom(DOMAIN + "." + connection.getXMPPServiceDomain()); 194 } catch (XmppStringprepException e) { 195 throw new AssertionError(e); 196 } 197 stunPacket.setTo(jid); 198 199 StanzaCollector collector = connection.createStanzaCollectorAndSend(stunPacket); 200 201 STUN response = collector.nextResult(); 202 203 // Cancel the collector. 204 collector.cancel(); 205 206 return response; 207 } 208 209 /** 210 * Check if the server support STUN Service. 211 * 212 * @param connection the connection 213 * @return true if the server support STUN 214 * @throws SmackException if Smack detected an exceptional situation. 215 * @throws XMPPException if an XMPP protocol error was received. 216 * @throws InterruptedException if the calling thread was interrupted. 217 */ 218 public static boolean serviceAvailable(XMPPConnection connection) throws XMPPException, SmackException, InterruptedException { 219 220 if (!connection.isConnected()) { 221 return false; 222 } 223 224 LOGGER.fine("Service listing"); 225 226 ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(connection); 227 DiscoverItems items = disco.discoverItems(connection.getXMPPServiceDomain()); 228 229 for (DiscoverItems.Item item : items.getItems()) { 230 DiscoverInfo info = disco.discoverInfo(item.getEntityID()); 231 232 for (DiscoverInfo.Identity identity : info.getIdentities()) { 233 if (identity.getCategory().equals("proxy") && identity.getType().equals("stun")) 234 if (info.containsFeature(NAMESPACE)) 235 return true; 236 } 237 238 LOGGER.fine(item.getName() + "-" + info.getType()); 239 240 } 241 242 return false; 243 } 244 245 /** 246 * Provides easy abstract to store STUN Server Addresses and Ports. 247 */ 248 public static class StunServerAddress { 249 250 private String server; 251 private String port; 252 253 public StunServerAddress(String server, String port) { 254 this.server = server; 255 this.port = port; 256 } 257 258 /** 259 * Get the Host Address. 260 * 261 * @return the host address 262 */ 263 public String getServer() { 264 return server; 265 } 266 267 /** 268 * Get the Server Port. 269 * 270 * @return the server port 271 */ 272 public String getPort() { 273 return port; 274 } 275 } 276}