Py-Micro-IPAM For Networking:
About:
Great utility to scan a network or series of subnets to learn available address in a range for IT Infrastructure & Engineering Purposes.
May be useful to add some kind of DNS Query as well to learn about stale or Out-Of-Use Addresses that may become active.
Goal: Find Available Addresses
Requirements:
scapy==2.4.5
psutil==5.9.3
tkinter==0.1.0
# By: MxC
import scapy.all as scapy
import concurrent.futures
import ipaddress
import tkinter as tk
from tkinter import scrolledtext
from time import time
import subprocess
import platform
import psutil
import re
import socket # Import socket for AF_INET
# Function to get all network interfaces
def get_network_interfaces():
return psutil.net_if_addrs()
# Function to obtain the actual IP address of a specific interface (e.g., eth0 or wlan0)
def get_interface_ip(interface_name):
interfaces = psutil.net_if_addrs()
if interface_name in interfaces:
for address in interfaces[interface_name]:
if address.family == socket.AF_INET: # IPv4 address family
return address.address
return None
# Function to get the local IP address using `ifconfig` or `ipconfig`
def get_local_ip():
try:
system = platform.system()
if system == "Linux" or system == "Darwin": # macOS or Linux
result = subprocess.run(["ifconfig"], capture_output=True, text=True)
match = re.search(r"inet (\d+\.\d+\.\d+\.\d+)", result.stdout)
if match:
return match.group(1)
elif system == "Windows": # Windows
result = subprocess.run(["ipconfig"], capture_output=True, text=True)
match = re.search(r"IPv4 Address.*?: (\d+\.\d+\.\d+\.\d+)", result.stdout)
if match:
return match.group(1)
return None
except Exception as e:
return None # If there was an error getting the local IP
# Function to check if IP is loopback
def is_loopback(ip):
return str(ip).startswith("127.") # Convert IP address object to string before checking
# Function to perform a TCP scan (SYN scan) on a given IP and port
def tcp_scan(ip, port=53, interface="Ethernet 4"):
scapy.conf.iface = interface # Set the interface globally before sending packets
pkt = scapy.IP(dst=ip) / scapy.TCP(dport=port, flags="S") # SYN scan
response = scapy.sr1(pkt, timeout=1, verbose=False) # Remove the iface from sr1() and rely on scapy.conf.iface
if response:
if response.haslayer(scapy.TCP) and response[scapy.TCP].flags == 18: # SYN-ACK
return ip, interface, f"Port {port} Open"
return ip, interface, f"Port {port} Closed or No Response"
# Function to perform ARP check with interface selection using sniff
def arp_ip(ip, interface="Ethernet 4"):
scapy.conf.iface = interface # Set the interface globally before sniffing
result = {"ip": ip, "interface": interface, "status": "Error - No ARP response"}
def packet_callback(packet):
# Check if the packet is an ARP reply
if packet.haslayer(scapy.ARP) and packet[scapy.ARP].op == 2: # ARP reply
# If the ARP reply is for the requested IP, return the result
if packet[scapy.ARP].psrc == ip:
result["status"] = "In Use"
return True # Stop sniffing when we get the result
return False
# Start sniffing on the specified interface
scapy.sniff(iface=interface, prn=packet_callback, count=1, timeout=1)
return result["ip"], result["interface"], result["status"]
# Function to perform ICMP ping check with fallback to ARP using sniff
def ping_ip(ip, interface="Ethernet 4"):
scapy.conf.iface = interface # Set the interface globally before sniffing
result = {"ip": ip, "interface": interface, "status": "Error - No ICMP response"}
def packet_callback(packet):
# Check if the packet is an ICMP reply (Echo Reply)
if packet.haslayer(scapy.ICMP) and packet[scapy.ICMP].type == 0: # ICMP Echo Reply
# If the IP matches, return the result
if packet[scapy.IP].src == ip:
result["status"] = "In Use"
return True # Stop sniffing when we get the result
return False
# Start sniffing on the specified interface
scapy.sniff(iface=interface, prn=packet_callback, count=1, timeout=1)
return result["ip"], result["interface"], result["status"]
# Function to scan a network range and check devices
def scan_network(cidr_range, tcp_port, perform_tcp, perform_arp, perform_ping, gui_output, threads):
start_time = time()
ip_list = list(ipaddress.IPv4Network(cidr_range).hosts()) # Generate list of IPs in range
successful_ips = [] # To store successful IP and interface results
# Update the GUI with starting message
gui_output.insert(tk.END, f"Scanning network: {cidr_range}\n")
gui_output.insert(tk.END, f"Total IPs to scan: {len(ip_list)}\n")
gui_output.update()
# Use ThreadPoolExecutor for concurrent scanning
with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
results = []
for ip in ip_list:
# Run ARP scan if selected
if perform_arp:
future_arp = executor.submit(arp_ip, str(ip))
results.append(future_arp)
# Run ICMP ping scan if selected
if perform_ping:
future_ping = executor.submit(ping_ip, str(ip))
results.append(future_ping)
# Run TCP scan if selected
if perform_tcp:
future_tcp = executor.submit(tcp_scan, str(ip), tcp_port)
results.append(future_tcp)
# Wait for all futures to complete and process the results
for future in concurrent.futures.as_completed(results):
ip, interface, status = future.result()
gui_output.insert(tk.END, f"{ip} ({interface}): {status}\n")
if status == "In Use" or "Open" in status:
successful_ips.append((ip, interface)) # Store successful IP and interface
gui_output.update()
end_time = time()
gui_output.insert(tk.END, f"\nScan complete in {end_time - start_time:.2f} seconds.\n")
gui_output.update()
return successful_ips # Return the successful IP and interface pairs
# Function to scan interfaces and display results, excluding APIPA addresses (169.x.x.x)
def on_start_int_click(gui_output):
interfaces = get_network_interfaces()
successful_interfaces = []
for interface_name, interface_addresses in interfaces.items():
for address in interface_addresses:
if address.family == socket.AF_INET: # Use socket.AF_INET to check IPv4
# Exclude APIPA address range (169.254.x.x)
if address.address.startswith("169.254"):
continue # Skip this address and go to the next one
gui_output.insert(tk.END, f"{interface_name} IP Address: {address.address}\n")
successful_interfaces.append((address.address, interface_name)) # Store interface IPs
gui_output.update()
return successful_interfaces
# Create a simple tkinter window for GUI
def create_gui():
window = tk.Tk()
window.title("MxC: IP Address Scanner")
window.geometry("600x670")
# Dark mode color scheme
window.config(bg="#2e2e2e")
# Create a scrolled text area for output
output_text = scrolledtext.ScrolledText(window, width=70, height=20, bg="#2e2e2e", fg="#ffffff", font=("Courier", 10))
output_text.pack(pady=10)
# Create a label and input field for CIDR range
label = tk.Label(window, text="Enter CIDR Range (e.g., 192.168.1.0/24):", bg="#2e2e2e", fg="#ffffff")
label.pack()
cidr_input = tk.Entry(window, width=30, bg="#555555", fg="#ffffff", font=("Courier", 10))
cidr_input.pack()
# Create a label and input field for TCP Port
port_label = tk.Label(window, text="Enter TCP Port (default 53):", bg="#2e2e2e", fg="#ffffff")
port_label.pack()
port_input = tk.Entry(window, width=30, bg="#555555", fg="#ffffff", font=("Courier", 10))
port_input.insert(0, "53") # Default value
port_input.pack()
# Create a label and input field for TCP Port
thread_label = tk.Label(window, text="Enter Thread Count #:", bg="#2e2e2e", fg="#ffffff")
thread_label.pack()
thread_input = tk.Entry(window, width=30, bg="#555555", fg="#ffffff", font=("Courier", 10))
thread_input.insert(0, "10") # Default value
thread_input.pack()
# Add checkboxes for TCP, ICMP, and ARP
tcp_var = tk.BooleanVar(value=True) # Default checked for TCP
icmp_var = tk.BooleanVar(value=True) # Default checked for ICMP
arp_var = tk.BooleanVar(value=True) # Default checked for ARP
tcp_checkbox = tk.Checkbutton(window, text="TCP Scan", variable=tcp_var, bg="#2e2e2e", fg="#ffffff")
tcp_checkbox.pack()
icmp_checkbox = tk.Checkbutton(window, text="ICMP Ping", variable=icmp_var, bg="#2e2e2e", fg="#ffffff")
icmp_checkbox.pack()
arp_checkbox = tk.Checkbutton(window, text="ARP Scan", variable=arp_var, bg="#2e2e2e", fg="#ffffff")
arp_checkbox.pack()
# Get and display local IP on start
local_ip = get_local_ip()
output_text.insert(tk.END, f"Local IP Address: {local_ip if local_ip else 'Could not determine local IP address.'}\n")
# Start button
def on_start_click():
cidr_range = cidr_input.get()
tcp_port = int(port_input.get()) # Get the TCP port from the input field
perform_tcp = tcp_var.get()
perform_ping = icmp_var.get()
perform_arp = arp_var.get()
perf_thread = int(thread_input.get())
if cidr_range:
successful_ips = scan_network(cidr_range, tcp_port, perform_tcp, perform_arp, perform_ping, output_text, perf_thread )
# Output successful IPs and interfaces
output_text.insert(tk.END, "\nSuccessful IPs and Interfaces:\n")
for ip, interface in successful_ips:
output_text.insert(tk.END, f"{ip} on {interface}\n")
output_text.update()
start_button = tk.Button(window, text="Start Scan", command=on_start_click, bg="#555555", fg="#ffffff", font=("Courier", 12))
start_button.pack(pady=10)
start_int_button = tk.Button(window, text="Start Interface Scan", command=lambda: on_start_int_click(output_text), bg="#555555", fg="#ffffff", font=("Courier", 12))
start_int_button.pack(pady=20)
window.mainloop()
if __name__ == "__main__":
create_gui()