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