Measure the latency

[Description]

    Based on Monitoring latency with OpenFlow, I will how to measure the latency in the POX controller. The example topology is shown as below.

 

 

where C is controller, S0 and S1 are switches, and H0 and H1 are hosts. Due to some reasons, e.g. varying traffic load, the delay from S0 to S1 may vary. So the controller should try to measure the delay and decide whether to route the traffic from S0 to S1 or change to other path. So the controller may instruct the S0 to send the probe packet to S1 and then forward back to C. Then the controller can get the delay from S0 to S1 (T3). The total time consumed is Ttotal=T1+T3+T2. T1 = 0.5 * ( Tb – Ta), where Ta is the time when sending out port_stats_request packet and Tb is the time when receiving port_stats_received packet. Similarly, the same method can be applied to get T2. As a consequence, T3 = Ttotal – T1 – T2.

 

[Simulation Script]

 

Generate simulated topology: Mininet11.py

#!/usr/bin/python

 

from mininet.net import Mininet

from mininet.node import Node

from mininet.link import TCLink

from mininet.log import  setLogLevel, info

from threading import Timer

from mininet.util import quietRun

from time import sleep

 

def myNet(cname='controller', cargs='-v ptcp:'):

    "Create network from scratch using Open vSwitch."

    info( "*** Creating nodes\n" )

    controller = Node( 'c0', inNamespace=False )

    switch = Node( 's0', inNamespace=False )

    switch1 = Node( 's1', inNamespace=False )

    h0 = Node( 'h0' )

    h1 = Node( 'h1' )

 

    info( "*** Creating links\n" )

    linkopts0=dict(bw=100, delay='1ms', loss=0)

    linkopts1=dict(bw=100, delay='10ms', loss=0)

    link0=TCLink( h0, switch, **linkopts0)

    #initially, the delay from switch to switch1 is 10ms

    link1 = TCLink( switch, switch1, **linkopts1)   

    link2 = TCLink( h1, switch1, **linkopts0)

 

    #print link0.intf1, link0.intf2

    link0.intf2.setMAC("0:0:0:0:0:1")

    link1.intf1.setMAC("0:0:0:0:0:2")

    link1.intf2.setMAC("0:1:0:0:0:1")

    link2.intf2.setMAC("0:1:0:0:0:2")

 

    info( "*** Configuring hosts\n" )

    h0.setIP( '192.168.123.1/24' )

    h1.setIP( '192.168.123.2/24' )

    h0.setMAC("a:a:a:a:a:a")

    h1.setMAC("8:8:8:8:8:8")

 

    info( "*** Starting network using Open vSwitch\n" )

    switch.cmd( 'ovs-vsctl del-br dp0' )

    switch.cmd( 'ovs-vsctl add-br dp0' )

    switch1.cmd( 'ovs-vsctl del-br dp1' )

    switch1.cmd( 'ovs-vsctl add-br dp1' )

 

    controller.cmd( cname + ' ' + cargs + '&' )   

    for intf in switch.intfs.values():

        print intf

        print switch.cmd( 'ovs-vsctl add-port dp0 %s' % intf )

 

    for intf in switch1.intfs.values():

        print intf

        print switch1.cmd( 'ovs-vsctl add-port dp1 %s' % intf )

 

    # Note: controller and switch are in root namespace, and we

    # can connect via loopback interface

    switch.cmd( 'ovs-vsctl set-controller dp0 tcp:127.0.0.1:6633' )

    switch1.cmd( 'ovs-vsctl set-controller dp1 tcp:127.0.0.1:6633' )

 

    info( '*** Waiting for switch to connect to controller' )

    while 'is_connected' not in quietRun( 'ovs-vsctl show' ):

        sleep( 1 )

        info( '.' )

    info( '\n' )

 

    def cDelay1():

       switch.cmdPrint('ethtool -K s0-eth1 gro off')

       switch.cmdPrint('tc qdisc del dev s0-eth1 root')

       switch.cmdPrint('tc qdisc add dev s0-eth1 root handle 10: netem delay 50ms')

       switch1.cmdPrint('ethtool -K s1-eth0 gro off')

       switch1.cmdPrint('tc qdisc del dev s1-eth0 root')

       switch1.cmdPrint('tc qdisc add dev s1-eth0 root handle 10: netem delay 50ms')

 

    def cDelay2():

       switch.cmdPrint('ethtool -K s0-eth1 gro off')

       switch.cmdPrint('tc qdisc del dev s0-eth1 root')

       switch.cmdPrint('tc qdisc add dev s0-eth1 root handle 10: netem delay 200ms')

       switch1.cmdPrint('ethtool -K s1-eth0 gro off')

       switch1.cmdPrint('tc qdisc del dev s1-eth0 root')

       switch1.cmdPrint('tc qdisc add dev s1-eth0 root handle 10: netem delay 200ms')

 

    # 15 seconds later, the delay from switch to switch 1 will change to 50ms

    t1=Timer(15, cDelay1)

    t1.start()

    # 30 seconds later, the delay from switch to switch 1 will change to 200ms

    t2=Timer(30,cDelay2)

    t2.start()            

 

    #info( "*** Running test\n" )

    h0.cmdPrint( 'ping -i 1 -c 45 ' + h1.IP() )

    sleep( 1 ) 

    info( "*** Stopping network\n" )

    controller.cmd( 'kill %' + cname )

    switch.cmd( 'ovs-vsctl del-br dp0' )

    switch.deleteIntfs()

    switch1.cmd( 'ovs-vsctl del-br dp1' )

    switch1.deleteIntfs()

    info( '\n' )

 

