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