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}