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