Implementing a Python Telnet server to test our development

Introduction

Intraway’s Real-Time Universal Service Activator (RTUSA) can communicate with all kinds of network devices in order to manage their configuration. This is done through different protocols such as Telnet, SNMP, TL1, among others.

One of the problems we are facing is that it’s difficult to replicate lab conditions for all these different kinds of equipment for testing purposes, and using our customers’ production environments is rarely a desirable option (we don’t want someone out there to lose their internet connection because we are testing a new feature!).

You might be thinking that mocking the network devices is the solution. Well… it is, but it’s not as easy as it sounds with all these network protocols involved. Luckily, for Telnet, it’s quite straightforward using Python and telnetsrv.

The solution

Setting up the environment

First, we need to set up our environment. All you have to do is install telnetsrv. So let’s get that out of the way.

$ sudo pip install telnetsrv

After pip is done installing, we can test the installation with a simple telnet server:

from telnetsrv.threaded import TelnetHandler
import SocketServer

#With this you can reuse the address, avoiding the socket TIME_WAIT state
class TelnetServer(SocketServer.TCPServer):
allow_reuse_address = True

server = TelnetServer((“localhost”, 10023), TelnetHandler)

try:
server.serve_forever()
except KeyboardInterrupt:
print “Exiting telnet server”

Now we can connect to our “Network element” with telnet:

$ telnet localhost 10003
Trying 127.0.0.1…
Connected to localhost.
Escape character is ‘^]’.
You have connected to the telnet server.
Telnet Server>

Our brand new network element is now working! It doesn’t do much for now, but we’ll get right on that.

Adding functionalities

First, we need to create a subclass of TelnetHandler that will give our simulated element a bit more personality.
To define a command, just use the command function decorator.

class MyHandler(TelnetHandler):
@command(‘version’)
def version(self, params):
self.writeresponse(“V1.0”)

server = TelnetServer((“localhost”, 10023), MyHandler)

Now if you connect to the server and enter the “version” command you will get the answer defined above.

Telnet Server> version
V1.0

Any number of commands can be added to our new handler. All you need to do is use the decorator again.

class MyLongHandler(TelnetHandler):
@command(command1)
def command1(self, params):
self.writeresponse(“command1”)
@command(command2)
def command2(self, params):
self.writeresponse(“command2”)
(…)
@command(commandN)
def commandN(self, params):
self.writeresponse(“commandN”)

The handler comes with a set of methods to communicate with the client:

#Output
#To send messages to the console without interrupting any current input
self.writemessage( TEXT )
#To emit a line of expected output
self.writeresponse( TEXT )
#To emit error messages
self.writeerror( TEXT )

#Input
#To read from the client, optionally displaying a prompt
self.readline( prompt=TEXT )

User validation

You usually need a user and password to log in to a network element. Adding this to our server is easy, what you need to do is set the attributes authNeedUser and authNeedPass as True and define the function authCallback to execute the authentication logic.

class MyHandler(TelnetHandler):
@command(‘version’)
def version(self, params):
self.writeresponse(“V1.0”)

authNeedUser = True
authNeedPass = True

def authCallback(self, username, password):
if username != “admin” or password != “admin”: #Security comes first
self.writeresponse(“Invalid username/password”)
raise Exception()

Customizing the prompt messages

This may sound as a minor feature but it’s quite important for us. As we communicate automatically with the network elements, we need to parse the output and recognize the prompts to know, for example, when the device is ready to receive input.
Changing the welcome message and the prompt is done by redefining some class attributes.

class MyHandler(TelnetHandler):

PROMPT = “NE_01#”
WELCOME = “Welcome!”

Unfortunately, the login and password prompts can’t be changed this way but it’s nothing that a little hack can’t solve. You will need to modify the TelnetHandlerBase from which our handler inherits the information. It’s defined in the file telnetsrvlib.py which is located in the telnetsrv library installation directory (in my case it’s in /usr/local/lib/python2.7/dist-packages/telnetsrv/ ).
Add to the TelnetHandlerBase two attributes with the default login and password prompts that you like and then use them in the authentication_ok method.

class TelnetHandlerBase(SocketServer.BaseRequestHandler):

….

#Login prompt
LOGIN_PROMPT = “Username: ”
#Password prompt
PASSWORD_PROMPT = “Password: ”

def authentication_ok(self):
….
if self.authNeedUser:
username = self.readline(prompt=self.LOGIN_PROMPT, use_history=False)
if self.authNeedPass:
password = self.readline(echo=False, prompt=self.PASSWORD_PROMPT, use_history=False)
….

After that, you can set new values for the login and password messages the same way we just did with the welcome message and the prompt.

Conclusion

Sometimes testing can be complicated because of the difficulty to simulate the environments needed. But you shouldn’t be discouraged by this. In fact, see it as an opportunity. We explored new possibilities to circumvent the problem of using real life Network Elements to test new implementations without involving the client. Telnetsrv is a simple solution to a complex problem.
Our intention was not only to show a solution to a particular problem that may or may not be useful for you but also to give an insight on how we work: when something looks hard, try harder to find a way.

You may also like

Jmeter

API testing with JMeter – Advantages

chaos engineering

Chaos Engineering

API Testing

API Testing Comes First

Menu