DNSUtil.java 11.4 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile: DNSUtil.java,v $
 * $Revision: 2867 $
 * $Date: 2005-09-22 03:40:04 -0300 (Thu, 22 Sep 2005) $
 *
6
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
7 8
 *
 * This software is published under the terms of the GNU Public License (GPL),
9 10
 * a copy of which is included in this distribution, or a commercial license
 * agreement with Jive.
11 12
 */

13
package org.jivesoftware.openfire.net;
14

15
import org.jivesoftware.util.JiveGlobals;
16 17
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
18

19
import javax.naming.NameNotFoundException;
20 21
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
22 23
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
24
import javax.naming.directory.InitialDirContext;
25

26
import java.io.Serializable;
27 28 29
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
30
import java.util.HashMap;
31
import java.util.Hashtable;
32
import java.util.List;
33 34
import java.util.Map;
import java.util.StringTokenizer;
35 36 37 38 39 40 41 42 43 44

/**
 * Utilty class to perform DNS lookups for XMPP services.
 *
 * @author Matt Tucker
 */
public class DNSUtil {

    private static DirContext context;

45 46
    private static Logger logger = LoggerFactory.getLogger(DNSUtil.class);

47 48 49 50
    /**
     * Internal DNS that allows to specify target IP addresses and ports to use for domains.
     * The internal DNS will be checked up before performing an actual DNS SRV lookup.
     */
51
    private static Map<String, HostAddress> dnsOverride;
52

53 54 55 56 57
    static {
        try {
            Hashtable<String,String> env = new Hashtable<String,String>();
            env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
            context = new InitialDirContext(env);
58

59
            String property = JiveGlobals.getProperty("dnsutil.dnsOverride");
60
            if (property != null) {
61
                dnsOverride = decode(property);
62
            }
63 64
        }
        catch (Exception e) {
65
            logger.error("Can't initialize DNS context!", e);
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
        }
    }

    /**
     * Returns the host name and port that the specified XMPP server can be
     * reached at for server-to-server communication. A DNS lookup for a SRV
     * record in the form "_xmpp-server._tcp.example.com" is attempted, according
     * to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form
     * of "_jabber._tcp.example.com" is attempted since servers that implement an
     * older version of the protocol may be listed using that notation. If that
     * lookup fails as well, it's assumed that the XMPP server lives at the
     * host resolved by a DNS lookup at the specified domain on the specified default port.<p>
     *
     * As an example, a lookup for "example.com" may return "im.example.com:5269".
     *
     * @param domain the domain.
     * @param defaultPort default port to return if the DNS look up fails.
     * @return a HostAddress, which encompasses the hostname and port that the XMPP
     *      server can be reached at for the specified domain.
85 86
     * @deprecated replaced with support for multiple srv records, see 
     *      {@link #resolveXMPPDomain(String, int)}
87
     */
88
    @Deprecated
89
    public static HostAddress resolveXMPPServerDomain(String domain, int defaultPort) {
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
        return resolveXMPPDomain(domain, defaultPort).get(0);
    }

    /**
     * Returns a sorted list of host names and ports that the specified XMPP domain
     * can be reached at for server-to-server communication. A DNS lookup for a SRV
     * record in the form "_xmpp-server._tcp.example.com" is attempted, according
     * to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form
     * of "_jabber._tcp.example.com" is attempted since servers that implement an
     * older version of the protocol may be listed using that notation. If that
     * lookup fails as well, it's assumed that the XMPP server lives at the
     * host resolved by a DNS lookup at the specified domain on the specified default port.<p>
     *
     * As an example, a lookup for "example.com" may return "im.example.com:5269".
     *
     * @param domain the domain.
     * @param defaultPort default port to return if the DNS look up fails.
     * @return a list of  HostAddresses, which encompasses the hostname and port that the XMPP
     *      server can be reached at for the specified domain.
     */
    public static List<HostAddress> resolveXMPPDomain(String domain, int defaultPort) {
111
        // Check if there is an entry in the internal DNS for the specified domain
112
        List<HostAddress> results = null;
113 114
        if (dnsOverride != null) {
            HostAddress hostAddress = dnsOverride.get(domain);
115
            if (hostAddress != null) {
116 117 118
                results = new ArrayList<HostAddress>();
                results.add(hostAddress);
                return results;
119 120
            }
        }
121 122

        // Attempt the SRV lookup.
123 124 125
        results = srvLookup("_xmpp-server._tcp." + domain);
        if (results == null || results.isEmpty()) {
            results = srvLookup("_jabber._tcp." + domain);
126
        }
127 128

        // Use domain and default port as fallback.
129
        if (results.isEmpty()) {
130
            results.add(new HostAddress(domain, defaultPort));
131
        }
132
        return results;
133 134
    }

135 136 137 138 139 140 141 142
    /**
     * Returns the internal DNS that allows to specify target IP addresses and ports
     * to use for domains. The internal DNS will be checked up before performing an
     * actual DNS SRV lookup.
     *
     * @return the internal DNS that allows to specify target IP addresses and ports
     *         to use for domains.
     */
143 144
    public static Map<String, HostAddress> getDnsOverride() {
        return dnsOverride;
145 146 147 148 149 150 151
    }

