001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS
026 */
027package org.opends.server.types;
028
029import java.net.*;
030import java.util.Enumeration;
031import java.util.HashSet;
032import java.util.Set;
033
034import org.forgerock.i18n.slf4j.LocalizedLogger;
035
036import static org.opends.messages.ReplicationMessages.*;
037
038/**
039 * This class defines a data structure that combines an address and port number,
040 * as may be used to accept a connection from or initiate a connection to a
041 * remote system.
042 * <p>
043 * Due to the possibility of live network configuration changes, instances of
044 * this class are not intended for caching and should be rebuilt on demand.
045 */
046@org.opends.server.types.PublicAPI(
047     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
048     mayInstantiate=false,
049     mayExtend=false,
050     mayInvoke=true)
051public final class HostPort
052{
053
054  /** The tracer object for the debug logger. */
055  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
056
057  /** Constant that represents the local host. */
058  private static final String LOCALHOST = "localhost";
059
060  /**
061   * The wildcard address allows to instruct a server to
062   * "listen to all addresses".
063   *
064   * @see InetSocketAddress#InetSocketAddress(int) InetSocketAddress javadoc
065   */
066  public static final String WILDCARD_ADDRESS = "0.0.0.0";
067
068
069
070  /**
071   * The supplied host for this object.
072   * <p>
073   * Keeping the supplied host name allows to rebuild the HostPort object in
074   * case the network configuration changed on the current machine.
075   */
076  private final String host;
077
078  /**
079   * The normalized host for this object.
080   * <p>
081   * Normalization consists of:
082   * <ul>
083   * <li>convert all local addresses to "localhost"</li>
084   * <li>convert remote host name / addresses to the equivalent IP address</li>
085   * </ul>
086   */
087  private final String normalizedHost;
088
089  /** The port for this object. */
090  private final int port;
091
092
093
094
095  /** Time-stamp acts as memory barrier for networkInterfaces. */
096  private static final long CACHED_LOCAL_ADDRESSES_TIMEOUT_MS = 30 * 1000;
097  private static volatile long localAddressesTimeStamp;
098  private static Set<InetAddress> localAddresses = new HashSet<>();
099
100  /**
101   * Returns {@code true} if the provided {@code InetAddress} represents the
102   * address of one of the interfaces on the current host machine.
103   *
104   * @param address
105   *          The network address.
106   * @return {@code true} if the provided {@code InetAddress} represents the
107   *         address of one of the interfaces on the current host machine.
108   */
109  public static boolean isLocalAddress(InetAddress address)
110  {
111    return address.isLoopbackAddress() || getLocalAddresses().contains(address);
112  }
113
114  /**
115   * Returns a Set of all the local addresses as detected by the Java
116   * environment from the operating system configuration.
117   * <p>
118   * The local addresses are temporarily cached to balance the cost of this
119   * expensive computation vs. refreshing the data that can be changed while the
120   * system is running.
121   *
122   * @return a Set containing all the local addresses
123   */
124  private static Set<InetAddress> getLocalAddresses()
125  {
126    final long currentTimeStamp = System.currentTimeMillis();
127    if (localAddressesTimeStamp
128        < (currentTimeStamp - CACHED_LOCAL_ADDRESSES_TIMEOUT_MS))
129    {
130      // Refresh the cache.
131      try
132      {
133        final Enumeration<NetworkInterface> i =
134            NetworkInterface.getNetworkInterfaces();
135        if (i != null) {
136          final Set<InetAddress> newLocalAddresses = new HashSet<>();
137          while (i.hasMoreElements())
138          {
139            NetworkInterface n = i.nextElement();
140            Enumeration<InetAddress> j = n.getInetAddresses();
141            while (j.hasMoreElements())
142            {
143              newLocalAddresses.add(j.nextElement());
144            }
145          }
146          localAddresses = newLocalAddresses;
147        }
148      }
149      catch (SocketException e)
150      {
151        // Ignore and keep the old set.
152        logger.traceException(e);
153      }
154      localAddressesTimeStamp = currentTimeStamp; // Publishes.
155    }
156    return localAddresses;
157  }
158
159  /**
160   * Returns a a new HostPort for all addresses, also known as a wildcard
161   * address.
162   *
163   * @param port
164   *          The port number for the new {@code HostPort} object.
165   * @return a newly constructed HostPort object
166   */
167  public static HostPort allAddresses(int port)
168  {
169    return new HostPort(port);
170  }
171
172  /**
173   * Builds a new instance of {@link HostPort} representing the local machine
174   * with the supplied port.
175   *
176   * @param port
177   *          the port to use when building the new {@link HostPort} object
178   * @return a new {@link HostPort} instance representing the local machine with
179   *         the supplied port.
180   */
181  public static HostPort localAddress(int port)
182  {
183    return new HostPort(LOCALHOST, port);
184  }
185
186  /**
187   * Creates a new {@code HostPort} object with the specified port number but no
188   * host.
189   *
190   * @param port
191   *          The port number for this {@code HostPort} object.
192   */
193  private HostPort(int port)
194  {
195    this.host = null;
196    this.normalizedHost = null;
197    this.port = normalizePort(port);
198  }
199
200
201
202  /**
203   * Creates a new {@code HostPort} object with the specified port
204   * number but no explicit host.
205   *
206   * @param  host  The host address or name for this {@code HostPort}
207   *               object, or {@code null} if there is none.
208   * @param  port  The port number for this {@code HostPort} object.
209   */
210  public HostPort(String host, int port)
211  {
212    this.host = removeExtraChars(host);
213    this.normalizedHost = normalizeHost(this.host);
214    this.port = normalizePort(port);
215  }
216
217
218
219  /**
220   * Creates a new {@code HostPort} object by parsing the supplied
221   * "hostName:port" String URL. This method also accepts IPV6 style
222   * "[hostAddress]:port" String URLs.
223   *
224   * @param hostPort
225   *          a String representing the URL made of a host and a port.
226   * @return a new {@link HostPort} built from the supplied string.
227   * @throws NumberFormatException
228   *           If the "port" in the supplied string cannot be converted to an
229   *           int
230   * @throws IllegalArgumentException
231   *           if no port could be found in the supplied string, or if the port
232   *           is not a valid port number
233   */
234  public static HostPort valueOf(String hostPort) throws NumberFormatException,
235      IllegalArgumentException
236  {
237    final int sepIndex = hostPort.lastIndexOf(':');
238    if ((hostPort.charAt(0) == '['
239        && hostPort.charAt(hostPort.length() - 1) == ']')
240        || sepIndex == -1)
241    {
242      throw new IllegalArgumentException(
243          "Invalid host/port string: no network port was provided in '"
244              + hostPort + "'");
245    }
246    else if (sepIndex == 0)
247    {
248      throw new IllegalArgumentException(
249          "Invalid host/port string: no host name was provided in '" + hostPort
250              + "'");
251    }
252    else if (hostPort.lastIndexOf(':', sepIndex - 1) != -1
253        && (hostPort.charAt(0) != '[' || hostPort.charAt(sepIndex - 1) != ']'))
254    {
255      throw new IllegalArgumentException(
256          "Invalid host/port string: Suspected IPv6 address provided in '"
257              + hostPort + "'. The only allowed format for providing IPv6 "
258              + "addresses is '[IPv6 address]:port'");
259    }
260    String host = hostPort.substring(0, sepIndex);
261    int port = Integer.parseInt(hostPort.substring(sepIndex + 1));
262    return new HostPort(host, port);
263  }
264
265  /**
266   * Removes extra characters from the host name: surrounding square brackets
267   * for IPv6 addresses.
268   *
269   * @param host
270   *          the host name to clean
271   * @return the cleaned up host name
272   */
273  private String removeExtraChars(String host)
274  {
275    final int startsWith = host.indexOf("[");
276    if (startsWith == -1)
277    {
278      return host;
279    }
280    return host.substring(1, host.length() - 1);
281  }
282
283  /**
284   * Returns a normalized String representation of the supplied host.
285   *
286   * @param host
287   *          the host address to normalize
288   * @return a normalized String representation of the supplied host.
289   * @see #normalizedHost what host normalization covers
290   */
291  private String normalizeHost(String host)
292  {
293    if (LOCALHOST.equals(host))
294    { // it is already normalized
295      return LOCALHOST;
296    }
297
298    try
299    {
300      final InetAddress inetAddress = InetAddress.getByName(host);
301      if (isLocalAddress(inetAddress))
302      {
303        // normalize to localhost for easier identification.
304        return LOCALHOST;
305      }
306      // else normalize to IP address for easier identification.
307      // FIXME, this does not fix the multi homing issue where a single machine
308      // has several IP addresses
309      return inetAddress.getHostAddress();
310    }
311    catch (UnknownHostException e)
312    {
313      // We could not resolve this host name, default to the provided host name
314      logger.error(ERR_COULD_NOT_SOLVE_HOSTNAME, host);
315      return host;
316    }
317  }
318
319  /**
320   * Ensures the supplied port number is valid.
321   *
322   * @param port
323   *          the port number to validate
324   * @return the port number if valid
325   */
326  private int normalizePort(int port)
327  {
328    if (1 <= port && port <= 65535)
329    {
330      return port;
331    }
332    throw new IllegalArgumentException("Invalid network port provided: " + port
333        + " is not included in the [1, 65535] range.");
334  }
335
336  /**
337   * Retrieves the host for this {@code HostPort} object.
338   *
339   * @return  The host for this {@code HostPort} object, or
340   *          {@code null} if none was provided.
341   */
342  public String getHost()
343  {
344    return host;
345  }
346
347
348
349  /**
350   * Retrieves the port number for this {@code HostPort} object.
351   *
352   * @return The valid port number in the [1, 65535] range for this
353   *         {@code HostPort} object.
354   */
355  public int getPort()
356  {
357    return port;
358  }
359
360  /**
361   * Whether the current object represents a local address.
362   *
363   * @return true if this represents a local address, false otherwise.
364   */
365  public boolean isLocalAddress()
366  {
367    return LOCALHOST.equals(this.normalizedHost);
368  }
369
370  /**
371   * Converts the current object to an equivalent {@link InetSocketAddress}
372   * object.
373   *
374   * @return a {@link InetSocketAddress} equivalent of the current object.
375   * @throws UnknownHostException
376   *           If the current host name cannot be resolved to an
377   *           {@link InetAddress}
378   */
379  public InetSocketAddress toInetSocketAddress() throws UnknownHostException
380  {
381    return new InetSocketAddress(InetAddress.getByName(getHost()), getPort());
382  }
383
384  /**
385   * Returns a string representation of this {@code HostPort} object. It will be
386   * the host element (or nothing if no host was given) followed by a colon and
387   * the port number.
388   *
389   * @return A string representation of this {@code HostPort} object.
390   */
391  @Override
392  public String toString()
393  {
394    return toString(host);
395  }
396
397  /**
398   * Returns a normalized string representation of this {@code HostPort} object.
399   *
400   * @return A string representation of this {@code HostPort} object.
401   * @see #normalizedHost what host normalization covers
402   */
403  private String toNormalizedString()
404  {
405    return toString(normalizedHost);
406  }
407
408  /**
409   * Inner computation for #toString() and {@link #toNormalizedString()}.
410   *
411   * @param hostName
412   *          the hostName to use for this computation
413   * @return the String representation fo4r this object
414   */
415  private String toString(String hostName)
416  {
417    if (hostName != null)
418    {
419      if (hostName.contains(":"))
420      {
421        return "[" + hostName + "]:" + port;
422      }
423      return hostName + ":" + port;
424    }
425    return WILDCARD_ADDRESS + ":" + port;
426  }
427
428  /**
429   * Checks whether the supplied HostPort is an equivalent to the current
430   * HostPort.
431   *
432   * @param other
433   *          the HostPort to compare to "this"
434   * @return true if the HostPorts are equivalent, false otherwise. False is
435   *         also return if calling {@link InetAddress#getAllByName(String)}
436   *         throws an UnknownHostException.
437   */
438  public boolean isEquivalentTo(final HostPort other)
439  {
440    try
441    {
442      // Get and compare ports of RS1 and RS2
443      if (getPort() != other.getPort())
444      {
445        return false;
446      }
447
448      // Get and compare addresses of RS1 and RS2
449      // Normalize local addresses to null for fast comparison.
450      final InetAddress[] thisAddresses =
451          isLocalAddress() ? null : InetAddress.getAllByName(getHost());
452      final InetAddress[] otherAddresses =
453          other.isLocalAddress() ? null : InetAddress.getAllByName(other
454              .getHost());
455
456      // Now compare addresses, if at least one match, this is the same server.
457      if (thisAddresses == null && otherAddresses == null)
458      {
459        // Both local addresses.
460        return true;
461      }
462      else if (thisAddresses == null || otherAddresses == null)
463      {
464        // One local address and one non-local.
465        return false;
466      }
467
468      // Both non-local addresses: check for overlap.
469      for (InetAddress thisAddress : thisAddresses)
470      {
471        for (InetAddress otherAddress : otherAddresses)
472        {
473          if (thisAddress.equals(otherAddress))
474          {
475            return true;
476          }
477        }
478      }
479      return false;
480    }
481    catch (UnknownHostException ex)
482    {
483      // Unknown RS: should not happen
484      return false;
485    }
486  }
487
488  /**
489   * Returns {@code true} if the provided Object is a HostPort object with the
490   * same host name and port than this HostPort object.
491   *
492   * @param obj
493   *          the reference object with which to compare.
494   * @return {@code true} if this object is the same as the obj argument;
495   *         {@code false} otherwise.
496   */
497  @Override
498  public boolean equals(Object obj)
499  {
500    if (obj == null)
501    {
502      return false;
503    }
504    if (obj == this)
505    {
506      return true;
507    }
508    if (getClass() != obj.getClass())
509    {
510      return false;
511    }
512
513    HostPort other = (HostPort) obj;
514    if (normalizedHost == null)
515    {
516      if (other.normalizedHost != null)
517      {
518        return false;
519      }
520    }
521    else if (!normalizedHost.equals(other.normalizedHost))
522    {
523      return false;
524    }
525
526    return port == other.port;
527  }
528
529  /**
530   * Retrieves a hash code for this HostPort object.
531   *
532   * @return A hash code for this HostPort object.
533   */
534  @Override
535  public int hashCode()
536  {
537    final int prime = 31;
538    int result = 1;
539    result = prime * result
540            + ((normalizedHost == null) ? 0 : normalizedHost.hashCode());
541    result = prime * result + port;
542    return result;
543  }
544
545}