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