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