Konstantine is the Head of QA department at ISS Art. He has over 25 years of experience in the field of IT and Quality Assurance. Konstantine is specialized in a variety of fields including load testing, functional and automated testing, security and other types of testing

Learn JMeter in 5 Hours

Start Learning
Slack

Run massively scalable performance tests on web, mobile, and APIs

Feb 06 2018

Diameter Server Load Testing - A Guide

Diameter is a client/server application level protocol of AAA (authentication, authorization, accounting) type. Just like the Radius protocol, which is also of AAA type, the Diameter protocol services heavy loads of users. Therefore, servers operating over the Diameter protocol have to be load tested before switching to production mode. In this blog post we are going to discuss how to create scripts in Apache JMeter™ to load test services that use the Diameter protocol. To read about load testing the Radius server, click here.

 

In addition to Diameter, the other well known AAA protocols are Radius and TACACS+. Compared to them, the Diameter protocol provides better security, by utilizing TLS or IPsec. The Diameter protocol message also defines more commands, has a wider format of value and attribute pairs (AVP) and both commands and attributes can be extended, by adding new ones. Unlike RADIUS, Diameter uses TCP or SCTP protocols as transport mechanism, whereas RADIUS is known to use UDP protocol as transport mechanism.

 

Diameter also introduces the concept of an application. A Diameter application is a protocol that is an extension to the basic Diameter protocol, defined by a set of Diameter commands with corresponding attribute - value pairs (AVP). The basic Diameter protocol is described in RFC6733. Several examples of Diameter applications are the Diameter Network Access Server application (NAS), the Diameter Credit Control application CCA and the Diameter Extensible Authentication Protocol (EAP) application, described in the corresponding standards. For instance, the Diameter SIP application is described in RFC4740. So if we are going to load test Diameter server, we have to take in account all the applications it supports and design use cases and load testing scripts correspondingly.

 

Diameter Architecture and Flow

 

The architecture of systems operating under the Diameter protocol is of clients processing requests from users and communicating with servers. But Diameter protocol systems may include other specific components, such as relay agents, proxy agents, redirect agents and translation agents. These are usually used in complex architectures with many Diameter servers as well as Diameter and Radius clients. The simplified architecture of a system working under the Diameter protocol is shown in the image below.

 

diameter server load testing

 

In this picture, Diameter clients, the Diameter server and the Diameter proxy agent are shown.

 

  • Users communicate with the Clients by requesting access to a service or resources and by providing information for accounting purposes, like an accounting plan selection.
  • Diameter clients receive users’ requests for a service and send them to the server.
  • The Diameter proxy agent is necessary if requests have to be redirected to other domains. In this image it’s included only for generalization purposes.
  • The communication between the client and the server can be done over the basic Diameter protocol or through a Diameter application with a specific commands set.

 

Building Your Diameter JMeter Script

 

As JMeter is a universal tool, it is the right decision to use it to generate load for Diameter protocol requests. There are no Diameter protocol samplers in the JMeter suite, nor are there any in the JMeter plugins. Therefore, the only way to imitate Diameter requests and process the responses of these requests is to implement them in the code of JSR223 samplers.

 

There are a few open Diameter java libraries available. One of them is used for the demonstration purpose in this blog post. It can be downloaded from here. This library implements the Diameter base protocol and supports some of the widely used Diameter applications, such as credit control application (CCA), online charging (Ro), offline charging (Rf) and others. This library provides the interface for users to interact with Diameter networks and build components in Diameter networks, such as EAP, NAS, Home subscriber server (HSS) and others. By using this library it’s possible to imitate the interaction of Diameter clients, such as NAS, HSS servers or others with the Diameter server, and load them with authentication, authorization, accounting and other requests. In order to work with this library, just drop the jar file to the /lib/ext folder of the JMeter installation directory.

 

Before we start with the implementation of the JMeter script, we need to set up the test bench against which the demonstrated script can be launched. The test bench is the Diameter server. There is a free Diameter server, that supports the basic set of the Diameter commands and several Diameter applications. This Diameter server can be downloaded from here. Instructions for the installation of the free Diameter server for different OS can be found there, too.

 

Besides the free version of the Diameter server, there are other versions, that have an evaluation period. One of them can be downloaded from here.

 

The basic configuration of the free Diameter includes the configuration of the Diameter server name, the realm that the Diameter server serves, protocol settings, TLS settings, list of peers and extensions, among which there are Diameter applications.

 

