|
|
@@ -1,364 +1,364 @@ |
|
|
#!/usr/bin/env python |
|
|
|
|
|
#!/usr/bin/env python |
|
|
|
|
|
""" |
|
|
A pure python ping implementation using raw socket. |
|
|
|
|
|
|
|
|
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. |
|
|
|
|
|
Bugs are naturally mine. I'd be glad to hear about them. There are |
|
|
certainly word - size dependenceies here. |
|
|
|
|
|
Copyright (c) Matthew Dixon Cowles, <http://www.visi.com/~mdc/>. |
|
|
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 |
|
|
|
|
|
Rewrite by Johannes Meyer: |
|
|
-> http://www.python-forum.de/viewtopic.php?p=183720 |
|
|
|
|
|
|
|
|
Revision history |
|
|
~~~~~~~~~~~~~~~~ |
|
|
|
|
|
November 1, 2010 |
|
|
Rewrite by Johannes Meyer: |
|
|
- changed entire code layout |
|
|
- changed some comments and docstrings |
|
|
- replaced time.clock() with time.time() in order |
|
|
to be able to use this module on linux, too. |
|
|
- added global __all__, ICMP_CODE and ERROR_DESCR |
|
|
- merged functions "do_one" and "send_one_ping" |
|
|
- placed icmp packet creation in its own function |
|
|
- removed timestamp from the icmp packet |
|
|
- added function "multi_ping_query" |
|
|
- added class "PingQuery" |
|
|
|
|
|
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: $ |
|
|
""" |
|
|
|
|
|
import time |
|
|
import socket |
|
|
import struct |
|
|
import select |
|
|
import random |
|
|
import asyncore |
|
|
|
|
|
# From /usr/include/linux/icmp.h; your milage may vary. |
|
|
ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris. |
|
|
|
|
|
ICMP_CODE = socket.getprotobyname('icmp') |
|
|
ERROR_DESCR = { |
|
|
1: ' - Note that ICMP messages can only be ' |
|
|
'sent from processes running as root.', |
|
|
10013: ' - Note that ICMP messages can only be sent by' |
|
|
' users or processes with administrator rights.' |
|
|
} |
|
|
|
|
|
__all__ = ['create_packet', 'do_one', 'verbose_ping', 'PingQuery', |
|
|
'multi_ping_query'] |
|
|
|
|
|
|
|
|
def 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. |
|
|
sum = 0 |
|
|
count_to = (len(source_string) / 2) * 2 |
|
|
count = 0 |
|
|
while count < count_to: |
|
|
this_val = ord(source_string[count + 1])*256+ord(source_string[count]) |
|
|
sum = sum + this_val |
|
|
sum = sum & 0xffffffff # Necessary? |
|
|
count = count + 2 |
|
|
if count_to < len(source_string): |
|
|
sum = sum + ord(source_string[len(source_string) - 1]) |
|
|
sum = sum & 0xffffffff # Necessary? |
|
|
sum = (sum >> 16) + (sum & 0xffff) |
|
|
sum = sum + (sum >> 16) |
|
|
answer = ~sum |
|
|
answer = answer & 0xffff |
|
|
# Swap bytes. Bugger me if I know why. |
|
|
answer = answer >> 8 | (answer << 8 & 0xff00) |
|
|
return answer |
|
|
|
|
|
|
|
|
def create_packet(id): |
|
|
"""Create a new echo request packet based on the given "id".""" |
|
|
# Header is type (8), code (8), checksum (16), id (16), sequence (16) |
|
|
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1) |
|
|
data = 192 * 'Q' |
|
|
# Calculate the checksum on the data and the dummy header. |
|
|
my_checksum = checksum(header + data) |
|
|
# 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) |
|
|
return header + data |
|
|
|
|
|
|
|
|
def do_one(dest_addr, timeout=1): |
|
|
""" |
|
|
A pure python ping implementation using raw socket. |
|
|
|
|
|
|
|
|
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. |
|
|
|
|
|
Bugs are naturally mine. I'd be glad to hear about them. There are |
|
|
certainly word - size dependenceies here. |
|
|
|
|
|
Copyright (c) Matthew Dixon Cowles, <http://www.visi.com/~mdc/>. |
|
|
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 |
|
|
|
|
|
Rewrite by Johannes Meyer: |
|
|
-> http://www.python-forum.de/viewtopic.php?p=183720 |
|
|
|
|
|
|
|
|
Revision history |
|
|
~~~~~~~~~~~~~~~~ |
|
|
|
|
|
November 1, 2010 |
|
|
Rewrite by Johannes Meyer: |
|
|
- changed entire code layout |
|
|
- changed some comments and docstrings |
|
|
- replaced time.clock() with time.time() in order |
|
|
to be able to use this module on linux, too. |
|
|
- added global __all__, ICMP_CODE and ERROR_DESCR |
|
|
- merged functions "do_one" and "send_one_ping" |
|
|
- placed icmp packet creation in its own function |
|
|
- removed timestamp from the icmp packet |
|
|
- added function "multi_ping_query" |
|
|
- added class "PingQuery" |
|
|
|
|
|
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(). |
|
|
Sends one ping to the given "dest_addr" which can be an ip or hostname. |
|
|
"timeout" can be any integer or float except negatives and zero. |
|
|
|
|
|
Returns either the delay (in seconds) or None on timeout and an invalid |
|
|
address, respectively. |
|
|
|
|
|
""" |
|
|
try: |
|
|
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE) |
|
|
except socket.error, (errno, msg): |
|
|
if errno in ERROR_DESCR: |
|
|
# Operation not permitted |
|
|
raise socket.error(''.join((msg, ERROR_DESCR[errno]))) |
|
|
raise # raise the original error |
|
|
try: |
|
|
host = socket.gethostbyname(dest_addr) |
|
|
except socket.gaierror: |
|
|
return |
|
|
# Maximum for an unsigned short int c object counts to 65535 so |
|
|
# we have to sure that our packet id is not greater than that. |
|
|
packet_id = int((id(timeout) * random.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 = my_socket.sendto(packet, (dest_addr, 1)) |
|
|
packet = packet[sent:] |
|
|
delay = receive_ping(my_socket, packet_id, time.time(), timeout) |
|
|
my_socket.close() |
|
|
return delay |
|
|
|
|
|
|
|
|
def receive_ping(my_socket, packet_id, time_sent, timeout): |
|
|
# Receive the ping from the socket. |
|
|
time_left = timeout |
|
|
while True: |
|
|
started_select = time.time() |
|
|
ready = select.select([my_socket], [], [], time_left) |
|
|
how_long_in_select = time.time() - started_select |
|
|
if ready[0] == []: # Timeout |
|
|
return |
|
|
time_received = time.time() |
|
|
rec_packet, addr = my_socket.recvfrom(1024) |
|
|
icmp_header = rec_packet[20:28] |
|
|
type, code, checksum, p_id, sequence = struct.unpack( |
|
|
'bbHHh', icmp_header) |
|
|
if p_id == packet_id: |
|
|
return time_received - time_sent |
|
|
time_left -= time_received - time_sent |
|
|
if time_left <= 0: |
|
|
return |
|
|
|
|
|
|
|
|
def verbose_ping(dest_addr, timeout=2, count=4): |
|
|
""" |
|
|
Sends one ping to the given "dest_addr" which can be an ip or hostname. |
|
|
|
|
|
"timeout" can be any integer or float except negatives and zero. |
|
|
"count" specifies how many pings will be sent. |
|
|
|
|
|
Displays the result on the screen. |
|
|
|
|
|
""" |
|
|
for i in xrange(count): |
|
|
print 'ping {}...'.format(dest_addr), |
|
|
delay = do_one(dest_addr, timeout) |
|
|
if delay == None: |
|
|
print 'failed. (Timeout within {} seconds.)'.format(timeout) |
|
|
else: |
|
|
delay = round(delay * 1000.0, 4) |
|
|
print 'get ping in {} milliseconds.'.format(delay) |
|
|
print |
|
|
|
|
|
|
|
|
class PingQuery(asyncore.dispatcher): |
|
|
def __init__(self, host, p_id, timeout=0.5, ignore_errors=False): |
|
|
""" |
|
|
Derived class from "asyncore.dispatcher" for sending and |
|
|
receiving an icmp echo request/reply. |
|
|
|
|
|
December 4, 2000 |
|
|
Changed the struct.pack() calls to pack the checksum and ID as |
|
|
unsigned. My thanks to Jerome Poincheval for the fix. |
|
|
Usually this class is used in conjunction with the "loop" |
|
|
function of asyncore. |
|
|
|
|
|
Once the loop is over, you can retrieve the results with |
|
|
the "get_result" method. Assignment is possible through |
|
|
the "get_host" method. |
|
|
|
|
|
Last commit info: |
|
|
~~~~~~~~~~~~~~~~~ |
|
|
$LastChangedDate: $ |
|
|
$Rev: $ |
|
|
$Author: $ |
|
|
""" |
|
|
|
|
|
import time |
|
|
import socket |
|
|
import struct |
|
|
import select |
|
|
import random |
|
|
import asyncore |
|
|
|
|
|
# From /usr/include/linux/icmp.h; your milage may vary. |
|
|
ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris. |
|
|
|
|
|
ICMP_CODE = socket.getprotobyname('icmp') |
|
|
ERROR_DESCR = { |
|
|
1: ' - Note that ICMP messages can only be ' |
|
|
'sent from processes running as root.', |
|
|
10013: ' - Note that ICMP messages can only be sent by' |
|
|
' users or processes with administrator rights.' |
|
|
} |
|
|
|
|
|
__all__ = ['create_packet', 'do_one', 'verbose_ping', 'PingQuery', |
|
|
'multi_ping_query'] |
|
|
|
|
|
|
|
|
def 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. |
|
|
sum = 0 |
|
|
count_to = (len(source_string) / 2) * 2 |
|
|
count = 0 |
|
|
while count < count_to: |
|
|
this_val = ord(source_string[count + 1])*256+ord(source_string[count]) |
|
|
sum = sum + this_val |
|
|
sum = sum & 0xffffffff # Necessary? |
|
|
count = count + 2 |
|
|
if count_to < len(source_string): |
|
|
sum = sum + ord(source_string[len(source_string) - 1]) |
|
|
sum = sum & 0xffffffff # Necessary? |
|
|
sum = (sum >> 16) + (sum & 0xffff) |
|
|
sum = sum + (sum >> 16) |
|
|
answer = ~sum |
|
|
answer = answer & 0xffff |
|
|
# Swap bytes. Bugger me if I know why. |
|
|
answer = answer >> 8 | (answer << 8 & 0xff00) |
|
|
return answer |
|
|
|
|
|
|
|
|
def create_packet(id): |
|
|
"""Create a new echo request packet based on the given "id".""" |
|
|
# Header is type (8), code (8), checksum (16), id (16), sequence (16) |
|
|
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1) |
|
|
data = 192 * 'Q' |
|
|
# Calculate the checksum on the data and the dummy header. |
|
|
my_checksum = checksum(header + data) |
|
|
# 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) |
|
|
return header + data |
|
|
|
|
|
|
|
|
def do_one(dest_addr, timeout=1): |
|
|
""" |
|
|
Sends one ping to the given "dest_addr" which can be an ip or hostname. |
|
|
"timeout" can be any integer or float except negatives and zero. |
|
|
"host" represents the address under which the server can be reached. |
|
|
"timeout" is the interval which the host gets granted for its reply. |
|
|
"p_id" must be any unique integer or float except negatives and zeros. |
|
|
|
|
|
Returns either the delay (in seconds) or None on timeout and an invalid |
|
|
address, respectively. |
|
|
If "ignore_errors" is True, the default behaviour of asyncore |
|
|
will be overwritten with a function which does just nothing. |
|
|
|
|
|
""" |
|
|
asyncore.dispatcher.__init__(self) |
|
|
try: |
|
|
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE) |
|
|
self.create_socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE) |
|
|
except socket.error, (errno, msg): |
|
|
if errno in ERROR_DESCR: |
|
|
# Operation not permitted |
|
|
raise socket.error(''.join((msg, ERROR_DESCR[errno]))) |
|
|
raise # raise the original error |
|
|
try: |
|
|
host = socket.gethostbyname(dest_addr) |
|
|
except socket.gaierror: |
|
|
return |
|
|
self.time_received = 0 |
|
|
self.time_sent = 0 |
|
|
self.timeout = timeout |
|
|
# Maximum for an unsigned short int c object counts to 65535 so |
|
|
# we have to sure that our packet id is not greater than that. |
|
|
packet_id = int((id(timeout) * random.random()) % 65535) |
|
|
packet = create_packet(packet_id) |
|
|
while packet: |
|
|
self.packet_id = int((id(timeout) / p_id) % 65535) |
|
|
self.host = host |
|
|
self.packet = create_packet(self.packet_id) |
|
|
if ignore_errors: |
|
|
# If it does not care whether an error occured or not. |
|
|
self.handle_error = self.do_not_handle_errors |
|
|
self.handle_expt = self.do_not_handle_errors |
|
|
|
|
|
def writable(self): |
|
|
return self.time_sent == 0 |
|
|
|
|
|
def handle_write(self): |
|
|
self.time_sent = time.time() |
|
|
while self.packet: |
|
|
# The icmp protocol does not use a port, but the function |
|
|
# below expects it, so we just give it a dummy port. |
|
|
sent = my_socket.sendto(packet, (dest_addr, 1)) |
|
|
packet = packet[sent:] |
|
|
delay = receive_ping(my_socket, packet_id, time.time(), timeout) |
|
|
my_socket.close() |
|
|
return delay |
|
|
|
|
|
|
|
|
def receive_ping(my_socket, packet_id, time_sent, timeout): |
|
|
# Receive the ping from the socket. |
|
|
time_left = timeout |
|
|
while True: |
|
|
started_select = time.time() |
|
|
ready = select.select([my_socket], [], [], time_left) |
|
|
how_long_in_select = time.time() - started_select |
|
|
if ready[0] == []: # Timeout |
|
|
return |
|
|
time_received = time.time() |
|
|
rec_packet, addr = my_socket.recvfrom(1024) |
|
|
icmp_header = rec_packet[20:28] |
|
|
type, code, checksum, p_id, sequence = struct.unpack( |
|
|
'bbHHh', icmp_header) |
|
|
if p_id == packet_id: |
|
|
return time_received - time_sent |
|
|
time_left -= time_received - time_sent |
|
|
if time_left <= 0: |
|
|
return |
|
|
|
|
|
|
|
|
def verbose_ping(dest_addr, timeout=2, count=4): |
|
|
""" |
|
|
Sends one ping to the given "dest_addr" which can be an ip or hostname. |
|
|
|
|
|
"timeout" can be any integer or float except negatives and zero. |
|
|
"count" specifies how many pings will be sent. |
|
|
|
|
|
Displays the result on the screen. |
|
|
|
|
|
""" |
|
|
for i in xrange(count): |
|
|
print 'ping {}...'.format(dest_addr), |
|
|
delay = do_one(dest_addr, timeout) |
|
|
if delay == None: |
|
|
print 'failed. (Timeout within {} seconds.)'.format(timeout) |
|
|
else: |
|
|
delay = round(delay * 1000.0, 4) |
|
|
print 'get ping in {} milliseconds.'.format(delay) |
|
|
print |
|
|
|
|
|
|
|
|
class PingQuery(asyncore.dispatcher): |
|
|
def __init__(self, host, p_id, timeout=0.5, ignore_errors=False): |
|
|
""" |
|
|
Derived class from "asyncore.dispatcher" for sending and |
|
|
receiving an icmp echo request/reply. |
|
|
|
|
|
Usually this class is used in conjunction with the "loop" |
|
|
function of asyncore. |
|
|
|
|
|
Once the loop is over, you can retrieve the results with |
|
|
the "get_result" method. Assignment is possible through |
|
|
the "get_host" method. |
|
|
|
|
|
"host" represents the address under which the server can be reached. |
|
|
"timeout" is the interval which the host gets granted for its reply. |
|
|
"p_id" must be any unique integer or float except negatives and zeros. |
|
|
|
|
|
If "ignore_errors" is True, the default behaviour of asyncore |
|
|
will be overwritten with a function which does just nothing. |
|
|
|
|
|
""" |
|
|
asyncore.dispatcher.__init__(self) |
|
|
try: |
|
|
self.create_socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE) |
|
|
except socket.error, (errno, msg): |
|
|
if errno in ERROR_DESCR: |
|
|
# Operation not permitted |
|
|
raise socket.error(''.join((msg, ERROR_DESCR[errno]))) |
|
|
raise # raise the original error |
|
|
self.time_received = 0 |
|
|
self.time_sent = 0 |
|
|
self.timeout = timeout |
|
|
# Maximum for an unsigned short int c object counts to 65535 so |
|
|
# we have to sure that our packet id is not greater than that. |
|
|
self.packet_id = int((id(timeout) / p_id) % 65535) |
|
|
self.host = host |
|
|
self.packet = create_packet(self.packet_id) |
|
|
if ignore_errors: |
|
|
# If it does not care whether an error occured or not. |
|
|
self.handle_error = self.do_not_handle_errors |
|
|
self.handle_expt = self.do_not_handle_errors |
|
|
|
|
|
def writable(self): |
|
|
return self.time_sent == 0 |
|
|
|
|
|
def handle_write(self): |
|
|
self.time_sent = time.time() |
|
|
while self.packet: |
|
|
# The icmp protocol does not use a port, but the function |
|
|
# below expects it, so we just give it a dummy port. |
|
|
sent = self.sendto(self.packet, (self.host, 1)) |
|
|
self.packet = self.packet[sent:] |
|
|
|
|
|
def readable(self): |
|
|
# As long as we did not sent anything, the channel has to be left open. |
|
|
if (not self.writable() |
|
|
# Once we sent something, we should periodically check if the reply |
|
|
# timed out. |
|
|
and self.timeout < (time.time() - self.time_sent)): |
|
|
self.close() |
|
|
return False |
|
|
# If the channel should not be closed, we do not want to read something |
|
|
# until we did not sent anything. |
|
|
return not self.writable() |
|
|
|
|
|
def handle_read(self): |
|
|
read_time = time.time() |
|
|
packet, addr = self.recvfrom(1024) |
|
|
header = packet[20:28] |
|
|
type, code, checksum, p_id, sequence = struct.unpack("bbHHh", header) |
|
|
if p_id == self.packet_id: |
|
|
# This comparison is necessary because winsocks do not only get |
|
|
# the replies for their own sent packets. |
|
|
self.time_received = read_time |
|
|
self.close() |
|
|
|
|
|
def get_result(self): |
|
|
"""Return the ping delay if possible, otherwise None.""" |
|
|
if self.time_received > 0: |
|
|
return self.time_received - self.time_sent |
|
|
|
|
|
def get_host(self): |
|
|
"""Return the host where to the request has or should been sent.""" |
|
|
return self.host |
|
|
|
|
|
def do_not_handle_errors(self): |
|
|
# Just a dummy handler to stop traceback printing, if desired. |
|
|
pass |
|
|
|
|
|
def create_socket(self, family, type, proto): |
|
|
# Overwritten, because the original does not support the "proto" arg. |
|
|
sock = socket.socket(family, type, proto) |
|
|
sock.setblocking(0) |
|
|
self.set_socket(sock) |
|
|
# Part of the original but is not used. (at least at python 2.7) |
|
|
# Copied for possible compatiblity reasons. |
|
|
self.family_and_type = family, type |
|
|
|
|
|
# If the following methods would not be there, we would see some very |
|
|
# "useful" warnings from asyncore, maybe. But we do not want to, or do we? |
|
|
def handle_connect(self): |
|
|
pass |
|
|
|
|
|
def handle_accept(self): |
|
|
pass |
|
|
|
|
|
def handle_close(self): |
|
|
sent = self.sendto(self.packet, (self.host, 1)) |
|
|
self.packet = self.packet[sent:] |
|
|
|
|
|
def readable(self): |
|
|
# As long as we did not sent anything, the channel has to be left open. |
|
|
if (not self.writable() |
|
|
# Once we sent something, we should periodically check if the reply |
|
|
# timed out. |
|
|
and self.timeout < (time.time() - self.time_sent)): |
|
|
self.close() |
|
|
|
|
|
|
|
|
def multi_ping_query(hosts, timeout=1, step=512, ignore_errors=False): |
|
|
""" |
|
|
Sends multiple icmp echo requests at once. |
|
|
|
|
|
"hosts" is a list of ips or hostnames which should be pinged. |
|
|
"timeout" must be given and a integer or float greater than zero. |
|
|
"step" is the amount of sockets which should be watched at once. |
|
|
|
|
|
See the docstring of "PingQuery" for the meaning of "ignore_erros". |
|
|
|
|
|
""" |
|
|
results, host_list, id = {}, [], 0 |
|
|
for host in hosts: |
|
|
try: |
|
|
host_list.append(socket.gethostbyname(host)) |
|
|
except socket.gaierror: |
|
|
results[host] = None |
|
|
while host_list: |
|
|
sock_list = [] |
|
|
for ip in host_list[:step]: # select supports only a max of 512 |
|
|
id += 1 |
|
|
sock_list.append(PingQuery(ip, id, timeout, ignore_errors)) |
|
|
host_list.remove(ip) |
|
|
# Remember to use a timeout here. The risk to get an infinite loop |
|
|
# is high, because noone can guarantee that each host will reply! |
|
|
asyncore.loop(timeout) |
|
|
for sock in sock_list: |
|
|
results[sock.get_host()] = sock.get_result() |
|
|
return results |
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
# Testing |
|
|
verbose_ping('www.heise.de') |
|
|
verbose_ping('google.com') |
|
|
verbose_ping('an-invalid-test-url.com') |
|
|
verbose_ping('127.0.0.1') |
|
|
host_list = ['www.heise.de', 'google.com', '127.0.0.1', |
|
|
'an-invalid-test-url.com'] |
|
|
for host, ping in multi_ping_query(host_list).iteritems(): |
|
|
print host, '=', ping |
|
|
return False |
|
|
# If the channel should not be closed, we do not want to read something |
|
|
# until we did not sent anything. |
|
|
return not self.writable() |
|
|
|
|
|
def handle_read(self): |
|
|
read_time = time.time() |
|
|
packet, addr = self.recvfrom(1024) |
|
|
header = packet[20:28] |
|
|
type, code, checksum, p_id, sequence = struct.unpack("bbHHh", header) |
|
|
if p_id == self.packet_id: |
|
|
# This comparison is necessary because winsocks do not only get |
|
|
# the replies for their own sent packets. |
|
|
self.time_received = read_time |
|
|
self.close() |
|
|
|
|
|
def get_result(self): |
|
|
"""Return the ping delay if possible, otherwise None.""" |
|
|
if self.time_received > 0: |
|
|
return self.time_received - self.time_sent |
|
|
|
|
|
def get_host(self): |
|
|
"""Return the host where to the request has or should been sent.""" |
|
|
return self.host |
|
|
|
|
|
def do_not_handle_errors(self): |
|
|
# Just a dummy handler to stop traceback printing, if desired. |
|
|
pass |
|
|
|
|
|
def create_socket(self, family, type, proto): |
|
|
# Overwritten, because the original does not support the "proto" arg. |
|
|
sock = socket.socket(family, type, proto) |
|
|
sock.setblocking(0) |
|
|
self.set_socket(sock) |
|
|
# Part of the original but is not used. (at least at python 2.7) |
|
|
# Copied for possible compatiblity reasons. |
|
|
self.family_and_type = family, type |
|
|
|
|
|
# If the following methods would not be there, we would see some very |
|
|
# "useful" warnings from asyncore, maybe. But we do not want to, or do we? |
|
|
def handle_connect(self): |
|
|
pass |
|
|
|
|
|
def handle_accept(self): |
|
|
pass |
|
|
|
|
|
def handle_close(self): |
|
|
self.close() |
|
|
|
|
|
|
|
|
def multi_ping_query(hosts, timeout=1, step=512, ignore_errors=False): |
|
|
""" |
|
|
Sends multiple icmp echo requests at once. |
|
|
|
|
|
"hosts" is a list of ips or hostnames which should be pinged. |
|
|
"timeout" must be given and a integer or float greater than zero. |
|
|
"step" is the amount of sockets which should be watched at once. |
|
|
|
|
|
See the docstring of "PingQuery" for the meaning of "ignore_erros". |
|
|
|
|
|
""" |
|
|
results, host_list, id = {}, [], 0 |
|
|
for host in hosts: |
|
|
try: |
|
|
host_list.append(socket.gethostbyname(host)) |
|
|
except socket.gaierror: |
|
|
results[host] = None |
|
|
while host_list: |
|
|
sock_list = [] |
|
|
for ip in host_list[:step]: # select supports only a max of 512 |
|
|
id += 1 |
|
|
sock_list.append(PingQuery(ip, id, timeout, ignore_errors)) |
|
|
host_list.remove(ip) |
|
|
# Remember to use a timeout here. The risk to get an infinite loop |
|
|
# is high, because noone can guarantee that each host will reply! |
|
|
asyncore.loop(timeout) |
|
|
for sock in sock_list: |
|
|
results[sock.get_host()] = sock.get_result() |
|
|
return results |
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
# Testing |
|
|
verbose_ping('www.heise.de') |
|
|
verbose_ping('google.com') |
|
|
verbose_ping('an-invalid-test-url.com') |
|
|
verbose_ping('127.0.0.1') |
|
|
host_list = ['www.heise.de', 'google.com', '127.0.0.1', |
|
|
'an-invalid-test-url.com'] |
|
|
for host, ping in multi_ping_query(host_list).iteritems(): |
|
|
print host, '=', ping |