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.JxmppContext; 043import org.jxmpp.jid.DomainBareJid; 044import org.jxmpp.jid.impl.JidCreate; 045import org.jxmpp.stringprep.XmppStringprepException; 046 047/** 048 * 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. 049 * 050 * High Level Usage Example: 051 * 052 * STUN stun = STUN.getSTUNServer(connection); 053 * 054 * @author Thiago Camargo 055 */ 056public class STUN extends SimpleIQ { 057 058 private static final Logger LOGGER = Logger.getLogger(STUN.class.getName()); 059 060 private final List<StunServerAddress> servers = new ArrayList<>(); 061 062 private String publicIp = null; 063 064 /** 065 * Element name of the stanza extension. 066 */ 067 public static final String DOMAIN = "stun"; 068 069 /** 070 * Element name of the stanza extension. 071 */ 072 public static final String ELEMENT_NAME = "query"; 073 074 /** 075 * Namespace of the stanza extension. 076 */ 077 public static final String NAMESPACE = "google:jingleinfo"; 078 079 static { 080 ProviderManager.addIQProvider(ELEMENT_NAME, NAMESPACE, new STUN.Provider()); 081 } 082 083 /** 084 * Creates a STUN IQ. 085 */ 086 public STUN() { 087 super(ELEMENT_NAME, NAMESPACE); 088 } 089 090 /** 091 * Get a list of STUN Servers recommended by the Server. 092 * 093 * @return the list of STUN servers 094 */ 095 public List<StunServerAddress> getServers() { 096 return servers; 097 } 098 099 /** 100 * Get Public Ip returned from the XMPP server. 101 * 102 * @return the public IP 103 */ 104 public String getPublicIp() { 105 return publicIp; 106 } 107 108 /** 109 * Set Public Ip returned from the XMPP server 110 * 111 * @param publicIp TODO javadoc me please 112 */ 113 private void setPublicIp(String publicIp) { 114 this.publicIp = publicIp; 115 } 116 117 /** 118 * IQProvider for RTP Bridge packets. 119 * Parse receive RTPBridge stanza to a RTPBridge instance 120 * 121 * @author Thiago Rocha 122 */ 123 public static class Provider extends IqProvider<STUN> { 124 125 @Override 126 public STUN parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment, JxmppContext jxmppContext) 127 throws XmlPullParserException, 128 IOException { 129 130 boolean done = false; 131 132 XmlPullParser.Event eventType; 133 String elementName; 134 135 if (!parser.getNamespace().equals(NAMESPACE)) 136 throw new IOException("Not a STUN packet"); 137 138 STUN iq = new STUN(); 139 140 // Start processing sub-elements 141 while (!done) { 142 eventType = parser.next(); 143 elementName = parser.getName(); 144 145 if (eventType == XmlPullParser.Event.START_ELEMENT) { 146 if (elementName.equals("server")) { 147 String host = null; 148 String port = null; 149 for (int i = 0; i < parser.getAttributeCount(); i++) { 150 if (parser.getAttributeName(i).equals("host")) 151 host = parser.getAttributeValue(i); 152 else if (parser.getAttributeName(i).equals("udp")) 153 port = parser.getAttributeValue(i); 154 } 155 if (host != null && port != null) 156 iq.servers.add(new StunServerAddress(host, port)); 157 } 158 else if (elementName.equals("publicip")) { 159 String host = null; 160 for (int i = 0; i < parser.getAttributeCount(); i++) { 161 if (parser.getAttributeName(i).equals("ip")) 162 host = parser.getAttributeValue(i); 163 } 164 if (host != null && !host.equals("")) 165 iq.setPublicIp(host); 166 } 167 } 168 else if (eventType == XmlPullParser.Event.END_ELEMENT) { 169 if (parser.getName().equals(ELEMENT_NAME)) { 170 done = true; 171 } 172 } 173 } 174 return iq; 175 } 176 } 177 178 /** 179 * Get a new STUN Server Address and port from the server. 180 * If a error occurs or the server don't support STUN Service, null is returned. 181 * 182 * @param connection TODO javadoc me please 183 * @return the STUN server address 184 * @throws NotConnectedException if the XMPP connection is not connected. 185 * @throws InterruptedException if the calling thread was interrupted. 186 * @throws XMPPErrorException if there was an XMPP error returned. 187 * @throws NoResponseException if there was no response from the remote entity. 188 */ 189 public static STUN getSTUNServer(XMPPConnection connection) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { 190 191 if (!connection.isConnected()) { 192 return null; 193 } 194 195 STUN stunPacket = new STUN(); 196 DomainBareJid jid; 197 try { 198 jid = JidCreate.domainBareFrom(DOMAIN + "." + connection.getXMPPServiceDomain()); 199 } catch (XmppStringprepException e) { 200 throw new AssertionError(e); 201 } 202 stunPacket.setTo(jid); 203 204 STUN response = connection.sendIqRequestAndWaitForResponse(stunPacket); 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}