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 public void fail(Exception e) { 88 throw new RuntimeException("Test failed", e); 89 } 90 91 boolean shutItDown(InitialContext ctx) { 92 try { 93 if (ctx != null) ctx.close(); 94 return true; 95 } catch (NamingException ex) { 96 return false; 97 } 98 } 99 100 public Boolean call() { 101 InitialContext ctx = null; 102 ScheduledFuture killer = null; 103 long start = System.nanoTime(); 104 105 try { 106 while(!server.accepting()) 107 Thread.sleep(200); // allow the server to start up 108 Thread.sleep(200); // to be sure 109 110 // if this is a hanging test, scheduled a thread to 111 // interrupt after a certain time 112 if (killSwitchPool != null) { 113 final Thread current = Thread.currentThread(); 114 killer = killSwitchPool.schedule( 115 new Callable<Void>() { 116 public Void call() throws Exception { 117 current.interrupt(); 118 return null; 119 } 120 }, HANGING_TEST_TIMEOUT, MILLISECONDS); 121 } 122 123 env.put(Context.PROVIDER_URL, "ldap://localhost:" + 124 server.getLocalPort()); 125 126 try { 127 ctx = new InitialDirContext(env); 128 performOp(ctx); 129 fail(); 130 } catch (NamingException e) { 131 long end = System.nanoTime(); 132 System.out.println(this.getClass().toString() + " - elapsed: " 133 + NANOSECONDS.toMillis(end - start)); 134 handleNamingException(e, start, end); 135 } finally { 136 if (killer != null && !killer.isDone()) 137 killer.cancel(true); 138 shutItDown(ctx); 139 server.close(); 140 } 141 return passed; 142 } catch (IOException|InterruptedException e) { 143 throw new RuntimeException(e); 144 } 145 } 146 } 147 148 abstract class ReadServerTest extends LdapTest { 149 150 public ReadServerTest(Hashtable env) throws IOException { 151 super(new BindableServer(), env); 152 } 153 154 public ReadServerTest(Hashtable env, 155 ScheduledExecutorService killSwitchPool) 156 throws IOException 157 { 158 super(new BindableServer(), env, killSwitchPool); 159 } 160 161 public void performOp(InitialContext ctx) throws NamingException { 162 SearchControls scl = new SearchControls(); 163 scl.setSearchScope(SearchControls.SUBTREE_SCOPE); 164 NamingEnumeration<SearchResult> answer = ((InitialDirContext)ctx) 165 .search("ou=People,o=JNDITutorial", "(objectClass=*)", scl); 166 } 167 } 168 169 abstract class DeadServerTest extends LdapTest { 170 171 public DeadServerTest(Hashtable env) throws IOException { 172 super(new DeadServer(), env); 173 } 174 175 public DeadServerTest(Hashtable env, 176 ScheduledExecutorService killSwitchPool) 177 throws IOException 178 { 179 super(new DeadServer(), env, killSwitchPool); 180 } 181 182 public void performOp(InitialContext ctx) throws NamingException {} 183 } 184 185 class DeadServerNoTimeoutTest extends DeadServerTest { 186 187 public DeadServerNoTimeoutTest(Hashtable env, 188 ScheduledExecutorService killSwitchPool) 189 throws IOException 190 { 191 super(env, killSwitchPool); 192 } 193 194 public void handleNamingException(NamingException e, long start, long end) { 195 if (e instanceof InterruptedNamingException) Thread.interrupted(); 196 197 if (NANOSECONDS.toMillis(end - start) < LdapTimeoutTest.MIN_TIMEOUT) { 198 System.err.printf("DeadServerNoTimeoutTest fail: timeout should be " + 199 "at least %s ms, actual time is %s ms%n", 200 LdapTimeoutTest.MIN_TIMEOUT, 201 NANOSECONDS.toMillis(end - start)); 202 fail(); 203 } else { 204 pass(); 205 } 206 } 207 } 208 209 class DeadServerTimeoutTest extends DeadServerTest { 210 211 public DeadServerTimeoutTest(Hashtable env) throws IOException { 212 super(env); 213 } 214 215 public void handleNamingException(NamingException e, long start, long end) 216 { 217 // non SSL connect will timeout via readReply using connectTimeout 218 if (NANOSECONDS.toMillis(end - start) < 2_900) { 219 pass(); 220 } else { 221 System.err.println("Fail: Waited too long"); 222 fail(); 223 } 224 } 225 } 226 227 class DeadServerTimeoutSSLTest extends DeadServerTest { 228 229 public DeadServerTimeoutSSLTest(Hashtable env) throws IOException { 230 super(env); 231 } 232 233 public void handleNamingException(NamingException e, long start, long end) { 234 if (e.getCause() instanceof SocketTimeoutException) { 235 // SSL connect will timeout via readReply using 236 // SocketTimeoutException 237 pass(); 238 } else { 239 fail(e); 240 } 241 } 242 } 243 244 245 class ReadServerNoTimeoutTest extends ReadServerTest { 246 247 public ReadServerNoTimeoutTest(Hashtable env, 248 ScheduledExecutorService killSwitchPool) 249 throws IOException 250 { 251 super(env, killSwitchPool); 252 } 253 254 public void handleNamingException(NamingException e, long start, long end) { 255 if (e instanceof InterruptedNamingException) Thread.interrupted(); 256 257 if (NANOSECONDS.toMillis(end - start) < LdapTimeoutTest.MIN_TIMEOUT) { 258 System.err.printf("ReadServerNoTimeoutTest fail: timeout should be " + 259 "at least %s ms, actual time is %s ms%n", 260 LdapTimeoutTest.MIN_TIMEOUT, 261 NANOSECONDS.toMillis(end - start)); 262 fail(); 263 } else { 264 pass(); 265 } 266 } 267 } 268 269 class ReadServerTimeoutTest extends ReadServerTest { 270 271 public ReadServerTimeoutTest(Hashtable env) throws IOException { 272 super(env); 273 } 274 275 public void handleNamingException(NamingException e, long start, long end) { 276 System.out.println("ReadServerTimeoutTest: end-start=" + NANOSECONDS.toMillis(end - start)); 277 if (NANOSECONDS.toMillis(end - start) < 2_500) { 278 fail(); 279 } else { 280 pass(); 281 } 282 } 283 } 284 285 class TestServer extends Thread { 286 ServerSocket serverSock; 287 boolean accepting = false; 288 289 public TestServer() throws IOException { 290 this.serverSock = new ServerSocket(0); 291 start(); 292 } 293 294 public int getLocalPort() { 295 return serverSock.getLocalPort(); 296 } 297 298 public boolean accepting() { 299 return accepting; 300 } 301 302 public void close() throws IOException { 303 serverSock.close(); 304 } 305 } 306 307 class BindableServer extends TestServer { 308 309 public BindableServer() throws IOException { 310 super(); 311 } 312 313 private byte[] bindResponse = { 314 0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A, 315 0x01, 0x00, 0x04, 0x00, 0x04, 0x00 316 }; 317 318 public void run() { 319 try { 320 accepting = true; 321 Socket socket = serverSock.accept(); 322 InputStream in = socket.getInputStream(); 323 OutputStream out = socket.getOutputStream(); 324 325 // Read the LDAP BindRequest 326 while (in.read() != -1) { 327 in.skip(in.available()); 328 break; 329 } 330 331 // Write an LDAP BindResponse 332 out.write(bindResponse); 333 out.flush(); 334 } catch (IOException e) { 335 // ignore 336 } 337 } 338 } 339 340 class DeadServer extends TestServer { 341 342 public DeadServer() throws IOException { 343 super(); 344 } 345 346 public void run() { 347 while(true) { 348 try { 349 accepting = true; 350 Socket socket = serverSock.accept(); 351 } catch (Exception e) { 352 break; 353 } 354 } 355 } 356 } 357 358 public class LdapTimeoutTest { 359 360 private static final ExecutorService testPool = 361 Executors.newFixedThreadPool(3); 362 private static final ScheduledExecutorService killSwitchPool = 363 Executors.newScheduledThreadPool(3); 364 public static int MIN_TIMEOUT = 18_000; 365 366 static Hashtable createEnv() { 367 Hashtable env = new Hashtable(11); 368 env.put(Context.INITIAL_CONTEXT_FACTORY, 369 "com.sun.jndi.ldap.LdapCtxFactory"); 370 return env; 371 } 372 373 public static void main(String[] args) throws Exception { 374 375 InitialContext ctx = null; 376 List<Future> results = new ArrayList<>(); 377 378 try { 379 // run the DeadServerTest with no timeouts set 380 // this should get stuck indefinitely, so we need to kill 381 // it after a timeout 382 System.out.println("Running connect timeout test with 20s kill switch"); 383 Hashtable env = createEnv(); 384 results.add( 385 testPool.submit(new DeadServerNoTimeoutTest(env, killSwitchPool))); 386 387 // run the ReadServerTest with connect timeout set 388 // this should get stuck indefinitely so we need to kill 389 // it after a timeout 390 System.out.println("Running read timeout test with 10ms connect timeout & 20s kill switch"); 391 Hashtable env1 = createEnv(); 392 env1.put("com.sun.jndi.ldap.connect.timeout", "10"); 393 results.add(testPool.submit( 394 new ReadServerNoTimeoutTest(env1, killSwitchPool))); 395 396 // run the ReadServerTest with no timeouts set 397 // this should get stuck indefinitely, so we need to kill 398 // it after a timeout 399 System.out.println("Running read timeout test with 20s kill switch"); 400 Hashtable env2 = createEnv(); 401 results.add(testPool.submit( 402 new ReadServerNoTimeoutTest(env2, killSwitchPool))); 403 404 // run the DeadServerTest with connect / read timeouts set 405 // this should exit after the connect timeout expires 406 System.out.println("Running connect timeout test with 10ms connect timeout, 3000ms read timeout"); 407 Hashtable env3 = createEnv(); 408 env3.put("com.sun.jndi.ldap.connect.timeout", "10"); 409 env3.put("com.sun.jndi.ldap.read.timeout", "3000"); 410 results.add(testPool.submit(new DeadServerTimeoutTest(env3))); 411 412 413 // run the ReadServerTest with connect / read timeouts set 414 // this should exit after the connect timeout expires 415 // 416 // NOTE: commenting this test out as it is failing intermittently. 417 // 418 // System.out.println("Running read timeout test with 10ms connect timeout, 3000ms read timeout"); 419 // Hashtable env4 = createEnv(); 420 // env4.put("com.sun.jndi.ldap.connect.timeout", "10"); 421 // env4.put("com.sun.jndi.ldap.read.timeout", "3000"); 422 // results.add(testPool.submit(new ReadServerTimeoutTest(env4))); 423 424 // run the DeadServerTest with connect timeout set 425 // this should exit after the connect timeout expires 426 System.out.println("Running connect timeout test with 10ms connect timeout"); 427 Hashtable env5 = createEnv(); 428 env5.put("com.sun.jndi.ldap.connect.timeout", "10"); 429 results.add(testPool.submit(new DeadServerTimeoutTest(env5))); 430 431 // 8000487: Java JNDI connection library on ldap conn is 432 // not honoring configured timeout 433 System.out.println("Running simple auth connection test"); 434 Hashtable env6 = createEnv(); 435 env6.put("com.sun.jndi.ldap.connect.timeout", "10"); 436 env6.put("com.sun.jndi.ldap.read.timeout", "3000"); 437 env6.put(Context.SECURITY_AUTHENTICATION, "simple"); 438 env6.put(Context.SECURITY_PRINCIPAL, "user"); 439 env6.put(Context.SECURITY_CREDENTIALS, "password"); 440 results.add(testPool.submit(new DeadServerTimeoutTest(env6))); 441 442 boolean testFailed = false; 443 for (Future test : results) { 444 while (!test.isDone()) { 445 if ((Boolean) test.get() == false) 446 testFailed = true; 447 } 448 } 449 450 // 451 // Running this test serially as it seems to tickle a problem 452 // on older kernels 453 // 454 // run the DeadServerTest with connect / read timeouts set 455 // and ssl enabled 456 // this should exit with a SocketTimeoutException as the root cause 457 // it should also use the connect timeout instead of the read timeout 458 System.out.println("Running connect timeout test with 10ms connect timeout, 3000ms read timeout & SSL"); 459 Hashtable sslenv = createEnv(); 460 sslenv.put("com.sun.jndi.ldap.connect.timeout", "10"); 461 sslenv.put("com.sun.jndi.ldap.read.timeout", "3000"); 462 sslenv.put(Context.SECURITY_PROTOCOL, "ssl"); 463 testFailed = (new DeadServerTimeoutSSLTest(sslenv).call()) ? false : true; 464 465 if (testFailed) { 466 throw new AssertionError("some tests failed"); 467 } 468 469 } finally { 470 LdapTimeoutTest.killSwitchPool.shutdown(); 471 LdapTimeoutTest.testPool.shutdown(); 472 } 473 } 474 475 } 476