1 /* 2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @run main/othervm LdapTimeoutTest 27 * @bug 7094377 8000487 6176036 7056489 28 * @summary Timeout tests for ldap 29 * @key intermittent 30 */ 31 32 import java.net.Socket; 33 import java.net.ServerSocket; 34 import java.net.SocketTimeoutException; 35 import java.io.*; 36 import javax.naming.*; 37 import javax.naming.directory.*; 38 import java.util.List; 39 import java.util.Hashtable; 40 import java.util.ArrayList; 41 import java.util.concurrent.Callable; 42 import java.util.concurrent.ExecutionException; 43 import java.util.concurrent.Executors; 44 import java.util.concurrent.ExecutorService; 45 import java.util.concurrent.Future; 46 import java.util.concurrent.ScheduledExecutorService; 47 import java.util.concurrent.ScheduledFuture; 48 import java.util.concurrent.TimeoutException; 49 import java.util.concurrent.TimeUnit; 50 51 import static java.util.concurrent.TimeUnit.MILLISECONDS; 52 import static java.util.concurrent.TimeUnit.NANOSECONDS; 53 54 55 abstract class LdapTest implements Callable { 56 57 Hashtable env; 58 TestServer server; 59 ScheduledExecutorService killSwitchPool; 60 boolean passed = false; 61 private int HANGING_TEST_TIMEOUT = 20_000; 62 63 public LdapTest (TestServer server, Hashtable env) { 64 this.server = server; 65 this.env = env; 66 } 67 68 public LdapTest(TestServer server, Hashtable env, 69 ScheduledExecutorService killSwitchPool) 70 { 71 this(server, env); 72 this.killSwitchPool = killSwitchPool; 73 } 74 75 public abstract void performOp(InitialContext ctx) throws NamingException; 76 public abstract void handleNamingException( 77 NamingException e, long start, long end); 78 79 public void pass() { 80 this.passed = true; 81 } 82 83 public void fail() { 84 throw new RuntimeException("Test failed"); 85 } 86 87 boolean shutItDown(InitialContext ctx) { 88 try { 89 if (ctx != null) ctx.close(); 90 return true; 91 } catch (NamingException ex) { 92 return false; 93 } 94 } 95 96 public Boolean call() { 97 InitialContext ctx = null; 98 ScheduledFuture killer = null; 99 long start = System.nanoTime(); 100 101 try { 102 while(!server.accepting()) 103 Thread.sleep(200); // allow the server to start up 104 Thread.sleep(200); // to be sure 105 106 // if this is a hanging test, scheduled a thread to 107 // interrupt after a certain time 108 if (killSwitchPool != null) { 109 final Thread current = Thread.currentThread(); 110 killer = killSwitchPool.schedule( 111 new Callable<Void>() { 112 public Void call() throws Exception { 113 current.interrupt(); 114 return null; 115 } 116 }, HANGING_TEST_TIMEOUT, MILLISECONDS); 117 } 118 119 env.put(Context.PROVIDER_URL, "ldap://localhost:" + 120 server.getLocalPort()); 121 122 try { 123 ctx = new InitialDirContext(env); 124 performOp(ctx); 125 fail(); 126 } catch (NamingException e) { 127 long end = System.nanoTime(); 128 System.out.println(this.getClass().toString() + " - elapsed: " 129 + NANOSECONDS.toMillis(end - start)); 130 handleNamingException(e, start, end); 131 } finally { 132 if (killer != null && !killer.isDone()) 133 killer.cancel(true); 134 shutItDown(ctx); 135 server.close(); 136 } 137 return passed; 138 } catch (IOException|InterruptedException e) { 139 throw new RuntimeException(e); 140 } 141 } 142 } 143 144 abstract class ReadServerTest extends LdapTest { 145 146 public ReadServerTest(Hashtable env) throws IOException { 147 super(new BindableServer(), env); 148 } 149 150 public ReadServerTest(Hashtable env, 151 ScheduledExecutorService killSwitchPool) 152 throws IOException 153 { 154 super(new BindableServer(), env, killSwitchPool); 155 } 156 157 public void performOp(InitialContext ctx) throws NamingException { 158 SearchControls scl = new SearchControls(); 159 scl.setSearchScope(SearchControls.SUBTREE_SCOPE); 160 NamingEnumeration<SearchResult> answer = ((InitialDirContext)ctx) 161 .search("ou=People,o=JNDITutorial", "(objectClass=*)", scl); 162 } 163 } 164 165 abstract class DeadServerTest extends LdapTest { 166 167 public DeadServerTest(Hashtable env) throws IOException { 168 super(new DeadServer(), env); 169 } 170 171 public DeadServerTest(Hashtable env, 172 ScheduledExecutorService killSwitchPool) 173 throws IOException 174 { 175 super(new DeadServer(), env, killSwitchPool); 176 } 177 178 public void performOp(InitialContext ctx) throws NamingException {} 179 } 180 181 class DeadServerNoTimeoutTest extends DeadServerTest { 182 183 public DeadServerNoTimeoutTest(Hashtable env, 184 ScheduledExecutorService killSwitchPool) 185 throws IOException 186 { 187 super(env, killSwitchPool); 188 } 189 190 public void handleNamingException(NamingException e, long start, long end) { 191 if (e instanceof InterruptedNamingException) Thread.interrupted(); 192 193 if (NANOSECONDS.toMillis(end - start) < LdapTimeoutTest.MIN_TIMEOUT) { 194 System.err.printf("DeadServerNoTimeoutTest fail: timeout should be " + 195 "at least %s ms, actual time is %s ms%n", 196 LdapTimeoutTest.MIN_TIMEOUT, 197 NANOSECONDS.toMillis(end - start)); 198 fail(); 199 } else { 200 pass(); 201 } 202 } 203 } 204 205 class DeadServerTimeoutTest extends DeadServerTest { 206 207 public DeadServerTimeoutTest(Hashtable env) throws IOException { 208 super(env); 209 } 210 211 public void handleNamingException(NamingException e, long start, long end) 212 { 213 // non SSL connect will timeout via readReply using connectTimeout 214 if (NANOSECONDS.toMillis(end - start) < 2_900) { 215 pass(); 216 } else { 217 System.err.println("Fail: Waited too long"); 218 fail(); 219 } 220 } 221 } 222 223 class DeadServerTimeoutSSLTest extends DeadServerTest { 224 225 public DeadServerTimeoutSSLTest(Hashtable env) throws IOException { 226 super(env); 227 } 228 229 public void handleNamingException(NamingException e, long start, long end) { 230 if (e.getCause() instanceof SocketTimeoutException) { 231 // SSL connect will timeout via readReply using 232 // SocketTimeoutException 233 pass(); 234 } else { 235 fail(); 236 } 237 } 238 } 239 240 241 class ReadServerNoTimeoutTest extends ReadServerTest { 242 243 public ReadServerNoTimeoutTest(Hashtable env, 244 ScheduledExecutorService killSwitchPool) 245 throws IOException 246 { 247 super(env, killSwitchPool); 248 } 249 250 public void handleNamingException(NamingException e, long start, long end) { 251 if (e instanceof InterruptedNamingException) Thread.interrupted(); 252 253 if (NANOSECONDS.toMillis(end - start) < LdapTimeoutTest.MIN_TIMEOUT) { 254 System.err.printf("ReadServerNoTimeoutTest fail: timeout should be " + 255 "at least %s ms, actual time is %s ms%n", 256 LdapTimeoutTest.MIN_TIMEOUT, 257 NANOSECONDS.toMillis(end - start)); 258 fail(); 259 } else { 260 pass(); 261 } 262 } 263 } 264 265 class ReadServerTimeoutTest extends ReadServerTest { 266 267 public ReadServerTimeoutTest(Hashtable env) throws IOException { 268 super(env); 269 } 270 271 public void handleNamingException(NamingException e, long start, long end) { 272 if (NANOSECONDS.toMillis(end - start) < 2_900) { 273 fail(); 274 } else { 275 pass(); 276 } 277 } 278 } 279 280 class TestServer extends Thread { 281 ServerSocket serverSock; 282 boolean accepting = false; 283 284 public TestServer() throws IOException { 285 this.serverSock = new ServerSocket(0); 286 start(); 287 } 288 289 public int getLocalPort() { 290 return serverSock.getLocalPort(); 291 } 292 293 public boolean accepting() { 294 return accepting; 295 } 296 297 public void close() throws IOException { 298 serverSock.close(); 299 } 300 } 301 302 class BindableServer extends TestServer { 303 304 public BindableServer() throws IOException { 305 super(); 306 } 307 308 private byte[] bindResponse = { 309 0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A, 310 0x01, 0x00, 0x04, 0x00, 0x04, 0x00 311 }; 312 313 public void run() { 314 try { 315 accepting = true; 316 Socket socket = serverSock.accept(); 317 InputStream in = socket.getInputStream(); 318 OutputStream out = socket.getOutputStream(); 319 320 // Read the LDAP BindRequest 321 while (in.read() != -1) { 322 in.skip(in.available()); 323 break; 324 } 325 326 // Write an LDAP BindResponse 327 out.write(bindResponse); 328 out.flush(); 329 } catch (IOException e) { 330 // ignore 331 } 332 } 333 } 334 335 class DeadServer extends TestServer { 336 337 public DeadServer() throws IOException { 338 super(); 339 } 340 341 public void run() { 342 while(true) { 343 try { 344 accepting = true; 345 Socket socket = serverSock.accept(); 346 } catch (Exception e) { 347 break; 348 } 349 } 350 } 351 } 352 353 public class LdapTimeoutTest { 354 355 private static final ExecutorService testPool = 356 Executors.newFixedThreadPool(3); 357 private static final ScheduledExecutorService killSwitchPool = 358 Executors.newScheduledThreadPool(3); 359 public static int MIN_TIMEOUT = 18_000; 360 361 static Hashtable createEnv() { 362 Hashtable env = new Hashtable(11); 363 env.put(Context.INITIAL_CONTEXT_FACTORY, 364 "com.sun.jndi.ldap.LdapCtxFactory"); 365 return env; 366 } 367 368 public static void main(String[] args) throws Exception { 369 370 InitialContext ctx = null; 371 List<Future> results = new ArrayList<>(); 372 373 try { 374 // run the DeadServerTest with no timeouts set 375 // this should get stuck indefinitely, so we need to kill 376 // it after a timeout 377 System.out.println("Running connect timeout test with 20s kill switch"); 378 Hashtable env = createEnv(); 379 results.add( 380 testPool.submit(new DeadServerNoTimeoutTest(env, killSwitchPool))); 381 382 // run the ReadServerTest with connect timeout set 383 // this should get stuck indefinitely so we need to kill 384 // it after a timeout 385 System.out.println("Running read timeout test with 10ms connect timeout & 20s kill switch"); 386 Hashtable env1 = createEnv(); 387 env1.put("com.sun.jndi.ldap.connect.timeout", "10"); 388 results.add(testPool.submit( 389 new ReadServerNoTimeoutTest(env1, killSwitchPool))); 390 391 // run the ReadServerTest with no timeouts set 392 // this should get stuck indefinitely, so we need to kill 393 // it after a timeout 394 System.out.println("Running read timeout test with 20s kill switch"); 395 Hashtable env2 = createEnv(); 396 results.add(testPool.submit( 397 new ReadServerNoTimeoutTest(env2, killSwitchPool))); 398 399 // run the DeadServerTest with connect / read timeouts set 400 // this should exit after the connect timeout expires 401 System.out.println("Running connect timeout test with 10ms connect timeout, 3000ms read timeout"); 402 Hashtable env3 = createEnv(); 403 env3.put("com.sun.jndi.ldap.connect.timeout", "10"); 404 env3.put("com.sun.jndi.ldap.read.timeout", "3000"); 405 results.add(testPool.submit(new DeadServerTimeoutTest(env3))); 406 407 408 // run the ReadServerTest with connect / read timeouts set 409 // this should exit after the connect timeout expires 410 System.out.println("Running read timeout test with 10ms connect timeout, 3000ms read timeout"); 411 Hashtable env4 = createEnv(); 412 env4.put("com.sun.jndi.ldap.connect.timeout", "10"); 413 env4.put("com.sun.jndi.ldap.read.timeout", "3000"); 414 results.add(testPool.submit(new ReadServerTimeoutTest(env4))); 415 416 // run the DeadServerTest with connect timeout set 417 // this should exit after the connect timeout expires 418 System.out.println("Running connect timeout test with 10ms connect timeout"); 419 Hashtable env5 = createEnv(); 420 env5.put("com.sun.jndi.ldap.connect.timeout", "10"); 421 results.add(testPool.submit(new DeadServerTimeoutTest(env5))); 422 423 // 8000487: Java JNDI connection library on ldap conn is 424 // not honoring configured timeout 425 System.out.println("Running simple auth connection test"); 426 Hashtable env6 = createEnv(); 427 env6.put("com.sun.jndi.ldap.connect.timeout", "10"); 428 env6.put("com.sun.jndi.ldap.read.timeout", "3000"); 429 env6.put(Context.SECURITY_AUTHENTICATION, "simple"); 430 env6.put(Context.SECURITY_PRINCIPAL, "user"); 431 env6.put(Context.SECURITY_CREDENTIALS, "password"); 432 results.add(testPool.submit(new DeadServerTimeoutTest(env6))); 433 434 boolean testFailed = false; 435 for (Future test : results) { 436 while (!test.isDone()) { 437 if ((Boolean) test.get() == false) 438 testFailed = true; 439 } 440 } 441 442 // 443 // Running this test serially as it seems to tickle a problem 444 // on older kernels 445 // 446 // run the DeadServerTest with connect / read timeouts set 447 // and ssl enabled 448 // this should exit with a SocketTimeoutException as the root cause 449 // it should also use the connect timeout instead of the read timeout 450 System.out.println("Running connect timeout test with 10ms connect timeout, 3000ms read timeout & SSL"); 451 Hashtable sslenv = createEnv(); 452 sslenv.put("com.sun.jndi.ldap.connect.timeout", "10"); 453 sslenv.put("com.sun.jndi.ldap.read.timeout", "3000"); 454 sslenv.put(Context.SECURITY_PROTOCOL, "ssl"); 455 testFailed = (new DeadServerTimeoutSSLTest(sslenv).call()) ? false : true; 456 457 if (testFailed) { 458 throw new AssertionError("some tests failed"); 459 } 460 461 } finally { 462 LdapTimeoutTest.killSwitchPool.shutdown(); 463 LdapTimeoutTest.testPool.shutdown(); 464 } 465 } 466 467 } 468