Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8051854

TCP connection failover based on multiple DNS results

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Unresolved
    • Icon: P4 P4
    • None
    • 7
    • core-libs
    • x86
    • windows_7

      A DESCRIPTION OF THE REQUEST :
      If Java tries to connect to a host for which the DNS record has multiple IP addresses in the A record, only the first returned IP is used.

      JUSTIFICATION :
        To be highly available (and also to efficiently distribute the flows), some web servers (eg: www.twitter.com) define several IP for the same DNS record.

      In that way, if a given IP is not accessible (route down for instance, server down, firewalls...), the service is still guarantee.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Java should transparently try to connect to the first address and, if a connection timeout occurs, then test the second address, ..
      ACTUAL -
      If Java tries to connect to a host for which the DNS record has multiple IP addresses in the A record, only the first returned IP is used.

      In fact, the constructor java.net.InetSocketAddress.InetSocketAddress(String, int) uses java.net.InetAddress.getByName(String) to resolve the IP, which does a "InetAddress.getAllByName(host)[0];"

      ---------- BEGIN SOURCE ----------
      Let's run the given source code :

      try {
      // Verify the different IP
      InetAddress[] addrs = InetAddress.getAllByName("www.twitter.com");
      for (InetAddress add : addrs) {
      System.out.println(add);
      }

      // Now, try to connect
      URL u = new URL("http://www.twitter.com/");
      HttpURLConnection con = (HttpURLConnection)u.openConnection();
      con.setConnectTimeout(2000);
      con.connect();
      } catch (Exception e) {
      e.printStackTrace();
      }

      Once you got the 3 defined IP, modify you firewall rules to block the first one. You'll get the following stack trace :

      java.net.SocketTimeoutException: connect timed out
      at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
      at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:75)
      at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
      at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
      at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
      at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:157)
      at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:391)
      at java.net.Socket.connect(Socket.java:579)
      at sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:612)
      at sun.net.NetworkClient.doConnect(NetworkClient.java:175)
      at sun.net.www.http.HttpClient.openServer(HttpClient.java:388)
      at sun.net.www.http.HttpClient.openServer(HttpClient.java:483)
      at sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:278)
      at sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:335)
      at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:191)
      at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:928)
      at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:177)
      at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:153)


      We expect to check the second or the third IP in that case...
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The only way I found to bypass this bug is to patch the method sun.net.NetworkClient.doConnect(String, int)

          /**
           * Return a socket connected to the server, with any
           * appropriate options pre-established
           */
          protected Socket doConnect (String server, int port)
          throws IOException, UnknownHostException {
              Socket s = null;
              // Some hosts have multiple DNS records (fail over). So let's try the different
           // addresses to connect the server as the InetAddress.getByName(server) always
           // return the first result
           InetAddress[] multipleAddr = InetAddress.getAllByName(server);
           boolean connected = false;
           for (int i = 0; i < multipleAddr.length && !connected; i++) {
      if (proxy != null) {
      if (proxy.type() == Proxy.Type.SOCKS) {
      s = AccessController.doPrivileged(
      new PrivilegedAction<Socket>() {
      public Socket run() {
      return new Socket(proxy);
      }});
      } else if (proxy.type() == Proxy.Type.DIRECT) {
      s = createSocket();
      } else {
      // Still connecting through a proxy
      // server & port will be the proxy address and port
      s = new Socket(Proxy.NO_PROXY);
      }
      } else
      s = createSocket();

      // Instance specific timeouts do have priority, that means
      // connectTimeout & readTimeout (-1 means not set)
      // Then global default timeouts
      // Then no timeout.
      InetAddress addr = multipleAddr[i];
      try {
      if (connectTimeout >= 0) {
      s.connect(new InetSocketAddress(addr, port), connectTimeout);
      } else {
      if (defaultConnectTimeout > 0) {
      s.connect(new InetSocketAddress(addr, port), defaultConnectTimeout);
      } else {
      s.connect(new InetSocketAddress(addr, port));
      }
      }
      connected = true;
      } catch (SocketTimeoutException e) {
      s.close(); // We'll recreate it

      // Host cannot be reached ; let's try the other host if found
      if (i == multipleAddr.length - 1) {
      // This is the last host, throw the exception in that case
      throw e;
      }
      }
           }
          
           // s may not be null here
           if(s != null) {
      if (readTimeout >= 0)
      s.setSoTimeout(readTimeout);
      else if (defaultSoTimeout > 0) {
      s.setSoTimeout(defaultSoTimeout);
      }
           }
              return s;
          }


      Then, I did a jar file and modifyied the bootclasspath :
      java -Xbootclasspath/p:d:\tmp\dns-rt.jar ...

            Unassigned Unassigned
            coffeys Sean Coffey
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Imported: