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