From 09b9a7c3e72c2e0ed6e2ab6d8a10642097606928 Mon Sep 17 00:00:00 2001 From: Satvik-Singh192 Date: Wed, 22 Oct 2025 20:02:46 +0530 Subject: [PATCH 1/2] feat: add Windows support using netsh for IP blocking --- src/firewall/blocking.py | 34 ++++++++++--- src/network/interface.py | 103 ++++++++++++++++++++++++--------------- 2 files changed, 92 insertions(+), 45 deletions(-) diff --git a/src/firewall/blocking.py b/src/firewall/blocking.py index 4fad861..7662572 100644 --- a/src/firewall/blocking.py +++ b/src/firewall/blocking.py @@ -157,7 +157,7 @@ def _unblock_ip_macos(self, ip: str) -> bool: return result.returncode == 0 def _block_ip_windows(self, ip: str) -> bool: - """Block IP using Windows Firewall""" + """Block IP using Windows Firewall (netsh)""" rule_name = f"SimpleFirewall_Block_{ip.replace('.', '_')}" cmd = [ 'netsh', 'advfirewall', 'firewall', 'add', 'rule', @@ -166,18 +166,38 @@ def _block_ip_windows(self, ip: str) -> bool: 'action=block', f'remoteip={ip}' ] - result = subprocess.run(cmd, capture_output=True, text=True) - return result.returncode == 0 - + try: + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + self.logger.debug(f"netsh add rule stdout: {result.stdout.strip()}") + return True + else: + self.logger.error(f"netsh add rule failed: rc={result.returncode} stdout={result.stdout.strip()} stderr={result.stderr.strip()}") + return False + except Exception as e: + self.logger.error(f"Exception when running netsh add rule: {e}") + return False + def _unblock_ip_windows(self, ip: str) -> bool: - """Unblock IP using Windows Firewall""" + """Unblock IP using Windows Firewall (netsh)""" rule_name = f"SimpleFirewall_Block_{ip.replace('.', '_')}" cmd = [ 'netsh', 'advfirewall', 'firewall', 'delete', 'rule', f'name={rule_name}' ] - result = subprocess.run(cmd, capture_output=True, text=True) - return result.returncode == 0 + try: + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + self.logger.debug(f"netsh delete rule stdout: {result.stdout.strip()}") + return True + else: + # Sometimes netsh returns 1 when rule not found; log and return False + self.logger.error(f"netsh delete rule failed: rc={result.returncode} stdout={result.stdout.strip()} stderr={result.stderr.strip()}") + return False + except Exception as e: + self.logger.error(f"Exception when running netsh delete rule: {e}") + return False + def get_blocked_ips(self) -> Dict[str, str]: """Get currently blocked IPs with their block times""" diff --git a/src/network/interface.py b/src/network/interface.py index 0c840df..17fff93 100644 --- a/src/network/interface.py +++ b/src/network/interface.py @@ -1,88 +1,115 @@ +# src/network/interface.py """Network interface detection and management""" import netifaces +import platform from typing import Optional, List +# Use psutil on Windows to get friendly interface names +try: + import psutil +except Exception: + psutil = None + class NetworkInterface: """Handles network interface detection and management""" - + def __init__(self, interface: str = None): + self._platform = platform.system().lower() self.interface = interface or self._get_default_interface() - + def _get_default_interface(self) -> str: - """Get the default network interface""" + """Get the default network interface (Windows-friendly names when available)""" try: - interfaces = netifaces.interfaces() - - # Filter out loopback and virtual interfaces - physical_interfaces = [ - iface for iface in interfaces - if not iface.startswith(('lo', 'docker', 'veth', 'br-')) - ] - - # Prefer ethernet interfaces, then wireless - for iface in physical_interfaces: - if iface.startswith(('eth', 'en')): - return iface - - for iface in physical_interfaces: - if iface.startswith(('wl', 'wlan')): - return iface - - # Fallback to first available interface - return physical_interfaces[0] if physical_interfaces else 'eth0' - + # On Windows prefer psutil names (friendly names) + if self._platform == 'windows' and psutil is not None: + # psutil returns a mapping of friendly names + names = [name for name in psutil.net_if_addrs().keys() + if not name.startswith(('Loopback', 'loopback', 'vEthernet'))] + # Prefer "Ethernet" or "Wi-Fi" heuristically + for n in names: + if n.lower().startswith('ether') or 'ethernet' in n.lower(): + return n + for n in names: + if 'wi' in n.lower() or 'wifi' in n.lower() or 'wi-fi' in n.lower(): + return n + return names[0] if names else '' + else: + interfaces = netifaces.interfaces() + + # Filter out loopback and virtual interfaces + physical_interfaces = [ + iface for iface in interfaces + if not iface.startswith(('lo', 'docker', 'veth', 'br-')) + ] + + # Prefer ethernet interfaces, then wireless + for iface in physical_interfaces: + if iface.startswith(('eth', 'en')): + return iface + + for iface in physical_interfaces: + if iface.startswith(('wl', 'wlan')): + return iface + + # Fallback to first available interface + return physical_interfaces[0] if physical_interfaces else 'eth0' + except Exception: - return 'eth0' - + return '' + def get_interface_info(self) -> dict: """Get information about the current interface""" try: - addrs = netifaces.ifaddresses(self.interface) + addrs = netifaces.ifaddresses(self.interface) if self.interface else {} info = { 'name': self.interface, 'ipv4': [], 'ipv6': [], 'mac': None } - + # Get IPv4 addresses - if netifaces.AF_INET in addrs: + if addrs and netifaces.AF_INET in addrs: for addr in addrs[netifaces.AF_INET]: info['ipv4'].append({ 'addr': addr.get('addr'), 'netmask': addr.get('netmask'), 'broadcast': addr.get('broadcast') }) - + # Get IPv6 addresses - if netifaces.AF_INET6 in addrs: + if addrs and netifaces.AF_INET6 in addrs: for addr in addrs[netifaces.AF_INET6]: info['ipv6'].append({ 'addr': addr.get('addr'), 'netmask': addr.get('netmask') }) - + # Get MAC address - if netifaces.AF_LINK in addrs: + if addrs and netifaces.AF_LINK in addrs: info['mac'] = addrs[netifaces.AF_LINK][0].get('addr') - + return info - + except Exception as e: return {'name': self.interface, 'error': str(e)} - + def list_all_interfaces(self) -> List[str]: - """List all available network interfaces""" + """List all available network interfaces (friendly names preferred on Windows)""" try: + if self._platform == 'windows' and psutil is not None: + return list(psutil.net_if_addrs().keys()) return netifaces.interfaces() except Exception: return [] - + def is_interface_up(self) -> bool: """Check if the interface is up and running""" try: + if self._platform == 'windows' and psutil is not None: + return self.interface in psutil.net_if_stats() return self.interface in netifaces.interfaces() except Exception: - return False \ No newline at end of file + return False From 152f010448c2c52af271af1c799c2b7578dd23d9 Mon Sep 17 00:00:00 2001 From: Satvik-Singh192 Date: Fri, 24 Oct 2025 14:34:14 +0530 Subject: [PATCH 2/2] feat: address maintainer feedback for Windows support --- README.md | 30 ++++++++++++++++++++++++++++++ requirements-windows.txt | 8 ++++++++ src/network/interface.py | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 requirements-windows.txt diff --git a/README.md b/README.md index 5181825..5369247 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,36 @@ This firewall uses a **threshold-based detection approach**: python3 test_attacks.py 127.0.0.1 ``` +## 🏃 Running on Windows + +This program uses `netsh` to manage firewall rules, which requires administrative privileges. + +1. **Open as Administrator:** Right-click on **Command Prompt** or **Windows PowerShell** and select **"Run as administrator"**. + +2. **Navigate to Folder:** Change to the project directory: + ```sh + cd path\to\simple_firewall + ``` + +3. **(Optional) Activate Virtual Environment:** If you use a virtual environment: + ```sh + .\venv\Scripts\activate + ``` + +4. **Install Requirements:** Install the main requirements and the Windows-specific ones: + ```sh + pip install -r requirements.txt + pip install -r requirements-windows.txt + ``` + +5. **Run the Program:** + ```sh + python main.py -i "Your Interface Name" + + # Example: + python main.py -i "Ethernet" + ``` + ## Usage ### Basic Commands diff --git a/requirements-windows.txt b/requirements-windows.txt new file mode 100644 index 0000000..37a3830 --- /dev/null +++ b/requirements-windows.txt @@ -0,0 +1,8 @@ +# This file lists Python packages required *only* for running +# this project on Windows, which are not in the main requirements.txt. + +# psutil (used for Windows-specific interface detection) is +# already listed in the main requirements.txt as a core +# dependency for all platforms. + +# Future Windows-only dependencies can be added here. \ No newline at end of file diff --git a/src/network/interface.py b/src/network/interface.py index 17fff93..528c33c 100644 --- a/src/network/interface.py +++ b/src/network/interface.py @@ -34,7 +34,7 @@ def _get_default_interface(self) -> str: for n in names: if 'wi' in n.lower() or 'wifi' in n.lower() or 'wi-fi' in n.lower(): return n - return names[0] if names else '' + return names[0] if names else 'Ethernet' else: interfaces = netifaces.interfaces()