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