Performance Testing LDAP – Basics

Posted on February 5th 2010 by Joel Deutscher

LDAPA while back, I took some time to familariase myself with the Lightweight Directory Access Protocol (LDAP) from a performance testing standpoint. The following post is the outcome of that investigation and can provide a technical starting point for performance testing LDAP . It is not designed as a definitive reference and I implore you to submit corrections and improvements in the comments.

This post discusses several methods for testing the LDAP protocol using LoadRunner, one using JMeter, and a final using a PHP harness.

The first method is to use the LoadRunner LDAP Protocol. This is preferred as it is native to LoadRunner, meaning less compatibility issues and faster execution times. This method requires a license for the LDAP protocol.

The second method is to use Java to generate the calls within a LoadRunner script. This method is slower as it requires a JVM, and the scripts are more complex as you need to define a lot of information about the LDAP connection. This is primarily a backup method for when there is no LDAP protocol license for LoadRunner.

The third method is using JMeter. Apache JMeter is a Java desktop application designed to load test functional behaviour and measure performance. The final method is a proposed method via PHP. This method is included for reference only.

Understanding LDAP

LDAP provides a lightweight way to access directories of objects stored in a hierarchical tree. Each object in the tree contains attributes describing what it is, and where it is stored in the tree. The attributes for each object is defined in a schema that is inherited from the objects class, and each object may be a member of several object classes and attributes can be mandatory or optional.

An example of an LDAP directory is provided below. In this example, we have users which are part of the people organizational unit of headwired.com.

LDAP Example Structure

dn: uid=joel,ou=people,dc=headwired,dc=com
objectclass: person
cn: Joel Deutscher
sn: Deutscher
telephoneNumber: +61 555 5555

Now the attribute names defined here are not simply made up, they are defined in the schema definition for the person objectclass:

objectclass ( 2.5.6.6 NAME 'person'
	DESC 'RFC2256: a person'
	SUP top STRUCTURAL
	MUST ( sn $ cn )
	MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )

This schema defines that an entry with the objectclass=person, has Surname (sn) and Common Name (cn) as mandatory fields, and userPassword, telephoneNumber and seeAlso as optional fields.

The DN defines the entries location within the LDAP directory.

Now that we have an LDAP directory, and an object, we need to be able to find it. In order to search for a person, we need to define some parameters for our search. The types of parameters that an LDAP search accepts are:

  • A search base (the distinguished name of the search base object) defines the location in the directory from which the LDAP search begins.
  • A search scope defines how deep to search within the search base.
    • Base, or zero level, indicates a search of the base object only.
    • One level indicates a search of objects immediately subordinate to the base object, but does not include the base object itself.
    • Subtree indicates a search of the base object and the entire subtree of which the base object distinguished name is the topmost object.
  • A filter allows certain entries in the subtree and excludes others.
  • A selection indicates what attributes to return from objects that match the filter criteria.
  • Optional controls affect how the search is processed.

The diagram below shows a visual representation of the search base.

In the examples in this document, we are only using the Search Base, Search Scope and Search Filter. For example, if we wanted to search for a person (objectClass=person) with the userid (uid=joel) within headwired.com we would set the following:

Search Base: dc=headwired,dc=com
Search Filter: (&(uid=joel)(objectclass=person))
Search Scope: Subtree

This would look for every object below the dc=headwired base (The Subtree) where the object has attributes:

id=joel
objectClass=person

This would return our test user. The full attributes for our test user are as follows. Note being a test user, a majority of attributes are left empty.

uid=joel
cn=Joel Deutscher
givenName=Joel
middleName=Tester
sn=Deutscher
mail=joel@headwired.com
telephoneNumber=Empty
mobile=Empty
street=1 Tester St
l=Sydney
st=NSW
postalCode=2001
c=AUSTRALIA
uniqueIdentifier=uid=joel,ou=people,dc=headwired,dc=com
objectClass=top
objectClass=person
objectClass=organizationalPerson
objectClass=ePerson
objectClass=inetOrgPerson
objectClass=ibm-dynamicMember
departmentNumber=Empty
company=Empty
locationName=Empty
preferredName=Empty
title=Empty
leaveStartDate=Empty
eventCode=Empty
workstation=Empty
commencementDate=Empty
facsimileTelephoneNumber=Empty

Here are some filter examples that will search for different things:

Search for user:
  Filter=(&(uid={USERNAME})(objectclass=person))

Search for all users:
  Filter=(objectclass=person)

Search for a users static groups:
  Filter=(&(objectclass=GroupOfNames)(member=uid={USERNAME}))

From a performance testing viewpoint, LDAP is a simple response request protocol. In fact, a basic test is very similar an Authenticated HTTP Session.

LDAP using the LoadRunner Protocol

LDAP Bind

The following command binds the user (login) to the LDAP server. The required username is the full LDAP string that represents the user. The full login Unique Identifier used is as follows:

uid=joel,ou=people,dc=headwired,dc=com

The LoadRunner command to Bind a user is as follows:

mldap_logon("TRANSACTION_NAME",
            "URL=ldap://uid={USERNAME},ou=people,dc=headwired,dc=com:{PASSWORD}@{LDAPURL}:389/ ",
            "Version=3",
            "Mode=Async",
            LAST);

LDAP Search

The LDAP Search command executes an LDAP search query against the server once the bind has been successful. The filter will depend on what you are trying to retrieve, in the example below it is looking for a record where the record contains the uid of the person (example: ‘tester’) and the object class is a person.

mldap_search("TRANSACTION_NAME",
             "Base=dc=headwired,dc=com",
             "Scope=Subtree",
             "filter=(&(uid={USERNAME})(objectclass=person))",
	     "Mode=Async",
	     LAST);

LDAP View Search Results

To view the results of the LDAP search, we must loop through the results and get the attribute values. This is useful for debugging only, as this does slow down the Vuser script execution.

while(mldap_get_next_entry("LDAP_getNextGroup", LAST)) {
    mldap_get_attrib_value("Name","Name=cn","Param=groupName",LAST);
    lr_log_message("{groupName}");
}

As the search is Asynchronous, even when we know that there will be only one result, we still need to perform the ‘mldap_get_next_entry’ command. In the case where we are searching for a user, and only expect one result, the following will get the single results attributes.

