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