IPchangeNG/menu-ip-change.py

551 lines
22 KiB
Python

import tkinter as tk
from tkinter import ttk, messagebox
import json
import os
import subprocess
import re
import ctypes
import sys
import ipaddress
from typing import List, Optional
from dataclasses import dataclass
import threading
import time
try:
import wmi
WMI_AVAILABLE = True
except ImportError:
WMI_AVAILABLE = False
print("WMI module not found. Using alternative method for network interface detection.")
@dataclass
class NetworkInterface:
name: str
description: str
ip_address: str = ""
subnet_mask: str = ""
gateway: str = ""
dhcp_enabled: bool = True
active: bool = True
adapter_id: str = ""
class IPChangeConfig:
def __init__(self):
self.config_file = "ip_config.json"
self.history_file = "ip_history.json"
self.load_config()
def load_config(self):
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r') as f:
config = json.load(f)
self.last_interface = config.get('last_interface', '')
self.last_ip_prefix = config.get('last_ip_prefix', '')
else:
self.last_interface = ''
self.last_ip_prefix = ''
except Exception as e:
print(f"Error loading config: {e}")
self.last_interface = ''
self.last_ip_prefix = ''
def save_config(self):
try:
config = {
'last_interface': self.last_interface,
'last_ip_prefix': self.last_ip_prefix
}
with open(self.config_file, 'w') as f:
json.dump(config, f)
except Exception as e:
print(f"Error saving config: {e}")
class IPChangerApp:
def __init__(self, master):
self.master = master
self.master.title("Network Interface IP Configuration")
self.master.geometry("900x700")
# Inicializar configuración antes que nada
self.config = IPChangeConfig()
# Inicializar estructuras de datos
self.interfaces: List[NetworkInterface] = []
self.ip_history: List[str] = []
# Inicializar WMI si está disponible
if WMI_AVAILABLE:
self.wmi_client = wmi.WMI()
# Cargar datos guardados antes de crear widgets
self.load_ip_history()
# Crear la interfaz
self.create_widgets()
# Actualizar la lista de IPs en el combo
self.update_history_display()
# Refrescar interfaces
self.refresh_interfaces()
# Configurar pesos de la cuadrícula
self.master.grid_columnconfigure(0, weight=1)
self.master.grid_rowconfigure(1, weight=1) # El log expandible
def get_ip_prefix(self, ip: str) -> str:
"""Extrae los primeros 3 octetos de una dirección IP"""
parts = ip.split('.')
if len(parts) >= 3:
return '.'.join(parts[:3])
return ip
def create_widgets(self):
# Panel superior para controles
self.control_frame = ttk.Frame(self.master, padding="10")
self.control_frame.grid(row=0, column=0, sticky='new')
# Sección de interfaces
self.interfaces_frame = ttk.LabelFrame(self.control_frame, text="Network Interfaces", padding="5")
self.interfaces_frame.grid(row=0, column=0, sticky='ew', pady=(0,10))
ttk.Label(self.interfaces_frame, text="Select Interface:").grid(row=0, column=0, sticky='w', padx=5)
self.if_var = tk.StringVar()
self.if_combo = ttk.Combobox(self.interfaces_frame, textvariable=self.if_var, state='readonly', width=50)
self.if_combo.grid(row=0, column=1, sticky='ew', padx=5)
self.if_combo.bind('<<ComboboxSelected>>', self.on_interface_selected)
# Info de configuración actual
self.current_frame = ttk.LabelFrame(self.control_frame, text="Current Configuration", padding="5")
self.current_frame.grid(row=1, column=0, sticky='ew', pady=(0,10))
self.current_ip_var = tk.StringVar()
self.current_mask_var = tk.StringVar()
self.current_gateway_var = tk.StringVar()
self.dhcp_status_var = tk.StringVar()
ttk.Label(self.current_frame, text="Current IP:").grid(row=0, column=0, sticky='w', padx=5)
ttk.Label(self.current_frame, textvariable=self.current_ip_var).grid(row=0, column=1, sticky='w')
ttk.Label(self.current_frame, text="Subnet Mask:").grid(row=1, column=0, sticky='w', padx=5)
ttk.Label(self.current_frame, textvariable=self.current_mask_var).grid(row=1, column=1, sticky='w')
ttk.Label(self.current_frame, text="Gateway:").grid(row=2, column=0, sticky='w', padx=5)
ttk.Label(self.current_frame, textvariable=self.current_gateway_var).grid(row=2, column=1, sticky='w')
ttk.Label(self.current_frame, text="DHCP Status:").grid(row=3, column=0, sticky='w', padx=5)
ttk.Label(self.current_frame, textvariable=self.dhcp_status_var).grid(row=3, column=1, sticky='w')
# Sección de configuración IP
self.ip_frame = ttk.LabelFrame(self.control_frame, text="IP Configuration", padding="5")
self.ip_frame.grid(row=2, column=0, sticky='ew', pady=(0,10))
ttk.Label(self.ip_frame, text="IP Prefix (first 3 octets):").grid(row=0, column=0, sticky='w', padx=5)
self.ip_prefix = tk.StringVar(value=self.config.last_ip_prefix)
self.ip_entry = ttk.Entry(self.ip_frame, textvariable=self.ip_prefix, width=30)
self.ip_entry.grid(row=0, column=1, sticky='w', padx=5)
ttk.Label(self.ip_frame, text="Last Octet:").grid(row=0, column=2, sticky='w', padx=5)
self.last_octet = tk.StringVar(value="249")
self.last_octet_entry = ttk.Entry(self.ip_frame, textvariable=self.last_octet, width=5)
self.last_octet_entry.grid(row=0, column=3, sticky='w', padx=5)
# Sección de historial
self.history_frame = ttk.LabelFrame(self.control_frame, text="IP History", padding="5")
self.history_frame.grid(row=3, column=0, sticky='ew', pady=(0,10))
ttk.Label(self.history_frame, text="Previous IPs:").grid(row=0, column=0, sticky='w', padx=5)
self.history_var = tk.StringVar()
self.history_combo = ttk.Combobox(
self.history_frame,
textvariable=self.history_var,
state='readonly',
width=50
)
self.history_combo.grid(row=0, column=1, sticky='ew', padx=5)
self.history_combo.bind('<<ComboboxSelected>>', self.on_history_selected)
# Botones de acción
self.button_frame = ttk.Frame(self.control_frame)
self.button_frame.grid(row=4, column=0, sticky='ew', pady=(0,10))
ttk.Button(self.button_frame, text="Set Static IP", command=self.set_static_ip).grid(row=0, column=0, padx=5)
ttk.Button(self.button_frame, text="Enable DHCP", command=self.set_dhcp).grid(row=0, column=1, padx=5)
ttk.Button(self.button_frame, text="Refresh Interfaces", command=self.refresh_interfaces).grid(row=0, column=2, padx=5)
# Log en la parte inferior
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)
self.log_scrollbar_y = ttk.Scrollbar(self.log_frame, orient="vertical", command=self.log_text.yview)
self.log_scrollbar_x = ttk.Scrollbar(self.log_frame, orient="horizontal", command=self.log_text.xview)
self.log_text.configure(
yscrollcommand=self.log_scrollbar_y.set,
xscrollcommand=self.log_scrollbar_x.set
)
self.log_text.grid(row=0, column=0, sticky='nsew')
self.log_scrollbar_y.grid(row=0, column=1, sticky='ns')
self.log_scrollbar_x.grid(row=1, column=0, sticky='ew')
ttk.Button(self.log_frame, text="Clear Log", command=self.clear_log).grid(row=2, column=0, columnspan=2, pady=5)
self.log_frame.grid_columnconfigure(0, weight=1)
self.log_frame.grid_rowconfigure(0, weight=1)
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")
self.log_text.see(tk.END)
self.log_text.update_idletasks()
def load_ip_history(self):
try:
if os.path.exists(self.config.history_file):
with open(self.config.history_file, 'r') as f:
self.ip_history = json.load(f)
else:
self.ip_history = []
except Exception as e:
self.log_message(f"Error loading IP history: {e}")
self.ip_history = []
def save_ip_history(self):
try:
with open(self.config.history_file, 'w') as f:
json.dump(self.ip_history, f)
except Exception as e:
self.log_message(f"Error saving IP history: {e}")
def add_to_history(self, ip_prefix: str):
if ip_prefix and ip_prefix not in self.ip_history:
self.ip_history.insert(0, ip_prefix)
self.ip_history = self.ip_history[:15] # Mantener solo las últimas 15 IPs
self.save_ip_history()
self.update_history_display()
def update_history_display(self):
self.history_combo['values'] = self.ip_history
if self.ip_history:
self.history_combo.set(self.ip_history[0])
def on_history_selected(self, event):
selected_ip = self.history_var.get()
if selected_ip:
# Ensure we only get the first three octets
ip_parts = selected_ip.split('.')
if len(ip_parts) >= 3:
ip_prefix = '.'.join(ip_parts[:3])
self.ip_prefix.set(ip_prefix)
self.config.last_ip_prefix = ip_prefix
self.config.save_config()
self.log_message(f"Selected IP prefix from history: {ip_prefix}")
def refresh_interfaces(self):
self.interfaces.clear()
try:
if WMI_AVAILABLE:
self._refresh_interfaces_wmi()
else:
self._refresh_interfaces_netsh()
# Actualizar combobox
self.if_combo['values'] = [inf.name for inf in self.interfaces if inf.active]
# Seleccionar última interfaz usada si está disponible
if self.config.last_interface in self.if_combo['values']:
self.if_combo.set(self.config.last_interface)
self.on_interface_selected()
self.log_message("Interfaces refreshed successfully")
except Exception as e:
self.log_message(f"Error refreshing interfaces: {str(e)}")
self.show_error(f"Error refreshing interfaces: {str(e)}")
def _refresh_interfaces_wmi(self):
try:
adapters = self.wmi_client.Win32_NetworkAdapter(PhysicalAdapter=True)
configs = self.wmi_client.Win32_NetworkAdapterConfiguration()
for adapter in adapters:
config = next((cfg for cfg in configs if cfg.InterfaceIndex == adapter.InterfaceIndex), None)
if config and config.IPEnabled:
interface = NetworkInterface(
name=adapter.NetConnectionID,
description=adapter.Description,
ip_address=config.IPAddress[0] if config.IPAddress else "",
subnet_mask=config.IPSubnet[0] if config.IPSubnet else "",
gateway=config.DefaultIPGateway[0] if config.DefaultIPGateway else "",
dhcp_enabled=config.DHCPEnabled,
active=adapter.NetEnabled,
adapter_id=adapter.GUID
)
self.interfaces.append(interface)
except Exception as e:
raise Exception(f"WMI refresh error: {str(e)}")
def _refresh_interfaces_netsh(self):
try:
output = subprocess.check_output("netsh interface ipv4 show config", shell=True, text=True)
configs = output.split("\n\n")
for config in configs:
if not config.strip():
continue
name_match = re.search(r"Configuración de\s+\"(.+?)\"", config)
if not name_match:
continue
name = name_match.group(1)
ip_match = re.search(r"Dirección IP:\s+(\d+\.\d+\.\d+\.\d+)", config)
mask_match = re.search(r"Máscara de subred:\s+(\d+\.\d+\.\d+\.\d+)", config)
gateway_match = re.search(r"Puerta de enlace predeterminada:\s+(\d+\.\d+\.\d+\.\d+)", config)
dhcp_match = re.search(r"DHCP habilitado:\s+(\w+)", config)
interface = NetworkInterface(
name=name,
description=name,
ip_address=ip_match.group(1) if ip_match else "",
subnet_mask=mask_match.group(1) if mask_match else "",
gateway=gateway_match.group(1) if gateway_match else "",
dhcp_enabled=dhcp_match.group(1).lower() == "" if dhcp_match else True,
active=True
)
self.interfaces.append(interface)
except subprocess.CalledProcessError as e:
raise Exception(f"Error executing netsh: {str(e)}")
except Exception as e:
raise Exception(f"Error parsing netsh output: {str(e)}")
def on_interface_selected(self, event=None):
selected = self.if_var.get()
interface = next((inf for inf in self.interfaces if inf.name == selected), None)
if interface:
self.current_ip_var.set(interface.ip_address)
self.current_mask_var.set(interface.subnet_mask)
self.current_gateway_var.set(interface.gateway)
self.dhcp_status_var.set("Enabled" if interface.dhcp_enabled else "Disabled")
# Actualizar IP prefix si hay una IP válida
if interface.ip_address:
prefix = '.'.join(interface.ip_address.split('.')[:3])
self.ip_prefix.set(prefix)
# Guardar interfaz seleccionada
self.config.last_interface = selected
self.config.save_config()
self.log_message(f"Selected interface: {selected}")
def validate_ip_input(self) -> tuple[bool, str]:
try:
prefix = self.ip_prefix.get().strip()
last_octet = self.last_octet.get().strip()
# Validar formato del prefijo
if not re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}', prefix):
return False, "IP prefix must be in format: xxx.xxx.xxx"
# Validar último octeto
if not last_octet.isdigit() or not 0 <= int(last_octet) <= 255:
return False, "Last octet must be between 0 and 255"
# Construir y validar IP completa
ip = f"{prefix}.{last_octet}"
ipaddress.IPv4Address(ip)
return True, ip
except ValueError as e:
return False, f"Invalid IP address: {str(e)}"
def execute_admin_script(self, interface: str, mode: str, debug: bool = True) -> bool:
"""
Ejecuta el script administrativo con los argumentos proporcionados
mode puede ser una IP o 'dhcp'
"""
# Obtener rutas absolutas
current_dir = os.path.dirname(os.path.abspath(__file__))
script_path = os.path.join(current_dir, "ip-changer-admin.py")
python_exe = sys.executable
# Verificar que el script existe
if not os.path.exists(script_path):
error_msg = f"Admin script not found at: {script_path}"
self.log_message(error_msg)
return False
# Construir la línea de argumentos
args = f'"{script_path}" "{interface}" "{mode}"'
if debug:
self.log_message("Debug Information:")
self.log_message(f"Current Directory: {current_dir}")
self.log_message(f"Script Path: {script_path}")
self.log_message(f"Python Executable: {python_exe}")
self.log_message(f"Full Command: {python_exe} {args}")
try:
self.log_message(f"Attempting to execute admin script with elevated privileges")
ret = ctypes.windll.shell32.ShellExecuteW(
None, # hwnd
"runas", # operation
python_exe, # file
args, # parameters
current_dir, # directory
1 # show command
)
result_code = int(ret)
if result_code > 32:
self.log_message(f"ShellExecuteW successful (return code: {result_code})")
return True
else:
error_codes = {
0: "The operating system is out of memory or resources",
2: "The specified file was not found",
3: "The specified path was not found",
5: "Access denied",
8: "Insufficient memory to complete the operation",
11: "Bad format",
26: "A sharing violation occurred",
27: "The file name association is incomplete or invalid",
28: "The DDE operation timed out",
29: "The DDE operation failed",
30: "The DDE operation is busy",
31: "The file name association is unavailable",
32: "No application is associated with the file"
}
self.log_message(f"Failed to execute admin script. Error code {result_code}: {error_msg}")
return False
except Exception as e:
self.log_message(f"Exception while executing admin script: {str(e)}")
return False
def set_static_ip(self):
interface = self.if_var.get()
if not interface:
self.show_error("Please select a network interface")
return
# Validar IP
is_valid, result = self.validate_ip_input()
if not is_valid:
self.show_error(result)
return
ip = result
# Guardar IP actual en el historial
self.add_to_history(self.ip_prefix.get())
# Ejecutar script con privilegios
if self.execute_admin_script(interface, ip, debug=True):
time.sleep(2) # Esperar a que se apliquen los cambios
self.refresh_interfaces()
self.log_message(f"Successfully set static IP {ip} on {interface}")
else:
self.show_error("Failed to set static IP. Check the log for details.")
def set_dhcp(self):
interface = self.if_var.get()
if not interface:
self.show_error("Please select a network interface")
return
# Ejecutar script con privilegios
if self.execute_admin_script(interface, "dhcp", debug=True):
time.sleep(2) # Esperar a que se apliquen los cambios
self.refresh_interfaces()
self.log_message(f"Successfully enabled DHCP on {interface}")
else:
self.show_error("Failed to enable DHCP. Check the log for details.")
def run_command(self, cmd: str) -> bool:
self.log_message(f"Executing command: {cmd}")
try:
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.log_message(output.strip())
retcode = process.poll()
error_output = process.stderr.read()
if error_output:
self.log_message(f"Error output:\n{error_output}")
if retcode == 0:
self.log_message("Command executed successfully")
return True
else:
self.log_message(f"Command failed with return code: {retcode}")
return False
except Exception as e:
self.log_message(f"Error executing command: {str(e)}")
return False
def is_admin(self):
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except Exception as e:
self.log_message(f"Error checking admin privileges: {str(e)}")
return False
def elevate_privileges(self):
if sys.platform == 'win32':
self.show_info("The application needs administrator privileges to change network settings. "
"Please confirm the UAC prompt.")
script_path = os.path.abspath(sys.argv[0])
try:
ctypes.windll.shell32.ShellExecuteW(
None,
"runas",
sys.executable,
f'"{script_path}"',
None,
1
)
self.master.quit()
except Exception as e:
self.log_message(f"Error elevating privileges: {str(e)}")
self.show_error("Failed to elevate privileges")
def show_error(self, message: str):
self.log_message(f"ERROR: {message}")
messagebox.showerror("Error", message)
def show_info(self, message: str):
self.log_message(f"INFO: {message}")
messagebox.showinfo("Information", message)
def main():
root = tk.Tk()
app = IPChangerApp(root)
root.mainloop()
if __name__ == "__main__":
main()