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