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