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