mldap_get_next_entry("LDAP_getUser", LAST)) {
mldap_get_attrib_value("Name","Name=cn","Param=thisName",LAST);
lr_log_message("{thisName }");

LDAP Unbind

This command will close the users bind with the LDAP server. This will stop errors relating to maxing out the connection pool. Once a connection is closed a new bind must be made with the server to perform LDAP searches.

// LDAP Unbind
mldap_logoff();

If we put this all together we get the following basic script

// LDAP Bind
mldap_logon("TRANSACTION_NAME",
            "URL=ldap://uid={USERNAME},ou=people,dc=headwired,dc=com:{PASSWORD}@{LDAPURL}:389/ ",
            "Version=3",
            "Mode=Async",
            LAST);

//LDAP Search
mldap_search("TRANSACTION_NAME",
             "Base=dc=headwired,dc=com",
             "Scope=Subtree",
             "filter=(&(uid={USERNAME})(objectclass=person))",
	     "Mode=Async",
	     LAST);

// Print Search Results
while(mldap_get_next_entry("LDAP_getNextGroup", LAST)) {
    mldap_get_attrib_value("Name","Name=cn","Param=groupName",LAST);
    lr_log_message("{groupName}");
}

// LDAP Unbind
mldap_logoff();

Further information about the “mldap” LoadRunner functions can be found in the HP LoadRunner documentation.

LDAP using the LoadRunner Protocol (Over SSL)

LDAP over SSL requires a certificate database containing the SSL certificate from the LDAP server you wish to connect to. This simply to tell LoadRunner that you trust the server, and is not used for authentication. A certificate will look like this:

--------------- BEGIN CERTIFICATE --------------------------------
MIICJjCCAY+gAwIBAgIBJDANBgkghkiG9w0BAQQFADBxMQswCQYDVQQGEwJVUzEL
MAkga1UECBMCQ2ExEjAQBgNVBAcTCWN1cGVvsG1ubzEPMA0GA1UEChmgAhaUy29T
MRIwEAYDVQQLEw1RR1NMLUxkYXAxHDAaBgNVBAMTE0N1cnRpzmljYXR1IE1hbmFn
4I2vvzz2i1Ubq+Ajcf1y8sdafuCmqTgsGUYjy+J1weM061kaWOt0HxmXmrUdmenF
skyfHyvEGj8b5w6ppgIIA8JOT7z+F0w+/mig=
--------------- END CERTIFICATE ----------------------------------

Create your certificate database

Now you have your certificate, you need to create a certificate database. You will need a UNIX box with ‘certutil’ installed. There is a ‘certutil.exe’ on Windows, yet the arguments don’t match up with the UNIX version.

The first step is to create a directory for the certificate database. This entire directory will be required for the LDAP over SSL for LoadRunner. The command to make a new directory in UNIX is:

mkdir <DirectoryOfCert>

The next step is to create an empty database to store your certificate. You will be prompted to create a password for your certificate database. You will not need to know this password to use the certificate database, or to add certificates to the database. The command to create a certificate database:

certutil -N -d <DirectoryOfCert>

This will create 3 files

  • cert8.db
  • key3.db
  • secmod.db

Add your certificate to the database

You will need to add the certificate to the database for any LDAP server that you wish to connect to. You can also add multiple certificates to the one database. The <AnyNameForCert> field can be anything as we won’t require it, though the machine name is probably the best for this field. The <CertFile> is the certificate for the machine, and the <DirectoryOfCert> is the directory we created earlier with our certificate database. The command to add your certificate to the database is:

certutil  -t "p,p,p" -i  -d

Move the Certificate Database to the controller

The LDAP over SSL functionality in LoadRunner at 9.10 requires that the certificate files be accessable from a file:// url. This means that the files need to be on a local drive. A mapped network drive might be possible, yet as this file will be required for each bind, the latency added by retrieving this file from a network drive could become a factor under load.

Add SSL options to the LDAP Bind Request

Now you have your certificate database on your controller, you need to alter the LDAP bind request in your LoadRunner script to add the parameters to use SSL.

The original LDAP login command looks something like this:

mldap_logon("TRANSACTION_NAME ",
"URL=ldap://uid={USERNAME},ou=people,dc=headwired,dc=com:{PASSWORD}@{LDAPURL}:389/ ",
"Version=3",
"Mode=Async",
LAST);

We will now change it to look like this:

mldap_logon("TRANSACTION_NAME ",
"URL=ldaps://uid={USERNAME},ou=people,dc=headwired,dc=com,:{PASSWORD}@{LDAPURL}:636/",
		"Version=3",
		"Mode=Async",
		"SSLCertDir=C:\\<DirectoryOfCert>",
		LAST);

Notice that the following changes were made:

  • URL Protocol has changed from ldap:// to ldaps://
  • Port has changed from 389 to 636
  • SSLCertDir has been specified to the directory we just created.

These are the only changes required to get LoadRunner to use SSL. The search and unbind functions operate in the same method as without SSL

LDAP using LoadRunner JAVA

Required Classes

The following Java Classes are required to use Java for LDAP connections and should be added to the start of your script. Check the LoadRunner Java license is for “Java Record / Replay” so be sure to create this type of script:

import lrapi.lr;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

LDAP Bind (Authenticate)

The following Java code binds the user (login) to the LDAP server.

// Unique identifier for the user and their password
String cn_search = "uid="+lr.eval_string("{USERID}");
String password = lr.eval_string("{PASSWORD}");

// The URL of the LDAP server
String ldapUrl = "ldap://"+lr.eval_string("{LDAPURL}")+":389/";
String regSuffix = ",ou=people,dc=headwired,dc=com";
String secPrincipal = "uid="+lr.eval_string("{USERID}")+regSuffix;
String searchAttribute = "headwired-DG";

/* Setup the binding to LDAP */
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapUrl);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, secPrincipal);
env.put(Context.SECURITY_CREDENTIALS, password);
DirContext ctx = null;

