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}