After the test environment is configured and Diameter java client library is downloaded and compiled, the next step is the creation of the JMeter script.

 

The JMeter script, that implements Diameter authentication and accounting requests is shown in the screenshot below.

 

jmeter load testing of diameter servers

 

The script implements a simple flow - authentication of users and sending a few accounting requests from each authenticated user.

 

1. The first step is to create constants that are used for the protocol initialization and the interaction between the JMeter and Diameter server. These constants are placed to the user defined variables configuration element of JMeter or they can be placed to the user defined variables section of the test plan. The screenshot of the constants is shown below. The constants include: server URL, realm definition, application ID, port, several command codes, and the paths to the configuration files.

 

how to load test diameter

 

2. The second step is configuring your thread group.

 

3. The third is writing the code for JSR223 samplers. The code of the JSR223 samplers that imitates DIAMETER protocol requests, can be written in JAVA, Groovy or other supported programming languages. In this article we will provide an example of JAVA code.

 

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;

import org.jdiameter.api.*;
import org.jdiameter.client.impl.helpers.XMLConfiguration;
import org.jdiameter.server.impl.*;
import org.mobicents.diameter.dictionary.*;

public class TestDiameter implements EventListener<Request, Answer> {
    private static final String client_cfgFile = vars.get("client_cfg");
    private static final String dictionaryFile = vars.get("dictionary_cfg");
    private static final String server_ip = vars.get("serverIP");
    private static final String server_port = vars.get("sport");
    private static final String server_URI = vars.get("serverURI");
    private static final String realmName = vars.get("realm");
    private static final int cmd_code = vars.get("cmdCode");
    private static final long appID = vars.get("customApplicationID");
    private ApplicationId authAppId = ApplicationId.createByAuthAppId(appID);
    private static final long vendorID = vars.get("vendorID");
    private static final int exTypeCode = vars.get("exTypeCode");
    private static final int exDataCode = vars.get("exDataCode");

    private static final int EX_TYPE_INITIAL = 0;
    private static final int EX_TYPE_INTERMEDIATE = 1;
    private static final int EX_TYPE_TERMINATING = 2;
    public static final String[] TO_SEND = new String[] {"Establishiing connection", " ", "Terminating auth session"};

    private AvpDictionary dictionary = AvpDictionary.INSTANCE;
    private Stack stack;
    private SessionFactory sessionFactory;
    private Session session;
    private int toSendIndex = 0;
    public boolean finished = true;

    public void initStack(){
        InputStream is = null;
        log.info("Stack is being initialized...");

        try {

            dictionary.parseDictionary(this.getClass().getResourceAsStream(dictionaryFile));
            log.info("Dictionary has been parsed");
            this.stack = new StackImpl();

            is = this.getClass().getResourceAsStream(client_cfgFile);
            Configuration cfg = new XMLConfiguration(is);
            sessionFactory = stack.init(cfg);

            log.info("Stack configuration has been successfully loaded");

            Set<org.jdiameter.api.ApplicationId> appIds = stack.getMetaData().getLocalPeer().getCommonApplications();
            log.info("Diameter stack supports " + appIds.size() + " applications");

            is.close();

            Network network = stack.unwrap(Network.class);
            network.addNetworkReqListener(new NetworkReqListener() {
                @Override
                public Answer processRequest(Request request) {
                    return null;
                }
            }, this.authAppId);
        }
        catch(Exception e){
            if (this.stack != null)
                this.stack.destroy();
            if (is != null){
                try{
                    is.close();
                }
                catch (Exception ex){
                	ex.printStackTrace();
                }
            }
            vars.put("operation_result","failure");
            return;
        }

        MetaData metaData = stack.getMetaData();
        if ((metaData.getStackType() != StackType.TYPE_SERVER)||(metaData.getMinorVersion() <= 0)){
            stack.destroy();
            log.error("Incorrect driver");
            vars.put("operation_result","failure");
            return;
        }
        try {
            stack.start();
            log.info("Stack is running...");
        }
        catch (Exception e){
            stack.destroy();
            vars.put("operation_result","failure");
            return;
        }
        vars.put("operation_result","success");
        log.info("Stack has been initialized");
    }