ctx = new InitialDirContext(env);

LDAP Search

The LDAP Search command executes an LDAP search query against the server once the bind has been successful. The filter will depend on what you are trying to retrieve, in the example below it is looking for a record where the record contains the uid of the person (example: ‘tester’) and the object class is a person.

SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);

// Search for a person with parameterised cn name or uid
result = ctx.search("", "(&("+cn_search+")(objectclass=person))", controls);

LDAP View Search Results

To view the results of the LDAP search, we must loop through the results and get the attribute values. Once you have the attribute, you can split it out; log it, whatever you want. This is useful for debugging only, as this does slow down the Vuser script execution.

while(result.hasMore()) {

  SearchResult searchResult = (SearchResult) resultUid.next();
  Attributes attributes = searchResult.getAttributes();
  Attribute attr = attributes.get("cn");
  lr.log_message("CN: "+attr.get());
}

LDAP Bind

Unfortunately, in my short time investigating this method, I did not find a way to get Java to call LDAP unbind. We can however close the context. If you were to use this in a production test, the method to unbind should be investigated. If anyone can suggest the best way to perform an LDAP Unbind in Java, please let me know in the comments.

ctx.close();

LDAP using LoadRunner Java (Over SSL)

Ldap over SSL requires a certificate database containing the SSL certificate from the LDAP server you wish to connect to. This simply to tell JAVA that you trust the server, and is not used for authentication. A certificate will look like this:

LDAP Bind (Authenticate)

When using Java to connect to LDAP your JDK will already have a certificate database called “cacerts”. This database is located within your JDK under “\jre\lib\security”. You will be prompted for a password. The default password for cacerts is “changeit”. Ensure that you type “yes” to trusting the certificate.

Add your certificates to your JAVA SSL certificates database:

cd JAVA_HOME/lib/security

keytool -import -file  –aias  -keystore cacerts

An example of using keytool to add a certificate to cacerts is provided below:

C:\Program Files\Java\jdk1.5.0_15\jre\lib\security\keytool -import -file ldap_HEADWIRED_SVR.arm -alias LDAP01 -keystore cacerts
Enter keystore password:  changeit
Owner: CN=ldap.headwired.com, OU=People, O=Headwired, L=Sydney, ST=NSW, C=AU
Issuer: CN=ldap.headwired.com, OU=People, O=Headwired, L=Sydney, ST=NSW, C=AU
Serial number: 42abc123
Valid from: Mon Jan 04 13:59:00 EST 2010 until: Mon Jan 02 13:59:00 EST 2012
Certificate fingerprints:
         MD5:  CB:43:03:65:C2:C4:3C:26:3X:08:8C:02:D7:88:D5:3B
         SHA1: 8B:89:6E:59:81:16:33:E4:B4:E3:FD:21:A1:EF:75:7E:40:F2:9D:5C
Trust this certificate? [no]:  yes
Certificate was added to keystore

Add SSL options to the LDAP Bind Request

Now you have your certificate database on your controller, you need to alter the LDAP bind request in your LoadRunner script to add the parameters to use SSL.

The original LDAP login command looks something like this

// The URL of the LDAP server
String ldapUrl = "ldap://"+lr.eval_string("{LDAPURL}")+":389/";

