You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jmeter-dev@jakarta.apache.org by se...@apache.org on 2007/06/02 15:48:57 UTC

svn commit: r543739 - in /jakarta/jmeter/branches/rel-2-2: bin/user.properties src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java src/core/org/apache/jmeter/util/JsseSSLManager.java xdocs/changes.xml xdocs/usermanual/component_reference.xml

Author: sebb
Date: Sat Jun  2 06:48:56 2007
New Revision: 543739

URL: http://svn.apache.org/viewvc?view=rev&rev=543739
Log:
Bug 42506 - JMeter threads all use the same SSL session

Modified:
    jakarta/jmeter/branches/rel-2-2/bin/user.properties
    jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java
    jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java
    jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml
    jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml

Modified: jakarta/jmeter/branches/rel-2-2/bin/user.properties
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/bin/user.properties?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/bin/user.properties (original)
+++ jakarta/jmeter/branches/rel-2-2/bin/user.properties Sat Jun  2 06:48:56 2007
@@ -16,4 +16,8 @@
 ##   limitations under the License.
 #
 #search_paths=../addons/addons.jar
-#log_level.jorphan.reflect=DEBUG
\ No newline at end of file
+#log_level.jorphan.reflect=DEBUG
+# Warning: enabling the next debug line causes javax.net.ssl.SSLException: Received fatal alert: unexpected_message
+# for certain sites when used with the default HTTP Sampler
+#log_level.jmeter.util.HttpSSLProtocolSocketFactory=DEBUG
+#log_level.jmeter.util.JsseSSLManager=DEBUG
\ No newline at end of file

Modified: jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java (original)
+++ jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java Sat Jun  2 06:48:56 2007
@@ -23,8 +23,10 @@
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
 
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
@@ -46,37 +48,53 @@
 
 	private static final Logger log = LoggingManager.getLoggerForClass();
 	
-	private SSLSocketFactory sslfac;
+	private JsseSSLManager sslManager;
 
     private HttpSSLProtocolSocketFactory(){
     }
     
-    public HttpSSLProtocolSocketFactory(SSLContext context) {
+    public HttpSSLProtocolSocketFactory(JsseSSLManager sslManager) {
         super();
-		sslfac=context.getSocketFactory();
+        this.sslManager = sslManager;
     }
     
-    private static final String protocolList = JMeterUtils.getPropDefault("https.socket.protocols", "");
+    private static final String protocolList = 
+    	JMeterUtils.getPropDefault("https.socket.protocols", ""); // $NON-NLS-1$ $NON-NLS-2$
     
     static {
     	if (protocolList.length()>0){
     		log.info("Using protocol list: "+protocolList);
     	}
     }
-    private static final String[] protocols = protocolList.split(" ");
 
-    private void setSocket(Socket sock){
-    	if (protocolList.length() <= 0) return;
-    	if (sock instanceof SSLSocket){
-    		try {
-				((SSLSocket) sock).setEnabledProtocols(protocols);
-			} catch (IllegalArgumentException e) {
-				log.warn("Could not set protocol list: "+protocolList+".");
-				log.warn("Valid protocols are: "+join(((SSLSocket) sock).getSupportedProtocols()));
-			}
-    	} else {
-    		throw new IllegalArgumentException("Expecting only SSL socket; found "+sock.getClass().getName());
+    private static final String[] protocols = protocolList.split(" "); // $NON-NLS-1$
+
+    private void setSocket(Socket socket){
+    	if (!(socket instanceof SSLSocket)) {
+    		throw new IllegalArgumentException("Expected SSLSocket");
     	}
+    	SSLSocket sock = (SSLSocket) socket;
+        if (log.isDebugEnabled()) {
+            SSLSession sslSession = sock.getSession();
+            byte[] bytes = sslSession.getId();
+
+            StringBuffer buffer = new StringBuffer("SSL session id: ");
+            for (int i = 0; i < bytes.length; i++) {
+                int b = bytes[i] & 0xff;
+                buffer.append(Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)));
+                buffer.append(Character.toUpperCase(Character.forDigit(b & 0xF, 16)));
+            }
+            buffer.append(" for ").append(Thread.currentThread().getName());
+            log.debug(buffer.toString());
+        }
+        if (protocolList.length() > 0) {
+            try {
+                sock.setEnabledProtocols(protocols);
+            } catch (IllegalArgumentException e) {
+                log.warn("Could not set protocol list: " + protocolList + ".");
+                log.warn("Valid protocols are: " + join(sock.getSupportedProtocols()));
+            }
+        }
     }
 
     private String join(String[] strings) {
@@ -87,6 +105,15 @@
     	}
     	return sb.toString();
 	}
+    
+    private SSLSocketFactory getSSLSocketFactory() throws IOException {
+        try {
+            SSLContext sslContext = this.sslManager.getContext();
+            return sslContext.getSocketFactory();
+        } catch (GeneralSecurityException ex) {
+            throw new IOException(ex.getMessage());
+        }
+    }
 
 	/**
      * Attempts to get a new socket connection to the given host within the given time limit.
@@ -114,6 +141,8 @@
             throw new IllegalArgumentException("Parameters may not be null");
         }
         int timeout = params.getConnectionTimeout();
+        
+        SSLSocketFactory sslfac = getSSLSocketFactory();
         Socket socket;
         if (timeout == 0) {
         	socket = sslfac.createSocket(host, port, localAddress, localPort);
@@ -133,6 +162,7 @@
      */
     public Socket createSocket(String host, int port)
         throws IOException, UnknownHostException {
+        SSLSocketFactory sslfac = getSSLSocketFactory();
     	Socket sock = sslfac.createSocket(
             host,
             port
@@ -150,6 +180,7 @@
         int port,
         boolean autoClose)
         throws IOException, UnknownHostException {
+        SSLSocketFactory sslfac = getSSLSocketFactory();
     	Socket sock = sslfac.createSocket(
             socket,
             host,
@@ -170,6 +201,7 @@
         int clientPort)
         throws IOException, UnknownHostException {
 
+        SSLSocketFactory sslfac = getSSLSocketFactory();
         Socket sock = sslfac.createSocket(
             host,
             port,
@@ -181,22 +213,34 @@
     }
 
 	public Socket createSocket(InetAddress host, int port) throws IOException {
+        SSLSocketFactory sslfac = getSSLSocketFactory();
 		Socket sock=sslfac.createSocket(host,port);
         setSocket(sock);
 		return sock;
 	}
 
 	public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+        SSLSocketFactory sslfac = getSSLSocketFactory();
 		Socket sock=sslfac.createSocket(address, port, localAddress, localPort);
         setSocket(sock);
 		return sock;
 	}
 
 	public String[] getDefaultCipherSuites() {
-		return sslfac.getDefaultCipherSuites();
+        try {
+            SSLSocketFactory sslfac = getSSLSocketFactory();
+            return sslfac.getDefaultCipherSuites();
+        } catch (IOException ex) {
+            return new String[] {};
+        }
 	}
 
 	public String[] getSupportedCipherSuites() {
-		return sslfac.getSupportedCipherSuites();
+        try {
+            SSLSocketFactory sslfac = getSSLSocketFactory();
+            return sslfac.getSupportedCipherSuites();
+        } catch (IOException ex) {
+            return new String[] {};
+		}
 	}
 }

Modified: jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java (original)
+++ jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java Sat Jun  2 06:48:56 2007
@@ -20,18 +20,13 @@
 
 import java.net.HttpURLConnection;
 import java.net.Socket;
+import java.security.GeneralSecurityException;
 import java.security.Principal;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
 
-import org.apache.commons.httpclient.protocol.Protocol;
-import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
-import org.apache.jmeter.util.keystore.JmeterKeyStore;
-import org.apache.jorphan.logging.LoggingManager;
-import org.apache.log.Logger;
-
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.KeyManager;
@@ -43,6 +38,12 @@
 import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
 
+import org.apache.commons.httpclient.protocol.Protocol;
+import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
+import org.apache.jmeter.util.keystore.JmeterKeyStore;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+
 /**
  * The SSLManager handles the KeyStore information for JMeter. Basically, it
  * handles all the logic for loading and initializing all the JSSE parameters
@@ -53,8 +54,6 @@
  * 
  * TODO: does not actually prompt
  * 
- * @author <a href="bloritsch@apache.org">Berin Loritsch</a> Created March 21,
- *         2002
  */
 public class JsseSSLManager extends SSLManager {
 	private static final Logger log = LoggingManager.getLoggerForClass();
@@ -65,8 +64,13 @@
 	private static final String DEFAULT_SSL_PROTOCOL = 
 		JMeterUtils.getPropDefault("https.default.protocol","TLS"); // $NON-NLS-1$ // $NON-NLS-2$
 
+	// Allow reversion to original shared session context
+	private static final boolean SHARED_SESSION_CONTEXT = 
+		JMeterUtils.getPropDefault("https.sessioncontext.shared",false); // $NON-NLS-1$
+
 	static {
 		log.info("Using default SSL protocol: "+DEFAULT_SSL_PROTOCOL);
+		log.info("SSL session context: "+(SHARED_SESSION_CONTEXT ? "shared" : "per-thread"));
 	}
 
 	/**
@@ -74,13 +78,11 @@
 	 */
 	private SecureRandom rand;
 
-	/**
-	 * Cache the Context so we can retrieve it from other places
-	 */
-	private SSLContext context = null;
-
 	private Provider pro = null;
 
+    private SSLContext defaultContext; // If we are using a single session
+    private ThreadLocal threadlocal; // Otherwise
+       
 	/**
 	 * Create the SSLContext, and wrap all the X509KeyManagers with
 	 * our X509KeyManager so that we can choose our alias.
@@ -91,11 +93,37 @@
 	public JsseSSLManager(Provider provider) {
 		log.debug("ssl Provider =  " + provider);
 		setProvider(provider);
-		if (null == this.rand) {
+		if (null == this.rand) { // Surely this is always null in the constructor?
 			this.rand = new SecureRandom();
 		}
-
-		this.getContext();
+		try {
+			if (SHARED_SESSION_CONTEXT) {
+				log.debug("Creating shared context");
+                this.defaultContext = createContext();
+			} else {
+		        this.threadlocal = new ThreadLocal();
+			}
+            
+            HttpSSLProtocolSocketFactory sockFactory = new HttpSSLProtocolSocketFactory(this);
+            
+            HttpsURLConnection.setDefaultSSLSocketFactory(sockFactory);
+            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
+                public boolean verify(String hostname, SSLSession session) {
+                    return true;
+                }
+            });
+            /*
+             * Also set up HttpClient defaults
+             */
+            Protocol protocol = new Protocol(
+                    JsseSSLManager.HTTPS, 
+                    (ProtocolSocketFactory) sockFactory,
+                    443);
+            Protocol.registerProtocol(JsseSSLManager.HTTPS, protocol);
+            log.debug("SSL stuff all set");
+        } catch (GeneralSecurityException ex) {
+            log.error("Could not set up SSLContext", ex);
+        }
 		log.debug("JsseSSLManager installed");
 	}
 
