How can I trigger my python script to automatically run via a ping?

368 Views Asked by At

I wrote a script that recurses through a set of Cisco routers in a network, and gets traffic statistics. On the router itself, I have it ping to the loopback address of my host PC, after a traffic threshold, however now I need the script to run. Now I have one of two options. To either trigger the python script via a ping from the router to the loopback, or from an FTP upload to a folder on my PC. Problem with the folder idea is I'm not sure if I can make it run without constantly scanning through the folder. Since network traffic jumps up and down, I'd like to monitor it immediately as the router sends a message(whether it's ftp or ping etc).

Today, i tried a socket listener, but the router doesn't have native support for sending UDP datagrams. Also ping doesn't work with ports. I tried a ip address listener without using a port, currently my knowledge isn't good enough to know how to implement this. I've done socket programming before but not simply listening for ICMP pings(even still I'd have to capture the packet and look at the source IP address to make sure it's sourced from the router.)

Could anyone give me a clue how to implement this? My best preference would be a loopback listening script that captures packets and triggers the script once a source IP is from the router. Any ideas?

Ok, so by reading chapter 3 of Black Hat Python i borrowed their code and am almost done with a working solution. Everything works , except for 1 snag - The listener is printing out both the source ip and destination ip as resolved back to the loopback(in this case the destination). I've tried pinging from the routers, and from cmd by changing the source, the source address always resolved to my loopback address. I checked with wireshark and it showed a different source address, the one I was expecting. I attempted to see if I was looking at echo replies (although this should mean my source pops up as dest), and changed some socket.IPPROTO_ICMP parameters around, as well as toy around with header lengths. It's doing just that. it's printing my source address as my destination loopback. Any thoughts?

class IP:
def __init__(self, buff=None):
    header = struct.unpack('<BBHHHBBH4s4s4s', buff)
    self.ver = header[0] >> 4
    self.ihl = header[0] & 0xF
    self.tos = header[1]
    self.len = header[2]
    self.id = header[3]
    self.offset = header[4]
    self.ttl = header[5]
    self.protocol_num = header[6]
    self.sum = header[7]
    self.src = header[8]
    self.dst = header[9]
    self.trash = header[10]
    self.src_address = ipaddress.ip_address(self.src)
    self.dst_address = ipaddress.ip_address(self.dst)
    self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
    try:
        self.protocol = self.protocol_map[self.protocol_num]
    except Exception as e:
        print('%s No protocol for %s' % (e, self.protocol_num))
    self.protocol = str(self.protocol_num)




    def sniff(host):
        socket_protocol = socket.IPPROTO_ICMP
        sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, 
                                         socket.IPPROTO_IP)
        sniffer.bind((host, 0))
        sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
        if os.name == 'nt':
            sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
 try:
        while True:
            raw_buffer = sniffer.recvfrom(65535)[0]

            ip_header = IP(raw_buffer[0:24])
            print(f'Version: {ip_header.ver}')
            print(f'Header Length: {ip_header.ihl} TTL: {ip_header.ttl}')
            offset = ip_header.ihl * 4
            buf = raw_buffer[offset:offset + 8]
            icmp_header = ICMP(buf)
            print('ICMP -> Type: %s Code: %s\n' %
                  (icmp_header.type, icmp_header.code))
            print('Protocol: %s %s -> %s' % (ip_header.protocol,
                                     ip_header.src_address,
                                     ip_header.dst_address))
host = '192.168.56.1'
IP.sniff(host)
1

There are 1 best solutions below

4
metatoaster On

You were pretty close in the code you have included in your previous (self-deleted) question, so with apologies, I will pull out the relevant part:

Can someone give me an idea of how to listen for incoming packets from 10.0.0.1 from my loopback address of 192.168.56.1? I've done some socket programming a good while ago, and haven't done much since, so having lots of trouble with this one.

You can in fact listen for ICMP from a Python process, provided that the process has the permission to open a raw socket to deal with that. The code you had included was basically there, this is the most minimal code that may demonstrate how this works:

import socket
import sys
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

if sys.platform == 'win32':
    # bind on the actual host is required on Windows
    host = socket.gethostbyname(socket.gethostname())
    s.bind((host, 0))  # bind on everything
    # on Windows, including the IP headers...
    s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
    # ... and listening to everything is required for ICMP
    s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

(payload, (ip_src, _)) = s.recvfrom(65535)
# skip the first 28 bytes of IP/ICMP headers
print("payload %r from %s" % (payload[28:].decode('latin1'), ip_src))

Do note that raw sockets generally need root on Linux, or go through the dance to enable CAP_NET_RAW for the spawned Python process.

As for Windows, that ioctl call is needed to configure the socket to receive all packets (borrowed from the example from the official documentation) - likewise, administrator privileges is required to modify the interface like so.

On Linux, I initiated this command (in a separate terminal):

$ ping 192.168.178.50 -p 68656c6c6f20776f726c64 -s11 -c1
PATTERN: 0x68656c6c6f20776f726c64
PING 192.168.178.50 (192.168.178.50) 11(39) bytes of data.
19 bytes from 192.168.178.50: icmp_seq=1 ttl=64

--- 192.168.178.50 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms

This is the output that gets produced:

payload 'hello world' from 192.168.178.50

Now to handle inputs triggers, the following bare bone listener is a skeleton that may be used to start building with:

import socket
import sys

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

if sys.platform == 'win32':
    # Windows require this addition to make this all work
    host = socket.gethostbyname(socket.gethostname())
    s.bind((host, 0))  # bind on everything
    s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
    s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

while True:
    (payload, (ip_src, _)) = s.recvfrom(65535)
    text = payload[28:].decode('latin1')
    # skip the first 28 bytes of IP/ICMP headers
    print("payload %r from %s" % (text, ip_src))
    if ip_src == b'10.0.0.1':
        print('Received packet from 10.0.0.1, doing something...')
        # do_something()

    # unconditionally quit if the quit sequence was received
    if text == 'quit':
        print('quit received, terminating')
        break

If one were to send a payload (e.g. ping 192.168.178.50 -p 71756974 -s4 -c1) to the server, it will quit - if the source address is 10.0.0.1, the specific condition may be triggered.