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}