You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2018/01/23 20:50:53 UTC

svn commit: r1822052 - in /jmeter/trunk: ./ bin/ src/core/org/apache/jmeter/ src/core/org/apache/jmeter/engine/ src/core/org/apache/jmeter/rmi/ src/core/org/apache/jmeter/samplers/ src/core/org/apache/jmeter/threads/ xdocs/

Author: pmouawad
Date: Tue Jan 23 20:50:52 2018
New Revision: 1822052

URL: http://svn.apache.org/viewvc?rev=1822052&view=rev
Log:
Bug 62039 - Distributed testing : Provide ability to use SSL
Bugzilla Id: 62039

Added:
    jmeter/trunk/bin/create-rmi-keystore.bat   (with props)
    jmeter/trunk/bin/create-rmi-keystore.sh   (with props)
    jmeter/trunk/src/core/org/apache/jmeter/rmi/
    jmeter/trunk/src/core/org/apache/jmeter/rmi/AliasKeyManager.java   (with props)
    jmeter/trunk/src/core/org/apache/jmeter/rmi/RMIServerSocketFactoryImpl.java   (with props)
    jmeter/trunk/src/core/org/apache/jmeter/rmi/RmiUtils.java   (with props)
    jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIClientSocketFactory.java   (with props)
    jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIServerSocketFactory.java   (with props)
Modified:
    jmeter/trunk/bin/jmeter.properties
    jmeter/trunk/bin/user.properties
    jmeter/trunk/build.xml
    jmeter/trunk/src/core/org/apache/jmeter/JMeter.java
    jmeter/trunk/src/core/org/apache/jmeter/engine/ClientJMeterEngine.java
    jmeter/trunk/src/core/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java
    jmeter/trunk/src/core/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java
    jmeter/trunk/xdocs/changes.xml

Added: jmeter/trunk/bin/create-rmi-keystore.bat
URL: http://svn.apache.org/viewvc/jmeter/trunk/bin/create-rmi-keystore.bat?rev=1822052&view=auto
==============================================================================
--- jmeter/trunk/bin/create-rmi-keystore.bat (added)
+++ jmeter/trunk/bin/create-rmi-keystore.bat Tue Jan 23 20:50:52 2018
@@ -0,0 +1,40 @@
+@echo off
+
+rem   Licensed to the Apache Software Foundation (ASF) under one or more
+rem   contributor license agreements.  See the NOTICE file distributed with
+rem   this work for additional information regarding copyright ownership.
+rem   The ASF licenses this file to You under the Apache License, Version 2.0
+rem   (the "License"); you may not use this file except in compliance with
+rem   the License.  You may obtain a copy of the License at
+rem 
+rem       http://www.apache.org/licenses/LICENSE-2.0
+rem 
+rem   Unless required by applicable law or agreed to in writing, software
+rem   distributed under the License is distributed on an "AS IS" BASIS,
+rem   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem   See the License for the specific language governing permissions and
+rem   limitations under the License.
+rem
+keytool -genkey -keyalg RSA -alias rmi -keystore rmi_keystore.jks -storepass changeit -validity 7 -keysize 2048
+
+echo "Copy the generated rmi_keystore.jks to jmeter/bin folder or reference it in property 'server.rmi.ssl.keystore.file'"
+@echo off
+
+rem   Licensed to the Apache Software Foundation (ASF) under one or more
+rem   contributor license agreements.  See the NOTICE file distributed with
+rem   this work for additional information regarding copyright ownership.
+rem   The ASF licenses this file to You under the Apache License, Version 2.0
+rem   (the "License"); you may not use this file except in compliance with
+rem   the License.  You may obtain a copy of the License at
+rem 
+rem       http://www.apache.org/licenses/LICENSE-2.0
+rem 
+rem   Unless required by applicable law or agreed to in writing, software
+rem   distributed under the License is distributed on an "AS IS" BASIS,
+rem   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem   See the License for the specific language governing permissions and
+rem   limitations under the License.
+rem
+keytool -genkey -keyalg RSA -alias rmi -keystore rmi_keystore.jks -storepass changeit -validity 7 -keysize 2048
+
+echo "Copy the generated rmi_keystore.jks to jmeter/bin folder or reference it in property 'server.rmi.ssl.keystore.file'"
\ No newline at end of file

Propchange: jmeter/trunk/bin/create-rmi-keystore.bat
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jmeter/trunk/bin/create-rmi-keystore.sh
URL: http://svn.apache.org/viewvc/jmeter/trunk/bin/create-rmi-keystore.sh?rev=1822052&view=auto
==============================================================================
--- jmeter/trunk/bin/create-rmi-keystore.sh (added)
+++ jmeter/trunk/bin/create-rmi-keystore.sh Tue Jan 23 20:50:52 2018
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+##   Licensed to the Apache Software Foundation (ASF) under one or more
+##   contributor license agreements.  See the NOTICE file distributed with
+##   this work for additional information regarding copyright ownership.
+##   The ASF licenses this file to You under the Apache License, Version 2.0
+##   (the "License"); you may not use this file except in compliance with
+##   the License.  You may obtain a copy of the License at
+## 
+##       http://www.apache.org/licenses/LICENSE-2.0
+## 
+##   Unless required by applicable law or agreed to in writing, software
+##   distributed under the License is distributed on an "AS IS" BASIS,
+##   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+##   See the License for the specific language governing permissions and
+##   limitations under the License.
+##
+keytool -genkey -keyalg RSA -alias rmi -keystore rmi_keystore.jks -storepass changeit -validity 7 -keysize 2048
+
+echo "Copy the generated rmi_keystore.jks to jmeter/bin folder or reference it in property 'server.rmi.ssl.keystore.file'"
+#!/bin/sh
+
+##   Licensed to the Apache Software Foundation (ASF) under one or more
+##   contributor license agreements.  See the NOTICE file distributed with
+##   this work for additional information regarding copyright ownership.
+##   The ASF licenses this file to You under the Apache License, Version 2.0
+##   (the "License"); you may not use this file except in compliance with
+##   the License.  You may obtain a copy of the License at
+## 
+##       http://www.apache.org/licenses/LICENSE-2.0
+## 
+##   Unless required by applicable law or agreed to in writing, software
+##   distributed under the License is distributed on an "AS IS" BASIS,
+##   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+##   See the License for the specific language governing permissions and
+##   limitations under the License.
+##
+keytool -genkey -keyalg RSA -alias rmi -keystore rmi_keystore.jks -storepass changeit -validity 7 -keysize 2048
+
+echo "Copy the generated rmi_keystore.jks to jmeter/bin folder or reference it in property 'server.rmi.ssl.keystore.file'"
\ No newline at end of file

