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.jingle.nat; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.logging.Logger; 022 023import org.jivesoftware.smack.SmackException; 024import org.jivesoftware.smack.SmackException.NotConnectedException; 025import org.jivesoftware.smack.XMPPConnection; 026import org.jivesoftware.smack.PacketCollector; 027import org.jivesoftware.smack.XMPPException; 028import org.jivesoftware.smack.packet.IQ; 029import org.jivesoftware.smack.provider.IQProvider; 030import org.jivesoftware.smack.provider.ProviderManager; 031import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 032import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 033import org.jivesoftware.smackx.disco.packet.DiscoverItems; 034import org.xmlpull.v1.XmlPullParser; 035 036/** 037 * STUN IQ Packet 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. 038 * <p/> 039 * High Level Usage Example: 040 * <p/> 041 * STUN stun = STUN.getSTUNServer(connection); 042 * 043 * @author Thiago Camargo 044 */ 045public class STUN extends IQ { 046 047 private static final Logger LOGGER = Logger.getLogger(STUN.class.getName()); 048 049 private List<StunServerAddress> servers = new ArrayList<StunServerAddress>(); 050 051 private String publicIp = null; 052 053 /** 054 * Element name of the packet extension. 055 */ 056 public static final String DOMAIN = "stun"; 057 058 /** 059 * Element name of the packet extension. 060 */ 061 public static final String ELEMENT_NAME = "query"; 062 063 /** 064 * Namespace of the packet extension. 065 */ 066 public static final String NAMESPACE = "google:jingleinfo"; 067 068 static { 069 ProviderManager.addIQProvider(ELEMENT_NAME, NAMESPACE, new STUN.Provider()); 070 } 071 072 /** 073 * Creates a STUN IQ 074 */ 075 public STUN() { 076 } 077 078 /** 079 * Get a list of STUN Servers recommended by the Server 080 * 081 * @return the list of STUN servers 082 */ 083 public List<StunServerAddress> getServers() { 084 return servers; 085 } 086 087 /** 088 * Get Public Ip returned from the XMPP server 089 * 090 * @return the public IP 091 */ 092 public String getPublicIp() { 093 return publicIp; 094 } 095 096 /** 097 * Set Public Ip returned from the XMPP server 098 * 099 * @param publicIp 100 */ 101 private void setPublicIp(String publicIp) { 102 this.publicIp = publicIp; 103 } 104 105 /** 106 * Get the Child Element XML of the Packet 107 * 108 * @return the child element XML 109 */ 110 public String getChildElementXML() { 111 StringBuilder str = new StringBuilder(); 112 str.append("<" + ELEMENT_NAME + " xmlns='" + NAMESPACE + "'/>"); 113 return str.toString(); 114 } 115 116 /** 117 * IQProvider for RTP Bridge packets. 118 * Parse receive RTPBridge packet to a RTPBridge instance 119 * 120 * @author Thiago Rocha 121 */ 122 public static class Provider implements IQProvider { 123 124 public Provider() { 125 super(); 126 } 127 128 public IQ parseIQ(XmlPullParser parser) throws Exception { 129 130 boolean done = false; 131 132 int eventType; 133 String elementName; 134 135 if (!parser.getNamespace().equals(NAMESPACE)) 136 throw new Exception("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.START_TAG) { 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.END_TAG) { 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 183 * @return the STUN server address 184 * @throws NotConnectedException 185 */ 186 public static STUN getSTUNServer(XMPPConnection connection) throws NotConnectedException { 187 188 if (!connection.isConnected()) { 189 return null; 190 } 191 192 STUN stunPacket = new STUN(); 193 stunPacket.setTo(DOMAIN + "." + connection.getServiceName()); 194 195 PacketCollector collector = connection.createPacketCollectorAndSend(stunPacket); 196 197 STUN response = (STUN) collector.nextResult(); 198 199 // Cancel the collector. 200 collector.cancel(); 201 202 return response; 203 } 204 205 /** 206 * Check if the server support STUN Service. 207 * 208 * @param connection the connection 209 * @return true if the server support STUN 210 * @throws SmackException 211 * @throws XMPPException 212 */ 213 public static boolean serviceAvailable(XMPPConnection connection) throws XMPPException, SmackException { 214 215 if (!connection.isConnected()) { 216 return false; 217 } 218 219 LOGGER.fine("Service listing"); 220 221 ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(connection); 222 DiscoverItems items = disco.discoverItems(connection.getServiceName()); 223 224 for (DiscoverItems.Item item : items.getItems()) { 225 DiscoverInfo info = disco.discoverInfo(item.getEntityID()); 226 227 for (DiscoverInfo.Identity identity : info.getIdentities()) { 228 if (identity.getCategory().equals("proxy") && identity.getType().equals("stun")) 229 if (info.containsFeature(NAMESPACE)) 230 return true; 231 } 232 233 LOGGER.fine(item.getName() + "-" + info.getType()); 234 235 } 236 237 return false; 238 } 239 240 /** 241 * Provides easy abstract to store STUN Server Addresses and Ports 242 */ 243 public static class StunServerAddress { 244 245 private String server; 246 private String port; 247 248 public StunServerAddress(String server, String port) { 249 this.server = server; 250 this.port = port; 251 } 252 253 /** 254 * Get the Host Address 255 * 256 * @return the host address 257 */ 258 public String getServer() { 259 return server; 260 } 261 262 /** 263 * Get the Server Port 264 * 265 * @return the server port 266 */ 267 public String getPort() { 268 return port; 269 } 270 } 271}