#!/usr/bin/python ############################################################################## # # # twampy.py # # # # History Change Log: # # # # 1.0 [SW] 2017/08/18 first version # # # # Objective: # # Python implementation of the Two-Way Active Measurement Protocol # # (TWAMP and TWAMP light) as defined in RFC5357. This tool was # # developed to validate the Nokia SR OS TWAMP implementation. # # # # Features supported: # # - unauthenticated mode # # - IPv4 and IPv6 # # - Support for DSCP, Padding, JumboFrames, IMIX # # - Support to set DF flag (don't fragment) # # - Basic Delay, Jitter, Loss statistics (jitter according to RFC1889) # # # # Modes of operation: # # - TWAMP Controller # # combined Control Client, Session Sender # # - TWAMP Control Client # # to run TWAMP light test session sender against TWAMP server # # - TWAMP Test Session Sender # # same as TWAMP light # # - TWAMP light Reflector # # same as TWAMP light # # # # Limitations: # # As there is no hardware based timestamping, latency and jitter values # # measured by twampy are not very precise. DF flag implementation is # # currently not supported on OS X (darwin) and FreeBSD. # # # # Not yet supported: # # - authenticated and encrypted mode # # - sending intervals variation # # - enhanced statistics # # => bining and interim statistics # # => late arrived packets # # => smokeping like graphics # # => median on latency # # => improved jitter (rfc3393, statistical variance formula): # # jitter:=sqrt(SumOf((D[i]-average(D))^2)/ReceivedProbesCount) # # - daemon mode: NETCONF/YANG controlled, ... # # - enhanced failure handling (catch exceptions) # # - per probe time-out for statistics (late arrival) # # - Validation with other operating systems (such as FreeBSD) # # - Support for RFC 5938 Individual Session Control # # - Support for RFC 6038 Reflect Octets Symmetrical Size # # # # License: # # Licensed under the BSD license # # See LICENSE.md delivered with this project for more information. # # # # Author: # # # # Sven Wisotzky # # mail: sven.wisotzky(at)nokia.com # ############################################################################## """ TWAMP validation tool for Python Version 1.0 Copyright (C) 2013-2017 Nokia. All Rights Reserved. """ __title__ = "twampy" __version__ = "1.0" __status__ = "released" __author__ = "Sven Wisotzky" __date__ = "2017 August 18th" ############################################################################# import os import struct import sys import time import socket import logging import binascii import threading import random import argparse import signal import select ############################################################################# if (sys.platform == "win32"): time0 = time.time() - time.clock() if sys.version_info > (3,): long = int # Constants to convert between python timestamps and NTP 8B binary format [RFC1305] TIMEOFFSET = long(2208988800) # Time Difference: 1-JAN-1900 to 1-JAN-1970 ALLBITS = long(0xFFFFFFFF) # To calculate 32bit fraction of the second def now(): if (sys.platform == "win32"): return time.clock() + time0 return time.time() def time_ntp2py(data): """ Convert NTP 8 byte binary format [RFC1305] to python timestamp """ ta, tb = struct.unpack('!2I', data) t = ta - TIMEOFFSET + float(tb) / float(ALLBITS) return t def zeros(nbr): return struct.pack('!%sB' % nbr, *[0 for x in range(nbr)]) def dp(ms): if abs(ms) > 60000: return "%7.1fmin" % float(ms / 60000) if abs(ms) > 10000: return "%7.1fsec" % float(ms / 1000) if abs(ms) > 1000: return "%7.2fsec" % float(ms / 1000) if abs(ms) > 1: return "%8.2fms" % ms return "%8dus" % long(ms * 1000) def parse_addr(addr, default_port=20000): if addr == '': # no address given (default: localhost IPv4 or IPv6) return "", default_port, 0 elif ']:' in addr: # IPv6 address with port ip, port = addr.rsplit(':', 1) return ip.strip('[]'), int(port), 6 elif ']' in addr: # IPv6 address without port return addr.strip('[]'), default_port, 6 elif addr.count(':') > 1: # IPv6 address without port return addr, default_port, 6 elif ':' in addr: # IPv4 address with port ip, port = addr.split(':') return ip, int(port), 4 else: # IPv4 address without port return addr, default_port, 4 ############################################################################# class udpSession(threading.Thread): def __init__(self, addr="", port=20000, tos=0, ttl=64, do_not_fragment=False, ipversion=4): threading.Thread.__init__(self) if ipversion == 6: self.bind6(addr, port, tos, ttl) else: self.bind(addr, port, tos, ttl, do_not_fragment) self.running = True def bind(self, addr, port, tos, ttl, df): log.debug( "bind(addr=%s, port=%d, tos=%d, ttl=%d)", addr, port, tos, ttl) self.socket = socket.socket( socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, tos) self.socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((addr, port)) if df: if (sys.platform == "linux2"): self.socket.setsockopt(socket.SOL_IP, 10, 2) elif (sys.platform == "win32"): self.socket.setsockopt(socket.SOL_IP, 14, 1) elif (sys.platform == "darwin"): log.error("do-not-fragment can not be set on darwin") else: log.error("unsupported OS, ignore do-not-fragment option") else: if (sys.platform == "linux2"): self.socket.setsockopt(socket.SOL_IP, 10, 0) def bind6(self, addr, port, tos, ttl): log.debug( "bind6(addr=%s, port=%d, tos=%d, ttl=%d)", addr, port, tos, ttl) self.socket = socket.socket( socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_TCLASS, tos) self.socket.setsockopt( socket.IPPROTO_IPV6, socket.IPV6_UNICAST_HOPS, ttl) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((addr, port)) log.info("Wait to receive test packets on [%s]:%d", addr, port) def sendto(self, data, address): log.debug("transmit: %s", binascii.hexlify(data)) self.socket.sendto(data, address) def recvfrom(self): data, address = self.socket.recvfrom(9216) log.debug("received: %s", binascii.hexlify(data)) return data, address def stop(self, signum, frame): log.info("SIGINT received: Stop TWL session reflector") self.running = False self.socket.shutdown(socket.SHUT_RDWR) self.socket.close() class twampStatistics(): def __init__(self): self.count = 0 def add(self, delayRT, delayOB, delayIB, rseq, sseq): if self.count == 0: self.minOB = delayOB self.minIB = delayIB self.minRT = delayRT self.maxOB = delayOB self.maxIB = delayIB self.maxRT = delayRT self.sumOB = delayOB self.sumIB = delayIB self.sumRT = delayRT self.lossIB = rseq self.lossOB = sseq - rseq self.jitterOB = 0 self.jitterIB = 0 self.jitterRT = 0 self.lastOB = delayOB self.lastIB = delayIB self.lastRT = delayRT else: self.minOB = min(self.minOB, delayOB) self.minIB = min(self.minIB, delayIB) self.minRT = min(self.minRT, delayRT) self.maxOB = max(self.maxOB, delayOB) self.maxIB = max(self.maxIB, delayIB) self.maxRT = max(self.maxRT, delayRT) self.sumOB += delayOB self.sumIB += delayIB self.sumRT += delayRT self.lossIB = rseq - self.count self.lossOB = sseq - rseq if self.count == 1: self.jitterOB = abs(self.lastOB - delayOB) self.jitterIB = abs(self.lastIB - delayIB) self.jitterRT = abs(self.lastRT - delayRT) else: self.jitterOB = self.jitterOB + \ (abs(self.lastOB - delayOB) - self.jitterOB) / 16 self.jitterIB = self.jitterIB + \ (abs(self.lastIB - delayIB) - self.jitterIB) / 16 self.jitterRT = self.jitterRT + \ (abs(self.lastRT - delayRT) - self.jitterRT) / 16 self.lastOB = delayOB self.lastIB = delayIB self.lastRT = delayRT self.count += 1 def dump(self, total): print("===============================================================================") print("Direction Min Max Avg Jitter Loss") print("-------------------------------------------------------------------------------") if self.count > 0: self.lossRT = total - self.count print(" Outbound: %s %s %s %s %5.1f%%" % (dp(self.minOB), dp(self.maxOB), dp(self.sumOB / self.count), dp(self.jitterOB), 100 * float(self.lossOB) / total)) print(" Inbound: %s %s %s %s %5.1f%%" % (dp(self.minIB), dp(self.maxIB), dp(self.sumIB / self.count), dp(self.jitterIB), 100 * float(self.lossIB) / total)) print(" Roundtrip: %s %s %s %s %5.1f%%" % (dp(self.minRT), dp(self.maxRT), dp(self.sumRT / self.count), dp(self.jitterRT), 100 * float(self.lossRT) / total)) else: print(" NO STATS AVAILABLE (100% loss)") print("-------------------------------------------------------------------------------") print(" Jitter Algorithm [RFC1889]") print("===============================================================================") sys.stdout.flush() ############################################################################# class twampySessionSender(udpSession): def __init__(self, args): # Session Sender / Session Reflector: # get Address, UDP port, IP version from near_end/far_end attributes sip, spt, sipv = parse_addr(args.near_end, 20000) rip, rpt, ripv = parse_addr(args.far_end, 20001) ipversion = 6 if (sipv == 6) or (ripv == 6) else 4 udpSession.__init__(self, sip, spt, args.tos, args.ttl, args.do_not_fragment, ipversion) self.remote_addr = rip self.remote_port = rpt self.interval = float(args.interval) / 1000 self.count = args.count self.stats = twampStatistics() if args.padding != -1: self.padmix = [args.padding] elif ipversion == 6: self.padmix = [0, 0, 0, 0, 0, 0, 0, 514, 514, 514, 514, 1438] else: self.padmix = [8, 8, 8, 8, 8, 8, 8, 534, 534, 534, 534, 1458] def run(self): schedule = now() endtime = schedule + self.count * self.interval + 5 idx = 0 while self.running: while select.select([self.socket], [], [], 0)[0]: t4 = now() data, address = self.recvfrom() if len(data) < 36: log.error("short packet received: %d bytes", len(data)) continue t3 = time_ntp2py(data[4:12]) t2 = time_ntp2py(data[16:24]) t1 = time_ntp2py(data[28:36]) delayRT = max(0, 1000 * (t4 - t1 + t2 - t3)) # round-trip delay delayOB = max(0, 1000 * (t2 - t1)) # out-bound delay delayIB = max(0, 1000 * (t4 - t3)) # in-bound delay rseq = struct.unpack('!I', data[0:4])[0] sseq = struct.unpack('!I', data[24:28])[0] log.info("Reply from %s [rseq=%d sseq=%d rtt=%.2fms outbound=%.2fms inbound=%.2fms]", address[0], rseq, sseq, delayRT, delayOB, delayIB) self.stats.add(delayRT, delayOB, delayIB, rseq, sseq) if sseq + 1 == self.count: log.info("All packets received back") self.running = False t1 = now() if (t1 >= schedule) and (idx < self.count): schedule = schedule + self.interval data = struct.pack('!L2IH', idx, long(TIMEOFFSET + t1), long((t1 - long(t1)) * ALLBITS), 0x3fff) pbytes = zeros(self.padmix[long(len(self.padmix) * random.random())]) self.sendto(data + pbytes, (self.remote_addr, self.remote_port)) log.info("Sent to %s [sseq=%d]", self.remote_addr, idx) idx = idx + 1 if schedule > t1: r, w, e = select.select([self.socket], [], [], schedule - t1) if (t1 > endtime): log.info("Receive timeout for last packet (don't wait anymore)") self.running = False self.stats.dump(idx) class twampySessionReflector(udpSession): def __init__(self, args): addr, port, ipversion = parse_addr(args.near_end, 20001) if args.padding != -1: self.padmix = [args.padding] elif ipversion == 6: self.padmix = [0, 0, 0, 0, 0, 0, 0, 514, 514, 514, 514, 1438] else: self.padmix = [8, 8, 8, 8, 8, 8, 8, 534, 534, 534, 534, 1458] udpSession.__init__(self, addr, port, args.tos, args.ttl, args.do_not_fragment, ipversion) def run(self): index = {} reset = {} while self.running: try: data, address = self.recvfrom() t2 = now() sec = long(TIMEOFFSET + t2) # seconds since 1-JAN-1900 msec = long((t2 - long(t2)) * ALLBITS) # 32bit fraction of the second sseq = struct.unpack('!I', data[0:4])[0] t1 = time_ntp2py(data[4:12]) log.info("Request from %s:%d [sseq=%d outbound=%.2fms]", address[0], address[1], sseq, 1000 * (t2 - t1)) idx = 0 if address not in index.keys(): log.info("set rseq:=0 (new remote address/port)") elif reset[address] < t2: log.info("reset rseq:=0 (session timeout, 30sec)") elif sseq == 0: log.info("reset rseq:=0 (received sseq==0)") else: idx = index[address] rdata = struct.pack('!L2I2H2I', idx, sec, msec, 0x001, 0, sec, msec) pbytes = zeros(self.padmix[long(len(self.padmix) * random.random())]) self.sendto(rdata + data[0:14] + pbytes, address) index[address] = idx + 1 reset[address] = t2 + 30 # timeout is 30sec except Exception as e: log.debug('Exception: %s', str(e)) break log.info("TWL session reflector stopped") class twampyControlClient: def __init__(self, server="", tcp_port=862, tos=0x88, ipversion=4): if ipversion == 6: self.connect6(server, tcp_port, tos) else: self.connect(server, tcp_port, tos) def connect(self, server="", port=862, tos=0x88): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, tos) self.socket.connect((server, port)) def connect6(self, server="", port=862, tos=0x88): self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_TCLASS, tos) self.socket.connect((server, port)) def send(self, data): log.debug("CTRL.TX %s", binascii.hexlify(data)) try: self.socket.send(data) except Exception as e: log.critical('*** Sending data failed: %s', str(e)) def receive(self): data = self.socket.recv(9216) log.debug("CTRL.RX %s (%d bytes)", binascii.hexlify(data), len(data)) return data def close(self): self.socket.close() def connectionSetup(self): log.info("CTRL.RX <<Server Greeting>>") data = self.receive() self.smode = struct.unpack('!I', data[12:16])[0] log.info("TWAMP modes supported: %d", self.smode) if self.smode & 1 == 0: log.critical('*** TWAMPY only supports unauthenticated mode(1)') log.info("CTRL.TX <<Setup Response>>") self.send(struct.pack('!I', 1) + zeros(160)) log.info("CTRL.RX <<Server Start>>") data = self.receive() rval = ord(data[15]) if rval != 0: # TWAMP setup request not accepted by server log.critical("*** ERROR CODE %d in <<Server Start>>", rval) self.nbrSessions = 0 def reqSession(self, sender="", s_port=20001, receiver="", r_port=20002, startTime=0, timeOut=3, dscp=0, padding=0): typeP = dscp << 24 if startTime != 0: startTime += now() + TIMEOFFSET if sender == "": request = struct.pack('!4B L L H H 13L 4ILQ4L', 5, 4, 0, 0, 0, 0, s_port, r_port, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, padding, startTime, 0, timeOut, 0, typeP, 0, 0, 0, 0, 0) elif sender == "::": request = struct.pack('!4B L L H H 13L 4ILQ4L', 5, 6, 0, 0, 0, 0, s_port, r_port, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, padding, startTime, 0, timeOut, 0, typeP, 0, 0, 0, 0, 0) elif ':' in sender: s = socket.inet_pton(socket.AF_INET6, sender) r = socket.inet_pton(socket.AF_INET6, receiver) request = struct.pack('!4B L L H H 16s 16s 4L L 4ILQ4L', 5, 6, 0, 0, 0, 0, s_port, r_port, s, r, 0, 0, 0, 0, padding, startTime, 0, timeOut, 0, typeP, 0, 0, 0, 0, 0) else: s = socket.inet_pton(socket.AF_INET, sender) r = socket.inet_pton(socket.AF_INET, receiver) request = struct.pack('!4B L L H H 16s 16s 4L L 4ILQ4L', 5, 4, 0, 0, 0, 0, s_port, r_port, s, r, 0, 0, 0, 0, padding, startTime, 0, timeOut, 0, typeP, 0, 0, 0, 0, 0) log.info("CTRL.TX <<Request Session>>") self.send(request) log.info("CTRL.RX <<Session Accept>>") data = self.receive() rval = ord(data[0]) if rval != 0: log.critical("ERROR CODE %d in <<Session Accept>>", rval) return False return True def startSessions(self): request = struct.pack('!B', 2) + zeros(31) log.info("CTRL.TX <<Start Sessions>>") self.send(request) log.info("CTRL.RX <<Start Accept>>") self.receive() def stopSessions(self): request = struct.pack('!BBHLQQQ', 3, 0, 0, self.nbrSessions, 0, 0, 0) log.info("CTRL.TX <<Stop Sessions>>") self.send(request) self.nbrSessions = 0 ############################################################################# def twl_responder(args): reflector = twampySessionReflector(args) reflector.setDaemon(True) reflector.setName("twl_responder") reflector.start() signal.signal(signal.SIGINT, reflector.stop) while reflector.isAlive(): time.sleep(0.1) def twl_sender(args): sender = twampySessionSender(args) sender.setDaemon(True) sender.setName("twl_responder") sender.start() signal.signal(signal.SIGINT, sender.stop) while sender.isAlive(): time.sleep(0.1) def twamp_controller(args): # Session Sender / Session Reflector: # get Address, UDP port, IP version from near_end/far_end attributes sip, spt, ipv = parse_addr(args.near_end, 20000) rip, rpt, ipv = parse_addr(args.far_end, 20001) client = twampyControlClient(server=rip, ipversion=ipv) client.connectionSetup() if client.reqSession(s_port=spt, r_port=rpt): client.startSessions() sender = twampySessionSender(args) sender.setDaemon(True) sender.setName("twl_responder") sender.start() signal.signal(signal.SIGINT, sender.stop) while sender.isAlive(): time.sleep(0.1) time.sleep(5) client.stopSessions() def twamp_ctclient(args): # Session Sender / Session Reflector: # get Address, UDP port, IP version from twamp sender/server attributes sip, spt, ipv = parse_addr(args.twl_send, 20000) rip, rpt, ipv = parse_addr(args.twserver, 20001) client = twampyControlClient(server=rip, ipversion=ipv) client.connectionSetup() # if client.reqSession(sender=sip, s_port=spt, receiver=rip, r_port=rpt): if client.reqSession(sender=sip, s_port=spt, receiver="0.0.0.0", r_port=rpt): client.startSessions() while True: time.sleep(0.1) client.stopSessions() ############################################################################# dscpmap = {"be": 0, "cp1": 1, "cp2": 2, "cp3": 3, "cp4": 4, "cp5": 5, "cp6": 6, "cp7": 7, "cs1": 8, "cp9": 9, "af11": 10, "cp11": 11, "af12": 12, "cp13": 13, "af13": 14, "cp15": 15, "cs2": 16, "cp17": 17, "af21": 18, "cp19": 19, "af22": 20, "cp21": 21, "af23": 22, "cp23": 23, "cs3": 24, "cp25": 25, "af31": 26, "cp27": 27, "af32": 28, "cp29": 29, "af33": 30, "cp31": 31, "cs4": 32, "cp33": 33, "af41": 34, "cp35": 35, "af42": 36, "cp37": 37, "af43": 38, "cp39": 39, "cs5": 40, "cp41": 41, "cp42": 42, "cp43": 43, "cp44": 44, "cp45": 45, "ef": 46, "cp47": 47, "nc1": 48, "cp49": 49, "cp50": 50, "cp51": 51, "cp52": 52, "cp53": 53, "cp54": 54, "cp55": 55, "nc2": 56, "cp57": 57, "cp58": 58, "cp59": 59, "cp60": 60, "cp61": 61, "cp62": 62, "cp63": 63} def dscpTable(): print(""" ============================================================ DSCP Mapping ============================================================ DSCP Name DSCP Value TOS (bin) TOS (hex) ------------------------------------------------------------ be 0 0000 0000 00 cp1 1 0000 0100 04 cp2 2 0000 1000 08 cp3 3 0000 1100 0C cp4 4 0001 0000 10 cp5 5 0001 0100 14 cp6 6 0001 1000 18 cp7 7 0001 1100 1C cs1 8 0010 0000 20 cp9 9 0010 0100 24 af11 10 0010 1000 28 cp11 11 0010 1100 2C af12 12 0011 0000 30 cp13 13 0011 0100 34 af13 14 0011 1000 38 cp15 15 0011 1100 3C cs2 16 0100 0000 40 cp17 17 0100 0100 44 af21 18 0100 1000 48 cp19 19 0100 1100 4C af22 20 0101 0000 50 cp21 21 0101 0100 54 af23 22 0101 1000 58 cp23 23 0101 1100 5C cs3 24 0110 0000 60 cp25 25 0110 0100 64 af31 26 0110 1000 68 cp27 27 0110 1100 6C af32 28 0111 0000 70 cp29 29 0111 0100 74 af33 30 0111 1000 78 cp31 31 0111 1100 7C cs4 32 1000 0000 80 cp33 33 1000 0100 84 af41 34 1000 1000 88 cp35 35 1000 1100 8C af42 36 1001 0000 90 cp37 37 1001 0100 94 af43 38 1001 1000 98 cp39 39 1001 1100 9C cs5 40 1010 0000 A0 cp41 41 1010 0100 A4 cp42 42 1010 1000 A8 cp43 43 1010 1100 AC cp44 44 1011 0000 B0 cp45 45 1011 0100 B4 ef 46 1011 1000 B8 cp47 47 1011 1100 BC nc1 48 1100 0000 C0 cp49 49 1100 0100 C4 cp50 50 1100 1000 C8 cp51 51 1100 1100 CC cp52 52 1101 0000 D0 cp53 53 1101 0100 D4 cp54 54 1101 1000 D8 cp55 55 1101 1100 DC nc2 56 1110 0000 E0 cp57 57 1110 0100 E4 cp58 58 1110 1000 E8 cp59 59 1110 1100 EC cp60 60 1111 0000 F0 cp61 61 1111 0100 F4 cp62 62 1111 1000 F8 cp63 63 1111 1100 FC ============================================================""") sys.stdout.flush() ############################################################################# if __name__ == '__main__': debug_parser = argparse.ArgumentParser(add_help=False) debug_options = debug_parser.add_argument_group("Debug Options") debug_options.add_argument('-l', '--logfile', metavar='filename', type=argparse.FileType('wb', 0), default='-', help='Specify the logfile (default: <stdout>)') group = debug_options.add_mutually_exclusive_group() group.add_argument('-q', '--quiet', action='store_true', help='disable logging') group.add_argument('-v', '--verbose', action='store_true', help='enhanced logging') group.add_argument('-d', '--debug', action='store_true', help='extensive logging') ipopt_parser = argparse.ArgumentParser(add_help=False) group = ipopt_parser.add_argument_group("IP socket options") group.add_argument('--tos', metavar='type-of-service', default=0x88, type=int, help='IP TOS value') group.add_argument('--dscp', metavar='dscp-value', help='IP DSCP value') group.add_argument('--ttl', metavar='time-to-live', default=64, type=int, help='[1..128]') group.add_argument('--padding', metavar='bytes', default=0, type=int, help='IP/UDP mtu value') group.add_argument('--do-not-fragment', action='store_true', help='keyword (do-not-fragment)') parser = argparse.ArgumentParser() parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__) subparsers = parser.add_subparsers(help='twampy sub-commands') p_responder = subparsers.add_parser('responder', help='TWL responder', parents=[debug_parser, ipopt_parser]) group = p_responder.add_argument_group("TWL responder options") group.add_argument('near_end', nargs='?', metavar='local-ip:port', default=":20001") group.add_argument('--timer', metavar='value', default=0, type=int, help='TWL session reset') p_sender = subparsers.add_parser('sender', help='TWL sender', parents=[debug_parser, ipopt_parser]) group = p_sender.add_argument_group("TWL sender options") group.add_argument('far_end', nargs='?', metavar='remote-ip:port', default="127.0.0.1:20001") group.add_argument('near_end', nargs='?', metavar='local-ip:port', default=":20000") group.add_argument('-i', '--interval', metavar='msec', default=100, type=int, help="[100,1000]") group.add_argument('-c', '--count', metavar='packets', default=100, type=int, help="[1..9999]") p_control = subparsers.add_parser('controller', help='TWAMP controller', parents=[debug_parser, ipopt_parser]) group = p_control.add_argument_group("TWAMP controller options") group.add_argument('far_end', nargs='?', metavar='remote-ip:port', default="127.0.0.1:20001") group.add_argument('near_end', nargs='?', metavar='local-ip:port', default=":20000") group.add_argument('-i', '--interval', metavar='msec', default=100, type=int, help="[100,1000]") group.add_argument('-c', '--count', metavar='packets', default=100, type=int, help="[1..9999]") p_ctclient = subparsers.add_parser('controlclient', help='TWAMP control client', parents=[debug_parser, ipopt_parser]) group = p_ctclient.add_argument_group("TWAMP control client options") group.add_argument('twl_send', nargs='?', metavar='twamp-sender-ip:port', default="127.0.0.1:20001") group.add_argument('twserver', nargs='?', metavar='twamp-server-ip:port', default=":20000") group.add_argument('-c', '--count', metavar='packets', default=100, type=int, help="[1..9999]") p_dscptab = subparsers.add_parser('dscptable', help='print DSCP table', parents=[debug_parser]) # methods to call p_sender.set_defaults(parseop=True, func=twl_sender) p_control.set_defaults(parseop=True, func=twamp_controller) p_ctclient.set_defaults(parseop=True, func=twamp_ctclient) p_responder.set_defaults(parseop=True, func=twl_responder) p_dscptab.set_defaults(parseop=False, func=dscpTable) ############################################################################# options = parser.parse_args() if not options.parseop: print(options) options.func() exit(-1) ############################################################################# # SETUP logging level: # logging.NOTSET, logging.CRITICAL, logging.ERROR, # logging.WARNING, logging.INFO, logging.DEBUG ############################################################################# if options.quiet: logfile = open(os.devnull, 'a') loghandler = logging.StreamHandler(logfile) loglevel = logging.NOTSET elif options.debug: logformat = '%(asctime)s,%(msecs)-3d %(levelname)-8s %(message)s' timeformat = '%y/%m/%d %H:%M:%S' loghandler = logging.StreamHandler(options.logfile) loghandler.setFormatter(logging.Formatter(logformat, timeformat)) loglevel = logging.DEBUG elif options.verbose: logformat = '%(asctime)s,%(msecs)-3d %(levelname)-8s %(message)s' timeformat = '%y/%m/%d %H:%M:%S' loghandler = logging.StreamHandler(options.logfile) loghandler.setFormatter(logging.Formatter(logformat, timeformat)) loglevel = logging.INFO else: logformat = '%(asctime)s,%(msecs)-3d %(levelname)-8s %(message)s' timeformat = '%y/%m/%d %H:%M:%S' loghandler = logging.StreamHandler(options.logfile) loghandler.setFormatter(logging.Formatter(logformat, timeformat)) loglevel = logging.WARNING log = logging.getLogger("twampy") log.setLevel(loglevel) log.addHandler(loghandler) ############################################################################# if options.dscp: if options.dscp in dscpmap: options.tos = dscpmap[options.dscp] else: parser.error("Invalid DSCP Value '%s'" % options.dscp) options.func(options) # EOF