    /**
     * Sets the internal DNS that allows to specify target IP addresses and ports
     * to use for domains. The internal DNS will be checked up before performing an
     * actual DNS SRV lookup.
     *
152
     * @param dnsOverride the internal DNS that allows to specify target IP addresses and ports
153 154
     *        to use for domains.
     */
155 156 157
    public static void setDnsOverride(Map<String, HostAddress> dnsOverride) {
        DNSUtil.dnsOverride = dnsOverride;
        JiveGlobals.setProperty("dnsutil.dnsOverride", encode(dnsOverride));
158 159 160 161 162 163 164 165 166 167 168
    }

    private static String encode(Map<String, HostAddress> internalDNS) {
        if (internalDNS == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder(100);
        for (String key : internalDNS.keySet()) {
            if (sb.length() > 0) {
                sb.append(",");
            }
169 170 171
            sb.append("{").append(key).append(",");
            sb.append(internalDNS.get(key).getHost()).append(":");
            sb.append(internalDNS.get(key).getPort()).append("}");
172 173 174 175 176 177
        }
        return sb.toString();
    }

    private static Map<String, HostAddress> decode(String encodedValue) {
        Map<String, HostAddress> answer = new HashMap<String, HostAddress>();
178
        StringTokenizer st = new StringTokenizer(encodedValue, "{},:");
179 180 181 182 183 184 185
        while (st.hasMoreElements()) {
            String key = st.nextToken();
            answer.put(key, new HostAddress(st.nextToken(), Integer.parseInt(st.nextToken())));
        }
        return answer;
    }

186 187 188
    private static List<HostAddress> srvLookup(String lookup) {
        if (lookup == null) {
            throw new NullPointerException("DNS lookup can't be null");
189
        }
190 191 192 193 194 195 196 197 198 199 200 201
        try {
            Attributes dnsLookup =
                    context.getAttributes(lookup, new String[]{"SRV"});
            Attribute srvRecords = dnsLookup.get("SRV");
            HostAddress[] hosts = new WeightedHostAddress[srvRecords.size()];
            for (int i = 0; i < srvRecords.size(); i++) {
                hosts[i] = new WeightedHostAddress(((String)srvRecords.get(i)).split(" "));
            }
            if (srvRecords.size() > 1) {
                Arrays.sort(hosts, new SrvRecordWeightedPriorityComparator());
            }
            return Arrays.asList(hosts);
202
        }
203
        catch (NameNotFoundException e) {
204
            logger.debug("No SRV record found for: " + lookup, e);
205 206
        }
        catch (NamingException e) {
207
            logger.error("Can't process DNS lookup!", e);
208
        }
209
        return new ArrayList<HostAddress>();
210 211
    }

212 213 214 215 216
    /**
     * Encapsulates a hostname and port.
     */
    public static class HostAddress {

217 218
        private final String host;
        private final int port;
219 220

        private HostAddress(String host, int port) {
221 222 223 224 225 226 227
            // Host entries in DNS should end with a ".".
            if (host.endsWith(".")) {
                this.host = host.substring(0, host.length()-1);
            }
            else {
                this.host = host;
            }
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
            this.port = port;
        }

        /**
         * Returns the hostname.
         *
         * @return the hostname.
         */
        public String getHost() {
            return host;
        }

        /**
         * Returns the port.
         *
         * @return the port.
         */
        public int getPort() {
            return port;
        }

        public String toString() {
            return host + ":" + port;
        }
    }
253 254 255 256 257 258

    /**
     * The representation of weighted address.
     */
    public static class WeightedHostAddress extends HostAddress {

259 260
        private final int priority;
        private final int weight;
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

        private WeightedHostAddress(String [] srvRecordEntries) {
            super(srvRecordEntries[srvRecordEntries.length-1], 
                    Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]));
            weight = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-3]);
            priority = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-4]);
        }

        private WeightedHostAddress(String host, int port, int priority, int weight) {
            super(host, port);
            this.priority = priority;
            this.weight = weight;
        }

        /**
         * Returns the priority.
         * 
         * @return the priority.
         */
        public int getPriority() {
            return priority;
        }

        /**
         * Returns the weight.
         * 
         * @return the weight.
         */
        public int getWeight() {
            return weight;
        }
    }

    /**
     * A comparator for sorting multiple weighted host addresses according to RFC 2782.
     */
297 298 299
    public static class SrvRecordWeightedPriorityComparator implements Comparator<HostAddress>, Serializable {
        private static final long serialVersionUID = -9207293572898848260L;

300 301 302 303 304 305 306 307 308 309 310 311 312
        public int compare(HostAddress o1, HostAddress o2) {
            if (o1 instanceof WeightedHostAddress && o2 instanceof WeightedHostAddress) {
                WeightedHostAddress srv1 = (WeightedHostAddress) o1;
                WeightedHostAddress srv2 = (WeightedHostAddress) o2;
                // 16 bit unsigned priority is more important as the 16 bit weight
                return ((srv1.priority << 15) - (srv2.priority << 15)) + (srv2.weight - srv1.weight);
            }
            else {
                // This shouldn't happen but if we don't have priorities we sort the addresses
                return o1.toString().compareTo(o2.toString());
            }
        }
    }
313
}