
#!/usr/bin/env python2.7
# vim: set fileencoding: utf-8
'''
Test network latency by creating TCP sockets and timing the 3-way handshake
compat-check: 2.6 2.7
notes: expert mode is a work in progress, but not using it shouldn't affect
normal user mode.
NOTES:
* Modified to ensure a DNS lookup is done every time
'''
__AUTHOR__ = 'Peter Downs <padowns@gmail.com>'
__VERSION__ = '0.2.3'
__DATE__ = '2016-02-26'
import platform
__major_version, __minor_version = platform.python_version().split(".")[:2]
from datetime import datetime
import signal
import sys
import os
import socket
import select
import time
import timeit
import string
import getopt
import struct
total_conns = 0
times = []
def signal_handler(signal, frame):
'''
used to catch ctrl-c or SIGINT when the user wants to interrupt and terminate the program
'''
print_statistics()
sys.exit(0)
def checksum(msg):
s = 0
for i in range(0, len(msg), 2):
w = ord(msg[i]) + (ord(msg[i+1]) << 8 )
s = s + w
s = (s>>16) + (s & 0xffff);
s = s + (s >> 16);
s = ~s & 0xffff
return s
def stddev(list):
'''
Let list = a list of numbers
Given a list with len > 0, compute the standard deviation of the data set
'''
if (len(list) > 0):
from math import sqrt
mean = float(sum(list) / len(list))
error = [ (list[i]-mean)**2 for i in range(len(list)) ]
return sqrt(float(sum(error)/len(error)))
else:
return 0
def print_statistics():
global total_conns
global times
print('\n--- tcpping statistics ---')
if (len(times) == 0):
print('No times were recorded.')
return 0
print('%d connections attempted, %d complete, %d failed, %.2f%% success rate, time %f ms') % (total_conns, len(times), total_conns-len(times), float(len(times))/float(total_conns)*100.0, sum(times))
print('rtt min/avg/max/mdev = %f/%f/%f/%f ms') % (min(times), sum(times)/len(times), max(times), stddev(times))
signal.signal(signal.SIGINT, signal_handler)
def usage():
print('tcpping version %s (c)%s %s') % (__VERSION__, __DATE__, __AUTHOR__)
print('usage: tcpping [ -h ] [ -c <count> ] [ -r <rate> ] -d <host> -p <port> [ -m ] [ -s ] [ -t <timeout> ] [ -x ] [ -v ]')
print('')
print(' short options:')
print(' -h - display this help message')
print(' -m - output timestamps with microsecond precision - useful for comparing with tcpdump or strace output')
print(' -s - output source ip and port per connection')
print(' -x - expert mode; require root')
print(' -v - print version and exit')
print('')
print(' long options:')
print(' --help')
print(' --dest | --destination <host>')
print(' --port <port>')
print(' --count <count>')
print(' --rate <rate>')
print(' --microseconds')
print(' --timeout <timeout>')
print(' --expert')
print(' --version')
print('')
print(' host - host or ip address to connect to')
print(' port - port to connect to on <host>')
print(' count - number of connections to try. Default is infinite')
print(' rate - sleep time (in seconds) between connections. Default is 1.')
print(' Use a floating point number for microsecond precision (e.g. 0.200 for every 200ms)')
print(' timeout - connection timeout in seconds')
print(' microseconds - output timestamps with microsecond precision')
print(' expert - enable expert mode; requires root')
print(' version - print version and exit')
print('')
def main(argv):
global total_conns
global times
__EXPERT__ = False
# Defaults
TIMEOUT = None
SRC = None
SRC_FLAG = 0
DEST = None
PORT = None
COUNT = -1 # ping until interrupted
RATE = 1 # ping every second
MS_FLAG = 0
try:
opts, args = getopt.getopt(sys.argv[1:], "c:hd:mp:r:st:xv", ["count=", "help", "dest=", "destination=", "microseconds", "rate=", "port=", "source", "src", "timeout=", "expert", "version"])
except getopt.GetoptError:
usage()
sys.exit(2)
for o, a in opts:
if o in ("-c", "--count"):
COUNT = a
if o in ("-r", "--rate"):
RATE = a
if o in ("-d", "--dest", "--destination"):
DEST = a
if o in ("-m", "--microseconds"):
MS_FLAG = 1
if o in ("-p", "--port"):
PORT = a
if o in ("-s", "--source", "--src"):
SRC_FLAG = 1
if o in ("-t", "--timeout"):
TIMEOUT = float(a)
socket.setdefaulttimeout(TIMEOUT)
if o in ("-h", "--help"):
usage()
sys.exit(1)
if o in ("-x", "--expert"):
__EXPERT__ = True
if o in ("-v", "--version"):
print('tcpping version %s') % (__VERSION__)
sys.exit(0)
if (__EXPERT__ and os.geteuid() != 0):
print('Expert mode requires root privileges.')
sys.exit(1)
if (DEST==None or PORT==None):
usage()
sys.exit(2)
if (float(RATE) < 0.0001):
print('Connection rate should be greater than 0.0001 sec')
sys.exit(3)
if (__EXPERT__):
#Initialize TCP and IP header fields
packet=''
ip_ihl = 5
ip_ver = 4
ip_tos = 0
ip_tot_len = 0
ip_id = 10101
ip_frag_off = 0
ip_ttl = 255
ip_proto = socket.IPPROTO_TCP
ip_check = 0
ip_saddr = socket.inet_aton ( src_ip )
ip_daddr = socket.inet_aton ( dst_ip )
ip_ihl_ver = (ip_ver << 4) + ip_ihl
ip_header = struct.pack('!BBHHHBBH4s4s' , ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr)
tcp_source = 1234 # source port
tcp_dest = PORT
tcp_seq = 454
tcp_ack_seq = 0
tcp_doff = 5 #4 bit field, size of tcp header, 5 * 4 = 20 bytes
#tcp flags
tcp_fin = 0
tcp_syn = 1
tcp_rst = 0
tcp_psh = 0
tcp_ack = 0
tcp_urg = 0
tcp_window = socket.htons (5840) # maximum allowed window size
tcp_check = 0
tcp_urg_ptr = 0
tcp_offset_res = (tcp_doff << 4) + 0
tcp_flags = tcp_fin + (tcp_syn << 1) + (tcp_rst << 2) + (tcp_psh <<3) + (tcp_ack << 4) + (tcp_urg << 5)
tcp_header = struct.pack('!HHLLBBHHH' , tcp_source, tcp_dest, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags, tcp_window, tcp_check, tcp_urg_ptr)
while int(total_conns) != int(COUNT):
#port = socket.getservbyname(PORT)
hostname = socket.gethostname()
src_ip = socket.gethostbyname(hostname)
dst_ip = socket.gethostbyname(DEST)
total_conns += 1
seq = total_conns
src_port = "-1"
dst_port = PORT
if(not __EXPERT__):
if MS_FLAG:
date = datetime.now().strftime("%H:%M:%S.%f")
else:
date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
print('Unable to obtain a socket (%s)') % (msg)
sys.exit(4)
begin_time = time.time()
try:
s.connect((DEST, int(PORT)))
if TIMEOUT != None:
fd=s.fileno()
rlist, wlist, xlist = select.select([],[fd],[],TIMEOUT)
if wlist == []:
raise socket.timeout
except socket.timeout, msg:
end_time = time.time()
connect_time = (end_time - begin_time) * 1000
RESULT = "%f ms Connection Failed (%s)" % (connect_time, msg)
except socket.error, msg:
RESULT = '0ms Connection Failed (%s)' % (msg)
else:
end_time = time.time()
src_ip, src_port = s.getsockname()
dst_ip, dst_port = s.getpeername()
connect_time = (end_time - begin_time) * 1000
if (connect_time == 0.000000):
RESULT = "0.000000 ms Spurious measurement discarded"
else:
times.append(connect_time)
RESULT = "%f ms Connection Succeeded" % (connect_time)
s.shutdown(socket.SHUT_RDWR)
s.close()
if SRC_FLAG:
output_fmt = "%(date)s %(src_ip)s:%(src_port)s %(DEST)s:%(PORT)s (%(dst_ip)s:%(dst_port)s) seq=%(seq)s %(RESULT)s"
else:
output_fmt = "%(date)s %(DEST)s:%(PORT)s (%(dst_ip)s:%(dst_port)s) seq=%(seq)s %(RESULT)s"
print(output_fmt) % vars()
sys.stdout.flush()
time.sleep(float(RATE))
else:
# Open a RAW socket so IP headers can be manipulated directly
try:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
except socket.error, msg:
print('Unable to obtain a socket.')
print('Errno: ' + str(msg[0]) + ' Message: ' + msg[1])
sys.exit(4)
packet = ''
print_statistics()
if __name__ == '__main__':
sys.exit(main(sys.argv))