    private void start(){
        try{
            try{
                Thread.currentThread().sleep(5000);
            }
            catch (InterruptedException e){
                e.printStackTrace();
            }
            finished = false;
            this.session = this.sessionFactory.getNewSession("System message"+System.currentTimeMillis());
            sendNextRequest(EX_TYPE_INITIAL);
        }
        catch (InternalException e){
            e.printStackTrace();
        }
        catch (OverloadException e){
            e.printStackTrace();
        }
        catch (RouteException e){
            e.printStackTrace();
        }
        catch (IllegalDiameterStateException e){
            e.printStackTrace();
        }
    }

    public boolean finished(){
        return this.finished;
    }

    private void sendNextRequest(int enumType) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
        Request req = this.session.createRequest(cmd_code, authAppId, realmName, server_URI);
        AvpSet requestAvps = req.getAvps();
        Avp exchangeType = requestAvps.addAvp(exTypeCode, (long) enumType, vendorID, true, false, true);
        Avp exchangeData = requestAvps.addAvp(exDataCode, TO_SEND[toSendIndex++], true, false, false);

        this.session.send(req);
    }

    @Override
    public void receivedSuccessMessage(Request req, Answer ans){
        if(ans.getCommandCode() != cmd_code){
            return;
        }

        AvpSet answerAvps = ans.getAvps();
        Avp exchangeType = answerAvps.getAvp(exTypeCode, vendorID);
        Avp exchangeData = answerAvps.getAvp(exDataCode, vendorID);

        Avp resultAvp = ans.getResultCode();
        try {
            if((resultAvp.getUnsigned32() == 5005)||(resultAvp.getUnsigned32() == 5004)){
                this.session.release();
                this.session = null;
                finished = true;
            }
            switch ((int)exchangeType.getUnsigned32()){
                case EX_TYPE_INITIAL:
                    String ansdata = exchangeData.getUTF8String();
                    if (ansdata.equals(TO_SEND[toSendIndex - 1]))
                        sendNextRequest(EX_TYPE_INTERMEDIATE);
                    break;
                case EX_TYPE_INTERMEDIATE:
                    ansdata = exchangeData.getUTF8String();
                    if (ansdata.equals(TO_SEND[toSendIndex - 1]))
                        sendNextRequest(EX_TYPE_TERMINATING);
                    break;
                case EX_TYPE_TERMINATING:
                    ansdata = exchangeData.getUTF8String();
                    if (ansdata.equals(TO_SEND[toSendIndex - 1])){
                        this.session.release();
                        this.session = null;
                        finished = true;
                    }
                    break;
                default:
                    break;
            }
        }
        catch (AvpDataException e){
            e.printStackTrace();
        }
        catch (InternalException e){
            e.printStackTrace();
        }
        catch (IllegalDiameterStateException e){
            e.printStackTrace();
        }
        catch (RouteException e){
            e.printStackTrace();
        }
        catch (OverloadException e){
            e.printStackTrace();
        }
    }

    @Override
    public void timeoutExpired(Request request){

    }
}

TestDiameter diameterClient = new TestDiameter();
diameterClient.initStack();

vars.putObject("dc", diameterClient);

 

The code of the JSR223 samplers implements the following steps:

 

  • Initialization of the Diameter protocol stack, the session factory and the network listener, which processes responses from the diameter server.
  • After the session is established, the authentication request to the Diameter server is sent. If the user is successfully authenticated a few accounting requests follow.
  • Accounting requests are implemented in another JSR223 sampler.

 

4. In order to verify the responses from the Diameter server, we need to add assertions. The JSR223 assertions, which are child elements for JSR223 samplers verify that samplers have returned successful responses. The assertions code is written in JAVA code as well.

 

if (vars.get("operation_result") != "failure"
	Failure == false;
else
	Failure == true;

 

5. To scale up the test, you need to add CSV files with Diameter clients accounts and users credentials to the thread group.

 

6. For debugging purposes, add a listener to the test plan and don’t forget to remove it after the script is debugged and the load to the target Diameter server is ready to be launched.

 

This script demonstrates the simple load testing scenario of Diameter server with authentication and accounting requests. The scenarios of specific implementations can be different, but the approach stays the same.

 

Learn more advanced JMeter from our free JMeter academy.

 

To learn more about BlazeMeter, which enhances JMeter, put your URL or JMX file in the box right here, or request a BlazeMeter demo.

 

Click here to subscribe to our newsletter.

Interested in writing for our Blog?Send us a pitch!

Your email is required to complete the test. If you proceed, your test will be aborted.