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="[EMAIL PROTECTED]">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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to