-1

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)
hfakoor222
  • 25
  • 7
  • the OS will get in your way because it's supposed to automatically respond to pings and not deliver them to programs, but you could try a raw socket – user253751 May 24 '23 at 02:02
  • Please don't delete this question quite just yet, I realize there isn't a question about ICMP listener in Python, so this thread can be made into a canonical for this, so hold on. – metatoaster May 24 '23 at 02:45
  • I won't delete it at all. I'll even provide the finished script if I can get it to work - the script is done via reading the traffic and providing output, I can't trigger it. Basically it logs into every device in the network, from a single source ip address, recursing through connections, and produces traffic statistics, and if it's not possible (like a vmnet interface) it just skips over the address. – hfakoor222 May 24 '23 at 02:59
  • @user253751 Ok, I've seen a few walkthroughs using raw socket. I might look more into it this week, I think it's going to take some time if I take this approach. I may do it however, but I'm also wondering if anyone has done this before rather than spending a whole week figuring it out. – hfakoor222 May 24 '23 at 03:01
  • probably because ICMP listeners aren't supposed to be a thing. ICMP is for the operating system, not listeners – user253751 May 24 '23 at 03:02
  • @user253751 you _can_ in fact listen for ICMP packets via Python. – metatoaster May 24 '23 at 03:03
  • Should probably fix the code formatting for the code that you just updated into the question. Also, may be useful to include a simple diagram using (redacted) IP addresses of the layout of your network devices to make it easier to visualise how the setup is and what is to be expected. – metatoaster May 26 '23 at 00:38

1 Answers1

0

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.

metatoaster
  • 17,419
  • 5
  • 55
  • 66
  • Thanks. I'm going to test this in the morning. Right before I read this, I took @user253751's adviced, used raw sockets and got python to respond to a router ping from my Cisco device. I will go over what you posted later tonight or in the morning, so i can give it the attention it deserves and ingest it. I've been working on this/the script since 11 am and it's now 1 am and I'm tired. – hfakoor222 May 24 '23 at 03:57
  • I tried using both your code, and my above code which I edited in to my original post. I have a problem with my souce address showing up as the destination address from no matter where I ping. At one point I assumed it may have been due to host unreachable, but the pings are going through, so I don't think it's that. Let me know if you have any suggestions. Btw your code worked fine, except the same issue of resolving my source ip to the destination loopback and not the source. – hfakoor222 May 25 '23 at 20:26
  • @hfakoor222 By "destination loopback", what exactly do you mean? Are the packets seen from within Python seeing the actual loopback IPv4 address `127.0.0.1` or something else? Given the variety of machines you seem to have you will need to be more specific with descriptions, include details e.g. "running the listener script on WIndows 10 with ip `10.0.0.10`, sending ping from cisco router with ip `10.0.0.1`, caused the process on Windows giving the output with `payload ... from 127.0.0.1`", and repeat this for all the other combinations you care to include. – metatoaster May 26 '23 at 01:28
  • Also include running `tcpdump` or Wireshark observations from the relevant machine, and ensure that there is no random firewall/iptables doing unexpected things like rewriting packets. – metatoaster May 26 '23 at 01:29