551 lines
22 KiB
Python
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() == "sí" 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() |