Create a gist now

Instantly share code, notes, and snippets.

@luizfb /
Last active Sep 29, 2016

A pure python ping implementation

Don't Use This

This fork has been ageing here for a while. The original script was written in 1998, and has been kept alive by a number of people (myself included) who kept doing small tweaks here and there to make it compliant with the latest Python versions. This has been proved by the mere fact that there are at least three variants of this in PyPI, still none of which seem to have improved the overall quality of the code, which quite honestly makes me ashamed of having this on my GitHub.

I had plans on updating this for a while now, given that my latest commits have been noticed to be buggy by the people in the comments, but this codebase is just too old to be saved in my opinion. I thought this would benefit much more from a complete rewrite, especially cause this is GPL Licensed and therefore impossible to use for any serious commercial project. So now I am attempting to do just that.

So what should I use?

If your needs are basic, there is this script right here written by @pyos, which covers the base functionality you should need: Send a request, get a reply, return the delay.

However some people might want something more feature-packed, and that is what my full rewrite aims for. It has completely new features such as a modular set of functions that can be used separately from the script if you wish to, functions that are easier to interface with for things that are used frequently such as a probing function, which determines if the host is up depending on a given threshold. It also does things the other one doesn't and neither does this one, such as actually verifying checksums for corrupted packets or for the data itself, it actually increments the seqn in the request packets, it actually generates identifiers in a way that is not completely idiotic (int((id(timeout) * random()) % 65535)), and many other things. Also, it's BSD Licensed so you can use it for commercial projects however you'd like.

It's still not working, but it's pretty close. If you need an immediate solution, go with pyos' version. If you need features, feel free to annoy me on Twitter until I deliver.

The only reason you should still use this version is if you're still running Python 2. In that case you should probably look at another fork of this (there are plenty) cause mine is broken and I don't intend to fix this legacy nightmare codebase anymore.

Thanks for reading.

