ICMP packet is not returning filtered

I am trying to get a print out from a crafted packet to show if a host is filtered. So far I can isolate the None responses and the echo-reply but I cannot get it to show filtered.

This is what I have

for add in ip:
    ip_list.append(add)

for ips in ip_list:
    pac = Ether(dst = "ff:ff:ff:ff:ff:ff")/IP(dst=ips)/ICMP()
    ans = srp1(pac,timeout=0.5, verbose = verb)
    
    if ans == None:
        clients.append(ips)
        status.append("down")
    elif ans(ICMP).type == 3:
        clients.append(ips)
        status.append("Filtered”)

When debugging something like this i ask my self what i need to know to check the code is working.

In the case that would be what is ans, ans(ICMP) and last ans(ICMP).type using print.

Also is 3 correct? Can you find a symbolic definition of what every the 3 means in the package you are using?

Can you show us what are your identifiers Ether, IP, ICMP, srp1? It is difficult to help you when we can only guess what they are doing.

I do not understand if you are sending and receiving packets, if you are creating them just in memory or something else. To me it looks like you are capturing packets from a network, right?

Can you also add the functional part of your code detecting the ICMP echo reply?

I can confirm that ICMP type 3 is “destination unreachable”. To know about filtering by devices which correctly tell about that you have to further check the code (the second number in ICMP) for the “administratively prohibited” codes.

In reality most of filtering simply drops the packet without sending anything back. In such cases you do not receive any “destination unreachable” messages.

Thankyou Barry and Vaclac for your replies, I tried to post minimal code as I was reading someone’s post and they got asked only to post relevant to the question.

Vaclav you will have seen this from the table question I asked.

Code purpose:

To scan a network/ip address and return if the host is UP, DOWN or UNREACHABLE. then to print the outcome to a file in a table with centralised header.

So, I think I am most of the way there, but I just can’t figure out what I am missing in this.

This is the packet I have created, and I am sending.

 pac = Ether(dst = "ff:ff:ff:ff:ff:ff")/IP(dst=ips)/ICMP()
 ans = srp1(pac,timeout=0.5, verbose = verb)

 if ans is None:
        clients.append(ips)
        status.append("down")
    elif ans.getlayer(ICMP).type == 3:
        clients.append(ips)
        status.append("up")
    else:
        clients.append(ips)
        status.append("up")

Above is what I have been playing around with, the only parts that seem to detect anything is the None and the reply. Maybe I am missing something in the creation of the packet or I’m not getting the correct layer.
I have tried adding the code fault in place of the type but still no difference.

elif ans.getlayer(ICMP).type == 3 and ans.getlayer(ICMP).code == 0:
    print("yes")

But still it does not do anything (don’t think this threw an error either)

import sys
import argparse
import ipaddress
from scapy.all import *
from tabulate import tabulate
import tabulate_cell_merger.tabulate_cell_merger

def merge(clients, status):
    merged_list = tuple(zip(clients, status))
    return merged_list


parser = argparse.ArgumentParser()
parser.add_argument("IP", help = """Insert the address you want to check.
                    Use CIDR for a subnet""") #IP is the vaiable where the CLI input is stored
parser.add_argument("-v", "--verbosity", required = False, action = "count",
                    help = "Increase the output verbosity") #Changes the amount of data shown
args = parser.parse_args()
ip_add = args.IP

try:
    if "/" in ip_add:
        ipaddress.ip_network(ip_add, strict=False)
        print("This is a Valid CIDR address.... Processing!")
    else:
        ipaddress.ip_address(ip_add)
        print("This is a Valid IP address..... Processing!")
except ValueError:
    print("You did not enter a vaild IP or CIDR address")
    sys.exit(0)

ip = Net(ip_add)
ip_list = []
clients = []
status = []
    
verb = False
if args.verbosity == None:
    verb = False
elif args.verbosity > 0:
    verb = True
    
for add in ip:
    ip_list.append(add)

for ips in ip_list:
    pac = Ether(dst = "ff:ff:ff:ff:ff:ff")/IP(dst=ips)/ICMP()
    ans = srp1(pac,timeout=0.5, verbose = verb)
    
    if ans is None:
        clients.append(ips)
        status.append("down")
    else:
        clients.append(ips)
        status.append("up")
        
header = [ip_add]
table = merge(clients, status)
colspan = {(0,1):2}
with open('My_file.txt', "w") as f:
    f.write(tabulate(table, header, tablefmt = "pretty"))
print('Done')

Could there also be something with the network, as I can ping my network through the code, but I can’t get anything back from something like 1.1.1.1 or 8.8.8.8 even with disabling my firewall

Sorry haven’t commented my code yet…
Hope this is a bit clearer.

It is often a problem to import * as its is not clear what variables get setup.

Replacing * with the list of variables you know you want makes code easier to maintain. It avoids some surprising issues.

1 Like

Thanks Barry I will change it when I get home. This is the way it is being taught at TAFE currently

I have done a few changes and run some simple pings and print ls(pac) to try and find something to differentiate the types of packets but I cant see any difference in them.

from scapy.all import *

pac = Ether(dst = "ff:ff:ff:ff:ff:ff")/IP(dst="10.0.0.2")/ICMP()
ans, unans = srp(pac, timeout=0.5, verbose = 0)
print(ls(pac))

Here is a ls(packet) print out for a host that is DOWN


And here is a ls(packet) of a host that is UP

There is no difference.

Even if I do a print(packet.show()) I get the same print out every time no matter what information is provided as below:

Code being used

import sys
import argparse
import ipaddress
from scapy.all import ICMP, IP, sr1, sr, Ether, srp, srp1, Net
from prettytable import PrettyTable

#====CLI Commands================================================

parser = argparse.ArgumentParser()
parser.add_argument("IP", help = """Insert the address you want to check.
                    Use CIDR for a subnet""") #IP is the vaiable where the CLI input is stored
parser.add_argument("-v", "--verbosity", required = False, action = "count",
                    help = "Increase the output verbosity") #Changes the amount of data shown
args = parser.parse_args()
ip_add = args.IP

#====Validate Input===============================================

try:
    if "/" in ip_add:
        ipaddress.ip_network(ip_add, strict=False)
        print("This is a Valid CIDR address.... Processing!")
    else:
        ipaddress.ip_address(ip_add)
        print("This is a Valid IP address..... Processing!")
except ValueError:
    print("You did not enter a vaild IP or CIDR address")
    sys.exit(0)
    
#====Declare Variables============================================

ip = Net(ip_add)
ip_list = []
clients = []
status = []
verb = False

if args.verbosity == None:
    verb = False
elif args.verbosity > 0:
    verb = True
    
#====SEND ICMP====================================================

for add in ip:
    ip_list.append(add)
    print(add)

for ips in ip_list:
    pac = Ether(dst = "ff:ff:ff:ff:ff:ff")/IP(dst=ips)/ICMP()
    re = srp1(pac, timeout=0.5, verbose = verb)
    print(pac.show())
    if re == None:
        print("Host {} is down".format(pac[IP].dst))
    else:
        print("Host is Up")

Please post text output at quoted text and not images.

I think you have moved from python questions to network questions.
There are network people that may reply, but if you do not get an answer here seek out network experts.

It was important to know that Ether, IP, ICMP, srp1 come from Scapy. Without knowing that we can only guess what they do.

Why do you specify the L2 destination address? Do you have a strong reason for that? If you want to avoid using ARP then be aware of the fact that it will be needed for sending the reply back anyway. I think using the broadcast MAC can rather cause more problems.

I would suggest crafting from L3 up. I would also add a payload as I saw that ping without a payload can be blocked sometimes.

probe = IP(dst=ips) / ICMP() / Raw('any payload')
response = sr1(probe, timeout=0.5, verbose=verb)

I see that you still did not figure out how to dissect the response packet. What about this?

ICMP_TYPE_UNREACHABLE = 3
ICMP_CODES_PROHIBITED = {9, 10, 13}

if reponse.layers()[1] == ICMP:
    ... # put your general processing of ICMP here
    if (
            response['ICMP'].type == ICMP_TYPE_UNREACHABLE
            and response['ICMP'].code in ICMP_CODES_PROHIBITED):
        ...  # your code taking care of a filtered packet with response sent

In your screenshots you printed your ICMP requests. Of course they are always the same because you generate them the same way. You have to analyze the response.

The import * form is an easy way to get all the “exported” symbols
from a package. I can well imagine that is very useful in teaching
situations as it avoids tedium like this:

 from os.path import (
     basename,
     exists as existspath,
     isdir as isdirpath,
     isfile as isfilepath,
     join as joinpath,
     splitext,
 )

which I’ve picked arbitrarily from one of my own programmes.

It lets the student focus on the task.

Karl: import * is pretty uncommon in the real world - it is mostly
good only for when your programme works very closely with one main
package (eg scrapy for you) and if the import * is the first
import. Typically programmes work with a few packages and import
specific names as needed.

Cheers,
Cameron Simpson cs@cskk.id.au

It was important to know that Ether, IP, ICMP, srp1 come from
Scapy. Without knowing that we can only guess what they do.

Why do you specify the L2 destination address? Do you have a strong reason for that? If you want to avoid using ARP then be aware of the fact that it will be needed for sending the reply back anyway. I think using the broadcast MAC can rather cause more problems.

A broadcast is a great way to ping all the nodes on your LAN segment,
without knowing the local IP range. I’ve used:

 ping -b 255.255.255.255

on occasion with good effect for some circumstances. Of course that’s an
IP-based broadcast, but I suspect the -b option is needed by the
ping command to get the braodcast MAC address.

So what Karl’s got is actually a very neat single packet ping of all
the local nodes, rather than pinging each node individually.

In your screenshots you printed your ICMP requests. Of course they are
always the same because you generate them the same way. You have to
analyze the response.

Aye, and print out the type and code of each response, for every
response. That will aid debugging and classification.

I was going to nark a little about the 0.5 timeout since some of my time
is on a satellite connection where best case round trips are over 600ms.
But my local link is of could ethernet or wifi and round trips to local
nodes are very small :slight_smile:

Cheers,
Cameron Simpson cs@cskk.id.au

Hi, Barry sorry for the snippets but when I pasted the text directly in it become a hot spaghetti mess.

Hello Vaclav,

This was something the lecturer told us to do, after removing it and doing the ICMP packet on a network outside of mine I started getting different results but if I try and use the ICMP while inside my network it throws up warning: MAC address not found using broadcast address which then gives me a incorrect result as well.

I have tried the way you have the used the code, but I kept getting errors such as list not callable and NoneType error (probably me not dealing with the packets returning None). This is the first time dealing with scapy and packets, so my knowledge is limited, and dissecting packets is alien to me as is everything else. Reading the documentation is sometimes very overwhelming for someone who knows very little about the way things are constructed.

I dont understand what you mean here. Is there specific ways to create a packet for each ping?

Hello Cameron,

Thanks for explaining this, I have since replaced the import* with each individual command.

I have changed this to 1 second :slight_smile:

This I am struggling to do… i just can’t get into the layers correctly.

Below is the code I am using at the moment and it seems to work although I am still not getting packets back that say filtered, and I cant figure out how to stop the Warning about the mac address.

import os
from tqdm import tqdm, notebook
import time
import sys
import argparse
import ipaddress
from scapy.all import ICMP, IP, sr1, sr, Ether, srp, srp1, Net
from prettytable import PrettyTable

#====Check for correct usage====================================

if len(sys.argv) <2 or len(sys.argv) >4:
    print("USAGE:>>>  {FILE_NAME}  {IP_ADDRESS}  {VERBOSE is optional: -v }  {TABLE is optional: -table}  <<<")
    exit(0)
    
#====CLI Commands================================================

parser = argparse.ArgumentParser(prog = "Network Scanner v1.2",
                                 description =  "Looks for available hosts on the network")
parser.add_argument("-v", "--v", action = "count",
                    help = "Increase the output verbosity") #Changes the amount of data shown
parser.add_argument("IP", help = """Insert the address you want to check.
                    Use CIDR for a subnet""") #IP is the vaiable where the CLI input is stored
parser.add_argument("-table", action = "count", help = "Prints a results table in the terminal window as well as saving to a file")
args = parser.parse_args()
ip_add = args.IP


#====Validate Input===============================================

try:
    if "/" in ip_add:
        ipaddress.ip_network(ip_add, strict=False)
        print("This is a Valid CIDR address.... Processing!")
    else:
        ipaddress.ip_address(ip_add)
        print("This is a Valid IP address..... Processing!")
except ValueError:
    print("You did not enter a vaild IP or CIDR address")
    sys.exit(0)
    
#====Declare Variables============================================

ip = Net(ip_add)
ip_list = []
clients = []
status = [] 
verb = 0
data =[]
file_path = os.path.abspath(__file__)

#====Set Verbose==================================================

verb_switch = args.v
if verb_switch == None:
    verb = 0
else:
    verb = 1
    
#====SEND ICMP====================================================

for add in ip:
    ip_list.append(add)

for ips in tqdm(ip_list, file=sys.stdout, desc = f"Processing IP address(s)", ncols = 100,  colour = 'BLUE'):
    packet = IP(dst=ips)/ICMP()/"Hello"
    received = sr1(packet, timeout=0.5, verbose = verb)
    if received == None:
        clients.append(ips)
        status.append("Down/Filtered")
    elif received.getlayer(ICMP).code == {0,1,2,3,9,10}:
        print(f"Host {ips} is Filtered")
    elif received.getlayer(ICMP).chksum == 0xdc2d:
        clients.append(ips)
        status.append("Up")
    

#====Make a Table=================================================

COLUMNS = ["IP", "STATUS"]
table = PrettyTable()
table.title = ip_add
table.header = True
table.padding = 2
table.add_column(COLUMNS[0], clients)
table.add_column(COLUMNS[1], status)

#====Save File====================================================

with open('Network Scan Results.txt', "w") as f:
    f.write(str(table))
    f.close()
if args.table == 1:
    print(table)

print(f"\n\nYour file has been successfully printed to {file_path}")

All criticism gratefully received :slight_smile:

Yes, you can ping multiple nodes using broadcast but Karl is using L2 broadcast + L3 unicast IP(dst=ips). In such case only the node with the L3 address will respond. This technique can be used when you suspect problems with ARP.

I think you should use broadcast only when you know very well what you are doing:

  • broadcasts can be filtered intentionally
  • broadcasts can cause high traffic (traffic amplification) especially in large broadcast domains
  • the previous point is especially important when the tool goes rogue and sends requests very rapidly
  • the behaviour of the communicating parties can be different when broadcast addressing is being used - the mentioned filtering, traffic shaping and other possible differences

Anyway Karl explained that in their course they were instructed to use the broadcast MAC.

1 Like

You can use the code fences for the output as well as thw python code.

  • NoneType ... — Yes, you did not take care of the situation when no response is received. You need to detect this case and not allow to run the branch of the code expecting to have a response.
  • list not callable — You are trying to call a list like a function using round brackets (). That is not possible. Did not you confuse square brackets and round brackets?

No, in your screenshots I see that you printed ICMP echo request - i.e. the request you are creating yourself and sending. You probably know what you are creating. You are interested in the response which brings you new information from the computer being probed. You should print the response and analyze it.

In your latest code you store the response to the variable received. Use ls(received) and analyze the response you received.

#====Imported modules===========================================

from warnings import filterwarnings
filterwarnings("ignore")
from tqdm import tqdm, notebook, trange
import time
import sys
import os
import argparse
import ipaddress
from scapy.all import ICMP, IP, sr1, sr, Ether, srp, srp1, Net, ls
from prettytable import PrettyTable

#====Check for correct usage====================================

if len(sys.argv) <2 or len(sys.argv) >4:
    print("USAGE:>>>  {FILE_NAME}  {IP_ADDRESS}  {VERBOSE is optional: -v }  {TABLE is optional: -table}  <<<")
    exit(0)
    
#====CLI Commands================================================

parser = argparse.ArgumentParser(prog = "Network Scanner v1.2",
                                 description =  "Looks for available hosts on the network")
parser.add_argument("-v", "--v", action = "count",
                    help = "Increase the output verbosity") #Changes the amount of data shown
parser.add_argument("IP", help = """Insert the address you want to check.
                    Use CIDR for a subnet""") #IP is the vaiable where the CLI input is stored
parser.add_argument("-table", action = "count", help = "Prints a results table in the terminal window as well as saving to a file")
parser.add_argument("-ether", action = "count", help = "Scans without Ether")
args = parser.parse_args()
ip_add = args.IP
verb_switch = args.v
ether_switch = args.ether

#====Validate Input===============================================

try:
    if "/" in ip_add:
        ipaddress.ip_network(ip_add, strict=False)
        print("\nThis is a Valid CIDR address.... \n")
    else:
        ipaddress.ip_address(ip_add)
        print("\nThis is a Valid IP address..... \n")
except ValueError:
    print("You did not enter a vaild IP or CIDR address")
    sys.exit(0)
    
#====Declare Variables============================================

ip = Net(ip_add)
ip_list = []
clients = []
status = [] 
verb = 0
file_path = os.path.abspath(__file__)
filtered = {1,2,3,9,10,13}
unreachable = 3
ether = Ether(dst = "ff:ff:ff:ff:ff:ff")
reachable = 0

#====Set Verbose==================================================

if verb_switch == None:
    verb = 0
else:
    verb = 1
    
#====SEND ICMP====================================================

for add in ip:
    ip_list.append(add)

for ips in tqdm(ip, file=sys.stdout, desc = "Processing IP address(s)", ncols = 100,  colour = 'BLUE'):
    if ether_switch == None:
        ip = IP(dst=ips)
        packet = ip/ICMP()/"I_see_you"
        re = sr1(packet, timeout=1, verbose = verb)
    else:
        ip = IP(dst=ips)
        packet = ether/ip/ICMP()/"I_see_you"
        re = srp1(packet, timeout=1, verbose = verb)
    if re == None:
        clients.append(ips)
        status.append("Down or filtered")
    elif re.layers()[0] == re['ICMP'].type in unreachable and re['ICMP'].code in filtered:
        clients.append(ips)
        status.append("Filtered")
    else:
        clients.append(ips)
        status.append("UP")


#====Make a Table=================================================

COLUMNS = ["IP", "STATUS"]
table = PrettyTable()
table.title = ip_add
table.header = True
table.padding = 2
table.add_column(COLUMNS[0], clients)
table.add_column(COLUMNS[1], status)

#====Save File====================================================

with open('Network Scan Results.txt', "w") as f:
    f.write(str(table))
    f.close()
if args.table == 1:
    print(table)

print(f"\n\nYour file has been successfully printed to {file_path}")type or paste code here

Ok…

So, I understand now what you mean about the sr packet. It has 2 lists that I can inspect, and I can access them through

re.layer()[0] # for sent
#and
re.layer()[1] # is the received packet

I have done the ls(re) for the packets that have returned the information but and for the ones that haven’t, and I don’t really know what information in there I am looking for.

if re == None:
        clients.append(ips)
        status.append("Down")
    elif re.layers()[0] == re['ICMP'].type in unreachable and re['ICMP'].code in filtered:
        clients.append(ips)
        status.append("Filtered")
    else:
        clients.append(ips)
        status.append("UP")

This seems to work as I get all the hosts that are down saying down and the ones that are up saysing up, but I can’t test the filtered bit as I dont know of filtered hosts to try against and those that are filtered would probably just come back as down anyway depending on how they have been filtered…

Please correct me on the above if I am wrong.

I do not know what is “sr packet” and what are the 2 lists you mention.

The comments look completely wrong to me. re in your code is a single response. It will be a packet or a frame - depending on whether you use sr1() or srp1(). See below.

I think you meant layers() instead of layer(). Also I think it is very confusing to name a variable re as there is a very commonly used module in the standard library named re.

It is essential to understand the concept of layers to be able to analyze network communication. Is not it what are you supposed to learn (or already know) on your lecture? You can start here: Internet protocol suite - Wikipedia — see the diagram Encapsulation of application data descending through the layers…

The method layers() returns a single list containing the individual protocol layers of the frame or packet. sr1() works with packets (L3 and up) while srp1() works with frames (starting one layer lower: L2 and up).

When you are using both sr1() and srp1() you have to be very careful because the layers differ! sr1() omits the Ethernet layer. In your learning process you should print the responses and examine them yourself to understand what is going on. In other words for a layer number n the layer response.layers()[n] will be a different layer for sr1() and a different one for srp1().

Do you know the precedence of all the operators in the expression in elif? I do not remember them so I had to look it up: 6. Expressions — Python 3.12.1 documentation Ok, in and == have the same precedence. The same expression with precedence marked by round brackets:

(
    (
        (re.layers()[0] == re['ICMP'].type)
        in unreachable
    )
    and
    (re['ICMP'].code in filtered)
)

In your code:

  • re.layers()[0] will be either Ether or IP - see above. (Add diagnostic print() calls to your code to see which values you are working with.)
  • re['ICMP'].type will be a number from the ICMP header so comparing those will be always False
  • unreachable is 3 in your code. You cannot test membership in an integer. Does not this code fail with TypeError?
  • Maybe you wanted to compare the type? re['ICMP'].type == unreachable
  • Maybe you wanted unreachable to be a set similarly to filtered? unreachable = {3}