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