001/** 002 * 003 * Copyright 2003-2005 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.net.InetAddress; 020import java.net.NetworkInterface; 021import java.net.SocketException; 022import java.net.UnknownHostException; 023import java.util.Enumeration; 024import java.util.HashMap; 025import java.util.Map; 026import java.util.Random; 027import java.util.logging.Level; 028import java.util.logging.Logger; 029 030import org.jivesoftware.smack.SmackException; 031import org.jivesoftware.smack.XMPPConnection; 032import org.jivesoftware.smack.XMPPException; 033import org.jivesoftware.smackx.jingleold.JingleSession; 034 035import de.javawi.jstun.test.demo.ice.Candidate; 036import de.javawi.jstun.test.demo.ice.ICENegociator; 037import de.javawi.jstun.util.UtilityException; 038 039/** 040 * ICE Resolver for Jingle transport method that results in sending data between two entities using the Interactive Connectivity Establishment (ICE) methodology. (XEP-0176) 041 * The goal of this resolver is to make possible to establish and manage out-of-band connections between two XMPP entities, even if they are behind Network Address Translators (NATs) or firewalls. 042 * To use this resolver you must have a STUN Server and be in a non STUN blocked network. Or use an XMPP server with public IP detection Service. 043 * 044 * @author Thiago Camargo 045 */ 046public class ICEResolver extends TransportResolver { 047 048 private static final Logger LOGGER = Logger.getLogger(ICEResolver.class.getName()); 049 050 XMPPConnection connection; 051 Random random = new Random(); 052 long sid; 053 String server; 054 int port; 055 static Map<String, ICENegociator> negociatorsMap = new HashMap<String, ICENegociator>(); 056 //ICENegociator iceNegociator = null; 057 058 public ICEResolver(XMPPConnection connection, String server, int port) { 059 super(); 060 this.connection = connection; 061 this.server = server; 062 this.port = port; 063 this.setType(Type.ice); 064 } 065 066 @Override 067 public void initialize() throws XMPPException { 068 if (!isResolving() && !isResolved()) { 069 LOGGER.fine("Initialized"); 070 071 // Negotiation with a STUN server for a set of interfaces is quite slow, but the results 072 // never change over then instance of a JVM. To increase connection performance considerably 073 // we now cache established/initialized negotiators for each STUN server, so that subsequent uses 074 // of the STUN server are much, much faster. 075 if (negociatorsMap.get(server) == null) { 076 // CHECKSTYLE:OFF 077 ICENegociator iceNegociator = new ICENegociator(server, port, (short) 1); 078 negociatorsMap.put(server, iceNegociator); 079 080 // gather candidates 081 iceNegociator.gatherCandidateAddresses(); 082 // priorize candidates 083 iceNegociator.prioritizeCandidates(); 084 // CHECKSTYLE:ON 085 } 086 087 } 088 this.setInitialized(); 089 } 090 091 @Override 092 public void cancel() throws XMPPException { 093 094 } 095 096 /** 097 * Resolve the IP and obtain a valid transport method. 098 * @throws SmackException 099 * @throws InterruptedException 100 */ 101 @Override 102 public synchronized void resolve(JingleSession session) throws XMPPException, SmackException, InterruptedException { 103 this.setResolveInit(); 104 105 for (TransportCandidate candidate : this.getCandidatesList()) { 106 if (candidate instanceof ICECandidate) { 107 ICECandidate iceCandidate = (ICECandidate) candidate; 108 iceCandidate.removeCandidateEcho(); 109 } 110 } 111 112 this.clear(); 113 114 // Create a transport candidate for each ICE negotiator candidate we have. 115 ICENegociator iceNegociator = negociatorsMap.get(server); 116 for (Candidate candidate : iceNegociator.getSortedCandidates()) 117 try { 118 Candidate.CandidateType type = candidate.getCandidateType(); 119 ICECandidate.Type iceType = ICECandidate.Type.local; 120 if (type.equals(Candidate.CandidateType.ServerReflexive)) 121 iceType = ICECandidate.Type.srflx; 122 else if (type.equals(Candidate.CandidateType.PeerReflexive)) 123 iceType = ICECandidate.Type.prflx; 124 else if (type.equals(Candidate.CandidateType.Relayed)) 125 iceType = ICECandidate.Type.relay; 126 else 127 iceType = ICECandidate.Type.host; 128 129 // JBW/GW - 17JUL08: Figure out the zero-based NIC number for this candidate. 130 short nicNum = 0; 131 try { 132 Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces(); 133 short i = 0; 134 NetworkInterface nic = NetworkInterface.getByInetAddress(candidate.getAddress().getInetAddress()); 135 while(nics.hasMoreElements()) { 136 NetworkInterface checkNIC = nics.nextElement(); 137 if (checkNIC.equals(nic)) { 138 nicNum = i; 139 break; 140 } 141 i++; 142 } 143 } catch (SocketException e1) { 144 LOGGER.log(Level.WARNING, "exeption", e1); 145 } 146 147 TransportCandidate transportCandidate = new ICECandidate(candidate.getAddress().getInetAddress().getHostAddress(), 1, nicNum, String.valueOf(Math.abs(random.nextLong())), candidate.getPort(), "1", candidate.getPriority(), iceType); 148 transportCandidate.setLocalIp(candidate.getBase().getAddress().getInetAddress().getHostAddress()); 149 transportCandidate.setPort(getFreePort()); 150 try { 151 transportCandidate.addCandidateEcho(session); 152 } 153 catch (SocketException e) { 154 LOGGER.log(Level.WARNING, "exception", e); 155 } 156 this.addCandidate(transportCandidate); 157 158 LOGGER.fine("Candidate addr: " + candidate.getAddress().getInetAddress() + "|" + candidate.getBase().getAddress().getInetAddress() + " Priority:" + candidate.getPriority()); 159 160 } 161 catch (UtilityException e) { 162 LOGGER.log(Level.WARNING, "exception", e); 163 } 164 catch (UnknownHostException e) { 165 LOGGER.log(Level.WARNING, "exception", e); 166 } 167 168 // Get a Relay Candidate from XMPP Server 169 170 if (RTPBridge.serviceAvailable(connection)) { 171// try { 172 173 String localIp; 174 int network; 175 176 177 // JBW/GW - 17JUL08: ICENegotiator.getPublicCandidate() always returned null in JSTUN 1.7.0, and now the API doesn't exist in JSTUN 1.7.1 178// if (iceNegociator.getPublicCandidate() != null) { 179// localIp = iceNegociator.getPublicCandidate().getBase().getAddress().getInetAddress().getHostAddress(); 180// network = iceNegociator.getPublicCandidate().getNetwork(); 181// } 182// else { 183 { 184 localIp = BridgedResolver.getLocalHost(); 185 network = 0; 186 } 187 188 sid = Math.abs(random.nextLong()); 189 190 RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, String.valueOf(sid)); 191 192 TransportCandidate localCandidate = new ICECandidate( 193 rtpBridge.getIp(), 1, network, String.valueOf(Math.abs(random.nextLong())), rtpBridge.getPortA(), "1", 0, ICECandidate.Type.relay); 194 localCandidate.setLocalIp(localIp); 195 196 TransportCandidate remoteCandidate = new ICECandidate( 197 rtpBridge.getIp(), 1, network, String.valueOf(Math.abs(random.nextLong())), rtpBridge.getPortB(), "1", 0, ICECandidate.Type.relay); 198 remoteCandidate.setLocalIp(localIp); 199 200 localCandidate.setSymmetric(remoteCandidate); 201 remoteCandidate.setSymmetric(localCandidate); 202 203 localCandidate.setPassword(rtpBridge.getPass()); 204 remoteCandidate.setPassword(rtpBridge.getPass()); 205 206 localCandidate.setSessionId(rtpBridge.getSid()); 207 remoteCandidate.setSessionId(rtpBridge.getSid()); 208 209 localCandidate.setConnection(this.connection); 210 remoteCandidate.setConnection(this.connection); 211 212 addCandidate(localCandidate); 213 214// } 215// catch (UtilityException e) { 216// LOGGER.log(Level.WARNING, "exception", e); 217// } 218// catch (UnknownHostException e) { 219// LOGGER.log(Level.WARNING, "exception", e); 220// } 221 222 // Get Public Candidate From XMPP Server 223 224 // JBW/GW - 17JUL08 - ICENegotiator.getPublicCandidate() always returned null in JSTUN 1.7.0, and now it doesn't exist in JSTUN 1.7.1 225 // if (iceNegociator.getPublicCandidate() == null) { 226 if (true) { 227 228 String publicIp = RTPBridge.getPublicIP(connection); 229 230 if (publicIp != null && !publicIp.equals("")) { 231 232 Enumeration<NetworkInterface> ifaces = null; 233 234 try { 235 ifaces = NetworkInterface.getNetworkInterfaces(); 236 } 237 catch (SocketException e) { 238 LOGGER.log(Level.WARNING, "exception", e); 239 } 240 241 // If detect this address in local machine, don't use it. 242 243 boolean found = false; 244 245 while (ifaces.hasMoreElements() && !false) { 246 247 NetworkInterface iface = ifaces.nextElement(); 248 Enumeration<InetAddress> iaddresses = iface.getInetAddresses(); 249 250 while (iaddresses.hasMoreElements()) { 251 InetAddress iaddress = iaddresses.nextElement(); 252 if (iaddress.getHostAddress().indexOf(publicIp) > -1) { 253 found = true; 254 break; 255 } 256 } 257 } 258 259 if (!found) { 260 try { 261 TransportCandidate publicCandidate = new ICECandidate( 262 publicIp, 1, 0, String.valueOf(Math.abs(random.nextLong())), getFreePort(), "1", 0, ICECandidate.Type.srflx); 263 publicCandidate.setLocalIp(InetAddress.getLocalHost().getHostAddress()); 264 265 try { 266 publicCandidate.addCandidateEcho(session); 267 } 268 catch (SocketException e) { 269 LOGGER.log(Level.WARNING, "exception", e); 270 } 271 272 addCandidate(publicCandidate); 273 } 274 catch (UnknownHostException e) { 275 LOGGER.log(Level.WARNING, "exception", e); 276 } 277 } 278 } 279 } 280 281 } 282 283 this.setResolveEnd(); 284 } 285 286}