From 5b6c23c8098ea533df007f57091deae3cadba7ff Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Tue, 9 Nov 2010 08:34:11 +0800 Subject: [PATCH] 6952519: kdc_timeout is not being honoured when using TCP Reviewed-by: valeriep --- .../classes/sun/security/krb5/KrbKdcReq.java | 67 +++------ .../{TCPClient.java => NetClient.java} | 76 +++++++++- .../sun/security/krb5/internal/UDPClient.java | 99 ------------- .../sun/security/krb5/auto/TcpTimeout.java | 138 ++++++++++++++++++ 4 files changed, 228 insertions(+), 152 deletions(-) rename jdk/src/share/classes/sun/security/krb5/internal/{TCPClient.java => NetClient.java} (66%) delete mode 100644 jdk/src/share/classes/sun/security/krb5/internal/UDPClient.java create mode 100644 jdk/test/sun/security/krb5/auto/TcpTimeout.java diff --git a/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java b/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java index 9f968014ea1..1919b70fc91 100644 --- a/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java +++ b/jdk/src/share/classes/sun/security/krb5/KrbKdcReq.java @@ -36,8 +36,7 @@ import java.security.PrivilegedAction; 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 @@ public abstract class KrbKdcReq { 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 @@ public abstract class KrbKdcReq { * 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; } diff --git a/jdk/src/share/classes/sun/security/krb5/internal/TCPClient.java b/jdk/src/share/classes/sun/security/krb5/internal/NetClient.java similarity index 66% rename from jdk/src/share/classes/sun/security/krb5/internal/TCPClient.java rename to jdk/src/share/classes/sun/security/krb5/internal/NetClient.java index 770871e7b6b..36e9a5c59c1 100644 --- a/jdk/src/share/classes/sun/security/krb5/internal/TCPClient.java +++ b/jdk/src/share/classes/sun/security/krb5/internal/NetClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -34,18 +34,38 @@ package sun.security.krb5.internal; import java.io.*; import java.net.*; -public class TCPClient { +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; - public TCPClient(String hostname, int port) throws IOException { + 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); @@ -55,6 +75,7 @@ public class TCPClient { out.flush(); } + @Override public byte[] receive() throws IOException { byte[] lenField = new byte[4]; int count = readFully(lenField, 4); @@ -94,6 +115,7 @@ public class TCPClient { } } + @Override public void close() throws IOException { tcpSocket.close(); } @@ -120,7 +142,7 @@ public class TCPClient { /** * Returns the integer represented by 4 bytes in network byte order. */ - private static final int networkByteOrderToInt(byte[] buf, int start, + private static int networkByteOrderToInt(byte[] buf, int start, int count) { if (count > 4) { throw new IllegalArgumentException( @@ -140,7 +162,7 @@ public class TCPClient { * Encodes an integer into 4 bytes in network byte order in the buffer * supplied. */ - private static final void intToNetworkByteOrder(int num, byte[] buf, + private static void intToNetworkByteOrder(int num, byte[] buf, int start, int count) { if (count > 4) { throw new IllegalArgumentException( @@ -153,3 +175,47 @@ public class TCPClient { } } } + +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(); + } +} diff --git a/jdk/src/share/classes/sun/security/krb5/internal/UDPClient.java b/jdk/src/share/classes/sun/security/krb5/internal/UDPClient.java deleted file mode 100644 index 59b53edfcd9..00000000000 --- a/jdk/src/share/classes/sun/security/krb5/internal/UDPClient.java +++ /dev/null @@ -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(); - } -} diff --git a/jdk/test/sun/security/krb5/auto/TcpTimeout.java b/jdk/test/sun/security/krb5/auto/TcpTimeout.java new file mode 100644 index 00000000000..9530803b5b2 --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/TcpTimeout.java @@ -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(); + } +}