#!/usr/bin/env python
A pure Python "ping" implementation, based on a rewrite by Johannes Meyer,
of a script originally by Matthew Dixon Cowles. Which in turn was derived
from "ping.c", distributed in Linux's netkit. The version this was forked
out of can be found here:
The versions this script derived from are licensed under GPL v2, this makes
mandatory that this is licensed under the same terms as well. If it were up
to me I would have made this edit MIT licensed, sorry for not being able to.
I've rewritten nearly everything for enhanced performance and readability,
and removed unnecessary functions (assynchroneous PingQuery and related).
Those of the original comments who still applied to this script were kept.
This new revision is almost fully compliant with Python 3, and packs several
improvements including: Modular importing, better use of operators, overall
code cleanup, separated verbose mode into a dedicated function, added
generator mode to recursive function and others...
A lot was changed on my rewrite, and as far as my tests went it is working
quite beautifully. In any case, bug reports are very much welcome.
Please note that ICMP messages can only be sent by processes ran as root.
from socket import socket, htons, AF_INET, SOCK_RAW, getprotobyname, gethostbyname, error, gaierror
from struct import pack, unpack
from random import random # Possibly switch to os.urandom()
from select import select
from time import time, sleep
from sys import version_info as py_version
# Remove this line to make all functions public, or selectively add needed functions to make them public.
__all__ = ["create_packet", "echo", "recursive"]
# Python 3 doesn't have the xrange function anymore, this line makes it compliant with both v2 and v3.
xrange = range if py_version[0] >= 3 else xrange
ICMP_CODE = getprotobyname("icmp")
ERROR_DESCR = {1: "ICMP messages can only be sent from processes running as root.",
10013: "ICMP messages can only be sent by users or processes with administrator rights."}
def checksum(source_string):
checksum = 0
count_to = len(source_string) & -2
count = 0
while count < count_to:
this_val = ord(source_string[count + 1]) * 256 + ord(source_string[count])
checksum += this_val
checksum &= 0xffffffff # Necessary?
count += 2
if count_to < len(source_string):
checksum += ord(source_string[len(source_string) - 1])
checksum &= 0xffffffff # Necessary?
checksum = (checksum >> 16) + (checksum & 0xffff)
checksum += checksum >> 16
answer = ~checksum
answer &= 0xffff
return answer >> 8 | (answer << 8 & 0xff00)
def create_packet(id):
"""Creates a new echo request packet based on the given "id"."""
# Builds Dummy Header
# Header is type (8), code (8), checksum (16), id (16), sequence (16)
header = pack("bbHHh", ICMP_ECHO_REQUEST, 0, 0, id, 1)
data = 192 * "Q"
# Builds Real Header
header = pack("bbHHh", ICMP_ECHO_REQUEST, 0, htons(checksum(header + data)), id, 1)
return header + data
def response_handler(sock, packet_id, time_sent, timeout):
"""Handles packet response, returning either the delay or timing out (returns "None")."""
while True:
ready = select([sock], [], [], timeout)
if ready[0] == []: # Timeout
time_received = time()
rec_packet, addr = sock.recvfrom(1024)
icmp_header = rec_packet[20:28]
type, code, checksum, rec_id, sequence = unpack("bbHHh", icmp_header)
if rec_id == packet_id:
return time_received - time_sent
timeout -= time_received - time_sent
if timeout <= 0:
def echo(dest_addr, timeout=1):
Sends one ICMP echo request to a given host.
"timeout" can be any integer or float except for negatives and zero.
Returns either the delay (in seconds), or "None" on timeout or an
invalid address, respectively.
sock = socket(AF_INET, SOCK_RAW, ICMP_CODE)
except error as err:
err_num, err_msg = err.args
if err_num in ERROR_DESCR:
raise error(ERROR_DESCR[err_num]) # Operation not permitted
raise error(err_msg)
except gaierror:
packet_id = int((id(timeout) * random()) % 65535)
packet = create_packet(packet_id)
while packet:
# The ICMP protocol does not use a port, but the function
# below expects it, so we just give it a dummy port.
sent = sock.sendto(packet, (dest_addr, 1))
packet = packet[sent:]
delay = response_handler(sock, packet_id, time(), timeout)
return delay
def recursive(dest_addr, count=8, timeout=1, floodlock=1, generator=False):
Pings "dest_addr" "count" times and returns a list of replies or yields
values as they come up if "generator" is True.
"count" is an integer that defines the ammount of requsts to perform.
"timeout" is the number of seconds to wait before dropping request.
"floodlock" regulates the interval between calls to prevent flood
and is set in seconds.
If replied, returns echo delay in seconds. If no response is recorded
"None" is set.
if generator:
for i in xrange(count):
yield echo(dest_addr, timeout)
log = []
for i in xrange(count):
log.append(echo(dest_addr, timeout))
return tuple(log)
def verbose(dest_addr, count=8, timeout=1, floodlock=1, infinite=False):
"""Recursive ping with live feedback. Mind the infinity."""
host = gethostbyname(dest_addr)
print("PING {} ({}): Ammount {}; Timeout {}s".format(dest_addr, gethostbyname(dest_addr), count, timeout))
if infinite:
while True:
reply = echo(dest_addr, timeout)
if reply is None:
print("echo timeout... icmp_seq={}".format(seqn))
print("echo from {}: icmp_seq={} delay={} ms").format(host, seqn, round(reply*1000, 3))
log = []
fail = 0
for seqn in xrange(count):
log.append(echo(dest_addr, timeout))
if log[-1] is None:
print("echo timeout... icmp_seq={}".format(seqn))
fail += 1
print("echo from {}: icmp_seq={} delay={} ms").format(host, seqn, round(log[-1]*1000, 3))
print("sent={} received={} ratio={}%".format(count, count-fail, (float(count-fail) * 100)/count))
print("{} / {} / {} (min/avg/max in ms)".format(round(min(log)*1000, 3),
round(sum([x*1000 for x in log if x is not None])/len(log), 3), round(max(log)*1000, 3)))
# Testing
if __name__ == "__main__":
verbose("", 4, 2)
verbose("", 4, 2)
verbose("", 4, 2)
verbose("", 4, 2)

Since it's derivative work from which is GPL licensed, I don't think you can distribute this on something none other than GPL. I was hoping to find a non-GPL alternative but looks like I have to write one myself.


Line 156 is wrong/broken in Python 2 as you can't return from a generator without yielding.


-        return tuple(log)
+        yield tuple(log)
+        return

Despite having the correct loops and output in place, you're not actually changing the ICMP sequence number. You can see on line 70 that it is fixed to '1' for every packet.

ganluo commented Jul 9, 2015

line 191, in verbose
print("{} / {} / {} (min/avg/max in ms)".format(round(min(log)*1000, 3),
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'

luizfb commented Nov 12, 2015

@chrisirhc @warvariuc @17twenty @daviesjamie @ganluo You guy's might want to check out this update on the Gist. If you're still interested in the project you can help me debug the current version on my rewrite.

Thanks, and sorry for being such a negligent maintainer to this fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment