hosts-ping
authorxchaos <xchaos@251d49ef-1d17-4917-a970-b30cf55b089b>
Tue, 7 May 2013 21:06:30 +0000 (21:06 +0000)
committerxchaos <xchaos@251d49ef-1d17-4917-a970-b30cf55b089b>
Tue, 7 May 2013 21:06:30 +0000 (21:06 +0000)
git-svn-id: https://dev.arachne.cz/repos/prometheus/trunk@223 251d49ef-1d17-4917-a970-b30cf55b089b

optional-tools/hosts-ping.py [new file with mode: 0755]
optional-tools/ping.py [new file with mode: 0644]

diff --git a/optional-tools/hosts-ping.py b/optional-tools/hosts-ping.py
new file mode 100755 (executable)
index 0000000..f1a8fb6
--- /dev/null
@@ -0,0 +1,149 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import ping, socket
+import os, time
+
+# (G)2013 xChaos, Arachne Labs http://arachne.cz + SPOJE.NET http://spoje.net
+
+hosts = "/etc/hosts"
+timeout = 1500 #timeout in ms
+interval = 200 #ping interval in ms
+attempts = 10
+
+tld = ".czf"
+domain = ".brevnov.czf"
+smokeping_prefix = "Klienti"
+smpater_prefix = "Backbone"
+smokeping_babble_length = 3
+smpater_babble_length = 2
+smokeping_html = "/var/www/html/web/sites/sysifos/hosts-ping/index.html"
+smpater_html = "/var/www/html/web/sites/sysifos/hosts-ping/backbone.html"
+smokeping_url = "http://sisyfos.brevnov.czf/cgi-bin/smokeping.cgi?filter=%s&amp;target=%s"
+smpater_url = "http://tartarus.brevnov.czf/cgi-bin/smokeping.cgi?filter=%s&amp;target=%s"
+table_head = """
+<table class="decorated last">
+<caption>hosts ping (%s)</caption><thead><tr>
+<th style="text-align: right;">#</th>
+<th>hostname</th>
+<th style="text-align: right;">received</th>
+<th style="text-align: right;">avg</th>
+<th style="text-align: right;">best</th>
+<th style="text-align: right;">worst</th>
+</tr></thead><tbody>
+"""
+table_end = """
+</tbody></table>
+<br />
+<p>Page generated by (G)2013 xChaos hosts-ping version 0.1-a</p>
+"""
+
+def try_to_ping(host):
+  sum = 0.0
+  best = None
+  worst = None
+  loss = 0
+
+  for i in range(0, attempts):
+    try:
+      delay = ping.Ping(host, timeout = timeout).do() #timeout in ms
+      time.sleep(interval/1000)
+
+      if delay:
+        sum += delay     
+
+        if not best or best > delay:
+          best = delay
+
+        if not worst or worst < delay:
+          worst = delay
+
+      else:
+        loss += 1
+
+    except socket.error, e:
+      loss += 1
+
+  return (sum/attempts, best, worst, loss)
+
+
+def smokenam_style(hostname, prefix, babble_length):
+
+  if not tld in hostname:
+    hostname += domain
+  
+  babble = hostname.split('.')
+  return '.'.join([prefix,] + [a_tooth for a_tooth in reversed(babble)][1:babble_length] + ['-'.join(babble),])
+
+
+def append_host(html, host, base_url, counter):
+  style = {'right': 'text-align: right;'}
+  columns = ('loss','avg','best','worst')
+  red_treshold = (0, 100, 50, 200)
+  green_treshold = (0, 2, 1, 10)
+  
+  for kolikaty, column in enumerate(columns):
+    style[column] = style['right']
+
+    if not host[column]:
+      host[column] = 0 #don't want it to be "None" type
+
+    if host[column] > red_treshold[kolikaty]:
+      style[column] += ' color: red;'
+    elif host[column] < green_treshold[kolikaty]:
+      style[column] += ' color: green;'
+  
+  received = attempts-host['loss']
+  html.write(   ('<tr class="%s"><td style="%s">%d</td><td><a href="%s" target="_blank" class="blue">%s</a></td><td style="%s">%d/%d</td>' + "\n")
+              % (('even', 'odd')[counter % 2], style['right'], counter, base_url % (host['name'], host['smokename']), host['name'], style['loss'], received, attempts))
+
+  if host['avg'] and host['best'] and host['worst']:
+    html.write(   ('<td style="%s">%1.2f</td><td style="%s">%1.2f</td><td style="%s">%1.2f</td></tr>' + "\n")
+                % (style['avg'], host['avg'], style['best'], host['best'], style['worst'], host['worst']))
+  else:
+    html.write(3*('<td style="%s">-</td>' % style['loss']) + "\n")
+
+# main program
+
+smokeping = []
+smpater = []
+
+for radek in open(hosts):
+  if radek[0] != '#':
+    is_smokeping = 'smokeping' in radek and not 'hidden' in radek
+    is_smpater = 'smpater' in radek
+    if is_smokeping or is_smpater:      
+      slovo = radek.split("\t")
+      host = { 'ip': slovo[0], 'name': slovo[1].split(' ')[0] }
+      (host['avg'], host['best'], host['worst'], host['loss']) = try_to_ping(host['ip'])
+
+      if is_smokeping:
+        host['smokename'] = smokenam_style(host['name'], smokeping_prefix, smokeping_babble_length)
+        smokeping.append(host)
+      else:
+        host['smokename'] = smokenam_style(host['name'], smpater_prefix, smpater_babble_length)
+        smpater.append(host)
+
+# smokeping
+
+html = open(smokeping_html, 'w')
+html.write("<h1>Smokeping - klientská zařízení</h1>");
+html.write(table_head % time.ctime());
+
+for kolikaty, host in enumerate(sorted(smokeping, key = lambda host: -host['loss']*attempts*timeout-host['avg'])):
+  append_host(html, host, smokeping_url, kolikaty+1)
+
+html.write(table_end);
+html.close();
+
+# smpater
+
+html = open(smpater_html, 'w')
+html.write("<h1>Smokeping - páteřní routery</h1>");
+html.write(table_head % time.ctime());
+
+for kolikaty, host in enumerate(sorted(smpater, key = lambda host: -host['loss']*attempts*timeout-host['avg'])):
+  append_host(html, host, smpater_url, kolikaty+1)
+
+html.write(table_end);
+html.close();
diff --git a/optional-tools/ping.py b/optional-tools/ping.py
new file mode 100644 (file)
index 0000000..4d66603
--- /dev/null
@@ -0,0 +1,358 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+"""
+    A pure python ping implementation using raw sockets.
+
+    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 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 array
+import os
+import select
+import signal
+import socket
+import struct
+import sys
+import time
+
+
+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
+
+
+# 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 calculate_checksum(source_string):
+    """
+    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
+    """
+    if len(source_string)%2:
+        source_string += "\x00"
+    converted = array.array("H", source_string)
+    if sys.byteorder == "big":
+        converted.byteswap()
+    val = sum(converted)
+
+    val &= 0xffffffff # Truncate val to 32 bits (a variance from ping.c, which
+                      # uses signed ints, but overflow is unlikely in ping)
+
+    val = (val >> 16) + (val & 0xffff)    # Add high 16 bits to low 16 bits
+    val += (val >> 16)                    # Add carry from above (if any)
+    answer = ~val & 0xffff                # Invert and truncate to 16 bits
+    answer = socket.htons(answer)
+
+    return answer
+
+
+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
+
+        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
+
+    #--------------------------------------------------------------------------
+
+    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)
+
+        # Calculate the checksum on the data and the dummy header.
+        checksum = calculate_checksum(header + data) # Checksum is in network order
+
+        # 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
+        )
+
+        packet = header + data
+
+        send_time = default_timer()
+
+        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 receive_one_ping(self, current_socket):
+        """
+        Receive the ping from the socket. timeout = in ms
+        """
+        timeout = self.timeout / 1000.0
+
+        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
+
+            receive_time = default_timer()
+
+            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]
+            )
+
+            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
+
+            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__':
+    # 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"
This page took 0.222862 seconds and 4 git commands to generate.