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
100     * @throws InterruptedException
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(Math.abs(random.nextLong())), 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 = Math.abs(random.nextLong());
190
191                RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, String.valueOf(sid));
192
193                TransportCandidate localCandidate = new ICECandidate(
194                        rtpBridge.getIp(), 1, network, String.valueOf(Math.abs(random.nextLong())), rtpBridge.getPortA(), "1", 0, ICECandidate.Type.relay);
195                localCandidate.setLocalIp(localIp);
196
197                TransportCandidate remoteCandidate = new ICECandidate(
198                        rtpBridge.getIp(), 1, network, String.valueOf(Math.abs(random.nextLong())), 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(Math.abs(random.nextLong())), 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}