diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..611e05b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.py[co]
+*~
+*.egg-info
+/dist
+/build
+.pydevproject
+/.settings
\ No newline at end of file
diff --git a/.project b/.project
new file mode 100644
index 0000000..6695946
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ python-ping
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..c0b9980
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,14 @@
+AUTHORS / CONTRIBUTORS (alphabetic order):
+
+ * Cowles, Matthew Dixon -- ftp://ftp.visi.com/users/mdc/ping.py
+ * Diemer, Jens -- http://www.jensdiemer.de
+ * Falatic, Martin -- http://www.falatic.com
+ * Hallman, Chris -- http://cdhallman.blogspot.com
+ * incidence -- https://github.com/incidence
+ * jcborras -- https://github.com/jcborras
+ * Notaras, George -- http://www.g-loaded.eu
+ * Poincheval, Jerome
+ * Sarkhel, Kunal -- https://github.com/techwizrd
+ * Stauffer, Samuel
+ * Zach Ware
+ * zed -- https://github.com/zed
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9cc1267
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,11 @@
+The original code derived from ping.c distributed in Linux's netkit.
+That code is copyright (c) 1989 by The Regents of the University of California.
+That code is in turn derived from code written by Mike Muuss of the
+US Army Ballistic Research Laboratory in December, 1983 and
+placed in the public domain. They have my thanks.
+
+Copyright (c) Matthew Dixon Cowles, .
+Distributable under the terms of the GNU General Public License
+version 2. Provided with no warranties of any sort.
+
+See AUTHORS for complete list of authors and contributors.
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..2cf163c
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include AUTHORS LICENSE MANIFEST.in README.creole
+recursive-exclude * *.pyc
+recursive-exclude * *.pyo
\ No newline at end of file
diff --git a/README b/README
deleted file mode 100644
index 1488af0..0000000
--- a/README
+++ /dev/null
@@ -1,2 +0,0 @@
-Originally from http://svn.pylucid.net/pylucid/CodeSnippets/ping.py
-This version maintained at http://github.com/samuel/python-ping
\ No newline at end of file
diff --git a/README.creole b/README.creole
new file mode 100644
index 0000000..b86e961
--- /dev/null
+++ b/README.creole
@@ -0,0 +1,112 @@
+A pure python ping implementation using raw sockets.
+
+Note that ICMP messages can only be sent from processes running as root
+(in Windows, you must run this script as 'Administrator').
+
+Original Version from [[ftp://ftp.visi.com/users/mdc/ping.py|Matthew Dixon Cowles]]
+
+* copyleft 1989-2011 by the python-ping team, see [[https://github.com/jedie/python-ping/blob/master/AUTHORS|AUTHORS]] for more details.
+* license: GNU GPL v2, see [[https://github.com/jedie/python-ping/blob/master/LICENSE|LICENSE]] for more details.
+
+
+=== usage ===
+
+{{{
+~/python-ping$ sudo ./ping.py google.com
+
+PYTHON-PING google.com (209.85.148.99): 55 data bytes
+64 bytes from google.com (209.85.148.99): icmp_seq=0 ttl=54 time=56.2 ms
+64 bytes from google.com (209.85.148.99): icmp_seq=1 ttl=54 time=55.7 ms
+64 bytes from google.com (209.85.148.99): icmp_seq=2 ttl=54 time=55.5 ms
+
+----google.com PYTHON PING Statistics----
+3 packets transmitted, 3 packets received, 0.0% packet loss
+round-trip (ms) min/avg/max = 55.468/55.795/56.232
+}}}
+
+
+== TODOs ==
+
+* refactor ping.py
+* create a CLI interface
+* add a "suprocess ping", with output parser
+
+
+== contribute ==
+
+[[http://help.github.com/fork-a-repo/|Fork this repo]] on [[https://github.com/jedie/python-ping/|GitHub]] and [[http://help.github.com/send-pull-requests/|send pull requests]]. Thank you.
+
+
+== Revision history ==
+
+==== Oct. 17, 2011 ====
+* [[https://github.com/jedie/python-ping/pull/6|Bugfix if host is unknown]]
+
+==== Oct. 12, 2011 ====
+Merge sources and create a seperate github repository:
+* https://github.com/jedie/python-ping
+
+Add a simple CLI interface.
+
+==== September 12, 2011 ====
+Bugfixes + cleanup by Jens Diemer
+Tested with Ubuntu + Windows 7
+
+==== September 6, 2011 ====
+[[http://www.falatic.com/index.php/39/pinging-with-python|Cleanup by Martin Falatic.]]
+Restored lost comments and docs. Improved functionality: constant time between
+pings, internal times consistently use milliseconds. Clarified annotations
+(e.g., in the checksum routine). Using unsigned data in IP & ICMP header
+pack/unpack unless otherwise necessary. Signal handling. Ping-style output
+formatting and stats.
+
+==== August 3, 2011 ====
+Ported to py3k by Zach Ware. Mostly done by 2to3; also minor changes to
+deal with bytes vs. string changes (no more ord() in checksum() because
+>source_string< is actually bytes, added .encode() to data in
+send_one_ping()). That's about it.
+
+==== March 11, 2010 ====
+changes by Samuel Stauffer:
+replaced time.clock with default_timer which is set to
+time.clock on windows and time.time on other systems.
+
+==== November 8, 2009 ====
+Fixes by [[http://www.g-loaded.eu/2009/10/30/python-ping/|George Notaras]],
+reported by [[http://cdhallman.blogspot.com|Chris Hallman]]:
+
+Improved compatibility with GNU/Linux systems.
+
+Changes in this release:
+
+Re-use time.time() instead of time.clock(). The 2007 implementation
+worked only under Microsoft Windows. Failed on GNU/Linux.
+time.clock() behaves differently under [[http://docs.python.org/library/time.html#time.clock|the two OSes]].
+
+==== May 30, 2007 ====
+little [[http://www.python-forum.de/post-69122.html#69122|rewrite by Jens Diemer]]:
+ * change socket asterisk import to a normal import
+ * replace time.time() with time.clock()
+ * delete "return None" (or change to "return" only)
+ * in checksum() rename "str" to "source_string"
+
+==== December 4, 2000 ====
+Changed the struct.pack() calls to pack the checksum and ID as
+unsigned. My thanks to Jerome Poincheval for the fix.
+
+==== November 22, 1997 ====
+Initial hack. Doesn't do much, but rather than try to guess
+what features I (or others) will want in the future, I've only
+put in what I need now.
+
+==== December 16, 1997 ====
+For some reason, the checksum bytes are in the wrong order when
+this is run under Solaris 2.X for SPARC but it works right under
+Linux x86. Since I don't know just what's wrong, I'll swap the
+bytes always and then do an htons().
+
+== Links ==
+
+| Sourcecode at GitHub | https://github.com/jedie/python-ping |
+| Python Package Index | http://pypi.python.org/pypi/python-ping/ |
+| IRC | [[http://www.pylucid.org/permalink/304/irc-channel|#pylucid on freenode.net]]
diff --git a/ping.py b/ping.py
old mode 100644
new mode 100755
index af41f1e..89cff36
--- a/ping.py
+++ b/ping.py
@@ -1,217 +1,373 @@
#!/usr/bin/env python
+# coding: utf-8
"""
- A pure python ping implementation using raw socket.
+ A pure python ping implementation using raw sockets.
-
- Note that ICMP messages can only be sent from processes running as root.
-
-
- Derived from ping.c distributed in Linux's netkit. That code is
- copyright (c) 1989 by The Regents of the University of California.
- That code is in turn derived from code written by Mike Muuss of the
- US Army Ballistic Research Laboratory in December, 1983 and
- placed in the public domain. They have my thanks.
+ Note that ICMP messages can only be send from processes running as root
+ (in Windows, you must run this script as 'Administrator').
Bugs are naturally mine. I'd be glad to hear about them. There are
- certainly word - size dependenceies here.
-
- Copyright (c) Matthew Dixon Cowles, .
- Distributable under the terms of the GNU General Public License
- version 2. Provided with no warranties of any sort.
-
- Original Version from Matthew Dixon Cowles:
- -> ftp://ftp.visi.com/users/mdc/ping.py
-
- Rewrite by Jens Diemer:
- -> http://www.python-forum.de/post-69122.html#69122
-
-
- Revision history
- ~~~~~~~~~~~~~~~~
-
- March 11, 2010
- changes by Samuel Stauffer:
- - replaced time.clock with default_timer which is set to
- time.clock on windows and time.time on other systems.
-
- May 30, 2007
- little rewrite by Jens Diemer:
- - change socket asterisk import to a normal import
- - replace time.time() with time.clock()
- - delete "return None" (or change to "return" only)
- - in checksum() rename "str" to "source_string"
-
- November 22, 1997
- Initial hack. Doesn't do much, but rather than try to guess
- what features I (or others) will want in the future, I've only
- put in what I need now.
-
- December 16, 1997
- For some reason, the checksum bytes are in the wrong order when
- this is run under Solaris 2.X for SPARC but it works right under
- Linux x86. Since I don't know just what's wrong, I'll swap the
- bytes always and then do an htons().
-
- December 4, 2000
- Changed the struct.pack() calls to pack the checksum and ID as
- unsigned. My thanks to Jerome Poincheval for the fix.
-
-
- Last commit info:
- ~~~~~~~~~~~~~~~~~
- $LastChangedDate: $
- $Rev: $
- $Author: $
+ certainly word - size dependencies here.
+
+ :homepage: https://github.com/jedie/python-ping/
+ :copyleft: 1989-2011 by the python-ping team, see AUTHORS for more details.
+ :license: GNU GPL v2, see LICENSE for more details.
"""
-import os, sys, socket, struct, select, time
+import os
+import select
+import signal
+import socket
+import struct
+import sys
+import time
-if sys.platform == "win32":
+
+if sys.platform.startswith("win32"):
# On Windows, the best timer is time.clock()
default_timer = time.clock
else:
# On most other platforms the best timer is time.time()
default_timer = time.time
-# From /usr/include/linux/icmp.h; your milage may vary.
-ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris.
+# ICMP parameters
+ICMP_ECHOREPLY = 0 # Echo reply (per RFC792)
+ICMP_ECHO = 8 # Echo request (per RFC792)
+ICMP_MAX_RECV = 2048 # Max size of incoming buffer
+
+MAX_SLEEP = 1000
-def checksum(source_string):
+
+def calculate_checksum(source_string):
"""
- I'm not too confident that this is right but testing seems
- to suggest that it gives the same answers as in_cksum in ping.c
+ A port of the functionality of in_cksum() from ping.c
+ Ideally this would act on the string as a series of 16-bit ints (host
+ packed), but this works.
+ Network data is big-endian, hosts are typically little-endian
"""
+ countTo = (int(len(source_string) / 2)) * 2
sum = 0
- countTo = (len(source_string)/2)*2
count = 0
- while count> 16) + (sum & 0xffff)
- sum = sum + (sum >> 16)
- answer = ~sum
- answer = answer & 0xffff
+ sum &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
+ # uses signed ints, but overflow is unlikely in ping)
- # Swap bytes. Bugger me if I know why.
- answer = answer >> 8 | (answer << 8 & 0xff00)
+ sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits
+ sum += (sum >> 16) # Add carry from above (if any)
+ answer = ~sum & 0xffff # Invert and truncate to 16 bits
+ answer = socket.htons(answer)
return answer
-def receive_one_ping(my_socket, ID, timeout):
- """
- receive the ping from the socket.
- """
- timeLeft = timeout
- while True:
- startedSelect = default_timer()
- whatReady = select.select([my_socket], [], [], timeLeft)
- howLongInSelect = (default_timer() - startedSelect)
- if whatReady[0] == []: # Timeout
- return
+def is_valid_ip4_address(addr):
+ parts = addr.split(".")
+ if not len(parts) == 4:
+ return False
+ for part in parts:
+ try:
+ number = int(part)
+ except ValueError:
+ return False
+ if number > 255:
+ return False
+ return True
+
+def to_ip(addr):
+ if is_valid_ip4_address(addr):
+ return addr
+ return socket.gethostbyname(addr)
+
+
+class Ping(object):
+ def __init__(self, destination, timeout=1000, packet_size=55, own_id=None):
+ self.destination = destination
+ self.timeout = timeout
+ self.packet_size = packet_size
+ if own_id is None:
+ self.own_id = os.getpid() & 0xFFFF
+ else:
+ self.own_id = own_id
- timeReceived = default_timer()
- recPacket, addr = my_socket.recvfrom(1024)
- icmpHeader = recPacket[20:28]
- type, code, checksum, packetID, sequence = struct.unpack(
- "bbHHh", icmpHeader
- )
- if packetID == ID:
- bytesInDouble = struct.calcsize("d")
- timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0]
- return timeReceived - timeSent
+ try:
+ # FIXME: Use destination only for display this line here? see: https://github.com/jedie/python-ping/issues/3
+ self.dest_ip = to_ip(self.destination)
+ except socket.gaierror as e:
+ self.print_unknown_host(e)
+ else:
+ self.print_start()
+
+ self.seq_number = 0
+ self.send_count = 0
+ self.receive_count = 0
+ self.min_time = 999999999
+ self.max_time = 0.0
+ self.total_time = 0.0
+
+ #--------------------------------------------------------------------------
- timeLeft = timeLeft - howLongInSelect
- if timeLeft <= 0:
+ def print_start(self):
+ print("\nPYTHON-PING %s (%s): %d data bytes" % (self.destination, self.dest_ip, self.packet_size))
+
+ def print_unknown_host(self, e):
+ print("\nPYTHON-PING: Unknown host: %s (%s)\n" % (self.destination, e.args[1]))
+ sys.exit(-1)
+
+ def print_success(self, delay, ip, packet_size, ip_header, icmp_header):
+ if ip == self.destination:
+ from_info = ip
+ else:
+ from_info = "%s (%s)" % (self.destination, ip)
+
+ print("%d bytes from %s: icmp_seq=%d ttl=%d time=%.1f ms" % (
+ packet_size, from_info, icmp_header["seq_number"], ip_header["ttl"], delay)
+ )
+ #print("IP header: %r" % ip_header)
+ #print("ICMP header: %r" % icmp_header)
+
+ def print_failed(self):
+ print("Request timed out.")
+
+ def print_exit(self):
+ print("\n----%s PYTHON PING Statistics----" % (self.destination))
+
+ lost_count = self.send_count - self.receive_count
+ #print("%i packets lost" % lost_count)
+ lost_rate = float(lost_count) / self.send_count * 100.0
+
+ print("%d packets transmitted, %d packets received, %0.1f%% packet loss" % (
+ self.send_count, self.receive_count, lost_rate
+ ))
+
+ if self.receive_count > 0:
+ print("round-trip (ms) min/avg/max = %0.3f/%0.3f/%0.3f" % (
+ self.min_time, self.total_time / self.receive_count, self.max_time
+ ))
+
+ print("")
+
+ #--------------------------------------------------------------------------
+
+ def signal_handler(self, signum, frame):
+ """
+ Handle print_exit via signals
+ """
+ self.print_exit()
+ print("\n(Terminated with signal %d)\n" % (signum))
+ sys.exit(0)
+
+ def setup_signal_handler(self):
+ signal.signal(signal.SIGINT, self.signal_handler) # Handle Ctrl-C
+ if hasattr(signal, "SIGBREAK"):
+ # Handle Ctrl-Break e.g. under Windows
+ signal.signal(signal.SIGBREAK, self.signal_handler)
+
+ #--------------------------------------------------------------------------
+
+ def header2dict(self, names, struct_format, data):
+ """ unpack the raw received IP and ICMP header informations to a dict """
+ unpacked_data = struct.unpack(struct_format, data)
+ return dict(zip(names, unpacked_data))
+
+ #--------------------------------------------------------------------------
+
+ def run(self, count=None, deadline=None):
+ """
+ send and receive pings in a loop. Stop if count or until deadline.
+ """
+ self.setup_signal_handler()
+
+ while True:
+ delay = self.do()
+
+ self.seq_number += 1
+ if count and self.seq_number >= count:
+ break
+ if deadline and self.total_time >= deadline:
+ break
+
+ if delay == None:
+ delay = 0
+
+ # Pause for the remainder of the MAX_SLEEP period (if applicable)
+ if (MAX_SLEEP > delay):
+ time.sleep((MAX_SLEEP - delay) / 1000.0)
+
+ self.print_exit()
+
+ def do(self):
+ """
+ Send one ICMP ECHO_REQUEST and receive the response until self.timeout
+ """
+ try: # One could use UDP here, but it's obscure
+ current_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
+ except socket.error, (errno, msg):
+ if errno == 1:
+ # Operation not permitted - Add more information to traceback
+ etype, evalue, etb = sys.exc_info()
+ evalue = etype(
+ "%s - Note that ICMP messages can only be send from processes running as root." % evalue
+ )
+ raise etype, evalue, etb
+ raise # raise the original error
+
+ send_time = self.send_one_ping(current_socket)
+ if send_time == None:
return
+ self.send_count += 1
+
+ receive_time, packet_size, ip, ip_header, icmp_header = self.receive_one_ping(current_socket)
+ current_socket.close()
+
+ if receive_time:
+ self.receive_count += 1
+ delay = (receive_time - send_time) * 1000.0
+ self.total_time += delay
+ if self.min_time > delay:
+ self.min_time = delay
+ if self.max_time < delay:
+ self.max_time = delay
+
+ self.print_success(delay, ip, packet_size, ip_header, icmp_header)
+ return delay
+ else:
+ self.print_failed()
+
+ def send_one_ping(self, current_socket):
+ """
+ Send one ICMP ECHO_REQUEST
+ """
+ # Header is type (8), code (8), checksum (16), id (16), sequence (16)
+ checksum = 0
+
+ # Make a dummy header with a 0 checksum.
+ header = struct.pack(
+ "!BBHHH", ICMP_ECHO, 0, checksum, self.own_id, self.seq_number
+ )
+ padBytes = []
+ startVal = 0x42
+ for i in range(startVal, startVal + (self.packet_size)):
+ padBytes += [(i & 0xff)] # Keep chars in the 0-255 range
+ data = bytes(padBytes)
-def send_one_ping(my_socket, dest_addr, ID):
- """
- Send one ping to the given >dest_addr<.
- """
- dest_addr = socket.gethostbyname(dest_addr)
+ # Calculate the checksum on the data and the dummy header.
+ checksum = calculate_checksum(header + data) # Checksum is in network order
- # Header is type (8), code (8), checksum (16), id (16), sequence (16)
- my_checksum = 0
+ # Now that we have the right checksum, we put that in. It's just easier
+ # to make up a new header than to stuff it into the dummy.
+ header = struct.pack(
+ "!BBHHH", ICMP_ECHO, 0, checksum, self.own_id, self.seq_number
+ )
- # Make a dummy heder with a 0 checksum.
- header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1)
- bytesInDouble = struct.calcsize("d")
- data = (192 - bytesInDouble) * "Q"
- data = struct.pack("d", default_timer()) + data
+ packet = header + data
- # Calculate the checksum on the data and the dummy header.
- my_checksum = checksum(header + data)
+ send_time = default_timer()
- # Now that we have the right checksum, we put that in. It's just easier
- # to make up a new header than to stuff it into the dummy.
- header = struct.pack(
- "bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1
- )
- packet = header + data
- my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1
+ try:
+ current_socket.sendto(packet, (self.destination, 1)) # Port number is irrelevant for ICMP
+ except socket.error as e:
+ print("General failure (%s)" % (e.args[1]))
+ current_socket.close()
+ return
+ return send_time
-def do_one(dest_addr, timeout):
- """
- Returns either the delay (in seconds) or none on timeout.
- """
- icmp = socket.getprotobyname("icmp")
- try:
- my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
- except socket.error, (errno, msg):
- if errno == 1:
- # Operation not permitted
- msg = msg + (
- " - Note that ICMP messages can only be sent from processes"
- " running as root."
- )
- raise socket.error(msg)
- raise # raise the original error
+ def receive_one_ping(self, current_socket):
+ """
+ Receive the ping from the socket. timeout = in ms
+ """
+ timeout = self.timeout / 1000.0
- my_ID = os.getpid() & 0xFFFF
+ while True: # Loop while waiting for packet or timeout
+ select_start = default_timer()
+ inputready, outputready, exceptready = select.select([current_socket], [], [], timeout)
+ select_duration = (default_timer() - select_start)
+ if inputready == []: # timeout
+ return None, 0, 0, 0, 0
- send_one_ping(my_socket, dest_addr, my_ID)
- delay = receive_one_ping(my_socket, my_ID, timeout)
+ receive_time = default_timer()
- my_socket.close()
- return delay
+ packet_data, address = current_socket.recvfrom(ICMP_MAX_RECV)
+ icmp_header = self.header2dict(
+ names=[
+ "type", "code", "checksum",
+ "packet_id", "seq_number"
+ ],
+ struct_format="!BBHHH",
+ data=packet_data[20:28]
+ )
-def verbose_ping(dest_addr, timeout = 2, count = 4):
- """
- Send >count< ping to >dest_addr< with the given >timeout< and display
- the result.
- """
- for i in xrange(count):
- print "ping %s..." % dest_addr,
- try:
- delay = do_one(dest_addr, timeout)
- except socket.gaierror, e:
- print "failed. (socket error: '%s')" % e[1]
- break
+ if icmp_header["packet_id"] == self.own_id: # Our packet
+ ip_header = self.header2dict(
+ names=[
+ "version", "type", "length",
+ "id", "flags", "ttl", "protocol",
+ "checksum", "src_ip", "dest_ip"
+ ],
+ struct_format="!BBHHHBBHII",
+ data=packet_data[:20]
+ )
+ packet_size = len(packet_data) - 28
+ ip = socket.inet_ntoa(struct.pack("!I", ip_header["src_ip"]))
+ # XXX: Why not ip = address[0] ???
+ return receive_time, packet_size, ip, ip_header, icmp_header
- if delay == None:
- print "failed. (timeout within %ssec.)" % timeout
- else:
- delay = delay * 1000
- print "get ping in %0.4fms" % delay
- print
+ timeout = timeout - select_duration
+ if timeout <= 0:
+ return None, 0, 0, 0, 0
+
+
+def verbose_ping(hostname, timeout=1000, count=3, packet_size=55):
+ p = Ping(hostname, timeout, packet_size)
+ p.run(count)
if __name__ == '__main__':
- verbose_ping("heise.de")
- verbose_ping("google.com")
- verbose_ping("a-test-url-taht-is-not-available.com")
- verbose_ping("192.168.1.1")
+ # FIXME: Add a real CLI
+ if len(sys.argv) == 1:
+ print "DEMO"
+
+ # These should work:
+ verbose_ping("heise.de")
+ verbose_ping("google.com")
+
+ # Inconsistent on Windows w/ ActivePython (Python 3.2 resolves correctly
+ # to the local host, but 2.7 tries to resolve to the local *gateway*)
+ verbose_ping("localhost")
+
+ # Should fail with 'getaddrinfo print_failed':
+ verbose_ping("foobar_url.foobar")
+
+ # Should fail (timeout), but it depends on the local network:
+ verbose_ping("192.168.255.254")
+
+ # Should fails with 'The requested address is not valid in its context':
+ verbose_ping("0.0.0.0")
+ elif len(sys.argv) == 2:
+ verbose_ping(sys.argv[1])
+ else:
+ print "Error: call ./ping.py domain.tld"
diff --git a/ping_header_info.txt b/ping_header_info.txt
new file mode 100644
index 0000000..371c729
--- /dev/null
+++ b/ping_header_info.txt
@@ -0,0 +1,50 @@
+IP header info from RFC791
+ -> http://tools.ietf.org/html/rfc791)
+
+0 1 2 3
+0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|Version| IHL |Type of Service| Total Length |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Identification |Flags| Fragment Offset |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Time to Live | Protocol | Header Checksum |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Source Address |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Destination Address |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Options | Padding |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+===========================================================================
+ICMP Echo / Echo Reply Message header info from RFC792
+ -> http://tools.ietf.org/html/rfc792
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Code | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identifier | Sequence Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data ...
+ +-+-+-+-+-
+
+===========================================================================
+ICMP parameter info:
+ -> http://www.iana.org/assignments/icmp-parameters/icmp-parameters.xml
+
+===========================================================================
+An example of ping's typical output:
+
+PING heise.de (193.99.144.80): 56 data bytes
+64 bytes from 193.99.144.80: icmp_seq=0 ttl=240 time=127 ms
+64 bytes from 193.99.144.80: icmp_seq=1 ttl=240 time=127 ms
+64 bytes from 193.99.144.80: icmp_seq=2 ttl=240 time=126 ms
+64 bytes from 193.99.144.80: icmp_seq=3 ttl=240 time=126 ms
+64 bytes from 193.99.144.80: icmp_seq=4 ttl=240 time=127 ms
+
+----heise.de PING Statistics----
+5 packets transmitted, 5 packets received, 0.0% packet loss
+round-trip (ms) min/avg/max/med = 126/127/127/127
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..a5ecc8c
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+"""
+ distutils setup
+ ~~~~~~~~~~~~~~~
+
+ :homepage: https://github.com/jedie/python-ping/
+ :copyleft: 1989-2011 by the python-ping team, see AUTHORS for more details.
+ :license: GNU GPL v2, see LICENSE for more details.
+"""
+
+import os
+import subprocess
+import sys
+import time
+import warnings
+
+from setuptools import setup, find_packages, Command
+
+PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__))
+
+
+#VERBOSE = True
+VERBOSE = False
+
+def _error(msg):
+ if VERBOSE:
+ warnings.warn(msg)
+ return ""
+
+def get_version_from_git():
+ try:
+ process = subprocess.Popen(
+ # %ct: committer date, UNIX timestamp
+ ["/usr/bin/git", "log", "--pretty=format:%ct-%h", "-1", "HEAD"],
+ shell=False, cwd=PACKAGE_ROOT,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ )
+ except Exception, err:
+ return _error("Can't get git hash: %s" % err)
+
+ process.wait()
+ returncode = process.returncode
+ if returncode != 0:
+ return _error(
+ "Can't get git hash, returncode was: %r"
+ " - git stdout: %r"
+ " - git stderr: %r"
+ % (returncode, process.stdout.readline(), process.stderr.readline())
+ )
+
+ output = process.stdout.readline().strip()
+ try:
+ raw_timestamp, hash = output.split("-", 1)
+ timestamp = int(raw_timestamp)
+ except Exception, err:
+ return _error("Error in git log output! Output was: %r" % output)
+
+ try:
+ timestamp_formatted = time.strftime("%Y.%m.%d", time.gmtime(timestamp))
+ except Exception, err:
+ return _error("can't convert %r to time string: %s" % (timestamp, err))
+
+ return "%s.%s" % (timestamp_formatted, hash)
+
+
+# convert creole to ReSt on-the-fly, see also:
+# https://code.google.com/p/python-creole/wiki/UseInSetup
+try:
+ from creole.setup_utils import get_long_description
+except ImportError:
+ if "register" in sys.argv or "sdist" in sys.argv or "--long-description" in sys.argv:
+ etype, evalue, etb = sys.exc_info()
+ evalue = etype("%s - Please install python-creole >= v0.8 - e.g.: pip install python-creole" % evalue)
+ raise etype, evalue, etb
+ long_description = None
+else:
+ long_description = get_long_description(PACKAGE_ROOT)
+
+
+def get_authors():
+ authors = []
+ try:
+ f = file(os.path.join(PACKAGE_ROOT, "AUTHORS"), "r")
+ for line in f:
+ if not line.strip().startswith("*"):
+ continue
+ if "--" in line:
+ line = line.split("--", 1)[0]
+ authors.append(line.strip(" *\r\n"))
+ f.close()
+ authors.sort()
+ except Exception, err:
+ authors = "[Error: %s]" % err
+ return authors
+
+
+setup(
+ name='python-ping',
+ version=get_version_from_git(),
+ description='A pure python ICMP ping implementation using raw sockets.',
+ long_description=long_description,
+ author=get_authors(),
+ maintainer="Jens Diemer",
+ maintainer_email="python-ping@jensdiemer.de",
+ url='https://github.com/jedie/python-ping/',
+ keywords="ping icmp network latency",
+ packages=find_packages(),
+ include_package_data=True, # include package data under svn source control
+ zip_safe=False,
+ scripts=["ping.py"],
+ classifiers=[
+ # http://pypi.python.org/pypi?%3Aaction=list_classifiers
+# "Development Status :: 4 - Beta",
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Web Environment",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Education",
+ "Intended Audience :: End Users/Desktop",
+ "License :: OSI Approved :: GNU General Public License (GPL)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Topic :: Internet",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: System :: Networking :: Monitoring",
+ ],
+ test_suite="tests",
+)
diff --git a/tests.py b/tests.py
new file mode 100644
index 0000000..74d6431
--- /dev/null
+++ b/tests.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+"""
+ python-ping unittests
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ Note that ICMP messages can only be send from processes running as root.
+ So you must run this tests also as root, e.g.:
+
+ .../python-ping$ sudo python tests.py
+
+ :homepage: https://github.com/jedie/python-ping/
+ :copyleft: 1989-2011 by the python-ping team, see AUTHORS for more details.
+ :license: GNU GPL v2, see LICENSE for more details.
+"""
+
+import socket
+import unittest
+
+from ping import Ping, is_valid_ip4_address, to_ip
+
+
+class PingTest(Ping):
+ """
+ Used in TestPythonPing for check if print methods are called.
+ This is also a way how to subclass Ping ;)
+ """
+ def __init__(self, *args, **kwargs):
+ self.start_call_count = 0
+ self.unknown_host_call_count = 0
+ self.success_call_count = 0
+ self.failed_call_count = 0
+ self.exit_call_count = 0
+ super(PingTest, self).__init__(*args, **kwargs)
+
+ def print_start(self):
+ self.start_call_count += 1
+
+ def print_unknown_host(self, e):
+ self.unknown_host_call_count += 1
+
+ def print_success(self, delay, ip, packet_size, ip_header, icmp_header):
+ self.success_call_count += 1
+
+ def print_failed(self):
+ self.failed_call_count += 1
+
+ def print_exit(self):
+ self.exit_call_count += 1
+
+
+class TestPythonPing(unittest.TestCase):
+ def testIp4AddrPositives(self):
+ self.assertTrue(is_valid_ip4_address('0.0.0.0'))
+ self.assertTrue(is_valid_ip4_address('1.2.3.4'))
+ self.assertTrue(is_valid_ip4_address('12.34.56.78'))
+ self.assertTrue(is_valid_ip4_address('255.255.255.255'))
+
+ def testIp4AddrNegatives(self):
+ self.assertFalse(is_valid_ip4_address('0.0.0.0.0'))
+ self.assertFalse(is_valid_ip4_address('1.2.3'))
+ self.assertFalse(is_valid_ip4_address('a2.34.56.78'))
+ self.assertFalse(is_valid_ip4_address('255.255.255.256'))
+
+ def testDestAddr1(self):
+ self.assertTrue(is_valid_ip4_address(to_ip('www.wikipedia.org')))
+ self.assertRaises(socket.gaierror, to_ip, ('www.doesntexist.tld'))
+
+ def testDestAddr2(self):
+ self.assertTrue(to_ip('10.10.10.1'))
+ self.assertTrue(to_ip('10.10.010.01'))
+ self.assertTrue(to_ip('10.010.10.1'))
+
+ def test_init_only(self):
+ p = PingTest("www.google.com")
+ self.assertEqual(p.start_call_count, 1)
+ self.assertEqual(p.unknown_host_call_count, 0)
+ self.assertEqual(p.success_call_count, 0)
+ self.assertEqual(p.failed_call_count, 0)
+ self.assertEqual(p.exit_call_count, 0)
+
+ def test_do_one_ping(self):
+ p = PingTest("www.google.com")
+ p.do()
+ self.assertEqual(p.send_count, 1)
+ self.assertEqual(p.receive_count, 1)
+
+ self.assertEqual(p.start_call_count, 1)
+ self.assertEqual(p.unknown_host_call_count, 0)
+ self.assertEqual(p.success_call_count, 1)
+ self.assertEqual(p.failed_call_count, 0)
+ self.assertEqual(p.exit_call_count, 0)
+
+ def test_do_one_failed_ping(self):
+ p = PingTest("www.doesntexist.tld")
+ self.assertEqual(p.start_call_count, 0)
+ self.assertEqual(p.unknown_host_call_count, 1)
+ self.assertEqual(p.success_call_count, 0)
+ self.assertEqual(p.failed_call_count, 0)
+ self.assertEqual(p.exit_call_count, 0)
+
+ def test_run_ping(self):
+ p = PingTest("www.google.com")
+ p.run(count=2)
+ self.assertEqual(p.send_count, 2)
+ self.assertEqual(p.receive_count, 2)
+
+ self.assertEqual(p.start_call_count, 1)
+ self.assertEqual(p.unknown_host_call_count, 0)
+ self.assertEqual(p.success_call_count, 2)
+ self.assertEqual(p.failed_call_count, 0)
+ self.assertEqual(p.exit_call_count, 1)
+
+ def test_run_failed_pings(self):
+ p = PingTest("www.google.com", timeout=0.01)
+ p.run(count=2)
+ self.assertEqual(p.send_count, 2)
+ self.assertEqual(p.receive_count, 0)
+
+ self.assertEqual(p.start_call_count, 1)
+ self.assertEqual(p.unknown_host_call_count, 0)
+ self.assertEqual(p.success_call_count, 0)
+ self.assertEqual(p.failed_call_count, 2)
+ self.assertEqual(p.exit_call_count, 1)
+
+
+if __name__ == '__main__':
+ unittest.main()
+