UDP IPv4/IPv6 Delay Measurements

The blog hasn’t seen a true technical post for some time. But, bad weather, a cup of morning coffee and the expectation of all the great things I will do with my Raspberry Pi when it comes, got me moving.

The number one idea I have in mind is to make a portable, tiny network test device. But will talk about that later…

For now – a simple Python app that sends, reflects and receives UDP packets. It is not a TWAMP implementation of course, but not that bad either. The whole fun in the end is the creation process.

In my tests, host processing delays were ~100 microseconds – which seems pretty accurate. Based on some simple socket programming. Of course, both IPv4 and IPv6 are supported :))

Details:

The picture is self-explanatory.

Data carried by UDP contains the Time (accuracy tested on Linux only) and the packet number.

The reflector part receives this data and if reflection is enabled sends it back. If not, simply reads the data and outputs it to a CSV file for processing later. The correct measurements in this situation really depends on the accuracy and sync of time on both machines. A risky business…

However, if reflection is enabled, the packet is sent back to the sender with unaltered data. After parsing the content the Receiver can extract the delay based on the same clock data that was used to send the packet. This shall be pretty accurate.

On the receiver side, the number of packets expected to be sent and the number of packets received will be counted for statistics.
On both ends the delays, packet numbers and time of the day will be written to CSV. Packet loss under this condition can be easily figured out.

As mentioned before, IPv4 and IPv6 are supported. On top, datagram size and packet rate is configurable as well.

How To Use:

Copy UDP_Client_Server.py and UDP_Reflector.py

On one machine open UDP_Client_Server.py, change the IP addresses, ports, packet size, etc.
On the other machine do the same with the UDP_Reflector.py

Start UDP_Reflector.py first: python UDP_Reflector.py
Start UDP_Client_Server.py: python UDP_Client_Server.py

Watch for the folder – the CSV file will soon appear.

Stop with CTRL+C

If a socket bind error is displayed do a lsof -n -i | grep yourport and make sure the ports you are trying to use are not used by another app.

Enjoy 🙂

Code:

UDP_Client_Server.py

import socket
import time
import sys
from threading import Thread

#DEFINE INPUTS HERE
#CLIENT - SENDER
UDP_DEST_IP='::1' #IP ADDRESS TO SEND DATAGRAMS TO (v4 or v6)
UDP_DEST_PORT=15005 #IP PORT TO SEND DATAGRAMS TO
PACKET_SIZE = 200 #DATAGRAM SIZE IN BYTES
NR_OF_PACKETS=10 #TOTAL NR. OF PACKETS TO SEND
PACKETS_PER_SEC=100 #PACKETS PER SECOND

#CLIENT - RECEIVER
UDP_RECEIVE_IP = '::1' #IP ADDRESS TO LISTEN FOR INCOMMING PACKETS (v4 or v6)
UDP_RECEIVE_PORT=55555 #IP PORT TO LISTEN FOR INCOMMING PACKETS
BUFFER = 4096

#CLIENT-RECEIVER PART
def udp_client_receive(UDP_RECEIVE_IP, UDP_RECEIVE_PORT):
ADDR = (UDP_RECEIVE_IP, UDP_RECEIVE_PORT)

#MAKE A DUMB IP VERSION CHECK
if ':' in UDP_RECEIVE_IP:
rcv_sock = socket.socket( socket.AF_INET6, socket.SOCK_DGRAM )
else:
rcv_sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )

global packet_count_rcvd
global cumulative_delay
packet_count_rcvd = 0
cumulative_delay=0.

try:
rcv_sock.bind(ADDR)
print 'Server Listening on', ADDR
except Exception:
print '***ERROR: Server Port Binding Failed'

#FIRE UP THE LISTENER ENGINES
while True:
data, addr = rcv_sock.recvfrom( BUFFER )
splitdata = data.split(',')
timecount = splitdata[0].strip("('")
rt_delay = (time.time() - float(timecount))
packet_number = str(splitdata[1].strip("' '"))
packet_number = packet_number.lstrip('0')
#WRITE TO FILE AND DO PACKET COUNT
outfile = open("udp_twoway_results.csv", "a").write(str(time.ctime()+','+'received , '+ packet_number+' , '+str(rt_delay)+'\n'))
print (time.ctime()+','+'received , '+ packet_number+' , '+str(rt_delay))
packet_count_rcvd=packet_count_rcvd+1
cumulative_delay=cumulative_delay+rt_delay

#CLIENT SERVER SIDE
def udp_client_send(UDP_DEST_IP, UDP_DEST_PORT, PACKET_SIZE, NR_OF_PACKETS, PACKETS_PER_SEC):

inter_departure_time = 1./PACKETS_PER_SEC
packet_count_snd=0

print "UDP Client Started"
print "UDP target IP:", UDP_DEST_IP
print "UDP target port:", UDP_DEST_PORT
print "UDP Packets to Send:", NR_OF_PACKETS

#IF IPv6
if ':' in UDP_DEST_IP:
if PACKET_SIZE > 97: #BUILD DATAGRAM OF DESIRED SIZE
padding=''
for j in range (98, PACKET_SIZE):
padding = padding+str(1)
for i in range (1,NR_OF_PACKETS+1): #SEND SPECIFIED NUMBER OF PACKETS
time.sleep(inter_departure_time )
snd_sock6 = socket.socket( socket.AF_INET6,socket.SOCK_DGRAM )
snd_sock6.sendto(str(("%.5f" % time.time(),str('%08d' % i), padding)), (UDP_DEST_IP, UDP_DEST_PORT) )
packet_count_snd = packet_count_snd+1

#IF NOT IPv6
else:
if PACKET_SIZE > 77:
padding=''
for j in range (78, PACKET_SIZE):
padding = padding+str(1)
for i in range (1,NR_OF_PACKETS+1):
time.sleep(inter_departure_time)
snd_sock = socket.socket( socket.AF_INET,socket.SOCK_DGRAM )
snd_sock.sendto(str(("%.5f" % time.time(),str('%08d' % i), padding)), (UDP_DEST_IP, UDP_DEST_PORT) )
packet_count_snd = packet_count_snd+1

#WAIT 5SEC FOR ALL PACKETS TO ARRIVE
time.sleep(5)
PLR = 100 - ((packet_count_rcvd*100.)/packet_count_snd)

print '\n', packet_count_snd, 'packets sent'
print packet_count_rcvd, 'packets received'
print 'packet loss ratio = ', round(PLR, 3), '%'

if packet_count_rcvd == 0:
pass
else:
print 'average rtt = ', cumulative_delay/packet_count_rcvd

#START THE THREADS FOR SENDER AND RECEIVER
if __name__ == "__main__":
receiver_thread = Thread(target=udp_client_receive, args=(UDP_RECEIVE_IP, UDP_RECEIVE_PORT))
receiver_thread.daemon=True
receiver_thread.start()
time.sleep(1)
sender_thread = Thread(target=udp_client_send, args=(UDP_DEST_IP, UDP_DEST_PORT, PACKET_SIZE, NR_OF_PACKETS, PACKETS_PER_SEC)).start()

UDP_Reflector.py


import socket
import time
import sys

#DEFINE INPUTS HERE
REFLECTOR_HOST = '::1' #IP ADDRESS TO LISTEN FOR INCOMMING PACKETS (v4 or v6)
REFLECTOR_PORT = 15005 #IP PORT TO LISTEN FOR INCOMMING PACKETS
REMOTE_PORT = 55555 #REMOTE PORT TO REFLECT PACKETS TO
REFLECT_SWITCH = 1 #REFLECTION ENABLED:1 (TWO-WAY DELAY), REFLECTION DISABLED:0 (ONE-WAY DELAY)
BUFFER = 4096

ADDR = (REFLECTOR_HOST, REFLECTOR_PORT)

#DUMB CHECK OF IP ADDRESS VERSION
if ':' in REFLECTOR_HOST:
EchoServer = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
else:
EchoServer = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

#BINDING, PROCESSING AND WRITING TO CSV
try:
EchoServer.bind(ADDR)
print 'echo server started on port', ADDR
except Exception:
print '***ERROR: Port Binding Failed'

while True:
data, addr = EchoServer.recvfrom(BUFFER)
addlst=addr[0],REMOTE_PORT

if REFLECT_SWITCH == 1:
EchoServer.sendto('%s' % (data), addlst)

splitdata = data.split(',')
timecount = splitdata[0].strip("('")
one_way_delay = (time.time() - float(timecount))
packet_number = str(splitdata[1].strip("' '"))
packet_number = packet_number.lstrip('0')

outfile = open("udp_oneway_results.csv", "a").write(str(time.ctime()+','+'received , '+ packet_number+' , '+str(one_way_delay)+'\n'))
print (time.ctime()+','+'received , '+ packet_number+' , '+str(one_way_delay))

EchoServer.close()


Advertisements

One thought on “UDP IPv4/IPv6 Delay Measurements

  1. Hello,
    Very interesting stuff, but I’m having problems with the code indentation when I copy and paste.
    Regards,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s