1 /* 2 * Copyright (c) 2011, 2016, 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 import javax.net.ssl.SSLHandshakeException; 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 228 class ReadServerNoTimeoutTest extends ReadServerTest { 229 230 public ReadServerNoTimeoutTest(Hashtable env, 231 ScheduledExecutorService killSwitchPool) 232 throws IOException 233 { 234 super(env, killSwitchPool); 235 } 236 237 public void handleNamingException(NamingException e, long start, long end) { 238 if (e instanceof InterruptedNamingException) Thread.interrupted(); 239 240 if (NANOSECONDS.toMillis(end - start) < LdapTimeoutTest.MIN_TIMEOUT) { 241 System.err.printf("ReadServerNoTimeoutTest fail: timeout should be " + 242 "at least %s ms, actual time is %s ms%n", 243 LdapTimeoutTest.MIN_TIMEOUT, 244 NANOSECONDS.toMillis(end - start)); 245 fail(); 246 } else { 247 pass(); 248 } 249 } 250 } 251 252 class ReadServerTimeoutTest extends ReadServerTest { 253 254 public ReadServerTimeoutTest(Hashtable env) throws IOException { 255 super(env); 256 } 257 258 public void handleNamingException(NamingException e, long start, long end) { 259 System.out.println("ReadServerTimeoutTest: end-start=" + NANOSECONDS.toMillis(end - start)); 260 if (NANOSECONDS.toMillis(end - start) < 2_500) { 261 fail(); 262 } else { 263 pass(); 264 } 265 } 266 } 267 268 class TestServer extends Thread { 269 ServerSocket serverSock; 270 boolean accepting = false; 271 272 public TestServer() throws IOException { 273 this.serverSock = new ServerSocket(0); 274 start(); 275 } 276 277 public int getLocalPort() { 278 return serverSock.getLocalPort(); 279 } 280 281 public boolean accepting() { 282 return accepting; 283 } 284 285 public void close() throws IOException { 286 serverSock.close(); 287 } 288 } 289 290 class BindableServer extends TestServer { 291 292 public BindableServer() throws IOException { 293 super(); 294 } 295 296 private byte[] bindResponse = { 297 0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A, 298 0x01, 0x00, 0x04, 0x00, 0x04, 0x00 299 }; 300 301 public void run() { 302 try { 303 accepting = true; 304 Socket socket = serverSock.accept(); 305 InputStream in = socket.getInputStream(); 306 OutputStream out = socket.getOutputStream(); 307 308 // Read the LDAP BindRequest 309 while (in.read() != -1) { 310 in.skip(in.available()); 311 break; 312 } 313 314 // Write an LDAP BindResponse 315 out.write(bindResponse); 316 out.flush(); 317 } catch (IOException e) { 318 // ignore 319 } 320 } 321 } 322 323 class DeadServer extends TestServer { 324 325 public DeadServer() throws IOException { 326 super(); 327 } 328 329 public void run() { 330 while(true) { 331 try { 332 accepting = true; 333 Socket socket = serverSock.accept(); 334 } catch (Exception e) { 335 break; 336 } 337 } 338 } 339 } 340 341 public class LdapTimeoutTest { 342 343 private static final ExecutorService testPool = 344 Executors.newFixedThreadPool(3); 345 private static final ScheduledExecutorService killSwitchPool = 346 Executors.newScheduledThreadPool(3); 347 public static int MIN_TIMEOUT = 18_000; 348 349 static Hashtable createEnv() { 350 Hashtable env = new Hashtable(11); 351 env.put(Context.INITIAL_CONTEXT_FACTORY, 352 "com.sun.jndi.ldap.LdapCtxFactory"); 353 return env; 354 } 355 356 public static void main(String[] args) throws Exception { 357 358 InitialContext ctx = null; 359 List<Future> results = new ArrayList<>(); 360 361 try { 362 // run the DeadServerTest with no timeouts set 363 // this should get stuck indefinitely, so we need to kill 364 // it after a timeout 365 System.out.println("Running connect timeout test with 20s kill switch"); 366 Hashtable env = createEnv(); 367 results.add( 368 testPool.submit(new DeadServerNoTimeoutTest(env, killSwitchPool))); 369 370 // run the ReadServerTest with connect timeout set 371 // this should get stuck indefinitely so we need to kill 372 // it after a timeout 373 System.out.println("Running read timeout test with 10ms connect timeout & 20s kill switch"); 374 Hashtable env1 = createEnv(); 375 env1.put("com.sun.jndi.ldap.connect.timeout", "10"); 376 results.add(testPool.submit( 377 new ReadServerNoTimeoutTest(env1, killSwitchPool))); 378 379 // run the ReadServerTest 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 read timeout test with 20s kill switch"); 383 Hashtable env2 = createEnv(); 384 results.add(testPool.submit( 385 new ReadServerNoTimeoutTest(env2, killSwitchPool))); 386 387 // run the DeadServerTest with connect / read timeouts set 388 // this should exit after the connect timeout expires 389 System.out.println("Running connect timeout test with 10ms connect timeout, 3000ms read timeout"); 390 Hashtable env3 = createEnv(); 391 env3.put("com.sun.jndi.ldap.connect.timeout", "10"); 392 env3.put("com.sun.jndi.ldap.read.timeout", "3000"); 393 results.add(testPool.submit(new DeadServerTimeoutTest(env3))); 394 395 396 // run the ReadServerTest with connect / read timeouts set 397 // this should exit after the connect timeout expires 398 // 399 // NOTE: commenting this test out as it is failing intermittently. 400 // 401 // System.out.println("Running read timeout test with 10ms connect timeout, 3000ms read timeout"); 402 // Hashtable env4 = createEnv(); 403 // env4.put("com.sun.jndi.ldap.connect.timeout", "10"); 404 // env4.put("com.sun.jndi.ldap.read.timeout", "3000"); 405 // results.add(testPool.submit(new ReadServerTimeoutTest(env4))); 406 407 // run the DeadServerTest with connect timeout set 408 // this should exit after the connect timeout expires 409 System.out.println("Running connect timeout test with 10ms connect timeout"); 410 Hashtable env5 = createEnv(); 411 env5.put("com.sun.jndi.ldap.connect.timeout", "10"); 412 results.add(testPool.submit(new DeadServerTimeoutTest(env5))); 413 414 // 8000487: Java JNDI connection library on ldap conn is 415 // not honoring configured timeout 416 System.out.println("Running simple auth connection test"); 417 Hashtable env6 = createEnv(); 418 env6.put("com.sun.jndi.ldap.connect.timeout", "10"); 419 env6.put("com.sun.jndi.ldap.read.timeout", "3000"); 420 env6.put(Context.SECURITY_AUTHENTICATION, "simple"); 421 env6.put(Context.SECURITY_PRINCIPAL, "user"); 422 env6.put(Context.SECURITY_CREDENTIALS, "password"); 423 results.add(testPool.submit(new DeadServerTimeoutTest(env6))); 424 425 boolean testFailed = false; 426 for (Future test : results) { 427 while (!test.isDone()) { 428 if ((Boolean) test.get() == false) 429 testFailed = true; 430 } 431 } 432 433 if (testFailed) { 434 throw new AssertionError("some tests failed"); 435 } 436 437 } finally { 438 LdapTimeoutTest.killSwitchPool.shutdown(); 439 LdapTimeoutTest.testPool.shutdown(); 440 } 441 } 442 443 } 444