001/* 002 * Copyright 2014-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2014-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2014-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk; 037 038 039 040import java.net.InetAddress; 041import java.net.UnknownHostException; 042import java.util.ArrayList; 043import java.util.Arrays; 044import java.util.Collections; 045import java.util.Hashtable; 046import java.util.List; 047import java.util.Map; 048import java.util.Properties; 049import java.util.StringTokenizer; 050import java.util.concurrent.atomic.AtomicLong; 051import java.util.concurrent.atomic.AtomicReference; 052import javax.naming.Context; 053import javax.naming.NamingEnumeration; 054import javax.naming.directory.Attribute; 055import javax.naming.directory.Attributes; 056import javax.naming.directory.InitialDirContext; 057import javax.net.SocketFactory; 058 059import com.unboundid.util.Debug; 060import com.unboundid.util.NotMutable; 061import com.unboundid.util.NotNull; 062import com.unboundid.util.Nullable; 063import com.unboundid.util.ObjectPair; 064import com.unboundid.util.PropertyManager; 065import com.unboundid.util.StaticUtils; 066import com.unboundid.util.ThreadLocalRandom; 067import com.unboundid.util.ThreadSafety; 068import com.unboundid.util.ThreadSafetyLevel; 069import com.unboundid.util.Validator; 070 071import static com.unboundid.ldap.sdk.LDAPMessages.*; 072 073 074 075/** 076 * This class provides a server set implementation that handles the case in 077 * which a given host name may resolve to multiple IP addresses. Note that 078 * while a setup like this is typically referred to as "round-robin DNS", this 079 * server set implementation does not strictly require DNS (as names may be 080 * resolved through alternate mechanisms like a hosts file or an alternate name 081 * service), and it does not strictly require round-robin use of those addresses 082 * (as alternate ordering mechanisms, like randomized or failover, may be used). 083 * <BR><BR> 084 * <H2>Example</H2> 085 * The following example demonstrates the process for creating a round-robin DNS 086 * server set for the case in which the hostname "directory.example.com" may be 087 * associated with multiple IP addresses, and the LDAP SDK should attempt to use 088 * them in a round robin manner. 089 * <PRE> 090 * // Define a number of variables that will be used by the server set. 091 * String hostname = "directory.example.com"; 092 * int port = 389; 093 * AddressSelectionMode selectionMode = 094 * AddressSelectionMode.ROUND_ROBIN; 095 * long cacheTimeoutMillis = 3600000L; // 1 hour 096 * String providerURL = "dns:"; // Default DNS config. 097 * SocketFactory socketFactory = null; // Default socket factory. 098 * LDAPConnectionOptions connectionOptions = null; // Default options. 099 * 100 * // Create the server set using the settings defined above. 101 * RoundRobinDNSServerSet serverSet = new RoundRobinDNSServerSet(hostname, 102 * port, selectionMode, cacheTimeoutMillis, providerURL, socketFactory, 103 * connectionOptions); 104 * 105 * // Verify that we can establish a single connection using the server set. 106 * LDAPConnection connection = serverSet.getConnection(); 107 * RootDSE rootDSEFromConnection = connection.getRootDSE(); 108 * connection.close(); 109 * 110 * // Verify that we can establish a connection pool using the server set. 111 * SimpleBindRequest bindRequest = 112 * new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password"); 113 * LDAPConnectionPool pool = 114 * new LDAPConnectionPool(serverSet, bindRequest, 10); 115 * RootDSE rootDSEFromPool = pool.getRootDSE(); 116 * pool.close(); 117 * </PRE> 118 */ 119@NotMutable() 120@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 121public final class RoundRobinDNSServerSet 122 extends ServerSet 123{ 124 /** 125 * The name of a system property that can be used to specify a comma-delimited 126 * list of IP addresses to use if resolution fails. This is intended 127 * primarily for testing purposes. 128 */ 129 @NotNull static final String PROPERTY_DEFAULT_ADDRESSES = 130 RoundRobinDNSServerSet.class.getName() + ".defaultAddresses"; 131 132 133 134 /** 135 * An enum that defines the modes that may be used to select the order in 136 * which addresses should be used in attempts to establish connections. 137 */ 138 public enum AddressSelectionMode 139 { 140 /** 141 * The address selection mode that will cause addresses to be consistently 142 * attempted in the order they are retrieved from the name service. 143 */ 144 FAILOVER, 145 146 147 148 /** 149 * The address selection mode that will cause the order of addresses to be 150 * randomized for each attempt. 151 */ 152 RANDOM, 153 154 155 156 /** 157 * The address selection mode that will cause connection attempts to be made 158 * in a round-robin order. 159 */ 160 ROUND_ROBIN; 161 162 163 164 /** 165 * Retrieves the address selection mode with the specified name. 166 * 167 * @param name The name of the address selection mode to retrieve. It 168 * must not be {@code null}. 169 * 170 * @return The requested address selection mode, or {@code null} if no such 171 * change mode is defined. 172 */ 173 @Nullable() 174 public static AddressSelectionMode forName(@NotNull final String name) 175 { 176 switch (StaticUtils.toLowerCase(name)) 177 { 178 case "failover": 179 return FAILOVER; 180 case "random": 181 return RANDOM; 182 case "roundrobin": 183 case "round-robin": 184 case "round_robin": 185 return ROUND_ROBIN; 186 default: 187 return null; 188 } 189 } 190 } 191 192 193 194 // The address selection mode that should be used if the provided hostname 195 // resolves to multiple addresses. 196 @NotNull private final AddressSelectionMode selectionMode; 197 198 // A counter that will be used to handle round-robin ordering. 199 @NotNull private final AtomicLong roundRobinCounter; 200 201 // A reference to an object that combines the resolved addresses with a 202 // timestamp indicating when the value should no longer be trusted. 203 @NotNull private final AtomicReference<ObjectPair<InetAddress[],Long>> 204 resolvedAddressesWithTimeout; 205 206 // The bind request to use to authenticate connections created by this 207 // server set. 208 @Nullable private final BindRequest bindRequest; 209 210 // The properties that will be used to initialize the JNDI context, if any. 211 @Nullable private final Hashtable<String,String> jndiProperties; 212 213 // The port number for the target server. 214 private final int port; 215 216 // The set of connection options to use for new connections. 217 @NotNull private final LDAPConnectionOptions connectionOptions; 218 219 // The maximum length of time, in milliseconds, to cache resolved addresses. 220 private final long cacheTimeoutMillis; 221 222 // The post-connect processor to invoke against connections created by this 223 // server set. 224 @Nullable private final PostConnectProcessor postConnectProcessor; 225 226 // The socket factory to use to establish connections. 227 @NotNull private final SocketFactory socketFactory; 228 229 // The hostname to be resolved. 230 @NotNull private final String hostname; 231 232 // The provider URL to use to resolve names, if any. 233 @Nullable private final String providerURL; 234 235 // The DNS record types that will be used to obtain the IP addresses for the 236 // specified hostname. 237 @NotNull private final String[] dnsRecordTypes; 238 239 240 241 /** 242 * Creates a new round-robin DNS server set with the provided information. 243 * 244 * @param hostname The hostname to be resolved to one or more 245 * addresses. It must not be {@code null}. 246 * @param port The port to use to connect to the server. Note 247 * that even if the provided hostname resolves to 248 * multiple addresses, the same port must be used 249 * for all addresses. 250 * @param selectionMode The selection mode that should be used if the 251 * hostname resolves to multiple addresses. It 252 * must not be {@code null}. 253 * @param cacheTimeoutMillis The maximum length of time in milliseconds to 254 * cache addresses resolved from the provided 255 * hostname. Caching resolved addresses can 256 * result in better performance and can reduce the 257 * number of requests to the name service. A 258 * that is less than or equal to zero indicates 259 * that no caching should be used. 260 * @param providerURL The JNDI provider URL that should be used when 261 * communicating with the DNS server. If this is 262 * {@code null}, then the underlying system's 263 * name service mechanism will be used (which may 264 * make use of other services instead of or in 265 * addition to DNS). If this is non-{@code null}, 266 * then only DNS will be used to perform the name 267 * resolution. A value of "dns:" indicates that 268 * the underlying system's DNS configuration 269 * should be used. 270 * @param socketFactory The socket factory to use to establish the 271 * connections. It may be {@code null} if the 272 * JVM-default socket factory should be used. 273 * @param connectionOptions The set of connection options that should be 274 * used for the connections. It may be 275 * {@code null} if a default set of connection 276 * options should be used. 277 */ 278 public RoundRobinDNSServerSet(@NotNull final String hostname, final int port, 279 @NotNull final AddressSelectionMode selectionMode, 280 final long cacheTimeoutMillis, 281 @Nullable final String providerURL, 282 @Nullable final SocketFactory socketFactory, 283 @Nullable final LDAPConnectionOptions connectionOptions) 284 { 285 this(hostname, port, selectionMode, cacheTimeoutMillis, providerURL, 286 null, null, socketFactory, connectionOptions); 287 } 288 289 290 291 /** 292 * Creates a new round-robin DNS server set with the provided information. 293 * 294 * @param hostname The hostname to be resolved to one or more 295 * addresses. It must not be {@code null}. 296 * @param port The port to use to connect to the server. Note 297 * that even if the provided hostname resolves to 298 * multiple addresses, the same port must be used 299 * for all addresses. 300 * @param selectionMode The selection mode that should be used if the 301 * hostname resolves to multiple addresses. It 302 * must not be {@code null}. 303 * @param cacheTimeoutMillis The maximum length of time in milliseconds to 304 * cache addresses resolved from the provided 305 * hostname. Caching resolved addresses can 306 * result in better performance and can reduce the 307 * number of requests to the name service. A 308 * that is less than or equal to zero indicates 309 * that no caching should be used. 310 * @param providerURL The JNDI provider URL that should be used when 311 * communicating with the DNS server.If both 312 * {@code providerURL} and {@code jndiProperties} 313 * are {@code null}, then then JNDI will not be 314 * used to interact with DNS and the hostname 315 * resolution will be performed via the underlying 316 * system's name service mechanism (which may make 317 * use of other services instead of or in addition 318 * to DNS). If this is non-{@code null}, then 319 * only DNS will be used to perform the name 320 * resolution. A value of "dns:" indicates that 321 * the underlying system's DNS configuration 322 * should be used. 323 * @param jndiProperties A set of JNDI-related properties that should be 324 * be used when initializing the context for 325 * interacting with the DNS server via JNDI. If 326 * both {@code providerURL} and 327 * {@code jndiProperties} are {@code null}, then 328 * then JNDI will not be used to interact with 329 * DNS and the hostname resolution will be 330 * performed via the underlying system's name 331 * service mechanism (which may make use of other 332 * services instead of or in addition to DNS). If 333 * {@code providerURL} is {@code null} and 334 * {@code jndiProperties} is non-{@code null}, 335 * then the provided properties must specify the 336 * URL. 337 * @param dnsRecordTypes Specifies the types of DNS records that will be 338 * used to obtain the addresses for the specified 339 * hostname. This will only be used if at least 340 * one of {@code providerURL} and 341 * {@code jndiProperties} is non-{@code null}. If 342 * this is {@code null} or empty, then a default 343 * record type of "A" (indicating IPv4 addresses) 344 * will be used. 345 * @param socketFactory The socket factory to use to establish the 346 * connections. It may be {@code null} if the 347 * JVM-default socket factory should be used. 348 * @param connectionOptions The set of connection options that should be 349 * used for the connections. It may be 350 * {@code null} if a default set of connection 351 * options should be used. 352 */ 353 public RoundRobinDNSServerSet(@NotNull final String hostname, final int port, 354 @NotNull final AddressSelectionMode selectionMode, 355 final long cacheTimeoutMillis, 356 @Nullable final String providerURL, 357 @Nullable final Properties jndiProperties, 358 @Nullable final String[] dnsRecordTypes, 359 @Nullable final SocketFactory socketFactory, 360 @Nullable final LDAPConnectionOptions connectionOptions) 361 { 362 this(hostname, port, selectionMode, cacheTimeoutMillis, providerURL, 363 jndiProperties, dnsRecordTypes, socketFactory, connectionOptions, null, 364 null); 365 } 366 367 368 369 /** 370 * Creates a new round-robin DNS server set with the provided information. 371 * 372 * @param hostname The hostname to be resolved to one or more 373 * addresses. It must not be {@code null}. 374 * @param port The port to use to connect to the server. 375 * Note that even if the provided hostname 376 * resolves to multiple addresses, the same 377 * port must be used for all addresses. 378 * @param selectionMode The selection mode that should be used if the 379 * hostname resolves to multiple addresses. It 380 * must not be {@code null}. 381 * @param cacheTimeoutMillis The maximum length of time in milliseconds to 382 * cache addresses resolved from the provided 383 * hostname. Caching resolved addresses can 384 * result in better performance and can reduce 385 * the number of requests to the name service. 386 * A that is less than or equal to zero 387 * indicates that no caching should be used. 388 * @param providerURL The JNDI provider URL that should be used 389 * when communicating with the DNS server. If 390 * both {@code providerURL} and 391 * {@code jndiProperties} are {@code null}, 392 * then then JNDI will not be used to interact 393 * with DNS and the hostname resolution will be 394 * performed via the underlying system's name 395 * service mechanism (which may make use of 396 * other services instead of or in addition to 397 * DNS). If this is non-{@code null}, then only 398 * DNS will be used to perform the name 399 * resolution. A value of "dns:" indicates that 400 * the underlying system's DNS configuration 401 * should be used. 402 * @param jndiProperties A set of JNDI-related properties that should 403 * be used when initializing the context for 404 * interacting with the DNS server via JNDI. If 405 * both {@code providerURL} and 406 * {@code jndiProperties} are {@code null}, then 407 * JNDI will not be used to interact with DNS 408 * and the hostname resolution will be 409 * performed via the underlying system's name 410 * service mechanism (which may make use of 411 * other services instead of or in addition to 412 * DNS). If {@code providerURL} is 413 * {@code null} and {@code jndiProperties} is 414 * non-{@code null}, then the provided 415 * properties must specify the URL. 416 * @param dnsRecordTypes Specifies the types of DNS records that will 417 * be used to obtain the addresses for the 418 * specified hostname. This will only be used 419 * if at least one of {@code providerURL} and 420 * {@code jndiProperties} is non-{@code null}. 421 * If this is {@code null} or empty, then a 422 * default record type of "A" (indicating IPv4 423 * addresses) will be used. 424 * @param socketFactory The socket factory to use to establish the 425 * connections. It may be {@code null} if the 426 * JVM-default socket factory should be used. 427 * @param connectionOptions The set of connection options that should be 428 * used for the connections. It may be 429 * {@code null} if a default set of connection 430 * options should be used. 431 * @param bindRequest The bind request that should be used to 432 * authenticate newly-established connections. 433 * It may be {@code null} if this server set 434 * should not perform any authentication. 435 * @param postConnectProcessor The post-connect processor that should be 436 * invoked on newly-established connections. It 437 * may be {@code null} if this server set should 438 * not perform any post-connect processing. 439 */ 440 public RoundRobinDNSServerSet(@NotNull final String hostname, final int port, 441 @NotNull final AddressSelectionMode selectionMode, 442 final long cacheTimeoutMillis, 443 @Nullable final String providerURL, 444 @Nullable final Properties jndiProperties, 445 @Nullable final String[] dnsRecordTypes, 446 @Nullable final SocketFactory socketFactory, 447 @Nullable final LDAPConnectionOptions connectionOptions, 448 @Nullable final BindRequest bindRequest, 449 @Nullable final PostConnectProcessor postConnectProcessor) 450 { 451 Validator.ensureNotNull(hostname); 452 Validator.ensureTrue((port >= 1) && (port <= 65_535)); 453 Validator.ensureNotNull(selectionMode); 454 455 this.hostname = hostname; 456 this.port = port; 457 this.selectionMode = selectionMode; 458 this.providerURL = providerURL; 459 this.bindRequest = bindRequest; 460 this.postConnectProcessor = postConnectProcessor; 461 462 if (jndiProperties == null) 463 { 464 if (providerURL == null) 465 { 466 this.jndiProperties = null; 467 } 468 else 469 { 470 this.jndiProperties = new Hashtable<>(2); 471 this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, 472 "com.sun.jndi.dns.DnsContextFactory"); 473 this.jndiProperties.put(Context.PROVIDER_URL, providerURL); 474 } 475 } 476 else 477 { 478 this.jndiProperties = new Hashtable<>(jndiProperties.size()+2); 479 for (final Map.Entry<Object,Object> e : jndiProperties.entrySet()) 480 { 481 this.jndiProperties.put(String.valueOf(e.getKey()), 482 String.valueOf(e.getValue())); 483 } 484 485 if (! this.jndiProperties.containsKey(Context.INITIAL_CONTEXT_FACTORY)) 486 { 487 this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, 488 "com.sun.jndi.dns.DnsContextFactory"); 489 } 490 491 if ((! this.jndiProperties.containsKey(Context.PROVIDER_URL)) && 492 (providerURL != null)) 493 { 494 this.jndiProperties.put(Context.PROVIDER_URL, providerURL); 495 } 496 } 497 498 if (dnsRecordTypes == null) 499 { 500 this.dnsRecordTypes = new String[] { "A" }; 501 } 502 else 503 { 504 this.dnsRecordTypes = dnsRecordTypes; 505 } 506 507 if (cacheTimeoutMillis > 0L) 508 { 509 this.cacheTimeoutMillis = cacheTimeoutMillis; 510 } 511 else 512 { 513 this.cacheTimeoutMillis = 0L; 514 } 515 516 if (socketFactory == null) 517 { 518 this.socketFactory = SocketFactory.getDefault(); 519 } 520 else 521 { 522 this.socketFactory = socketFactory; 523 } 524 525 if (connectionOptions == null) 526 { 527 this.connectionOptions = new LDAPConnectionOptions(); 528 } 529 else 530 { 531 this.connectionOptions = connectionOptions; 532 } 533 534 roundRobinCounter = new AtomicLong(0L); 535 resolvedAddressesWithTimeout = new AtomicReference<>(); 536 } 537 538 539 540 /** 541 * Retrieves the hostname to be resolved. 542 * 543 * @return The hostname to be resolved. 544 */ 545 @NotNull() 546 public String getHostname() 547 { 548 return hostname; 549 } 550 551 552 553 /** 554 * Retrieves the port to use to connect to the server. 555 * 556 * @return The port to use to connect to the server. 557 */ 558 public int getPort() 559 { 560 return port; 561 } 562 563 564 565 /** 566 * Retrieves the address selection mode that should be used if the provided 567 * hostname resolves to multiple addresses. 568 * 569 * @return The address selection 570 */ 571 @NotNull() 572 public AddressSelectionMode getAddressSelectionMode() 573 { 574 return selectionMode; 575 } 576 577 578 579 /** 580 * Retrieves the length of time in milliseconds that resolved addresses may be 581 * cached. 582 * 583 * @return The length of time in milliseconds that resolved addresses may be 584 * cached, or zero if no caching should be performed. 585 */ 586 public long getCacheTimeoutMillis() 587 { 588 return cacheTimeoutMillis; 589 } 590 591 592 593 /** 594 * Retrieves the provider URL that should be used when interacting with DNS to 595 * resolve the hostname to its corresponding addresses. 596 * 597 * @return The provider URL that should be used when interacting with DNS to 598 * resolve the hostname to its corresponding addresses, or 599 * {@code null} if the system's configured naming service should be 600 * used. 601 */ 602 @Nullable() 603 public String getProviderURL() 604 { 605 return providerURL; 606 } 607 608 609 610 /** 611 * Retrieves an unmodifiable map of properties that will be used to initialize 612 * the JNDI context used to interact with DNS. Note that the map returned 613 * will reflect the actual properties that will be used, and may not exactly 614 * match the properties provided when creating this server set. 615 * 616 * @return An unmodifiable map of properties that will be used to initialize 617 * the JNDI context used to interact with DNS, or {@code null} if 618 * JNDI will nto be used to interact with DNS. 619 */ 620 @Nullable() 621 public Map<String,String> getJNDIProperties() 622 { 623 if (jndiProperties == null) 624 { 625 return null; 626 } 627 else 628 { 629 return Collections.unmodifiableMap(jndiProperties); 630 } 631 } 632 633 634 635 /** 636 * Retrieves an array of record types that will be requested if JNDI will be 637 * used to interact with DNS. 638 * 639 * @return An array of record types that will be requested if JNDI will be 640 * used to interact with DNS. 641 */ 642 @NotNull() 643 public String[] getDNSRecordTypes() 644 { 645 return dnsRecordTypes; 646 } 647 648 649 650 /** 651 * Retrieves the socket factory that will be used to establish connections. 652 * This will not be {@code null}, even if no socket factory was provided when 653 * the server set was created. 654 * 655 * @return The socket factory that will be used to establish connections. 656 */ 657 @NotNull() 658 public SocketFactory getSocketFactory() 659 { 660 return socketFactory; 661 } 662 663 664 665 /** 666 * Retrieves the set of connection options that will be used for underlying 667 * connections. This will not be {@code null}, even if no connection options 668 * object was provided when the server set was created. 669 * 670 * @return The set of connection options that will be used for underlying 671 * connections. 672 */ 673 @NotNull() 674 public LDAPConnectionOptions getConnectionOptions() 675 { 676 return connectionOptions; 677 } 678 679 680 681 /** 682 * {@inheritDoc} 683 */ 684 @Override() 685 public boolean includesAuthentication() 686 { 687 return (bindRequest != null); 688 } 689 690 691 692 /** 693 * {@inheritDoc} 694 */ 695 @Override() 696 public boolean includesPostConnectProcessing() 697 { 698 return (postConnectProcessor != null); 699 } 700 701 702 703 /** 704 * {@inheritDoc} 705 */ 706 @Override() 707 @NotNull() 708 public LDAPConnection getConnection() 709 throws LDAPException 710 { 711 return getConnection(null); 712 } 713 714 715 716 /** 717 * {@inheritDoc} 718 */ 719 @Override() 720 @NotNull() 721 public synchronized LDAPConnection getConnection( 722 @Nullable final LDAPConnectionPoolHealthCheck healthCheck) 723 throws LDAPException 724 { 725 LDAPException firstException = null; 726 727 final LDAPConnection conn = 728 new LDAPConnection(socketFactory, connectionOptions); 729 for (final InetAddress a : orderAddresses(resolveHostname())) 730 { 731 boolean close = true; 732 try 733 { 734 conn.connect(hostname, a, port, 735 connectionOptions.getConnectTimeoutMillis()); 736 doBindPostConnectAndHealthCheckProcessing(conn, bindRequest, 737 postConnectProcessor, healthCheck); 738 close = false; 739 associateConnectionWithThisServerSet(conn); 740 return conn; 741 } 742 catch (final LDAPException le) 743 { 744 Debug.debugException(le); 745 if (firstException == null) 746 { 747 firstException = le; 748 } 749 } 750 finally 751 { 752 if (close) 753 { 754 conn.close(); 755 } 756 } 757 } 758 759 throw firstException; 760 } 761 762 763 764 /** 765 * Resolve the hostname to its corresponding addresses. 766 * 767 * @return The addresses resolved from the hostname. 768 * 769 * @throws LDAPException If 770 */ 771 @NotNull() 772 InetAddress[] resolveHostname() 773 throws LDAPException 774 { 775 // First, see if we can use the cached addresses. 776 final ObjectPair<InetAddress[],Long> pair = 777 resolvedAddressesWithTimeout.get(); 778 if (pair != null) 779 { 780 if (pair.getSecond() >= System.currentTimeMillis()) 781 { 782 return pair.getFirst(); 783 } 784 } 785 786 787 // Try to resolve the address. 788 InetAddress[] addresses = null; 789 try 790 { 791 if (jndiProperties == null) 792 { 793 addresses = connectionOptions.getNameResolver().getAllByName(hostname); 794 } 795 else 796 { 797 final Attributes attributes; 798 final InitialDirContext context = new InitialDirContext(jndiProperties); 799 try 800 { 801 attributes = context.getAttributes(hostname, dnsRecordTypes); 802 } 803 finally 804 { 805 context.close(); 806 } 807 808 if (attributes != null) 809 { 810 final ArrayList<InetAddress> addressList = new ArrayList<>(10); 811 for (final String recordType : dnsRecordTypes) 812 { 813 final Attribute a = attributes.get(recordType); 814 if (a != null) 815 { 816 final NamingEnumeration<?> values = a.getAll(); 817 while (values.hasMore()) 818 { 819 final Object value = values.next(); 820 addressList.add(getInetAddressForIP(String.valueOf(value))); 821 } 822 } 823 } 824 825 if (! addressList.isEmpty()) 826 { 827 addresses = new InetAddress[addressList.size()]; 828 addressList.toArray(addresses); 829 } 830 } 831 } 832 } 833 catch (final Exception e) 834 { 835 Debug.debugException(e); 836 addresses = getDefaultAddresses(); 837 } 838 839 840 // If we were able to resolve the hostname, then cache and return the 841 // resolved addresses. 842 if ((addresses != null) && (addresses.length > 0)) 843 { 844 final long timeoutTime; 845 if (cacheTimeoutMillis > 0L) 846 { 847 timeoutTime = System.currentTimeMillis() + cacheTimeoutMillis; 848 } 849 else 850 { 851 timeoutTime = System.currentTimeMillis() - 1L; 852 } 853 854 resolvedAddressesWithTimeout.set( 855 new ObjectPair<>(addresses, timeoutTime)); 856 return addresses; 857 } 858 859 860 // If we've gotten here, then we couldn't resolve the hostname. If we have 861 // cached addresses, then use them even though the timeout has expired 862 // because that's better than nothing. 863 if (pair != null) 864 { 865 return pair.getFirst(); 866 } 867 868 throw new LDAPException(ResultCode.CONNECT_ERROR, 869 ERR_ROUND_ROBIN_DNS_SERVER_SET_CANNOT_RESOLVE.get(hostname)); 870 } 871 872 873 874 /** 875 * Orders the provided array of InetAddress objects to reflect the order in 876 * which the addresses should be used to try to create a new connection. 877 * 878 * @param addresses The array of addresses to be ordered. 879 * 880 * @return A list containing the ordered addresses. 881 */ 882 @NotNull() 883 List<InetAddress> orderAddresses(@NotNull final InetAddress[] addresses) 884 { 885 final ArrayList<InetAddress> l = new ArrayList<>(addresses.length); 886 887 switch (selectionMode) 888 { 889 case RANDOM: 890 l.addAll(Arrays.asList(addresses)); 891 Collections.shuffle(l, ThreadLocalRandom.get()); 892 break; 893 894 case ROUND_ROBIN: 895 final int index = 896 (int) (roundRobinCounter.getAndIncrement() % addresses.length); 897 for (int i=index; i < addresses.length; i++) 898 { 899 l.add(addresses[i]); 900 } 901 for (int i=0; i < index; i++) 902 { 903 l.add(addresses[i]); 904 } 905 break; 906 907 case FAILOVER: 908 default: 909 // We'll use the addresses in the same order we originally got them. 910 l.addAll(Arrays.asList(addresses)); 911 break; 912 } 913 914 return l; 915 } 916 917 918 919 /** 920 * Retrieves a default set of addresses that may be used for testing. 921 * 922 * @return A default set of addresses that may be used for testing. 923 */ 924 @NotNull() 925 InetAddress[] getDefaultAddresses() 926 { 927 final String defaultAddrsStr = 928 PropertyManager.get(PROPERTY_DEFAULT_ADDRESSES); 929 if (defaultAddrsStr == null) 930 { 931 return null; 932 } 933 934 final StringTokenizer tokenizer = 935 new StringTokenizer(defaultAddrsStr, " ,"); 936 final InetAddress[] addresses = new InetAddress[tokenizer.countTokens()]; 937 for (int i=0; i < addresses.length; i++) 938 { 939 try 940 { 941 addresses[i] = getInetAddressForIP(tokenizer.nextToken()); 942 } 943 catch (final Exception e) 944 { 945 Debug.debugException(e); 946 return null; 947 } 948 } 949 950 return addresses; 951 } 952 953 954 955 /** 956 * Retrieves an InetAddress object with the configured hostname and the 957 * provided IP address. 958 * 959 * @param ipAddress The string representation of the IP address to use in 960 * the returned InetAddress. 961 * 962 * @return The created InetAddress. 963 * 964 * @throws UnknownHostException If the provided string does not represent a 965 * valid IPv4 or IPv6 address. 966 */ 967 @NotNull() 968 private InetAddress getInetAddressForIP(@NotNull final String ipAddress) 969 throws UnknownHostException 970 { 971 // We want to create an InetAddress that has the provided hostname and the 972 // specified IP address. To do that, we need to use 973 // InetAddress.getByAddress. But that requires the IP address to be 974 // specified as a byte array, and the easiest way to convert an IP address 975 // string to a byte array is to use InetAddress.getByName. 976 final InetAddress byName = connectionOptions.getNameResolver(). 977 getByName(String.valueOf(ipAddress)); 978 return InetAddress.getByAddress(hostname, byName.getAddress()); 979 } 980 981 982 983 /** 984 * {@inheritDoc} 985 */ 986 @Override() 987 public void toString(@NotNull final StringBuilder buffer) 988 { 989 buffer.append("RoundRobinDNSServerSet(hostname='"); 990 buffer.append(hostname); 991 buffer.append("', port="); 992 buffer.append(port); 993 buffer.append(", addressSelectionMode="); 994 buffer.append(selectionMode.name()); 995 buffer.append(", cacheTimeoutMillis="); 996 buffer.append(cacheTimeoutMillis); 997 998 if (providerURL != null) 999 { 1000 buffer.append(", providerURL='"); 1001 buffer.append(providerURL); 1002 buffer.append('\''); 1003 } 1004 1005 buffer.append(", includesAuthentication="); 1006 buffer.append(bindRequest != null); 1007 buffer.append(", includesPostConnectProcessing="); 1008 buffer.append(postConnectProcessor != null); 1009 buffer.append(')'); 1010 } 1011}