I am attempting to create a subnet scanner in python. The first part of the code takes input from a user and finds the subnet based on the input. The second part takes the subnet and goes through all 255 hosts.
The problem is, the code freezes when it tries to scan a host that is down.
main.py
import os
import socket
def spread():
ip_list = []
lst = []
new_ip = []
ip = input("Enter any ip on your network: ")
for ch in range(0, len(ip)):
new_ip = ip[ch]
if ip[ch] == ".":
print(ip[ch])
lst.append(ch)
t = True
while t == True:
try:
new_ip.pop(lst[2] 1)
except:
t = False
print(new_ip)
break
else:
pass
target = ""
for char in new_ip:
target = char
print(target)
#print(f"{ip} == {new_ip}")
for i in range(1,255):
print("socket initialized")
from portscanner import scanner as scan
for port in range(1,1025):
try:
scan(target str(i))
#HERE IS WHERE THE CODE FREEZES
except:
continue
else:
pass
portscanner.py
def scanner(ip):
for port in range(2, 1025):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.setdefaulttimeout(0.002)
# returns an error indicator
try:
result = s.connect_ex((ip, port))
#More specifically, here is where the code freezes
except:
continue
else:
pass
if result == 0:
print("Port {} is open".format(port))
else:
print("Port {} is CLOSED on ".format(port) ip)
continue
#print(port)
s.close()
My theory is that the code freezes because the host I am trying to connect to is down. How can I work around this and/or check to see if a host is up?
CodePudding user response:
First of all, no one would answer you in 2 ms (0.002 seconds). The correct way would be to use asyncio / multithreading / nonblocking select, and spread over the ports concurrently while waiting a little longer.
Second of all, as scanner
doesn't receive a port number, for every port between 1 and 1024 you scan every port between 2 and 1024. See the issue here?
Third of all, you set the default timeout for all sockets in every port scan. Why?
Fourth of all, you can use ipaddress.ip_address
to find a network or at least use str.rindex
to find the last byte and strip it off instead of the for
and while
loops.
Last of all, except: continue
means you just ignore all exceptions, some of them might be relevant. I'd suggest printing such errors so you will be able to further debug the problem, or at least change the result to -1 so it'll show it's closed. It also prevents you from using ctrl c
to stop the program.
I've refactored your code a little, and it works:
from ipaddress import ip_network
import socket
def main():
socket.setdefaulttimeout(0.4)
network = ip_network(input("Enter an IPv4 network: "))
for ip in network.hosts():
scan_ip(ip)
def scan_ip(ip):
for port in range(1, 1025):
with socket.socket() as s:
try:
result = s.connect_ex((str(ip), port))
except Exception as e:
print(f"Error on {ip=!s}, {port=}: {e}")
else:
if result == 0:
print(f"Port {port} is open on {ip}")
else:
print(f"Port {port} is CLOSED on {ip}")
Usage:
>>> main()
Enter an IPv4 network: 1.2.3.0/24
...
I leave the multithreading/asyncio for you to implement.
CodePudding user response:
First off, you do realize that an IPv4 subnet can have more or less than 255 hosts, don't you? An IPv4 address alone is not enough, you also need the IP's associated subnet mask, too. The way you are determining the host IPs on the subnet is not reliable. You are assuming the user's subnet mask is always 255.255.255.0
, but that is simply not always the case.
You need to AND
the user's IP with the subnet mask, that gets you the 1st host IP on the subnet. And then invert the mask with NOT
and OR
that with the AND
'ed IP, that gets you the last host IP on the subnet. Then you can loop through all of the IPs in between.
In any case, regarding your freezing issue, the reason is because you are simply not applying any kind of timeout to the socket.connect_ex()
call. You think you are, but your use of socket.setdefaulttimeout()
has no effect on socket.connect_ex()
(neither does socket.settimeout()
, for that matter). If a host is down, it may not be possible for the OS to determine that in a timely manner, hence the socket blocks for awhile.
A socket operates in blocking mode by default. That mode simply has no timeout capability on a connect operation. You need to instead use socket.setblocking(false)
to put the socket into non-blocking mode, then start a connect/_ex()
operation and check if fails with an EWOULDBLOCK
or EAGAIN
error, and if so then use select.select()
or select.(e)poll()
to wait up to a desired timeout interval for the operation to finish. If the timeout elapses, treat the connection as failed. Otherwise, use socket.getsockopt()
to retrieve the socket's SO_ERROR
error code to check if the connection was successful or not.
The other benefit of using non-blocking sockets is that you can then create and connect multiple sockets in parallel, instead of only 1 at a time as you are doing right now. That will greatly speed up your scanning capacity.