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}