Propchange: jmeter/trunk/bin/create-rmi-keystore.sh
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jmeter/trunk/bin/jmeter.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/bin/jmeter.properties?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/bin/jmeter.properties (original)
+++ jmeter/trunk/bin/jmeter.properties Tue Jan 23 20:50:52 2018
@@ -292,6 +292,39 @@ remote_hosts=127.0.0.1
 # Define the following property to cause JMeter to exit after the first test
 #server.exitaftertest=true
 
+
+#
+# Type of keystore : JKS
+#
+#server.rmi.ssl.keystore.type=JKS
+#
+# Keystore file that contains private key
+#
+#server.rmi.ssl.keystore.file=rmi_keystore.jks
+#
+# Password of Keystore
+#
+#server.rmi.ssl.keystore.password=changeit
+#
+# Key alias
+#
+#server.rmi.ssl.keystore.alias=rmi
+#
+# Type of truststore : JKS
+#
+#server.rmi.ssl.truststore.type=JKS
+#
+# Keystore file that contains certificate
+#
+#server.rmi.ssl.truststore.file=rmi_keystore.jks
+#
+# Password of Trust store
+#
+#server.rmi.ssl.truststore.password=changeit
+#
+# Set this if you don't want to use SSL for RMI
+#
+#server.rmi.ssl.disable=false
 #---------------------------------------------------------------------------
 #         Include Controller
 #---------------------------------------------------------------------------

Modified: jmeter/trunk/bin/user.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/bin/user.properties?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/bin/user.properties (original)
+++ jmeter/trunk/bin/user.properties Tue Jan 23 20:50:52 2018
@@ -100,3 +100,39 @@
 # This property is used by menu item "Export transactions for report"
 # It is used to select which transactions by default will be exported
 #jmeter.reportgenerator.exported_transactions_pattern=[a-zA-Z0-9_\\-{}\\$\\.]*[-_][0-9]*
+
+
+########################################################################
+################## DISTRIBUTED TESTING CONFIGURATION  ##################
+########################################################################
+# Type of keystore : JKS
+#
+#server.rmi.ssl.keystore.type=JKS
+#
+# Keystore file that contains private key
+#
+#server.rmi.ssl.keystore.file=rmi_keystore.jks
+#
+# Password of Keystore
+#
+#server.rmi.ssl.keystore.password=changeit
+#
+# Key alias
+#
+#server.rmi.ssl.keystore.alias=rmi
+#
+# Type of truststore : JKS
+#
+#server.rmi.ssl.truststore.type=JKS
+#
+# Keystore file that contains certificate
+#
+#server.rmi.ssl.truststore.file=rmi_keystore.jks
+#
+# Password of Trust store
+#
+#server.rmi.ssl.truststore.password=changeit
+#
+# Set this if you don't want to use SSL for RMI
+#
+#server.rmi.ssl.disable=false
\ No newline at end of file

Modified: jmeter/trunk/build.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/build.xml?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/build.xml (original)
+++ jmeter/trunk/build.xml Tue Jan 23 20:50:52 2018
@@ -2577,10 +2577,11 @@ run JMeter unless all the JMeter jars ar
     <property name="batchtestserver.out" location="${basedir}/bin"/>
     <property name="batchtestserver.log" value="BatchTestServer.log"/>
     <!-- rmi_force_localhost is used by the Travis build -->
+    <exec executable="hostname" outputproperty="computer.hostname"/>
     <condition property="rmi_hostname_option" value="-Ddummy=true">
       <not><isset property="rmi_force_localhost"/></not>
     </condition>
-    <condition property="rmi_hostname_option" value="-Djava.rmi.server.hostname=localhost">
+    <condition property="rmi_hostname_option" value="-Djava.rmi.server.hostname=${computer.hostname}">
       <isset property="rmi_force_localhost"/>
     </condition>
     <condition property="jacoco_params" value="${jacocoagent}" else="-Djacoco_dummy=">
@@ -2622,7 +2623,7 @@ run JMeter unless all the JMeter jars ar
         <!-- Increase as necessary to ensure server has time to start -->
         <sleep seconds="2"/>
         <antcall target="batchtest">
-          <param name="remote" value="-Rlocalhost:${rmi_port}"/>
+          <param name="remote" value="-R${computer.hostname}:${rmi_port}"/>
           <param name="taskname" value="client"/>
           <!-- Default the test name so we can override with a parameter -->
         </antcall>

Modified: jmeter/trunk/src/core/org/apache/jmeter/JMeter.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/JMeter.java?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/JMeter.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/JMeter.java Tue Jan 23 20:50:52 2018
@@ -78,6 +78,7 @@ import org.apache.jmeter.report.dashboar
 import org.apache.jmeter.report.dashboard.ReportGenerator;
 import org.apache.jmeter.reporters.ResultCollector;
 import org.apache.jmeter.reporters.Summariser;
+import org.apache.jmeter.rmi.RmiUtils;
 import org.apache.jmeter.samplers.Remoteable;
 import org.apache.jmeter.samplers.SampleEvent;
 import org.apache.jmeter.save.SaveService;