@@ -133,94 +161,101 @@
 	}
 
 	/**
-	 * Returns the SSLContext we are using. It is useful for obtaining the
-	 * SSLSocketFactory so that your created sockets are authenticated.
+	 * Returns the SSLContext we are using.
+	 * This is either a context per thread,
+	 * or, for backwards compatibility, a single shared context.
 	 * 
 	 * @return The Context value
 	 */
-	private SSLContext getContext() {
-		if (null == this.context) {
-			try {
-				if (pro != null) {
-					this.context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL, pro); // $NON-NLS-1$
-				} else {
-					this.context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); // $NON-NLS-1$
-				}
-				log.debug("SSL context = " + context);
-			} catch (Exception ee) {
-				log.error("Could not create SSLContext", ee);
-			}
-			try {
-				KeyManagerFactory managerFactory = 
-					KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());				
-				JmeterKeyStore keys = this.getKeyStore();
-				managerFactory.init(null, this.defaultpw.toCharArray());
-				KeyManager[] managers = managerFactory.getKeyManagers();
-				log.debug(keys.getClass().toString());
-				
-				// Now wrap the default managers with our key manager
-				for (int i = 0; i < managers.length; i++) {
-					if (managers[i] instanceof X509KeyManager) {
-						X509KeyManager manager = (X509KeyManager) managers[i];
-						managers[i] = new WrappedX509KeyManager(manager, keys);
-					}
-				}
-				
-				// Get the default trust managers
-		        TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
-		                TrustManagerFactory.getDefaultAlgorithm());
-		        tmfactory.init(this.getTrustStore());
-		        
-		        // Wrap the defaults in our custom trust manager
-		        TrustManager[] trustmanagers = tmfactory.getTrustManagers();
-		        for (int i = 0; i < trustmanagers.length; i++) {
-		            if (trustmanagers[i] instanceof X509TrustManager) {
-		                trustmanagers[i] = new CustomX509TrustManager(
-		                    (X509TrustManager)trustmanagers[i]); 
-		            }
-		        }
-		     	context.init(managers, trustmanagers, this.rand);
-				
-				/*
-				 * The following will need to be removed if the SSL properties are to be
-				 * applied on a per-connection basis
-				 */
-				HttpsURLConnection.setDefaultSSLSocketFactory(new HttpSSLProtocolSocketFactory(context));
-				HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
-					public boolean verify(String hostname, SSLSession session) {
-						return true;
-					}
-				});
-				/*
-				 * Also set up HttpClient defaults
-				 */
-				Protocol protocol = new Protocol(
-						JsseSSLManager.HTTPS,
-						(ProtocolSocketFactory) new HttpSSLProtocolSocketFactory(context),
-						443
-						);
-				Protocol.registerProtocol(JsseSSLManager.HTTPS, protocol);
-				log.debug("SSL stuff all set");
-			} catch (Exception e) {
-				log.error("Could not set up SSLContext", e);
-			}
-
-            if (log.isDebugEnabled()){
-    			String[] dCiphers = this.context.getSocketFactory().getDefaultCipherSuites();
-    			String[] sCiphers = this.context.getSocketFactory().getSupportedCipherSuites();
-    			int len = (dCiphers.length > sCiphers.length) ? dCiphers.length : sCiphers.length;
-    			for (int i = 0; i < len; i++) {
-    				if (i < dCiphers.length) {
-    					log.debug("Default Cipher: " + dCiphers[i]);
-    				}
-    				if (i < sCiphers.length) {
-    					log.debug("Supported Cipher: " + sCiphers[i]);
-    				}
-    			}
+    public SSLContext getContext() throws GeneralSecurityException {
+    	if (SHARED_SESSION_CONTEXT) {
+        	if (log.isDebugEnabled()){
+			    log.debug("Using shared SSL context for: "+Thread.currentThread().getName());
+        	}
+            return this.defaultContext;
+    	}
+    	
+        SSLContext sslContext = (SSLContext) this.threadlocal.get();
+        if (sslContext == null) {
+        	if (log.isDebugEnabled()){
+			    log.debug("Creating threadLocal SSL context for: "+Thread.currentThread().getName());
+        	}
+            sslContext = createContext();
+            this.threadlocal.set(sslContext);
+        }
+    	if (log.isDebugEnabled()){
+		    log.debug("Using threadLocal SSL context for: "+Thread.currentThread().getName());
+    	}
+        return sslContext;
+    }
+    
+    /**
+     * Resets the SSLContext if using per-thread contexts.
+     *
+     */
+    public void resetContext() {
+    	if (!SHARED_SESSION_CONTEXT) {
+    		log.debug("Clearing session context for current thread");
+            this.threadlocal.set(null);
+    	}
+    }
+    /*
+     * 
+     * Creates new SSL context
+     * @return SSL context
+     * @throws GeneralSecurityException
+     */
+	private SSLContext createContext() throws GeneralSecurityException {
+		SSLContext context;
+        if (pro != null) {
+            context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL, pro); // $NON-NLS-1$
+        } else {
+            context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); // $NON-NLS-1$
+        }
+        KeyManagerFactory managerFactory = 
+            KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());             
+        JmeterKeyStore keys = this.getKeyStore();
+        managerFactory.init(null, this.defaultpw.toCharArray());
+        KeyManager[] managers = managerFactory.getKeyManagers();
+        log.debug(keys.getClass().toString());
+        
+        // Now wrap the default managers with our key manager
+        for (int i = 0; i < managers.length; i++) {
+            if (managers[i] instanceof X509KeyManager) {
+                X509KeyManager manager = (X509KeyManager) managers[i];
+                managers[i] = new WrappedX509KeyManager(manager, keys);
             }
-		}
-		return this.context;
-	}
+        }
+        
+        // Get the default trust managers
+        TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
+                TrustManagerFactory.getDefaultAlgorithm());
+        tmfactory.init(this.getTrustStore());
+        
+        // Wrap the defaults in our custom trust manager
+        TrustManager[] trustmanagers = tmfactory.getTrustManagers();
+        for (int i = 0; i < trustmanagers.length; i++) {
+            if (trustmanagers[i] instanceof X509TrustManager) {
+                trustmanagers[i] = new CustomX509TrustManager(
+                    (X509TrustManager)trustmanagers[i]); 
+            }
+        }
+        context.init(managers, trustmanagers, this.rand);
+        if (log.isDebugEnabled()){
+            String[] dCiphers = context.getSocketFactory().getDefaultCipherSuites();
+            String[] sCiphers = context.getSocketFactory().getSupportedCipherSuites();
+            int len = (dCiphers.length > sCiphers.length) ? dCiphers.length : sCiphers.length;
+            for (int i = 0; i < len; i++) {
+                if (i < dCiphers.length) {
+                    log.debug("Default Cipher: " + dCiphers[i]);
+                }
+                if (i < sCiphers.length) {
+                    log.debug("Supported Cipher: " + sCiphers[i]);
+                }
+            }
+        }
+        return context;
+    }
 
 	/**
 	 * This is the X509KeyManager we have defined for the sole purpose of

Modified: jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml (original)
+++ jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml Sat Jun  2 06:48:56 2007
@@ -43,6 +43,7 @@
 <li>LDAP Ext sampler optionally parses result sets and supports secure mode</li>
 <li>FTP Sampler supports Ascii/Binary mode and upload</li>
 <li>Transaction Controller now generates Sample with subresults</li>
+<li>HTTPS session contexts are now per-thread, rather than shared. This gives better emulation of multiple users</li>
 </ul>
 <p>
 The main bug fixes are:
@@ -93,6 +94,13 @@
 <p>
 Control-Z no longer used for Remote Start All; replaced by Control+Shift+R
 </p>
+<p>
+By default, SSL session contexts are now created per-thread, rather than being shared.
+The original behaviour can be enabled by setting the JMeter property:
+<pre>
+https.sessioncontext.shared=true
+</pre>
+</p>
 <h4>Incompatible changes (development):</h4>
 <p>
 Calulator and SamplingStatCalculator classes no longer provide any formatting of their data.
@@ -149,6 +157,7 @@
 <li>Use ISO date-time format for Tree View Listener (previously the year was not shown)</li>
 <li>Improve loading of CSV files: if possible, use header to determine format; guess timestamp format if not milliseconds</li>
 <li>Bug 41913 - TransactionController now creates samples as sub-samples of the transaction</li>
+<li>Bug 42506 - JMeter threads all use the same SSL session</li>
 </ul>
 
 <h4>Non-functional improvements:</h4>

Modified: jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml (original)
+++ jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml Sat Jun  2 06:48:56 2007
@@ -122,6 +122,15 @@
          and the appropriate parameters from the form definition. 
          If the page uses HTTP, you can use the JMeter Proxy to capture the login sequence.
         </p>
+        <p>
+        In versions of JMeter up to 2.2, only a single SSL context was used for all threads and samplers.
+        This did not generate the proper load for multiple users.
+        A separate SSL context is now used for each thread.
+        To revert to the original behaviour, set the JMeter property:
+<pre>
+https.sessioncontext.shared=true
+</pre>
+        </p>
         <p>If the request uses cookies, then you will also need an
         <complink name="HTTP Cookie Manager"/>.  You can
         add either of these elements to the Thread Group or the HTTP Request.  If you have



---------------------------------------------------------------------
To unsubscribe, e-mail: jmeter-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: jmeter-dev-help@jakarta.apache.org