001/* 002 * Copyright 2011-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2011-2017 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.util.Collections; 026import java.util.Hashtable; 027import java.util.Map; 028import java.util.Properties; 029import javax.naming.Context; 030import javax.net.SocketFactory; 031 032import com.unboundid.util.Debug; 033import com.unboundid.util.NotMutable; 034import com.unboundid.util.ThreadSafety; 035import com.unboundid.util.ThreadSafetyLevel; 036 037 038 039/** 040 * This class provides a server set implementation that can discover information 041 * about available directory servers through DNS SRV records as described in 042 * <A HREF="http://www.ietf.org/rfc/rfc2782.txt">RFC 2782</A>. DNS SRV records 043 * make it possible for clients to use the domain name system to discover 044 * information about the systems that provide a given service, which can help 045 * avoid the need to explicitly configure clients with the addresses of the 046 * appropriate set of directory servers. 047 * <BR><BR> 048 * The standard service name used to reference LDAP directory servers is 049 * "_ldap._tcp". If client systems have DNS configured properly with an 050 * appropriate search domain, then this may be all that is needed to discover 051 * any available directory servers. Alternately, a record name of 052 * "_ldap._tcp.example.com" may be used to request DNS information about LDAP 053 * servers for the example.com domain. However, there is no technical 054 * requirement that "_ldap._tcp" must be used for this purpose, and it may make 055 * sense to use a different name if there is something special about the way 056 * clients should interact with the servers (e.g., "_ldaps._tcp" would be more 057 * appropriate if LDAP clients need to use SSL when communicating with the 058 * server). 059 * <BR><BR> 060 * DNS SRV records contain a number of components, including: 061 * <UL> 062 * <LI>The address of the system providing the service.</LI> 063 * <LI>The port to which connections should be established to access the 064 * service.</LI> 065 * <LI>The priority assigned to the service record. If there are multiple 066 * servers that provide the associated service, then the priority can be 067 * used to specify the order in which they should be contacted. Records 068 * with a lower priority value wil be used before those with a higher 069 * priority value.</LI> 070 * <LI>The weight assigned to the service record. The weight will be used if 071 * there are multiple service records with the same priority, and it 072 * controls how likely each record is to be chosen. A record with a 073 * weight of 2 is twice as likely to be chosen as a record with the same 074 * priority and a weight of 1.</LI> 075 * </UL> 076 * In the event that multiple SRV records exist for the target service, then the 077 * priorities and weights of those records will be used to determine the order 078 * in which the servers will be tried. Records with a lower priority value will 079 * always be tried before those with a higher priority value. For records with 080 * equal priority values and nonzero weights, then the ratio of those weight 081 * values will be used to control how likely one of those records is to be tried 082 * before another. Records with a weight of zero will always be tried after 083 * records with the same priority and nonzero weights. 084 * <BR><BR> 085 * This server set implementation uses JNDI to communicate with DNS servers in 086 * order to obtain the requested SRV records (although it does not use JNDI for 087 * any LDAP communication). In order to specify which DNS server(s) to query, a 088 * JNDI provider URL must be used. In many cases, a URL of "dns:", which 089 * indicates that the client should use the DNS servers configured for use by 090 * the underlying system, should be sufficient. However, if you wish to use a 091 * specific DNS server then you may explicitly specify it in the URL (e.g., 092 * "dns://1.2.3.4:53" would attempt to communicate with the DNS server listening 093 * on IP address 1.2.3.4 and port 53). If you wish to specify multiple DNS 094 * servers, you may provide multiple URLs separated with spaces and they will be 095 * tried in the order in which they were included in the list until a response 096 * can be retrieved (e.g., for a provider URL of "dns://1.2.3.4 dns://1.2.3.5", 097 * it will first try to use the DNS server running on system with IP address 098 * "1.2.3.4", but if that is not successful then it will try the DNS server 099 * running on the system with IP address "1.2.3.5"). See the <A HREF= 100 *"http://download.oracle.com/javase/6/docs/technotes/guides/jndi/jndi-dns.html" 101 * > JNDI DNS service provider documentation</A> for more details on acceptable 102 * formats for the provider URL. 103 */ 104@NotMutable() 105@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 106public final class DNSSRVRecordServerSet 107 extends ServerSet 108{ 109 /** 110 * The default SRV record name that will be retrieved if none is specified. 111 */ 112 private static final String DEFAULT_RECORD_NAME = "_ldap._tcp"; 113 114 115 116 /** 117 * The default time-to-live value (1 hour, represented in milliseconds) that 118 * will be used if no alternate value is specified. 119 */ 120 private static final long DEFAULT_TTL_MILLIS = 60L * 60L * 1000L; 121 122 123 124 /** 125 * The default provider URL that will be used for specifying which DNS 126 * server(s) to query. The default behavior will be to attempt to determine 127 * which DNS server(s) to use from the underlying system configuration. 128 */ 129 private static final String DEFAULT_DNS_PROVIDER_URL = "dns:"; 130 131 132 133 // The properties that will be used to initialize the JNDI context. 134 private final Hashtable<String,String> jndiProperties; 135 136 // The connection options to use for newly-created connections. 137 private final LDAPConnectionOptions connectionOptions; 138 139 // The maximum length of time in milliseconds that previously-retrieved 140 // information should be considered valid. 141 private final long ttlMillis; 142 143 // The socket factory that should be used to create connections. 144 private final SocketFactory socketFactory; 145 146 // The cached set of SRV records. 147 private volatile SRVRecordSet recordSet; 148 149 // The name of the DNS SRV record to retrieve. 150 private final String recordName; 151 152 // The DNS provider URL to use. 153 private final String providerURL; 154 155 156 157 /** 158 * Creates a new instance of this server set that will use the specified DNS 159 * record name, a default DNS provider URL that will attempt to determine DNS 160 * servers from the underlying system configuration, a default TTL of one 161 * hour, round-robin ordering for servers with the same priority, and default 162 * socket factory and connection options. 163 * 164 * @param recordName The name of the DNS SRV record to retrieve. If this is 165 * {@code null}, then a default record name of 166 * "_ldap._tcp" will be used. 167 */ 168 public DNSSRVRecordServerSet(final String recordName) 169 { 170 this(recordName, null, DEFAULT_TTL_MILLIS, null, null); 171 } 172 173 174 175 /** 176 * Creates a new instance of this server set that will use the provided 177 * settings. 178 * 179 * @param recordName The name of the DNS SRV record to retrieve. If 180 * this is {@code null}, then a default record name 181 * of "_ldap._tcp" will be used. 182 * @param providerURL The JNDI provider URL that may be used to 183 * specify the DNS server(s) to use. If this is 184 * not specified, then a default URL of "dns:" will 185 * be used, which will attempt to determine the 186 * appropriate servers from the underlying system 187 * configuration. 188 * @param ttlMillis Specifies the maximum length of time in 189 * milliseconds that DNS information should be 190 * cached before it needs to be retrieved again. A 191 * value less than or equal to zero will use the 192 * default TTL of one hour. 193 * @param socketFactory The socket factory that will be used when 194 * creating connections. It may be {@code null} if 195 * the JVM-default socket factory should be used. 196 * @param connectionOptions The set of connection options that should be 197 * used for the connections that are created. It 198 * may be {@code null} if the default connection 199 * options should be used. 200 */ 201 public DNSSRVRecordServerSet(final String recordName, 202 final String providerURL, final long ttlMillis, 203 final SocketFactory socketFactory, 204 final LDAPConnectionOptions connectionOptions) 205 { 206 this(recordName, providerURL, null, ttlMillis, socketFactory, 207 connectionOptions); 208 } 209 210 211 212 /** 213 * Creates a new instance of this server set that will use the provided 214 * settings. 215 * 216 * @param recordName The name of the DNS SRV record to retrieve. If 217 * this is {@code null}, then a default record name 218 * of "_ldap._tcp" will be used. 219 * @param providerURL The JNDI provider URL that may be used to 220 * specify the DNS server(s) to use. If this is 221 * not specified, then a default URL of "dns:" will 222 * be used, which will attempt to determine the 223 * appropriate servers from the underlying system 224 * configuration. 225 * @param jndiProperties A set of JNDI-related properties that should be 226 * be used when initializing the context for 227 * interacting with the DNS server via JNDI. If 228 * this is {@code null}, then a default set of 229 * properties will be used. 230 * @param ttlMillis Specifies the maximum length of time in 231 * milliseconds that DNS information should be 232 * cached before it needs to be retrieved again. A 233 * value less than or equal to zero will use the 234 * default TTL of one hour. 235 * @param socketFactory The socket factory that will be used when 236 * creating connections. It may be {@code null} if 237 * the JVM-default socket factory should be used. 238 * @param connectionOptions The set of connection options that should be 239 * used for the connections that are created. It 240 * may be {@code null} if the default connection 241 * options should be used. 242 */ 243 public DNSSRVRecordServerSet(final String recordName, 244 final String providerURL, 245 final Properties jndiProperties, 246 final long ttlMillis, 247 final SocketFactory socketFactory, 248 final LDAPConnectionOptions connectionOptions) 249 { 250 this.socketFactory = socketFactory; 251 this.connectionOptions = connectionOptions; 252 253 recordSet = null; 254 255 if (recordName == null) 256 { 257 this.recordName = DEFAULT_RECORD_NAME; 258 } 259 else 260 { 261 this.recordName = recordName; 262 } 263 264 if (providerURL == null) 265 { 266 this.providerURL = DEFAULT_DNS_PROVIDER_URL; 267 } 268 else 269 { 270 this.providerURL = providerURL; 271 } 272 273 this.jndiProperties = new Hashtable<String,String>(10); 274 if (jndiProperties != null) 275 { 276 for (final Map.Entry<Object,Object> e : jndiProperties.entrySet()) 277 { 278 this.jndiProperties.put(String.valueOf(e.getKey()), 279 String.valueOf(e.getValue())); 280 } 281 } 282 283 if (! this.jndiProperties.containsKey(Context.INITIAL_CONTEXT_FACTORY)) 284 { 285 this.jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, 286 "com.sun.jndi.dns.DnsContextFactory"); 287 } 288 289 if (! this.jndiProperties.containsKey(Context.PROVIDER_URL)) 290 { 291 this.jndiProperties.put(Context.PROVIDER_URL, this.providerURL); 292 } 293 294 if (ttlMillis <= 0L) 295 { 296 this.ttlMillis = DEFAULT_TTL_MILLIS; 297 } 298 else 299 { 300 this.ttlMillis = ttlMillis; 301 } 302 } 303 304 305 306 /** 307 * Retrieves the name of the DNS SRV record to retrieve. 308 * 309 * @return The name of the DNS SRV record to retrieve. 310 */ 311 public String getRecordName() 312 { 313 return recordName; 314 } 315 316 317 318 /** 319 * Retrieves the JNDI provider URL that specifies the DNS server(s) to use. 320 * 321 * @return The JNDI provider URL that specifies the DNS server(s) to use. 322 */ 323 public String getProviderURL() 324 { 325 return providerURL; 326 } 327 328 329 330 /** 331 * Retrieves an unmodifiable map of properties that will be used to initialize 332 * the JNDI context used to interact with DNS. Note that the map returned 333 * will reflect the actual properties that will be used, and may not exactly 334 * match the properties provided when creating this server set. 335 * 336 * @return An unmodifiable map of properties that will be used to initialize 337 * the JNDI context used to interact with DNS. 338 */ 339 public Map<String,String> getJNDIProperties() 340 { 341 return Collections.unmodifiableMap(jndiProperties); 342 } 343 344 345 346 /** 347 * Retrieves the maximum length of time in milliseconds that 348 * previously-retrieved DNS information should be cached before it needs to be 349 * refreshed. 350 * 351 * @return The maximum length of time in milliseconds that 352 * previously-retrieved DNS information should be cached before it 353 * needs to be refreshed. 354 */ 355 public long getTTLMillis() 356 { 357 return ttlMillis; 358 } 359 360 361 362 /** 363 * Retrieves the socket factory that will be used when creating connections, 364 * if any. 365 * 366 * @return The socket factory that will be used when creating connections, or 367 * {@code null} if the JVM-default socket factory will be used. 368 */ 369 public SocketFactory getSocketFactory() 370 { 371 return socketFactory; 372 } 373 374 375 376 /** 377 * Retrieves the set of connection options to use for connections that are 378 * created, if any. 379 * 380 * @return The set of connection options to use for connections that are 381 * created, or {@code null} if a default set of options should be 382 * used. 383 */ 384 public LDAPConnectionOptions getConnectionOptions() 385 { 386 return connectionOptions; 387 } 388 389 390 391 /** 392 * {@inheritDoc} 393 */ 394 @Override() 395 public LDAPConnection getConnection() 396 throws LDAPException 397 { 398 return getConnection(null); 399 } 400 401 402 403 /** 404 * {@inheritDoc} 405 */ 406 @Override() 407 public LDAPConnection getConnection( 408 final LDAPConnectionPoolHealthCheck healthCheck) 409 throws LDAPException 410 { 411 // If there is no cached record set, or if the cached set is expired, then 412 // try to get a new one. 413 if ((recordSet == null) || recordSet.isExpired()) 414 { 415 try 416 { 417 recordSet = SRVRecordSet.getRecordSet(recordName, jndiProperties, 418 ttlMillis); 419 } 420 catch (final LDAPException le) 421 { 422 Debug.debugException(le); 423 424 // We couldn't get a new record set. If we have an existing one, then 425 // it's expired but we'll keep using it anyway because it's better than 426 // nothing. But if we don't have an existing set, then we can't 427 // continue. 428 if (recordSet == null) 429 { 430 throw le; 431 } 432 } 433 } 434 435 436 // Iterate through the record set in an order based on priority and weight. 437 // Take the first one that we can connect to and that satisfies the health 438 // check (if any). 439 LDAPException firstException = null; 440 for (final SRVRecord r : recordSet.getOrderedRecords()) 441 { 442 final LDAPConnection conn; 443 try 444 { 445 conn = new LDAPConnection(socketFactory, connectionOptions, 446 r.getAddress(), r.getPort()); 447 } 448 catch (final LDAPException le) 449 { 450 Debug.debugException(le); 451 if (firstException == null) 452 { 453 firstException = le; 454 } 455 456 continue; 457 } 458 459 if (healthCheck != null) 460 { 461 try 462 { 463 healthCheck.ensureNewConnectionValid(conn); 464 } 465 catch (final LDAPException le) 466 { 467 Debug.debugException(le); 468 if (firstException == null) 469 { 470 firstException = le; 471 } 472 473 continue; 474 } 475 } 476 477 return conn; 478 } 479 480 // If we've gotten here, then we couldn't connect to any of the servers. 481 // Throw the first exception that we encountered. 482 throw firstException; 483 } 484 485 486 487 /** 488 * {@inheritDoc} 489 */ 490 @Override() 491 public void toString(final StringBuilder buffer) 492 { 493 buffer.append("DNSSRVRecordServerSet(recordName='"); 494 buffer.append(recordName); 495 buffer.append("', providerURL='"); 496 buffer.append(providerURL); 497 buffer.append("', ttlMillis="); 498 buffer.append(ttlMillis); 499 500 if (socketFactory != null) 501 { 502 buffer.append(", socketFactoryClass='"); 503 buffer.append(socketFactory.getClass().getName()); 504 buffer.append('\''); 505 } 506 507 if (connectionOptions != null) 508 { 509 buffer.append(", connectionOptions"); 510 connectionOptions.toString(buffer); 511 } 512 513 buffer.append(')'); 514 } 515}