@@ -496,7 +497,7 @@ public class JMeter implements JMeterPlu
             } else if (parser.getArgumentById(SERVER_OPT) != null) {
                 // Start the server
                 try {
-                    RemoteJMeterEngineImpl.startServer(JMeterUtils.getPropDefault("server_port", 0)); // $NON-NLS-1$
+                    RemoteJMeterEngineImpl.startServer(RmiUtils.getRmiRegistryPort()); // $NON-NLS-1$
                     startOptionalServers();
                 } catch (Exception ex) {
                     System.err.println("Server failed to start: "+ex);//NOSONAR

Modified: jmeter/trunk/src/core/org/apache/jmeter/engine/ClientJMeterEngine.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/engine/ClientJMeterEngine.java?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/engine/ClientJMeterEngine.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/engine/ClientJMeterEngine.java Tue Jan 23 20:50:52 2018
@@ -19,14 +19,15 @@
 package org.apache.jmeter.engine;
 
 import java.io.File;
-import java.net.MalformedURLException;
-import java.rmi.Naming;
 import java.rmi.NotBoundException;
 import java.rmi.Remote;
 import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
 import java.rmi.server.RemoteObject;
 import java.util.Properties;
 
+import org.apache.jmeter.rmi.RmiUtils;
 import org.apache.jmeter.services.FileServer;
 import org.apache.jmeter.threads.JMeterContextService;
 import org.apache.jmeter.util.JMeterUtils;
@@ -46,26 +47,41 @@ public class ClientJMeterEngine implemen
 
     private HashTree test;
 
-    private final String host;
+    /**
+     * Maybe only host or host:port
+     */
+    private final String hostAndPort;
 
-    private static RemoteJMeterEngine getEngine(String h) throws MalformedURLException, RemoteException,
-            NotBoundException {
-       final String name = "//" + h + "/" + RemoteJMeterEngineImpl.JMETER_ENGINE_RMI_NAME; // $NON-NLS-1$ $NON-NLS-2$
-       Remote remobj = Naming.lookup(name);
-       if (remobj instanceof RemoteJMeterEngine){
-           final RemoteJMeterEngine rje = (RemoteJMeterEngine) remobj;
-           if (remobj instanceof RemoteObject){
-               RemoteObject robj = (RemoteObject) remobj;
-               System.out.println("Using remote object: "+robj.getRef().remoteToString());
-           }
-           return rje;
-       }
-       throw new RemoteException("Could not find "+name);
-    }
-
-    public ClientJMeterEngine(String host) throws MalformedURLException, NotBoundException, RemoteException {
-        this.remote = getEngine(host);
-        this.host = host;
+    private static RemoteJMeterEngine getEngine(String hostAndPort) 
+            throws RemoteException, NotBoundException {
+        final String name = RemoteJMeterEngineImpl.JMETER_ENGINE_RMI_NAME; // $NON-NLS-1$ $NON-NLS-2$
+        String host = hostAndPort;
+        int port = RmiUtils.DEFAULT_RMI_PORT;
+        int indexOfSeparator = hostAndPort.indexOf(':');
+        if (indexOfSeparator >= 0) {
+            host = hostAndPort.substring(0, indexOfSeparator);
+            String portAsString = hostAndPort.substring(indexOfSeparator+1);
+            port = Integer.parseInt(portAsString);
+        }
+        Registry registry = LocateRegistry.getRegistry(
+               host, 
+               port,
+               RmiUtils.createClientSocketFactory());
+        Remote remobj = registry.lookup(name);
+        if (remobj instanceof RemoteJMeterEngine){
+            final RemoteJMeterEngine rje = (RemoteJMeterEngine) remobj;
+            if (remobj instanceof RemoteObject){
+                RemoteObject robj = (RemoteObject) remobj;
+                System.out.println("Using remote object: "+robj.getRef().remoteToString()); // NOSONAR
+            }
+            return rje;
+        }
+        throw new RemoteException("Could not find "+name);
+    }
+
+    public ClientJMeterEngine(String hostAndPort) throws NotBoundException, RemoteException {
+        this.remote = getEngine(hostAndPort);
+        this.hostAndPort = hostAndPort;
     }
 
     /** {@inheritDoc} */
@@ -79,7 +95,7 @@ public class ClientJMeterEngine implemen
     /** {@inheritDoc} */
     @Override
     public void stopTest(boolean now) {
-        log.info("about to "+(now ? "stop" : "shutdown")+" remote test on "+host);
+        log.info("about to {} remote test on {}", (now ? "stop" : "shutdown"), hostAndPort);
         try {
             remote.rstopTest(now);
         } catch (Exception ex) {
@@ -95,7 +111,7 @@ public class ClientJMeterEngine implemen
                 remote.rreset();
             } catch (java.rmi.ConnectException e) {
                 log.info("Retry reset after: "+e);
-                remote = getEngine(host);
+                remote = getEngine(hostAndPort);
                 remote.rreset();
             }
         } catch (Exception ex) {
@@ -132,13 +148,13 @@ public class ClientJMeterEngine implemen
             synchronized(LOCK)
             {
                 methodName="rconfigure()"; // NOSONAR Used for tracing
-                remote.rconfigure(testTree, host, baseDirRelative, scriptName);
+                remote.rconfigure(testTree, hostAndPort, baseDirRelative, scriptName);
             }
-            log.info("sent test to " + host + " basedir='"+baseDirRelative+"'"); // $NON-NLS-1$
+            log.info("sent test to {} basedir='{}'", hostAndPort, baseDirRelative); // $NON-NLS-1$
             if(savep == null) {
                 savep = new Properties();
             }
-            log.info("Sending properties "+savep);
+            log.info("Sending properties {}", savep);
             try {
                 methodName="rsetProperties()";// NOSONAR Used for tracing
                 remote.rsetProperties(savep);
@@ -147,9 +163,9 @@ public class ClientJMeterEngine implemen
             }
             methodName="rrunTest()";
             remote.rrunTest();
-            log.info("sent run command to "+ host);
+            log.info("sent run command to {}", hostAndPort);
         } catch (IllegalStateException ex) {
-            log.error("Error in "+methodName+" method "+ex); // $NON-NLS-1$ $NON-NLS-2$
+            log.error("Error in {} method ", methodName, ex); // $NON-NLS-1$ $NON-NLS-2$
             tidyRMI(log);
             throw ex; // Don't wrap this error - display it as is
         } catch (Exception ex) {
@@ -179,7 +195,7 @@ public class ClientJMeterEngine implemen
     // Called by JMeter ListenToTest if remoteStop is true
     @Override
     public void exit() {
-        log.info("about to exit remote server on {}", host);
+        log.info("about to exit remote server on {}", hostAndPort);
         try {
             remote.rexit();
         } catch (RemoteException e) {
@@ -201,7 +217,10 @@ public class ClientJMeterEngine implemen
         return true;
     }
 
+    /**
+     * @return host or host:port
+     */
     public String getHost() {
-        return host;
+        return hostAndPort;
     }
 }

Modified: jmeter/trunk/src/core/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/engine/RemoteJMeterEngineImpl.java Tue Jan 23 20:50:52 2018
@@ -20,7 +20,6 @@ package org.apache.jmeter.engine;
 
 import java.io.File;
 import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.rmi.NotBoundException;
 import java.rmi.RemoteException;
 import java.rmi.registry.LocateRegistry;
@@ -28,6 +27,7 @@ import java.rmi.registry.Registry;
 import java.rmi.server.ServerNotActiveException;
 import java.util.Properties;
 
+import org.apache.jmeter.rmi.RmiUtils;
 import org.apache.jmeter.services.FileServer;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jorphan.collections.HashTree;
@@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory;
  * This is the JMeter server main code.
  */
 public final class RemoteJMeterEngineImpl extends java.rmi.server.UnicastRemoteObject implements RemoteJMeterEngine {
-    private static final long serialVersionUID = 241L;
+    private static final long serialVersionUID = 242L;
 
     private static final Logger log = LoggerFactory.getLogger(RemoteJMeterEngineImpl.class);
 
@@ -48,63 +48,36 @@ public final class RemoteJMeterEngineImp
     
     private transient Thread ownerThread;
 
-    private static final int DEFAULT_RMI_PORT =
-        JMeterUtils.getPropDefault("server.rmi.port", 1099); // $NON-NLS-1$
-
-    private static final int DEFAULT_LOCAL_PORT =
-        JMeterUtils.getPropDefault("server.rmi.localport", 0); // $NON-NLS-1$
-
-    static{
-        if (DEFAULT_LOCAL_PORT != 0){
-            System.out.println("Using local port: "+DEFAULT_LOCAL_PORT);
-        }
-    }
-
     // Should we create our own copy of the RMI registry?
     private static final boolean CREATE_SERVER =
         JMeterUtils.getPropDefault("server.rmi.create", true); // $NON-NLS-1$
 
     private final Object LOCK = new Object();
 
-    private final int rmiPort;
+    /**
+     * RMI Registry port
+     */
+    private final int rmiRegistryPort;
 
     private Properties remotelySetProperties;
 
-    private RemoteJMeterEngineImpl(int localPort, int rmiPort) throws RemoteException {
-        super(localPort); // Create this object using the specified port (0 means anonymous)
-        this.rmiPort = rmiPort;
+    private RemoteJMeterEngineImpl(int localPort, int rmiRegistryPort) throws RemoteException {
+        super(localPort, RmiUtils.createClientSocketFactory(), RmiUtils.createServerSocketFactory()); // Create this object using the specified port (0 means anonymous)
+        this.rmiRegistryPort = rmiRegistryPort;
         System.out.println("Created remote object: "+this.getRef().remoteToString());
     }
 
-    public static void startServer(int rmiPort) throws RemoteException {
-        RemoteJMeterEngineImpl engine = new RemoteJMeterEngineImpl(DEFAULT_LOCAL_PORT, rmiPort == 0 ? DEFAULT_RMI_PORT : rmiPort);
+    public static void startServer(int rmiRegistryPort) throws RemoteException {
+        RemoteJMeterEngineImpl engine = 
+                new RemoteJMeterEngineImpl(
+                RmiUtils.DEFAULT_LOCAL_PORT, 
+                rmiRegistryPort);
         engine.init();
     }
 
     private void init() throws RemoteException {
-        log.info("Starting backing engine on {}", this.rmiPort);
-        InetAddress localHost=null;
-        // Bug 47980 - allow override of local hostname
-        String host = System.getProperties().getProperty("java.rmi.server.hostname"); // $NON-NLS-1$
-        try {
-            if( host==null ) {
-                log.info("System property 'java.rmi.server.hostname' is not defined, using localHost address");
-                localHost = InetAddress.getLocalHost();
-            } else {
-                log.info("Resolving by name the value of System property 'java.rmi.server.hostname': {}", host);
-                localHost = InetAddress.getByName(host);
-            }
-        } catch (UnknownHostException e1) {
-            throw new RemoteException("Cannot start. Unable to get local host IP address.", e1);
-        }
-        if (log.isInfoEnabled()) {
-            log.info("Local IP address={}", localHost.getHostAddress());
-        }
-        // BUG 52469 : Allow loopback address for SSH Tunneling of RMI traffic
-        if (host == null && localHost.isLoopbackAddress()){
-            String hostName = localHost.getHostName();
-            throw new RemoteException("Cannot start. "+hostName+" is a loopback address.");
-        }
+        log.info("Starting backing engine on {}", this.rmiRegistryPort);
+        InetAddress localHost = RmiUtils.getRmiHost();
         if (localHost.isSiteLocalAddress()){
             // should perhaps be log.warn, but this causes the client-server test to fail
             log.info("IP address is a site-local address; this may cause problems with remote access.\n"
@@ -115,7 +88,9 @@ public final class RemoteJMeterEngineImp
         if (CREATE_SERVER){
             log.info("Creating RMI registry (server.rmi.create=true)");
             try {
-                reg = LocateRegistry.createRegistry(this.rmiPort);
+                reg = LocateRegistry.createRegistry(this.rmiRegistryPort, 
+                        RmiUtils.createClientSocketFactory(), 
+                        RmiUtils.createServerSocketFactory());
                 log.debug("Created registry: {}", reg);
             } catch (RemoteException e){
                 String msg="Problem creating registry: "+e;
@@ -127,11 +102,14 @@ public final class RemoteJMeterEngineImp
         try {
             if (reg == null) {
                 log.debug("Locating registry");
-                reg = LocateRegistry.getRegistry(this.rmiPort);
+                reg = LocateRegistry.getRegistry(
+                        RmiUtils.getRmiHost().getHostName(),
+                        this.rmiRegistryPort,
+                        RmiUtils.createClientSocketFactory());
             }
             log.debug("About to rebind registry: {}", reg);
             reg.rebind(JMETER_ENGINE_RMI_NAME, this);
-            log.info("Bound to registry on port {}", this.rmiPort);
+            log.info("Bound to RMI registry on port {}", this.rmiRegistryPort);
         } catch (Exception ex) {
             log.error("rmiregistry needs to be running to start JMeter in server mode. {}", ex.toString());
             // Throw an Exception to ensure caller knows ...
@@ -170,14 +148,14 @@ public final class RemoteJMeterEngineImp
     }
 
     @Override
-    public void rrunTest() throws RemoteException, JMeterEngineException, IllegalStateException {
+    public void rrunTest() throws RemoteException, JMeterEngineException {
         log.info("Running test");
         checkOwner("runTest");
         backingEngine.runTest();
     }
 
     @Override
-    public void rreset() throws RemoteException, IllegalStateException {
+    public void rreset() throws RemoteException {
         // Mail on userlist reported NPE here - looks like only happens if there are network errors, but check anyway
         if (backingEngine != null) {
             log.info("Reset");
@@ -216,7 +194,10 @@ public final class RemoteJMeterEngineImp
         };
         et.setDaemon(false);
         // Tidy up any objects we created
-        Registry reg = LocateRegistry.getRegistry(this.rmiPort);        
+        Registry reg = LocateRegistry.getRegistry(
+                RmiUtils.getRmiHost().getHostName(),
+                this.rmiRegistryPort,
+                RmiUtils.createClientSocketFactory());        
         try {
             reg.unbind(JMETER_ENGINE_RMI_NAME);
         } catch (NotBoundException e) {
@@ -229,7 +210,7 @@ public final class RemoteJMeterEngineImp
     }
 
     @Override
-    public void rsetProperties(Properties p) throws RemoteException, IllegalStateException {
+    public void rsetProperties(Properties p) throws RemoteException {
         checkOwner("setProperties");
         if(remotelySetProperties != null) {
             Properties jmeterProperties = JMeterUtils.getJMeterProperties();
@@ -247,11 +228,11 @@ public final class RemoteJMeterEngineImp
      * @param methodName the name of the method for the log message
      * @throws IllegalStateException if the caller is not the owner.
      */
-    private void checkOwner(String methodName) throws IllegalStateException {
+    private void checkOwner(String methodName) {
         if (ownerThread != null && ownerThread != Thread.currentThread()){
             String msg = "The engine is not owned by this thread - cannot call "+methodName;
             log.warn(msg);
-            throw new IllegalStateException(msg);            
+            throw new IllegalStateException(msg);
         }
     }
 }

Added: jmeter/trunk/src/core/org/apache/jmeter/rmi/AliasKeyManager.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/rmi/AliasKeyManager.java?rev=1822052&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/rmi/AliasKeyManager.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/rmi/AliasKeyManager.java Tue Jan 23 20:50:52 2018
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.jmeter.rmi;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.X509KeyManager;
+
+/**
+ * Implementation of {@link KeyManager} that allows using an alias
+ * @since 4.0
+ *
+ */
+public class AliasKeyManager implements X509KeyManager {
+
+    private final String alias;
+    private final X509KeyManager km;
+
+    public AliasKeyManager(X509KeyManager km, String alias) {
+        this.km = km;
+        this.alias = alias;
+    }
+
+    /**
+     * Wraps the first found {@link X509KeyManager} that has a private key for
+     * the given {@code alias} as an {@link AliasKeyManager} and returns it as
+     * the only element in a newly created array.
+     * 
+     * @param kms
+     *            the KeyManagers to be searched for the {@code alias}
+     * @param alias
+     *            the name to be searched for
+     * @return an array with one {@link AliasKeyManager} that has a private key
+     *         named {@code alias}
+     * @throws IllegalArgumentException
+     *             if no valid KeyManager is found
+     */
+    public static AliasKeyManager[] wrap(KeyManager[] kms, String alias) {
+        AliasKeyManager validManager = Arrays.asList(kms).stream()
+                .filter(m -> m instanceof X509KeyManager)
+                .map(m -> (X509KeyManager) m)
+                .filter(m -> m.getPrivateKey(alias) != null)
+                .map(m -> new AliasKeyManager(m, alias)).findFirst()
+                .orElseThrow(() -> new IllegalArgumentException(
+                        "No key found for alias '" + alias + "'"));
+        return new AliasKeyManager[] { validManager };
+    }
+
+    @Override
+    public String chooseClientAlias(String[] keyType, Principal[] issuers,
+            Socket socket) {
+        return alias;
+    }
+
+    @Override
+    public String chooseServerAlias(String keyType, Principal[] issuers,
+            Socket socket) {
+        return alias;
+    }
+
+    @Override
+    public X509Certificate[] getCertificateChain(String alias) {
+        return this.km.getCertificateChain(alias);
+    }
+
+    @Override
+    public String[] getClientAliases(String keyType, Principal[] issuers) {
+        return this.km.getClientAliases(keyType, issuers);
+    }
+
+    @Override
+    public PrivateKey getPrivateKey(String alias) {
+        return this.km.getPrivateKey(alias);
+    }
+
+    @Override
+    public String[] getServerAliases(String keyType, Principal[] issuers) {
+        return this.km.getServerAliases(keyType, issuers);
+    }
+
+}

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/AliasKeyManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/AliasKeyManager.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/core/org/apache/jmeter/rmi/RMIServerSocketFactoryImpl.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/rmi/RMIServerSocketFactoryImpl.java?rev=1822052&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/rmi/RMIServerSocketFactoryImpl.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/rmi/RMIServerSocketFactoryImpl.java Tue Jan 23 20:50:52 2018
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.rmi;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.rmi.server.RMIServerSocketFactory;
+
+import javax.net.ServerSocketFactory;
+
+/**
+ * Custom {@link RMIServerSocketFactory} that binds RMI to particular host/ip
+ * @since 4.0
+ */
+public class RMIServerSocketFactoryImpl implements RMIServerSocketFactory, Serializable {
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 6106381149147208254L;
+    private final InetAddress localAddress;
+
+    /**
+     * 
+     */
+    public RMIServerSocketFactoryImpl(final InetAddress pAddress) {
+        this.localAddress = pAddress;
+    }
+
+    /**
+     * Creates a server socket that listens on localAddress:port
+     * @param port 
+     * @see java.rmi.server.RMIServerSocketFactory#createServerSocket(int)
+     */
+    @Override
+    public ServerSocket createServerSocket(int port) throws IOException {
+        return ServerSocketFactory.getDefault().createServerSocket(port, 0, localAddress);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        RMIServerSocketFactoryImpl other = (RMIServerSocketFactoryImpl) obj;
+        if (localAddress == null) {
+            if (other.localAddress != null)
+                return false;
+        } else if (!localAddress.equals(other.localAddress))
+            return false;
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return (localAddress == null) ? 0 : localAddress.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "RMIServerSocketFactoryImpl(host=" + localAddress + ")";
+    }
+}

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/RMIServerSocketFactoryImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/RMIServerSocketFactoryImpl.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/core/org/apache/jmeter/rmi/RmiUtils.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/rmi/RmiUtils.java?rev=1822052&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/rmi/RmiUtils.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/rmi/RmiUtils.java Tue Jan 23 20:50:52 2018
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.rmi;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.rmi.RemoteException;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.jmeter.util.JMeterUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * RMI Helper class
+ * @since 4.0
+ */
+public final class RmiUtils {
+    private static final Logger log = LoggerFactory.getLogger(RmiUtils.class);
+
+    public static final int DEFAULT_RMI_REGISTRY_PORT = JMeterUtils
+            .getPropDefault("server_port", 0); // $NON-NLS-1$
+
+    public static final int DEFAULT_RMI_PORT = JMeterUtils
+            .getPropDefault("server.rmi.port", 1099); // $NON-NLS-1$
+
+    public static final int DEFAULT_LOCAL_PORT = JMeterUtils
+            .getPropDefault("server.rmi.localport", 0); // $NON-NLS-1$
+
+    private static final String KEYSTORE_TYPE = JMeterUtils
+            .getPropDefault("server.rmi.ssl.keystore.type", "JKS");
+
+    private static final String KEYSTORE_FILE = JMeterUtils
+            .getPropDefault("server.rmi.ssl.keystore.file", "rmi_keystore.jks");
+
+    private static final String KEYSTORE_PASSWORD = JMeterUtils
+            .getPropDefault("server.rmi.ssl.keystore.password", "changeit");
+
+    private static final String KEYSTORE_ALIAS = JMeterUtils
+            .getPropDefault("server.rmi.ssl.keystore.alias", "rmi");
+
+    private static final String TRUSTSTORE_TYPE = JMeterUtils
+            .getPropDefault("server.rmi.ssl.truststore.type", KEYSTORE_TYPE);
+
+    private static final String TRUSTSTORE_FILE = JMeterUtils
+            .getPropDefault("server.rmi.ssl.truststore.file", KEYSTORE_FILE);
+
+    private static final String TRUSTSTORE_PASSWORD = JMeterUtils
+            .getPropDefault("server.rmi.ssl.truststore.password",
+                    KEYSTORE_PASSWORD);
+
+    private static final boolean SSL_DISABLED = JMeterUtils
+            .getPropDefault("server.rmi.ssl.disable", false);
+
+    static{
+        if (DEFAULT_LOCAL_PORT != 0){
+            System.out.println("Using local port: " + DEFAULT_LOCAL_PORT);
+        }
+    }
+
+    private RmiUtils() {
+        super();
+    }
+
+    public static RMIClientSocketFactory createClientSocketFactory() {
+        if (StringUtils.isBlank(KEYSTORE_FILE)) {
+            Validate.validState(SSL_DISABLED,
+                    "No keystore for RMI over SSL specified. Set 'server.rmi.ssl.disable' to true, if this is intentional,"
+                    + "if not run create-rmi-keystore.bat/create-rmi-keystore.sh to create a keystore and distribute it on client and servers"
+                    + "used for distributed testing.");
+            return null;
+        }
+        final SSLRMIClientSocketFactory factory = new SSLRMIClientSocketFactory();
+        factory.setAlias(KEYSTORE_ALIAS);
+        factory.setKeystore(KEYSTORE_FILE, KEYSTORE_TYPE, KEYSTORE_PASSWORD);
+        factory.setTruststore(TRUSTSTORE_FILE, TRUSTSTORE_TYPE, TRUSTSTORE_PASSWORD);
+        return factory;
+    }
+
+    public static RMIServerSocketFactory createServerSocketFactory() throws RemoteException {
+        if (StringUtils.isBlank(KEYSTORE_FILE)) {
+            Validate.validState(SSL_DISABLED,
+                    "No keystore for RMI over SSL specified. Set 'server.rmi.ssl.disable' to true, if this is intentional.");
+            return new RMIServerSocketFactoryImpl(getRmiHost());
+        }
+        SSLRMIServerSocketFactory factory = new SSLRMIServerSocketFactory(getRmiHost());
+        factory.setAlias(KEYSTORE_ALIAS);
+        factory.setKeystore(KEYSTORE_FILE, KEYSTORE_TYPE, KEYSTORE_PASSWORD);
+        factory.setTruststore(TRUSTSTORE_FILE, TRUSTSTORE_TYPE, TRUSTSTORE_PASSWORD);
+        return factory;
+    }
+
+    /**
+     * @return
+     * @throws RemoteException
+     */
+    public static InetAddress getRmiHost() throws RemoteException {
+        InetAddress localHost=null;
+        // Bug 47980 - allow override of local hostname
+        String host = System.getProperties().getProperty("java.rmi.server.hostname"); // $NON-NLS-1$
+        try {
+            if( host==null ) {
+                log.info("System property 'java.rmi.server.hostname' is not defined, using localHost address");
+                localHost = InetAddress.getLocalHost();
+            } else {
+                log.info("Resolving by name the value of System property 'java.rmi.server.hostname': {}", host);
+                localHost = InetAddress.getByName(host);
+            }
+        } catch (UnknownHostException e) {
+            throw new RemoteException("Cannot start. Unable to get local host IP address.", e);
+        }
+        if (log.isInfoEnabled()) {
+            log.info("Local IP address={}", localHost.getHostAddress());
+        }
+        // BUG 52469 : Allow loopback address for SSH Tunneling of RMI traffic
+        if (host == null && localHost.isLoopbackAddress()){
+            String hostName = localHost.getHostName();
+            throw new RemoteException("Cannot start. " + hostName + " is a loopback address.");
+        }
+        return localHost;
+    }
+
+    /**
+     * 
+     * @return port of RMI Registry
+     */
+    public static int getRmiRegistryPort() {
+        return DEFAULT_RMI_REGISTRY_PORT == 0 ? 
+                RmiUtils.DEFAULT_RMI_PORT : DEFAULT_RMI_REGISTRY_PORT;
+    }
+}

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/RmiUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/RmiUtils.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIClientSocketFactory.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIClientSocketFactory.java?rev=1822052&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIClientSocketFactory.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIClientSocketFactory.java Tue Jan 23 20:50:52 2018
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.rmi;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.Socket;
+import java.rmi.server.RMIClientSocketFactory;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * Implementation of {@link RMIClientSocketFactory} that:
+ * <ul>
+ *  <li>Establishes SSL connection</li>
+ * </ul>
+ * @since 4.0
+ */
+public class SSLRMIClientSocketFactory
+        implements RMIClientSocketFactory, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String alias;
+    private String keyStoreLocation;
+    private String keyStorePassword;
+    private String keyStoreType;
+    private String trustStoreLocation;
+    private String trustStorePassword;
+    private String trustStoreType;
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
+    public void setKeystore(String location, String type, String password) {
+        this.keyStoreLocation = location;
+        this.keyStoreType = type;
+        this.keyStorePassword = password;
+    }
+
+    public void setTruststore(String location, String type, String password) {
+        this.trustStoreLocation = location;
+        this.trustStoreType = type;
+        this.trustStorePassword = password;
+    }
+
+    @Override
+    public Socket createSocket(String host, int port) throws IOException {
+
+        char[] passphrase = null;
+        if (keyStorePassword != null) {
+            passphrase = keyStorePassword.toCharArray();
+        }
+
+        KeyStore keyStore = null;
+        if (keyStoreLocation != null) {
+            keyStore = loadStore(keyStoreLocation, passphrase, keyStoreType);
+        }
+
+        KeyStore trustStore;
+        if (trustStoreLocation != null) {
+            trustStore = loadStore(trustStoreLocation, trustStorePassword.toCharArray(), trustStoreType);
+        } else {
+            trustStore = keyStore;
+        }
+
+        if (alias == null) {
+            throw new IOException(
+                    "SSL certificate alias cannot be null; MUST be set for SSLServerSocketFactory!");
+        }
+
+        KeyManagerFactory kmf;
+        SSLContext ctx;
+        try {
+            kmf = KeyManagerFactory
+                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(keyStore, passphrase);
+            ctx = SSLContext.getInstance("TLS");
+            TrustManagerFactory tmf = TrustManagerFactory
+                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            tmf.init(trustStore);
+            ctx.init(AliasKeyManager.wrap(kmf.getKeyManagers(), alias), tmf.getTrustManagers(), null);
+        } catch (GeneralSecurityException e) {
+            throw new IOException(e);
+        }
+        SSLSocketFactory factory = ctx.getSocketFactory();
+        if (factory == null) {
+            throw new IOException(
+                    "Unable to obtain SSLSocketFactory for provided KeyStore");
+        }
+
+        return factory.createSocket(host, port);
+    }
+
+    private KeyStore loadStore(String location, char[] passphrase, String type)
+            throws IOException {
+        try {
+            KeyStore store = KeyStore.getInstance(type);
+            store.load(new FileInputStream(location), passphrase);
+            return store;
+        } catch (NoSuchAlgorithmException | CertificateException
+                | KeyStoreException e) {
+            throw new IOException("Can't load " + location + " as type " + type, e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "SSLRMIClientSocketFactory(keyStoreLocation=" + this.keyStoreLocation + ", type="
+                + this.keyStoreType +", trustStoreLocation=" + this.trustStoreLocation + ", type="
+                + this.trustStoreType + ", alias=" + this.alias + ')';
+    }
+}

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIClientSocketFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIClientSocketFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIServerSocketFactory.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIServerSocketFactory.java?rev=1822052&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIServerSocketFactory.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIServerSocketFactory.java Tue Jan 23 20:50:52 2018
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.rmi;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.rmi.server.RMIServerSocketFactory;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of {@link RMIServerSocketFactoryImpl} that:
+ * <ul>
+ *  <li>Binds socket to an address</li>
+ *  <li>Establishes SSL connection</li>
+ * </ul>
+ * @since 4.0
+ */
+public class SSLRMIServerSocketFactory implements RMIServerSocketFactory, Serializable {
+
+    private static final long serialVersionUID = 258730225720182190L;
+    private static final Logger LOGGER = LoggerFactory.getLogger(SSLRMIServerSocketFactory.class);
+    private final InetAddress localAddress;
+
+    private String alias;
+    private String keyStoreLocation;
+    private String keyStorePassword;
+    private String keyStoreType;
+    private String trustStoreLocation;
+    private String trustStorePassword;
+    private String trustStoreType;
+
+    private boolean clientAuth;
+
+    public SSLRMIServerSocketFactory(InetAddress pAddress) {
+        this.localAddress = pAddress;
+    }
+
+    public void setNeedClientAuth(boolean clientAuth) {
+        this.clientAuth = clientAuth;
+    }
+
+    public void setKeystore(String location, String type, String password) {
+        this.keyStoreLocation = location;
+        this.keyStoreType = type;
+        this.keyStorePassword = password;
+    }
+
+    public void setTruststore(String location, String type, String password) {
+        this.trustStoreLocation = location;
+        this.trustStoreType = type;
+        this.trustStorePassword = password;
+    }
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
+    @Override
+    public ServerSocket createServerSocket(int port) throws IOException {
+        char[] passphrase = null;
+        if (keyStorePassword != null) {
+            passphrase = keyStorePassword.toCharArray();
+        }
+
+        KeyStore keyStore = null;
+        if (keyStoreLocation != null) {
+            keyStore = loadStore(keyStoreLocation, passphrase, keyStoreType);
+        }
+
+        KeyStore trustStore;
+        if (trustStoreLocation != null) {
+            trustStore = loadStore(trustStoreLocation, trustStorePassword.toCharArray(), trustStoreType);
+        } else {
+            trustStore = keyStore;
+        }
+
+        if (alias == null) {
+            throw new IOException(
+                    "SSL certificate alias cannot be null; MUST be set for SSLServerSocketFactory!");
+        }
+
+        SSLContext ctx;
+        try {
+            KeyManagerFactory kmf = KeyManagerFactory
+                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(keyStore, passphrase);
+            TrustManagerFactory tmf = TrustManagerFactory
+                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            tmf.init(trustStore);
+            ctx = SSLContext.getInstance("TLS");
+            ctx.init(AliasKeyManager.wrap(kmf.getKeyManagers(), alias), tmf.getTrustManagers(), null);
+        } catch (GeneralSecurityException e) {
+            throw new IOException(e);
+        }
+        SSLServerSocketFactory factory = ctx.getServerSocketFactory();
+        if (factory == null) {
+            throw new IOException(
+                    "Unable to obtain SSLServerSocketFactory for provided KeyStore");
+        }
+
+        SSLServerSocket socket = (SSLServerSocket) factory
+                .createServerSocket(port, 0, localAddress);
+        socket.setNeedClientAuth(clientAuth);
+        LOGGER.info("Created SSLSocket: {}", socket);
+        return socket;
+    }
+
+    private KeyStore loadStore(String location, char[] passphrase, String type)
+            throws IOException {
+        try (FileInputStream fileInputStream = new FileInputStream(location)){
+            KeyStore store = KeyStore.getInstance(type);
+            store.load(fileInputStream, passphrase);
+            return store;
+        } catch (NoSuchAlgorithmException | CertificateException
+                | KeyStoreException e) {
+            throw new IOException(e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "SSLRMIServerSocketFactory(host=" + localAddress 
+                + ", keyStoreLocation=" + this.keyStoreLocation + ", type="
+                + this.keyStoreType +", trustStoreLocation=" + this.trustStoreLocation + ", type="
+                + this.trustStoreType + ", alias=" + this.alias + ')';
+    }
+
+}

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIServerSocketFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/core/org/apache/jmeter/rmi/SSLRMIServerSocketFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/src/core/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/samplers/RemoteSampleListenerImpl.java Tue Jan 23 20:50:52 2018
@@ -21,6 +21,7 @@ package org.apache.jmeter.samplers;
 import java.rmi.RemoteException;
 import java.util.List;
 
+import org.apache.jmeter.rmi.RmiUtils;
 import org.apache.jmeter.testelement.TestStateListener;
 import org.apache.jmeter.util.JMeterUtils;
 
@@ -40,7 +41,7 @@ public class RemoteSampleListenerImpl ex
         JMeterUtils.getPropDefault("client.rmi.localport", 0); // $NON-NLS-1$
 
     public RemoteSampleListenerImpl(Object listener) throws RemoteException {
-        super(DEFAULT_LOCAL_PORT);
+        super(DEFAULT_LOCAL_PORT, RmiUtils.createClientSocketFactory(),  RmiUtils.createServerSocketFactory());
         if (listener instanceof TestStateListener) {
             testListener = (TestStateListener) listener;
         } else {

Modified: jmeter/trunk/src/core/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/RemoteThreadsListenerImpl.java Tue Jan 23 20:50:52 2018
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.jmeter.gui.GuiPackage;
+import org.apache.jmeter.rmi.RmiUtils;
 import org.apache.jmeter.testelement.ThreadListener;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jorphan.reflect.ClassFinder;
@@ -55,7 +56,7 @@ public class RemoteThreadsListenerImpl e
      * @throws RemoteException if failed to export object
      */
     public RemoteThreadsListenerImpl() throws RemoteException {
-        super(DEFAULT_LOCAL_PORT);
+        super(DEFAULT_LOCAL_PORT, RmiUtils.createClientSocketFactory(), RmiUtils.createServerSocketFactory());
         try {
             List<String> listClasses = ClassFinder.findClassesThatExtend(
                     JMeterUtils.getSearchPaths(), 

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1822052&r1=1822051&r2=1822052&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Tue Jan 23 20:50:52 2018
@@ -275,7 +275,7 @@ itself does not have to be edited anymor
     <li><bug>56368</bug>Create and Deploy source artifacts to Maven central</li>
     <li><bug>61973</bug>Create and Deploy javadoc artifacts to Maven central</li>
     <li><pr>371</pr>Fix example in documentation for <complink name="XPath Assertion"/>. Contributed by Konstantin Kalinin (kkalinin at hotmail.com)</li>
-    <li><bug>62026</bug>Use a <code>setenv.sh</code> script if available to preset variables when starting JMeter with <code>bin/jmeter</code>.</li>
+    <li><bug>62039</bug>Distributed testing : Provide ability to use SSL</li>
 </ul>
 
 <ch_section>Non-functional changes</ch_section>