6952519: kdc_timeout is not being honoured when using TCP
authorweijun
Tue, 09 Nov 2010 08:34:11 +0800
changeset 7175 46ec4c2dbc16
parent 7174 5a56e43d896c
child 7176 19571999a46a
6952519: kdc_timeout is not being honoured when using TCP Reviewed-by: valeriep
jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java
jdk/src/share/classes/sun/security/krb5/internal/NetClient.java
jdk/src/share/classes/sun/security/krb5/internal/TCPClient.java
jdk/src/share/classes/sun/security/krb5/internal/UDPClient.java
jdk/test/sun/security/krb5/auto/TcpTimeout.java
--- a/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java	Mon Nov 08 09:29:18 2010 -0800
+++ b/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java	Tue Nov 09 08:34:11 2010 +0800
@@ -36,8 +36,7 @@
 import java.security.Security;
 import java.util.Locale;
 import sun.security.krb5.internal.Krb5;
-import sun.security.krb5.internal.UDPClient;
-import sun.security.krb5.internal.TCPClient;
+import sun.security.krb5.internal.NetClient;
 import java.io.IOException;
 import java.net.SocketTimeoutException;
 import java.util.StringTokenizer;
@@ -349,12 +348,16 @@
 
             byte[] ibuf = null;
 
-            if (useTCP) {
-                TCPClient kdcClient = new TCPClient(kdc, port);
+            for (int i=1; i <= retries; i++) {
+                String proto = useTCP?"TCP":"UDP";
+                NetClient kdcClient = NetClient.getInstance(
+                        proto, kdc, port, timeout);
                 if (DEBUG) {
                     System.out.println(">>> KDCCommunication: kdc=" + kdc
-                           + " TCP:"
-                           +  port
+                           + " " + proto + ":"
+                           +  port +  ", timeout="
+                           + timeout
+                           + ",Attempt =" + i
                            + ", #bytes=" + obuf.length);
                 }
                 try {
@@ -366,51 +369,19 @@
                      * And get a response.
                      */
                     ibuf = kdcClient.receive();
+                    break;
+                } catch (SocketTimeoutException se) {
+                    if (DEBUG) {
+                        System.out.println ("SocketTimeOutException with " +
+                                            "attempt: " + i);
+                    }
+                    if (i == retries) {
+                        ibuf = null;
+                        throw se;
+                    }
                 } finally {
                     kdcClient.close();
                 }
-
-            } else {
-                // For each KDC we try defaultKdcRetryLimit times to
-                // get the response
-                for (int i=1; i <= retries; i++) {
-                    UDPClient kdcClient = new UDPClient(kdc, port, timeout);
-
-                    if (DEBUG) {
-                        System.out.println(">>> KDCCommunication: kdc=" + kdc
-                               + (useTCP ? " TCP:":" UDP:")
-                               +  port +  ", timeout="
-                               + timeout
-                               + ",Attempt =" + i
-                               + ", #bytes=" + obuf.length);
-                    }
-                    try {
-                        /*
-                         * Send the data to the kdc.
-                         */
-
-                        kdcClient.send(obuf);
-
-                        /*
-                         * And get a response.
-                         */
-                        try {
-                            ibuf = kdcClient.receive();
-                            break;
-                        } catch (SocketTimeoutException se) {
-                            if (DEBUG) {
-                                System.out.println ("SocketTimeOutException with " +
-                                                    "attempt: " + i);
-                            }
-                            if (i == retries) {
-                                ibuf = null;
-                                throw se;
-                            }
-                        }
-                    } finally {
-                        kdcClient.close();
-                    }
-                }
             }
             return ibuf;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/krb5/internal/NetClient.java	Tue Nov 09 08:34:11 2010 +0800
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ *
+ *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
+ *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
+ */
+
+package sun.security.krb5.internal;
+
+import java.io.*;
+import java.net.*;
+
+public abstract class NetClient {
+    public static NetClient getInstance(String protocol, String hostname, int port,
+            int timeout) throws IOException {
+        if (protocol.equals("TCP")) {
+            return new TCPClient(hostname, port, timeout);
+        } else {
+            return new UDPClient(hostname, port, timeout);
+        }
+    }
+
+    abstract public void send(byte[] data) throws IOException;
+
+    abstract public byte[] receive() throws IOException;
+
+    abstract public void close() throws IOException;
+}
+
+class TCPClient extends NetClient {
+
+    private Socket tcpSocket;
+    private BufferedOutputStream out;
+    private BufferedInputStream in;
+
+    TCPClient(String hostname, int port, int timeout)
+            throws IOException {
+        tcpSocket = new Socket(hostname, port);
+        out = new BufferedOutputStream(tcpSocket.getOutputStream());
+        in = new BufferedInputStream(tcpSocket.getInputStream());
+        tcpSocket.setSoTimeout(timeout);
+    }
+
+    @Override
+    public void send(byte[] data) throws IOException {
+        byte[] lenField = new byte[4];
+        intToNetworkByteOrder(data.length, lenField, 0, 4);
+        out.write(lenField);
+
+        out.write(data);
+        out.flush();
+    }
+
+    @Override
+    public byte[] receive() throws IOException {
+        byte[] lenField = new byte[4];
+        int count = readFully(lenField, 4);
+
+        if (count != 4) {
+            if (Krb5.DEBUG) {
+                System.out.println(
+                    ">>>DEBUG: TCPClient could not read length field");
+            }
+            return null;
+        }
+
+        int len = networkByteOrderToInt(lenField, 0, 4);
+        if (Krb5.DEBUG) {
+            System.out.println(
+                ">>>DEBUG: TCPClient reading " + len + " bytes");
+        }
+        if (len <= 0) {
+            if (Krb5.DEBUG) {
+                System.out.println(
+                    ">>>DEBUG: TCPClient zero or negative length field: "+len);
+            }
+            return null;
+        }
+
+        byte data[] = new byte[len];
+        count = readFully(data, len);
+        if (count != len) {
+            if (Krb5.DEBUG) {
+                System.out.println(
+                    ">>>DEBUG: TCPClient could not read complete packet (" +
+                    len + "/" + count + ")");
+            }
+            return null;
+        } else {
+            return data;
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        tcpSocket.close();
+    }
+
+    /**
+     * Read requested number of bytes before returning.
+     * @return The number of bytes actually read; -1 if none read
+     */
+    private int readFully(byte[] inBuf, int total) throws IOException {
+        int count, pos = 0;
+
+        while (total > 0) {
+            count = in.read(inBuf, pos, total);
+
+            if (count == -1) {
+                return (pos == 0? -1 : pos);
+            }
+            pos += count;
+            total -= count;
+        }
+        return pos;
+    }
+
+    /**
+     * Returns the integer represented by 4 bytes in network byte order.
+     */
+    private static int networkByteOrderToInt(byte[] buf, int start,
+        int count) {
+        if (count > 4) {
+            throw new IllegalArgumentException(
+                "Cannot handle more than 4 bytes");
+        }
+
+        int answer = 0;
+
+        for (int i = 0; i < count; i++) {
+            answer <<= 8;
+            answer |= ((int)buf[start+i] & 0xff);
+        }
+        return answer;
+    }
+
+    /**
+     * Encodes an integer into 4 bytes in network byte order in the buffer
+     * supplied.
+     */
+    private static void intToNetworkByteOrder(int num, byte[] buf,
+        int start, int count) {
+        if (count > 4) {
+            throw new IllegalArgumentException(
+                "Cannot handle more than 4 bytes");
+        }
+
+        for (int i = count-1; i >= 0; i--) {
+            buf[start+i] = (byte)(num & 0xff);
+            num >>>= 8;
+        }
+    }
+}
+
+class UDPClient extends NetClient {
+    InetAddress iaddr;
+    int iport;
+    int bufSize = 65507;
+    DatagramSocket dgSocket;
+    DatagramPacket dgPacketIn;
+
+    UDPClient(String hostname, int port, int timeout)
+        throws UnknownHostException, SocketException {
+        iaddr = InetAddress.getByName(hostname);
+        iport = port;
+        dgSocket = new DatagramSocket();
+        dgSocket.setSoTimeout(timeout);
+    }
+
+    @Override
+    public void send(byte[] data) throws IOException {
+        DatagramPacket dgPacketOut = new DatagramPacket(data, data.length,
+                                                        iaddr, iport);
+        dgSocket.send(dgPacketOut);
+    }
+
+    @Override
+    public byte[] receive() throws IOException {
+        byte ibuf[] = new byte[bufSize];
+        dgPacketIn = new DatagramPacket(ibuf, ibuf.length);
+        try {
+            dgSocket.receive(dgPacketIn);
+        }
+        catch (SocketException e) {
+            dgSocket.receive(dgPacketIn);
+        }
+        byte[] data = new byte[dgPacketIn.getLength()];
+        System.arraycopy(dgPacketIn.getData(), 0, data, 0,
+                         dgPacketIn.getLength());
+        return data;
+    }
+
+    @Override
+    public void close() {
+        dgSocket.close();
+    }
+}
--- a/jdk/src/share/classes/sun/security/krb5/internal/TCPClient.java	Mon Nov 08 09:29:18 2010 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/*
- * Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- *
- *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
- *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
- */
-
-package sun.security.krb5.internal;
-
-import java.io.*;
-import java.net.*;
-
-public class TCPClient {
-
-    private Socket tcpSocket;
-    private BufferedOutputStream out;
-    private BufferedInputStream in;
-
-    public TCPClient(String hostname, int port) throws IOException {
-        tcpSocket = new Socket(hostname, port);
-        out = new BufferedOutputStream(tcpSocket.getOutputStream());
-        in = new BufferedInputStream(tcpSocket.getInputStream());
-    }
-
-    public void send(byte[] data) throws IOException {
-        byte[] lenField = new byte[4];
-        intToNetworkByteOrder(data.length, lenField, 0, 4);
-        out.write(lenField);
-
-        out.write(data);
-        out.flush();
-    }
-
-    public byte[] receive() throws IOException {
-        byte[] lenField = new byte[4];
-        int count = readFully(lenField, 4);
-
-        if (count != 4) {
-            if (Krb5.DEBUG) {
-                System.out.println(
-                    ">>>DEBUG: TCPClient could not read length field");
-            }
-            return null;
-        }
-
-        int len = networkByteOrderToInt(lenField, 0, 4);
-        if (Krb5.DEBUG) {
-            System.out.println(
-                ">>>DEBUG: TCPClient reading " + len + " bytes");
-        }
-        if (len <= 0) {
-            if (Krb5.DEBUG) {
-                System.out.println(
-                    ">>>DEBUG: TCPClient zero or negative length field: "+len);
-            }
-            return null;
-        }
-
-        byte data[] = new byte[len];
-        count = readFully(data, len);
-        if (count != len) {
-            if (Krb5.DEBUG) {
-                System.out.println(
-                    ">>>DEBUG: TCPClient could not read complete packet (" +
-                    len + "/" + count + ")");
-            }
-            return null;
-        } else {
-            return data;
-        }
-    }
-
-    public void close() throws IOException {
-        tcpSocket.close();
-    }
-
-    /**
-     * Read requested number of bytes before returning.
-     * @return The number of bytes actually read; -1 if none read
-     */
-    private int readFully(byte[] inBuf, int total) throws IOException {
-        int count, pos = 0;
-
-        while (total > 0) {
-            count = in.read(inBuf, pos, total);
-
-            if (count == -1) {
-                return (pos == 0? -1 : pos);
-            }
-            pos += count;
-            total -= count;
-        }
-        return pos;
-    }
-
-    /**
-     * Returns the integer represented by 4 bytes in network byte order.
-     */
-    private static final int networkByteOrderToInt(byte[] buf, int start,
-        int count) {
-        if (count > 4) {
-            throw new IllegalArgumentException(
-                "Cannot handle more than 4 bytes");
-        }
-
-        int answer = 0;
-
-        for (int i = 0; i < count; i++) {
-            answer <<= 8;
-            answer |= ((int)buf[start+i] & 0xff);
-        }
-        return answer;
-    }
-
-    /**
-     * Encodes an integer into 4 bytes in network byte order in the buffer
-     * supplied.
-     */
-    private static final void intToNetworkByteOrder(int num, byte[] buf,
-        int start, int count) {
-        if (count > 4) {
-            throw new IllegalArgumentException(
-                "Cannot handle more than 4 bytes");
-        }
-
-        for (int i = count-1; i >= 0; i--) {
-            buf[start+i] = (byte)(num & 0xff);
-            num >>>= 8;
-        }
-    }
-}
--- a/jdk/src/share/classes/sun/security/krb5/internal/UDPClient.java	Mon Nov 08 09:29:18 2010 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- *
- *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
- *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
- */
-
-package sun.security.krb5.internal;
-
-import java.io.*;
-import java.net.*;
-
-public class UDPClient {
-    InetAddress iaddr;
-    int iport;
-    int bufSize = 65507;
-    DatagramSocket dgSocket;
-    DatagramPacket dgPacketIn;
-
-    public UDPClient(InetAddress newIAddr, int port)
-        throws SocketException {
-        iaddr = newIAddr;
-        iport = port;
-        dgSocket = new DatagramSocket();
-    }
-
-    public UDPClient(String hostname, int port)
-        throws UnknownHostException, SocketException {
-        iaddr = InetAddress.getByName(hostname);
-        iport = port;
-        dgSocket = new DatagramSocket();
-    }
-
-    public UDPClient(String hostname, int port, int timeout)
-        throws UnknownHostException, SocketException {
-        iaddr = InetAddress.getByName(hostname);
-        iport = port;
-        dgSocket = new DatagramSocket();
-        dgSocket.setSoTimeout(timeout);
-    }
-
-    public void setBufSize(int newBufSize) {
-        bufSize = newBufSize;
-    }
-
-    public InetAddress getInetAddress() {
-        if (dgPacketIn != null)
-            return dgPacketIn.getAddress();
-        return null;
-    }
-
-    public void send(byte[] data) throws IOException {
-        DatagramPacket dgPacketOut = new DatagramPacket(data, data.length,
-                                                        iaddr, iport);
-        dgSocket.send(dgPacketOut);
-    }
-
-    public byte[] receive() throws IOException {
-        byte ibuf[] = new byte[bufSize];
-        dgPacketIn = new DatagramPacket(ibuf, ibuf.length);
-        try {
-            dgSocket.receive(dgPacketIn);
-        }
-        catch (SocketException e) {
-            dgSocket.receive(dgPacketIn);
-        }
-        byte[] data = new byte[dgPacketIn.getLength()];
-        System.arraycopy(dgPacketIn.getData(), 0, data, 0,
-                         dgPacketIn.getLength());
-        return data;
-    }
-
-    public void close() {
-        dgSocket.close();
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/TcpTimeout.java	Tue Nov 09 08:34:11 2010 +0800
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6952519
+ * @run main/timeout=40/othervm TcpTimeout
+ * @summary kdc_timeout is not being honoured when using TCP
+ */
+
+import java.io.*;
+import java.net.ServerSocket;
+import sun.security.krb5.Config;
+
+public class TcpTimeout {
+    public static void main(String[] args)
+            throws Exception {
+
+        System.setProperty("sun.security.krb5.debug", "true");
+        final int p1 = 10000 + new java.util.Random().nextInt(10000);
+        final int p2 = 20000 + new java.util.Random().nextInt(10000);
+        final int p3 = 30000 + new java.util.Random().nextInt(10000);
+
+        KDC k = new KDC(OneKDC.REALM, OneKDC.KDCHOST, p3, true);
+        k.addPrincipal(OneKDC.USER, OneKDC.PASS);
+        k.addPrincipalRandKey("krbtgt/" + OneKDC.REALM);
+
+        // Start two listener that does not communicate, simulate timeout
+        new Thread() {
+            public void run() {
+                try {
+                    new ServerSocket(p1).accept();
+                } catch (Exception e) {
+                }}
+        }.start();
+        new Thread() {
+            public void run() {
+                try {
+                    new ServerSocket(p2).accept();
+                } catch (Exception e) {
+                }}
+        }.start();
+
+        FileWriter fw = new FileWriter("alternative-krb5.conf");
+
+        fw.write("[libdefaults]\n" +
+                "udp_preference_limit = 1\n" +
+                "max_retries = 2\n" +
+                "default_realm = " + OneKDC.REALM + "\n" +
+                "kdc_timeout = 5000\n");
+        fw.write("[realms]\n" + OneKDC.REALM + " = {\n" +
+                "kdc = " + OneKDC.KDCHOST + ":" + p1 + "\n" +
+                "kdc = " + OneKDC.KDCHOST + ":" + p2 + "\n" +
+                "kdc = " + OneKDC.KDCHOST + ":" + p3 + "\n" +
+                "}\n");
+
+        fw.close();
+        System.setProperty("java.security.krb5.conf", "alternative-krb5.conf");
+        Config.refresh();
+
+        // The correct behavior should be:
+        // 5 sec on p1, 5 sec on p1, fail
+        // 5 sec on p2, 5 sec on p2, fail
+        // p3 ok, p3 ok again for preauth.
+        // The total time should be 20sec + 2x. x is processing time for AS-REQ.
+        int count = 6;
+        long start = System.nanoTime();
+
+        ByteArrayOutputStream bo = new ByteArrayOutputStream();
+        PrintStream oldout = System.out;
+        System.setOut(new PrintStream(bo));
+        Context c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        System.setOut(oldout);
+
+        String[] lines = new String(bo.toByteArray()).split("\n");
+        for (String line: lines) {
+            if (line.startsWith(">>> KDCCommunication")) {
+                System.out.println(line);
+                count--;
+            }
+        }
+        if (count != 0) {
+            throw new Exception("Retry count is " + count + " less");
+        }
+
+        long end = System.nanoTime();
+        if ((end - start)/1000000000L < 20) {
+            throw new Exception("Too fast? " + (end - start)/1000000000L);
+        }
+    }
+
+    private static KDC on(int p) throws Exception {
+        KDC k = new KDC(OneKDC.REALM, OneKDC.KDCHOST, p, true);
+        k.addPrincipal(OneKDC.USER, OneKDC.PASS);
+        k.addPrincipalRandKey("krbtgt/" + OneKDC.REALM);
+        return k;
+    }
+
+    private static void addFakeKDCs()
+            throws Exception {
+        BufferedReader fr = new BufferedReader(new FileReader(OneKDC.KRB5_CONF));
+        FileWriter fw = new FileWriter("alternative-krb5.conf");
+        while (true) {
+            String s = fr.readLine();
+            if (s == null) {
+                break;
+            }
+            if (s.trim().startsWith("kdc = ")) {
+                fw.write("    kdc = localhost:33333\n");
+                fw.write("    kdc = localhost:22222\n");
+            }
+            fw.write(s + "\n");
+        }
+        fr.close();
+        fw.close();
+        sun.security.krb5.Config.refresh();
+    }
+}