if __name__ == '__main__':

    setLogLevel( 'info' )

    info( '*** Scratch network demo (kernel datapath)\n' )

    Mininet.init()

    myNet()

 

 

Controller program: measure_delay.py

from pox.core import core

from pox.lib.util import dpidToStr

import pox.openflow.libopenflow_01 as of

from pox.lib.addresses import IPAddr, EthAddr

import pox.lib.packet as pkt

from pox.openflow.of_json import *

from pox.lib.recoco import Timer

import time

from pox.lib.packet.packet_base import packet_base

from pox.lib.packet.packet_utils import *

import struct

 

log = core.getLogger()

 

#global variables

start_time = 0.0

sent_time1=0.0

sent_time2=0.0

received_time1 = 0.0

received_time2 = 0.0

src_dpid=0

dst_dpid=0

mytimer = 0

OWD1=0.0

OWD2=0.0

 

#probe protocol, only timestamp field

class myproto(packet_base):

  "My Protocol packet struct"

 

  def __init__(self):

     packet_base.__init__(self)

     self.timestamp=0

 

  def hdr(self, payload):

     return struct.pack('!I', self.timestamp)

 

def _handle_ConnectionDown (event):

  global mytimer

  print "ConnectionDown: ", dpidToStr(event.connection.dpid)

  mytimer.cancel()

  

def _handle_ConnectionUp (event):

  global src_dpid, dst_dpid, mytimer

  print "ConnectionUp: ", dpidToStr(event.connection.dpid)

 

  #remember the connection dpid for switch to controller (src_dpid) and switch1 to controller(dst_dpid)

  for m in event.connection.features.ports:

    if m.name == "s0-eth0":

      src_dpid = event.connection.dpid

    elif m.name == "s1-eth0":

      dst_dpid = event.connection.dpid

 

  # when the controller knows both src_dpid and dst_dpid, the probe packet is sent out every 2 seconds

  if src_dpid<>0 and dst_dpid<>0:

    mytimer=Timer(2, _timer_func, recurring=True)

    mytimer.start()

 

def _handle_portstats_received (event):

   global start_time, sent_time1, sent_time2, received_time1, received_time2, src_dpid, dst_dpid,OWD1,OWD2

 

   received_time = time.time() * 1000 - start_time

   #measure T1

   if event.connection.dpid == src_dpid:

     OWD1=0.5*(received_time - sent_time1)

     #print "OWD1: ", OWD1, "ms"

   #measure T2

   elif event.connection.dpid == dst_dpid:

     OWD2=0.5*(received_time - sent_time1)

     #print "OWD2: ", OWD2, "ms"

 

