1 /*
   2  * Copyright (c) 2019, 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 import jtreg.SkippedException;
  25 
  26 import javax.naming.directory.InitialDirContext;
  27 
  28 import java.io.IOException;
  29 import java.net.BindException;
  30 import java.net.InetAddress;
  31 import java.net.ServerSocket;
  32 
  33 import static java.util.concurrent.TimeUnit.NANOSECONDS;
  34 import static jdk.test.lib.Utils.adjustTimeout;
  35 
  36 /*
  37  * @test
  38  * @bug 8228580
  39  * @summary Tests that we get a DNS response when the UDP DNS server returns a
  40  *          truncated response and the TCP DNS server does not respond at all
  41  *          after connect.
  42  * @library ../lib/
  43  * @library /test/lib
  44  * @modules java.base/sun.security.util
  45  * @run main TcpTimeout
  46  * @run main TcpTimeout -Dcom.sun.jndi.dns.timeout.initial=5000
  47  */
  48 
  49 public class TcpTimeout extends DNSTestBase {
  50     private TcpDnsServer tcpDnsServer;
  51     
  52     /* The acceptable variation in timeout measurement. */
  53     private static final long TOLERANCE = adjustTimeout(5_000);
  54     
  55     public static void main(String[] args) throws Exception {
  56         new TcpTimeout().run(args);
  57     }
  58 
  59     @Override
  60     public void runTest() throws Exception {
  61         /* Default timeout value is 1 second, as stated in jdk.naming.dns
  62            module docs. */
  63         long timeout = 1_000;
  64         var envTimeout = env().get("com.sun.jndi.dns.timeout.initial");
  65         if (envTimeout != null)
  66             timeout = Long.parseLong(String.valueOf(envTimeout));
  67 
  68         setContext(new InitialDirContext(env()));
  69         
  70         long startNanos = System.nanoTime();
  71 
  72         /* perform query */
  73         var attrs = context().getAttributes("host1");
  74 
  75         long elapsed = NANOSECONDS.toMillis(System.nanoTime() - startNanos);
  76         if (elapsed < timeout || elapsed > timeout + TOLERANCE) {
  77             throw new RuntimeException("Query took " + elapsed + " ms. "
  78                 + ". Timeout value is " + timeout);
  79         }
  80 
  81         DNSTestUtils.debug(attrs);
  82 
  83         /* Note that the returned attributes are truncated and the response
  84         is not valid. */
  85         var txtAttr = attrs.get("TXT");
  86         if (txtAttr == null)
  87             throw new RuntimeException("TXT attribute missing.");
  88     }
  89 
  90     @Override
  91     public void initTest(String[] args) {
  92         /* We need to bind the TCP server on the same port UDP server is
  93         listening to. This maybe not be possible if the port is in use. Retry
  94         several times relaying on UDP port randomness. */
  95         for (int i = 0; i < 5; i++) {
  96             super.initTest(args);
  97             var udpServer = (Server) env().get(DNSTestUtils.TEST_DNS_SERVER_THREAD);
  98             int port = udpServer.getPort();
  99             try {
 100                 tcpDnsServer = new TcpDnsServer(port);
 101                 break; // success
 102             } catch (BindException be) {
 103                 DNSTestUtils.debug("Failed to bind server socket on port " + port
 104                     + ", retry no. " + (i + 1));
 105             } catch (Exception ex) {
 106                 throw new RuntimeException("Unexpected exception during initTest", ex);
 107             } finally {
 108                 if (tcpDnsServer == null) { // cleaup behind exceptions
 109                     super.cleanupTest();
 110                 }
 111             }
 112         }
 113 
 114         if (tcpDnsServer == null) {
 115             throw new SkippedException("Cannot start TCP server after 5 tries, skip test");
 116         }
 117     }
 118 
 119     @Override
 120     public void cleanupTest() {
 121         super.cleanupTest();
 122         if (tcpDnsServer != null)
 123             tcpDnsServer.stopServer();
 124     }
 125 
 126     /**
 127      * A TCP server that accepts a connection and does nothing else: causes read
 128      * timeout on client side.
 129      */
 130     private static class TcpDnsServer {
 131         final ServerSocket serverSocket;
 132         
 133         TcpDnsServer(int port) throws IOException {
 134             serverSocket = new ServerSocket(port, 0, InetAddress.getLoopbackAddress());
 135             System.out.println("TcpDnsServer: listening on port " + port);
 136         }
 137 
 138         void stopServer() {
 139             try {
 140                 if (serverSocket != null)
 141                     serverSocket.close();
 142             }
 143             catch (Exception e) {}
 144         }
 145     }
 146 }