Agregado del tiempo de cada ping a la lista

This commit is contained in:
Miguel 2025-04-04 22:58:28 +02:00
parent ee37a10a97
commit 8f7bd969de
1 changed files with 211 additions and 80 deletions

View File

@ -173,6 +173,9 @@ class IPChangerApp:
self.continuous_ping = tk.BooleanVar(value=False)
self.ping_running = False
self.ping_stop_event = threading.Event()
self.last_ping_time = tk.StringVar(
value="N/A"
) # Add variable for last ping time
# Variables for IP scanning
self.scan_results = []
@ -263,7 +266,32 @@ class IPChangerApp:
self.log_frame = ttk.LabelFrame(self.master, text="Operation Log", padding="5")
self.log_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=(0, 10))
self.log_text = tk.Text(self.log_frame, wrap="none", height=10)
# Use a better font and enable tag configuration
log_font = (
"Consolas",
10,
) # Use a monospaced font like Consolas or Courier New
self.log_text = tk.Text(self.log_frame, wrap="none", height=10, font=log_font)
# Create tags for different message types with specific formatting
self.log_text.tag_configure("INFO", foreground="blue")
self.log_text.tag_configure(
"ERROR", foreground="red", font=(log_font[0], log_font[1], "bold")
)
self.log_text.tag_configure(
"SUCCESS", foreground="green", font=(log_font[0], log_font[1], "bold")
)
self.log_text.tag_configure("WARN", foreground="orange")
self.log_text.tag_configure("PING", foreground="purple")
self.log_text.tag_configure(
"DISCOVERY", foreground="navy", font=(log_font[0], log_font[1], "bold")
)
self.log_text.tag_configure("HOSTNAME", foreground="teal")
self.log_text.tag_configure("MAC", foreground="maroon")
self.log_text.tag_configure("COMMAND", foreground="sienna")
self.log_text.tag_configure("VENDOR", foreground="#8B008B") # Dark magenta
# Continue with existing scrollbar setup
self.log_scrollbar_y = ttk.Scrollbar(
self.log_frame, orient="vertical", command=self.log_text.yview
)
@ -459,7 +487,7 @@ class IPChangerApp:
1, weight=1
) # Make the scan frame expandable
# Network Tools section (moved from original layout)
# Network Tools section (ping tool)
self.ping_frame = ttk.LabelFrame(
self.tools_control_frame, text="Ping Tool", padding="5"
)
@ -479,17 +507,27 @@ class IPChangerApp:
# Bind the FocusOut event to save the ping target
self.ping_entry.bind("<FocusOut>", self.save_current_ping_target)
# Add last ping time display field
ttk.Label(self.ping_frame, text="Last RTT:").grid(
row=0, column=2, sticky="w", padx=5
)
self.last_ping_time = tk.StringVar(value="N/A")
ping_time_display = ttk.Entry(
self.ping_frame, textvariable=self.last_ping_time, width=8, state="readonly"
)
ping_time_display.grid(row=0, column=3, padx=5)
# Add continuous ping checkbox
self.continuous_ping_check = ttk.Checkbutton(
self.ping_frame, text="Continuous Ping", variable=self.continuous_ping
)
self.continuous_ping_check.grid(row=0, column=2, padx=5)
self.continuous_ping_check.grid(row=0, column=4, padx=5)
ttk.Button(self.ping_frame, text="Ping", command=self.do_ping).grid(
row=0, column=3, padx=5
row=0, column=5, padx=5
)
ttk.Button(self.ping_frame, text="Stop", command=self.stop_ping).grid(
row=0, column=4, padx=5
row=0, column=6, padx=5
)
# Network Scan section - make it expand both horizontally and vertically
@ -586,29 +624,33 @@ class IPChangerApp:
# Replace Listbox with Treeview that has columns - make sure it expands
self.scan_results_tree = ttk.Treeview(
self.results_frame,
columns=("ip", "hostname", "mac", "vendor"), # Add vendor column
columns=("ip", "delay", "hostname", "mac", "vendor"), # Added delay column
show="headings",
height=10,
)
# Define columns
self.scan_results_tree.heading("ip", text="IP Address")
self.scan_results_tree.heading(
"delay", text="Ping (ms)"
) # New column for ping delay
self.scan_results_tree.heading("hostname", text="Hostname")
self.scan_results_tree.heading("mac", text="MAC Address")
self.scan_results_tree.heading(
"vendor", text="MAC Vendor"
) # Add vendor heading
self.scan_results_tree.heading("vendor", text="MAC Vendor")
# Set column widths
# Set column widths with adjusted proportions
total_width = 650 # Base total width for the columns
self.scan_results_tree.column(
"ip", width=int(total_width * 0.18), anchor="w", stretch=True
"ip", width=int(total_width * 0.15), anchor="w", stretch=True
)
self.scan_results_tree.column(
"hostname", width=int(total_width * 0.30), anchor="w", stretch=True
"delay", width=int(total_width * 0.08), anchor="center", stretch=True
)
self.scan_results_tree.column(
"mac", width=int(total_width * 0.22), anchor="w", stretch=True
"hostname", width=int(total_width * 0.27), anchor="w", stretch=True
)
self.scan_results_tree.column(
"mac", width=int(total_width * 0.20), anchor="w", stretch=True
)
self.scan_results_tree.column(
"vendor", width=int(total_width * 0.30), anchor="w", stretch=True
@ -627,8 +669,52 @@ class IPChangerApp:
def clear_log(self):
self.log_text.delete("1.0", tk.END)
def log_message(self, message: str):
self.log_text.insert(tk.END, f"{message}\n")
def log_message(self, message: str, tag: str = None):
"""Enhanced log function with tags for color and formatting"""
# Try to automatically detect message type if tag not specified
if tag is None:
if "ERROR:" in message or "Error" in message or "failed" in message.lower():
tag = "ERROR"
elif (
"SUCCESS" in message
or "Successfully" in message
or "completed" in message
):
tag = "SUCCESS"
elif "WARNING" in message or "Warn" in message:
tag = "WARN"
elif "Host discovered:" in message:
tag = "DISCOVERY"
elif (
"Reply from" in message
or "ping" in message.lower()
or "Request timed out" in message
):
tag = "PING"
elif "Hostname:" in message:
tag = "HOSTNAME"
elif "MAC Address:" in message:
tag = "MAC"
elif "MAC Vendor:" in message or "Vendor:" in message:
tag = "VENDOR"
elif "Executing" in message and "command" in message.lower():
tag = "COMMAND"
elif "INFO:" in message:
tag = "INFO"
else:
tag = None
# Insert the timestamp and message
timestamp = time.strftime("%H:%M:%S", time.localtime())
self.log_text.insert(tk.END, f"[{timestamp}] ", "timestamp")
# Insert the message with appropriate tag
if tag:
self.log_text.insert(tk.END, f"{message}\n", tag)
else:
self.log_text.insert(tk.END, f"{message}\n")
# Scroll to see the latest message
self.log_text.see(tk.END)
self.log_text.update_idletasks()
@ -1017,6 +1103,7 @@ class IPChangerApp:
self.log_message("Stopping continuous ping...")
self.ping_stop_event.set()
self.ping_running = False
self.last_ping_time.set("N/A") # Reset when stopped
def _execute_continuous_ping(self, target: str):
"""Execute continuous ping until stopped"""
@ -1039,13 +1126,17 @@ class IPChangerApp:
for reply in response:
if reply.success:
rtt = reply.time_elapsed_ms
# Update the last ping time field
self.last_ping_time.set(f"{rtt:.1f} ms")
self.log_message(
f"Reply from {target}: time={rtt:.2f}ms (seq={count})"
)
break
else:
self.last_ping_time.set("Timeout")
self.log_message(f"Request timed out (seq={count})")
except Exception as ping_error:
self.last_ping_time.set("Error")
self.log_message(f"Ping error: {str(ping_error)} (seq={count})")
else:
# Fallback to socket
@ -1058,14 +1149,18 @@ class IPChangerApp:
s.close()
if result == 0:
# Update the last ping time field
self.last_ping_time.set(f"{elapsed_time:.1f} ms")
self.log_message(
f"Connected to {target}:80 in {elapsed_time:.2f}ms (seq={count})"
)
else:
self.last_ping_time.set("Failed")
self.log_message(
f"Failed to connect to {target}:80 (seq={count})"
)
except Exception as socket_error:
self.last_ping_time.set("Error")
self.log_message(
f"Connection error: {str(socket_error)} (seq={count})"
)
@ -1086,23 +1181,40 @@ class IPChangerApp:
self.log_message(f"Error in continuous ping: {str(e)}")
finally:
self.ping_running = False
self.last_ping_time.set("N/A") # Reset on stop
def _execute_ping(self, target: str):
"""Execute a single ping and display the results"""
try:
self.log_message(f"Pinging {target} with 4 echo requests...")
# Reset the last ping time to "Testing..."
self.last_ping_time.set("Testing...")
self.log_message(f"Pinging {target} with 4 echo requests...", "PING")
if PING_LIBRARY_AVAILABLE:
# Use PythonPing library
response = pythonping(target, count=4, timeout=1)
# Display individual replies
# Display individual replies with PING tag
total_rtt = 0
successful_pings = 0
for i, reply in enumerate(response):
if reply.success:
rtt = reply.time_elapsed_ms
self.log_message(f"Reply from {target}: time={rtt:.2f}ms")
total_rtt += rtt
successful_pings += 1
self.log_message(
f"Reply from {target}: time={rtt:.2f}ms", "PING"
)
# Update the last ping time with the most recent successful ping
self.last_ping_time.set(f"{rtt:.1f} ms")
else:
self.log_message(f"Request timed out (seq={i+1})")
self.log_message(f"Request timed out (seq={i+1})", "PING")
# If all pings failed, update the last ping time appropriately
if successful_pings == 0:
self.last_ping_time.set("Timeout")
# Display statistics
success_count = sum(1 for r in response if r.success)
@ -1179,14 +1291,15 @@ class IPChangerApp:
vendor = self.get_mac_vendor(mac_address)
if hostname:
self.log_message(f"Hostname: {hostname}")
self.log_message(f"Hostname: {hostname}", "HOSTNAME")
if mac_address:
self.log_message(f"MAC Address: {mac_address}")
self.log_message(f"MAC Address: {mac_address}", "MAC")
if vendor:
self.log_message(f"MAC Vendor: {vendor}")
self.log_message(f"MAC Vendor: {vendor}", "VENDOR")
except Exception as e:
self.log_message(f"Error executing ping: {str(e)}")
self.last_ping_time.set("Error")
self.log_message(f"Error executing ping: {str(e)}", "ERROR")
def start_scan(self):
"""Start network scanning between IP range"""
@ -1291,7 +1404,7 @@ class IPChangerApp:
self.scan_running = False
def _scan_worker(self):
"""Worker thread function to scan IPs from the queue - now only performs ping scan"""
"""Worker thread function to scan IPs from the queue - now with ping time recording"""
while not self.scan_stop_event.is_set():
try:
# Get next IP from queue with timeout
@ -1301,18 +1414,24 @@ class IPChangerApp:
# No more IPs to scan
break
# Ping the IP
is_active = self._ping_host(str(ip_address))
# Ping the IP and measure the time
ping_time = 0
ip_str = str(ip_address)
is_active, ping_delay = self._ping_host_with_time(ip_str)
# If active, add to results - but don't get host info yet
if is_active:
ip_str = str(ip_address)
self.log_message(f"Host discovered: {ip_str}")
self.log_message(
f"Host discovered: {ip_str} ({ping_delay:.1f}ms)", "DISCOVERY"
)
self.scan_results.append(ip_str)
# Add to results tree with placeholder values
# Add to results tree with placeholder values and ping time
self.master.after(
0, lambda ip=ip_str: self.add_scan_result(ip, "", "", "")
0,
lambda ip=ip_str, delay=ping_delay: self.add_scan_result(
ip, delay, "", "", ""
),
)
# Update progress
@ -1338,7 +1457,7 @@ class IPChangerApp:
"""Perform the actual host information gathering"""
try:
self.log_message(
f"Gathering information for {len(self.scan_results)} hosts..."
f"Gathering information for {len(self.scan_results)} hosts...", "INFO"
)
# Reset progress bar for this operation
@ -1349,7 +1468,7 @@ class IPChangerApp:
# Get information for each host
for i, ip in enumerate(self.scan_results):
if self.scan_stop_event.is_set():
self.log_message("Host information gathering stopped.")
self.log_message("Host information gathering stopped.", "WARN")
break
self.log_message(
@ -1364,21 +1483,20 @@ class IPChangerApp:
# Look up vendor information if we have a MAC address
if mac_address and MAC_LOOKUP_AVAILABLE:
vendor = self.get_mac_vendor(mac_address)
if vendor:
self.log_message(f" MAC Vendor: {vendor}")
else:
self.log_message(f" MAC Vendor: Unknown")
# Log what we found
# Log the results in a cleaner format
result_parts = []
if hostname:
self.log_message(f" Hostname: {hostname}")
else:
self.log_message(f" Hostname: Not resolved")
result_parts.append(f"Hostname: {hostname}")
if mac_address:
self.log_message(f" MAC Address: {mac_address}")
result_parts.append(f"MAC: {mac_address}")
if vendor:
result_parts.append(f"Vendor: {vendor}")
if result_parts:
self.log_message(f" {ip}" + " | ".join(result_parts))
else:
self.log_message(f" MAC Address: Not found")
self.log_message(f" {ip} → No additional information found")
# Update the tree with vendor information
self.update_host_in_tree(ip, hostname, mac_address, vendor)
@ -1386,10 +1504,10 @@ class IPChangerApp:
# Update progress
self.scan_progress_var.set(i + 1)
self.log_message("Host information gathering completed.")
self.log_message("Host information gathering completed.", "SUCCESS")
except Exception as e:
self.log_message(f"Error gathering host information: {str(e)}")
self.log_message(f"Error gathering host information: {str(e)}", "ERROR")
def update_host_in_tree(self, ip, hostname, mac, vendor=""):
"""Update an existing host in the treeview with obtained information"""
@ -1398,46 +1516,66 @@ class IPChangerApp:
for item_id in self.scan_results_tree.get_children():
values = self.scan_results_tree.item(item_id, "values")
if values and values[0] == ip:
# Update the values
# Get the current delay value (to preserve it)
delay = values[1] if len(values) > 1 else ""
# Update the values, preserving the delay value
hostname_str = hostname if hostname else "Not resolved"
mac_str = mac if mac else "Not found"
mac_str = (
mac.upper() if mac else "Not found"
) # Ensure uppercase MAC
vendor_str = vendor if vendor else "Unknown"
self.scan_results_tree.item(
item_id, values=(ip, hostname_str, mac_str, vendor_str)
item_id, values=(ip, delay, hostname_str, mac_str, vendor_str)
)
break
except Exception as e:
self.log_message(f"Error updating host in treeview: {str(e)}")
def add_scan_result(self, ip, hostname, mac, vendor=""):
def add_scan_result(self, ip, delay=0, hostname="", mac="", vendor=""):
"""Helper method to add scan result to the treeview"""
try:
# Insert the item with all values, even if some are empty
# Insert the item with all values
hostname = hostname if hostname else "Not resolved"
mac = mac if mac else "Not found"
vendor = vendor if vendor else "Unknown"
delay_str = f"{delay:.1f}" if delay > 0 else ""
self.scan_results_tree.insert("", "end", values=(ip, hostname, mac, vendor))
self.scan_results_tree.insert(
"", "end", values=(ip, delay_str, hostname, mac, vendor)
)
except Exception as e:
self.log_message(f"Error adding scan result to UI: {str(e)}")
def _ping_host(self, host: str) -> bool:
"""Ping a host to check if it's active, returns True if host responds"""
def _ping_host_with_time(self, host: str) -> tuple[bool, float]:
"""Ping a host to check if it's active, returns (success, delay_in_ms)"""
try:
if PING_LIBRARY_AVAILABLE:
# Use PythonPing with shorter timeout for scanning
start_time = time.time()
response = pythonping(host, count=1, timeout=0.5)
return response.success()
ping_time = 0
if response.success():
# Get the actual ping time from the response
for reply in response:
if reply.success:
ping_time = reply.time_elapsed_ms
break
return True, ping_time
return False, 0
else:
# Fallback using socket
# Fallback using socket with timing
start_time = time.time()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
result = s.connect_ex((host, 80)) # Try common port
ping_time = (time.time() - start_time) * 1000 # ms
s.close()
return result == 0
return result == 0, ping_time if result == 0 else 0
except:
return False
return False, 0
def set_static_ip(self):
interface = self.if_var.get()
@ -1553,11 +1691,11 @@ class IPChangerApp:
self.show_error("Failed to elevate privileges")
def show_error(self, message: str):
self.log_message(f"ERROR: {message}")
self.log_message(f"ERROR: {message}", "ERROR")
messagebox.showerror("Error", message)
def show_info(self, message: str):
self.log_message(f"INFO: {message}")
self.log_message(f"INFO: {message}", "INFO")
messagebox.showinfo("Information", message)
# Add new methods for subnet mask conversion
@ -1782,22 +1920,17 @@ class IPChangerApp:
def get_hostname(self, ip_address):
"""Try to resolve hostname for an IP address with better error handling"""
try:
self.log_message(f"Resolving hostname for {ip_address}...")
# Silently try to resolve without logging the attempt
hostname, _, _ = socket.gethostbyaddr(ip_address)
return hostname
except socket.herror as e:
self.log_message(f"Hostname resolution error for {ip_address}: {e}")
except socket.herror:
# Don't log the error details - just return empty
return ""
except socket.gaierror as e:
self.log_message(f"Address-related error for {ip_address}: {e}")
except socket.gaierror:
return ""
except socket.timeout:
self.log_message(f"Timeout resolving hostname for {ip_address}")
return ""
except Exception as e:
self.log_message(
f"Unknown error resolving hostname for {ip_address}: {str(e)}"
)
except Exception:
return ""
def get_mac_address(self, ip_address):
@ -1813,10 +1946,7 @@ class IPChangerApp:
)
if result.returncode == 0 and result.stdout:
# Log the raw output for debugging
self.log_message(
f"ARP output for {ip_address}: {result.stdout.strip()}"
)
# Don't log the raw ARP output anymore
# Parse the output to find the MAC address
# First try the typical format with hyphens or colons
@ -1826,7 +1956,8 @@ class IPChangerApp:
re.IGNORECASE,
)
if mac_match:
return mac_match.group(0)
# Convert to uppercase for consistent display
return mac_match.group(0).upper()
# Try an alternative format with spaces (Windows format)
mac_match = re.search(
@ -1835,7 +1966,8 @@ class IPChangerApp:
re.IGNORECASE,
)
if mac_match:
return mac_match.group(0)
# Convert to uppercase for consistent display
return mac_match.group(0).upper()
# Try yet another approach - look for any string of hex digits with separators
lines = result.stdout.strip().split("\n")
@ -1850,15 +1982,14 @@ class IPChangerApp:
parts[1],
re.IGNORECASE,
):
return parts[1]
# Convert to uppercase for consistent display
return parts[1].upper()
self.log_message(f"No MAC address found for {ip_address}")
# Only return empty string if MAC not found - don't log the failure
return ""
except subprocess.TimeoutExpired:
self.log_message(f"Timeout running ARP command for {ip_address}")
return ""
except Exception as e:
self.log_message(f"Error getting MAC address for {ip_address}: {str(e)}")
except Exception:
return ""
def get_mac_vendor(self, mac_address):