def _handle_PacketIn (event):

  global start_time,OWD1,OWD2

  packet = event.parsed

  #print packet

 

  received_time = time.time() * 1000 - start_time

  if packet.type==0x5577 and event.connection.dpid==dst_dpid:

    c=packet.find('ethernet').payload

    d,=struct.unpack('!I', c)

    print "delay:", received_time - d - OWD1-OWD2, "ms"

   

  a=packet.find('ipv4')

  b=packet.find('arp')

  if a:

    #print "IPv4 Packet:", packet

    msg = of.ofp_flow_mod()

    msg.priority =1

    msg.idle_timeout = 0

    msg.match.in_port =1

    msg.match.dl_type=0x0800

    msg.actions.append(of.ofp_action_output(port = 2))

    event.connection.send(msg)

  

    msg = of.ofp_flow_mod()

    msg.priority =1

    msg.idle_timeout = 0

    msg.match.in_port =2

    msg.match.dl_type=0x0800

    msg.actions.append(of.ofp_action_output(port = 1))

    event.connection.send(msg)   

 

  if b and b.opcode==1:

    #print "ARP Request Packet:", packet

    msg = of.ofp_flow_mod()

    msg.priority =1

    msg.idle_timeout = 0

    msg.match.in_port =1

    msg.match.dl_type=0x0806

    msg.actions.append(of.ofp_action_output(port = 2))

    if event.connection.dpid == src_dpid:

      #print "send to switch"

      event.connection.send(msg)

    elif event.connection.dpid == dst_dpid:

      #print "send to switch1"

      event.connection.send(msg)

 

  if b and b.opcode==2:

    #print "ARP Reply Packet:", packet

    msg = of.ofp_flow_mod()

    msg.priority =1

    msg.idle_timeout = 0

    msg.match.in_port =2

    msg.match.dl_type=0x0806

    msg.actions.append(of.ofp_action_output(port = 1))

    if event.connection.dpid == src_dpid:

      #print "send to switch"

      event.connection.send(msg)

    elif event.connection.dpid == dst_dpid:

      #print "send to switch1"

      event.connection.send(msg)

 

def _timer_func ():

  global start_time, sent_time1, sent_time2, src_dpid, dst_dpid

 

  if src_dpid <>0:

    sent_time1=time.time() * 1000 - start_time

    #print "sent_time1:", sent_time1

    #send out port_stats_request packet through src_dpid

    core.openflow.getConnection(src_dpid).send(of.ofp_stats_request(body=of.ofp_port_stats_request()))

   

    f = myproto()

    f.timestamp = int(time.time()*1000 - start_time)

    #print f.timestamp

    e = pkt.ethernet()

    e.src=EthAddr("0:0:0:0:0:2")

    e.dst=EthAddr("0:1:0:0:0:1")

    e.type=0x5577

    e.payload = f

    msg = of.ofp_packet_out()

    msg.data = e.pack()

    msg.actions.append(of.ofp_action_output(port=2))

    core.openflow.getConnection(src_dpid).send(msg)

   

  if dst_dpid <>0:

     sent_time2=time.time() * 1000 - start_time

     #print "sent_time2:", sent_time2

     #send out port_stats_request packet through dst_dpid

     core.openflow.getConnection(dst_dpid).send(of.ofp_stats_request(body=of.ofp_port_stats_request()))

  

def launch ():

  global start_time

  start_time = time.time() * 1000

  print "start_time:", start_time

  core.openflow.addListenerByName("ConnectionUp", _handle_ConnectionUp)

  core.openflow.addListenerByName("ConnectionDown", _handle_ConnectionDown)

  core.openflow.addListenerByName("PortStatsReceived",

    _handle_portstats_received)

  core.openflow.addListenerByName("PacketIn",

    _handle_PacketIn) 

 

[Results]

From the ping results, we can see the RTT varying from H0 to H1.

1

 

2

34

 

From the controller results, we can also see the delay varying from H0 to H1.

5

6

 

Dr. Chih-Heng Ke (smallko@gmail.com)

Department of Computer Science and Information Engineering,

National Quemoy University, Kinmen, Taiwan.