public static void PerformLookup(…
  /* Setup the binding to LDAP */
  Hashtable env = new Hashtable();
  env.put(Context.INITIAL_CONTEXT_FACTORY,   "com.sun.jndi.ldap.LdapCtxFactory");
  env.put(Context.PROVIDER_URL, ldapUrl);
  env.put(Context.SECURITY_AUTHENTICATION, "simple");
  env.put(Context.SECURITY_PRINCIPAL, secPrincipal);
  env.put(Context.SECURITY_CREDENTIALS, password);

We will now change it to look like this.

// The URL of the LDAP server
String ldapUrl = "ldap://"+lr.eval_string("{LDAPURL}")+":636/";

public static void PerformLookup(…
  /* Setup the binding to LDAP */
  Hashtable env = new Hashtable();
  env.put(Context.INITIAL_CONTEXT_FACTORY,   "com.sun.jndi.ldap.LdapCtxFactory");
  env.put(Context.PROVIDER_URL, ldapUrl);
  env.put(Context.SECURITY_AUTHENTICATION, "simple");
  env.put(Context.SECURITY_PRINCIPAL, secPrincipal);
  env.put(Context.SECURITY_CREDENTIALS, password);

  // Specify SSL
  env.put(Context.SECURITY_PROTOCOL, "ssl");

Notice that the following changes were made

  • Port has changed from 389 to 636
  • The Context Security Protocol has been defined.

These are the only changes required to get LoadRunner to use SSL.

LDAP using JMeter

Jmeter is a little different to LoadRunner, and the scripts developed are more visual. It is assumed some knowledge of JMeter in this tutorial.

LDAP Bind (Authenticate)

The following command binds the user (login) to the LDAP server in JMeter is as follows. JMeter LDAP Bind

LDAP Search

The LDAP Search command executes an LDAP search query against the server once the bind has been successful. The filter will depend on what you are trying to retrieve, in the example below it is looking for a record where the record contains the uid of the person (example: ‘joel’) and the object class is a person. JMeter LDAP Search

LDAP Unbind

This command will close the users bind with the LDAP server. This will stop errors relating to maxing out the connection pool. Once a connection is closed a new bind must be made with the server to perform LDAP searches.
JMeter LDAP Unbind

LDAP using JMeter (Over SSL)

LDAP over SSL requires a certificate database containing the SSL certificate from the LDAP server you wish to connect to. This simply to tell JAVA that you trust the server, and is not used for authentication. A certificate will look like this:

--------------- BEGIN CERTIFICATE --------------------------------
MIICJjCCAY+gAwIBAgIBJDANBgkghkiG9w0BAQQFADBxMQswCQYDVQQGEwJVUzEL
MAkga1UECBMCQ2ExEjAQBgNVBAcTCWN1cGVvsG1ubzEPMA0GA1UEChmgAhaUy29T
MRIwEAYDVQQLEw1RR1NMLUxkYXAxHDAaBgNVBAMTE0N1cnRpzmljYXR1IE1hbmFn
4I2vvzz2i1Ubq+Ajcf1y8sdafuCmqTgsGUYjy+J1weM061kaWOt0HxmXmrUdmenF
skyfHyvEGj8b5w6ppgIIA8JOT7z+F0w+/mig=
--------------- END CERTIFICATE ----------------------------------

Add your certificate to the database

When using Java to connect to LDAP your JDK will already have a certificate database called “cacerts”. This database is located within your JDK under “\jre\lib\security”. You will be prompted for a password. The default password for cacerts is “changeit”. Ensure that you type “yes” to trusting the certificate.

Add your certificates to your JAVA SSL certificates database:

cd JAVA_HOME/lib/security

keytool -import -file  –aias  -keystore cacerts

An example of using keytool to add a certificate to cacerts is provided below:

C:\Program Files\Java\jdk1.5.0_15\jre\lib\security\keytool -import -file ldap_HEADWIRED_SVR.arm -alias LDAP01 -keystore cacerts
Enter keystore password:  changeit
Owner: CN=ldap.headwired.com, OU=People, O=Headwired, L=Sydney, ST=NSW, C=AU
Issuer: CN=ldap.headwired.com, OU=People, O=Headwired, L=Sydney, ST=NSW, C=AU
Serial number: 42abc123
Valid from: Mon Jan 04 13:59:00 EST 2010 until: Mon Jan 02 13:59:00 EST 2012
Certificate fingerprints:
         MD5:  CB:43:03:65:C2:C4:3C:26:3X:08:8C:02:D7:88:D5:3B
         SHA1: 8B:89:6E:59:81:16:33:E4:B4:E3:FD:21:A1:EF:75:7E:40:F2:9D:5C
Trust this certificate? [no]:  yes
Certificate was added to keystore

Add SSL options to the LDAP Bind Request

Now you have your certificate database on your controller, you need to alter the LDAP bind request in your JMeter script to add the parameters to use SSL. Note that you must use the JDK that contains the ’cacerts’ certificate database to launch JMeter. JMeter LDAP Bind SSL

LDAP using PHP

Note that this section simply describes the way to get LDAP connections working within PHP for both SSL and non-SSL connections. It is intended that a PHP page that is driven by a load testing tool would act as a bridge to the LDAP server. While the overhead of the HTTP requests between the PHP server and the load generation tool would need to be taken into account, it could potentially provide an alterative for LDAP performance testing. It’s basically a last resort to generating load on an LDAP server.

Note that LDAP Bindings CAN NOT be stored in a session variable, meaning that each page must perform all LDAP commands in one page, rather than having bind.php, search.php, etc. This severely limits the usefulness of such a harness.

LDAP Connect

In PHP, you need to establish a connection to the LDAP server before you can authenticate (Bind). The command to connect to an LDAP server in PHP with the servername “ldap01” on port 389 in PHP is as follows:

// $connection = ldap_connect("Hostname", "port");
$lc = ldap_connect("ldap://ldap01", "389");

After the connection is made, it is possible to set some options to your LDAP connection, such as changing the protocol version to 3.

ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3);

LDAP Bind (Authenticate)

The following command binds the user (login) to the LDAP server. The command to bind user “tester” with password “test01” to the “lc” resource (setup in the LDAP connect) is as follows:

// ldap_bind("Connection Resource","username","password);
$ldapbind = ldap_bind($lc,uid=tester,ou=people,dc=headwired,dc=com","test01");

LDAP Search

The LDAP Search command executes an LDAP search query against the server once the bind has been successful. The filter will depend on what you are trying to retrieve, in the example below it is looking for a record where the record contains the uid of the person (example: ‘tester’) and the object class is a person.

$result = ldap_search($lc,"dc=headwired,dc=com","(&(uid=".$ldap_user.")(objectclass=person))");

LDAP View Search Results

To view the results of the LDAP search, we must loop through the results and get the attribute values. This is useful for debugging only, as this does slow down script execution.

The following command is used to value of the “mail” attribute from the first result found:

$entryid = ldap_first_entry($lc,$result);
$values = ldap_get_values($lc,$entryid,"mail");
print $values[0];
ldap_free_result($entryid);

A more detailed way to get the results from your search is to perform the ldap_get_entries command:

$entries = ldap_get_entries($lc, $result);

This will create an array of your results for you to sort, and print as you see fit.

LDAP Unbind

This command will close the users bind with the LDAP server. This will stop errors relating to maxing out the connection pool. Once a connection is closed a new bind must be made with the server to perform LDAP searches.

ldap_unbind($lc);

Further information about the “ldap” PHP functions can be found in the PHP documentation available at [1].

LDAP using PHP (Over SSL)

SSL Certificates

As we are not using the SSL certificates for authentication, we can tell PHP to just trust everything. This is performed by creating the following file:

C:\openldap\sysconf\ldap.conf

Now we add the trusting part. Oh PHP you naïve little tiger.

TLS_REQCERT never

Add SSL options to the LDAP Bind Request

Now you have your PHP trusting every SSL connection, you need to alter the LDAP connection request in your PHP script to add the parameters to use SSL.

// $connection = ldap_connect("Hostname", "port");
$lc = ldap_connect("ldaps://ssbbsn01", "636");

Notice that the following changes were made

* URL Protocol has changed from ldap:// to ldaps://
* Port has changed from 389 to 636

These are the only changes required to get PHP to use LDAP over SSL. The bind, search and unbind functions operate in the same method as without SSL.

About the Author

Joel Deutscher is an experienced performance test consultant, passionate about continuous improvement. Joel works with Planit's Technical Testing Services as a Principal Consultant in Sydney, Australia. You can read more about Joel on LinkedIn.

1 Comment

  1. Joel Deutscher says:

    When retrieving LDAP calls from a log, its open written as scope=0 rather than Base as used in LoadRunner. The following is a mapping of the the scopre number to name for LoadRunner.

    0 = “Scope=Base”
    1 = “Scope=Onelevel”
    2 = “Scope=Subtree”