Skip to content

Commit 4cf533b

Browse files
Merge pull request #4 from Satvik-Singh192/feature/windows-support
feat: add Windows support using netsh for IP blocking
2 parents a4ad940 + 152f010 commit 4cf533b

File tree

4 files changed

+130
-45
lines changed

4 files changed

+130
-45
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,36 @@ This firewall uses a **threshold-based detection approach**:
6868
python3 test_attacks.py 127.0.0.1
6969
```
7070

71+
## 🏃 Running on Windows
72+
73+
This program uses `netsh` to manage firewall rules, which requires administrative privileges.
74+
75+
1. **Open as Administrator:** Right-click on **Command Prompt** or **Windows PowerShell** and select **"Run as administrator"**.
76+
77+
2. **Navigate to Folder:** Change to the project directory:
78+
```sh
79+
cd path\to\simple_firewall
80+
```
81+
82+
3. **(Optional) Activate Virtual Environment:** If you use a virtual environment:
83+
```sh
84+
.\venv\Scripts\activate
85+
```
86+
87+
4. **Install Requirements:** Install the main requirements and the Windows-specific ones:
88+
```sh
89+
pip install -r requirements.txt
90+
pip install -r requirements-windows.txt
91+
```
92+
93+
5. **Run the Program:**
94+
```sh
95+
python main.py -i "Your Interface Name"
96+
97+
# Example:
98+
python main.py -i "Ethernet"
99+
```
100+
71101
## Usage
72102

73103
### Basic Commands

requirements-windows.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# This file lists Python packages required *only* for running
2+
# this project on Windows, which are not in the main requirements.txt.
3+
4+
# psutil (used for Windows-specific interface detection) is
5+
# already listed in the main requirements.txt as a core
6+
# dependency for all platforms.
7+
8+
# Future Windows-only dependencies can be added here.

src/firewall/blocking.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def _unblock_ip_macos(self, ip: str) -> bool:
157157
return result.returncode == 0
158158

159159
def _block_ip_windows(self, ip: str) -> bool:
160-
"""Block IP using Windows Firewall"""
160+
"""Block IP using Windows Firewall (netsh)"""
161161
rule_name = f"SimpleFirewall_Block_{ip.replace('.', '_')}"
162162
cmd = [
163163
'netsh', 'advfirewall', 'firewall', 'add', 'rule',
@@ -166,18 +166,38 @@ def _block_ip_windows(self, ip: str) -> bool:
166166
'action=block',
167167
f'remoteip={ip}'
168168
]
169-
result = subprocess.run(cmd, capture_output=True, text=True)
170-
return result.returncode == 0
171-
169+
try:
170+
result = subprocess.run(cmd, capture_output=True, text=True)
171+
if result.returncode == 0:
172+
self.logger.debug(f"netsh add rule stdout: {result.stdout.strip()}")
173+
return True
174+
else:
175+
self.logger.error(f"netsh add rule failed: rc={result.returncode} stdout={result.stdout.strip()} stderr={result.stderr.strip()}")
176+
return False
177+
except Exception as e:
178+
self.logger.error(f"Exception when running netsh add rule: {e}")
179+
return False
180+
172181
def _unblock_ip_windows(self, ip: str) -> bool:
173-
"""Unblock IP using Windows Firewall"""
182+
"""Unblock IP using Windows Firewall (netsh)"""
174183
rule_name = f"SimpleFirewall_Block_{ip.replace('.', '_')}"
175184
cmd = [
176185
'netsh', 'advfirewall', 'firewall', 'delete', 'rule',
177186
f'name={rule_name}'
178187
]
179-
result = subprocess.run(cmd, capture_output=True, text=True)
180-
return result.returncode == 0
188+
try:
189+
result = subprocess.run(cmd, capture_output=True, text=True)
190+
if result.returncode == 0:
191+
self.logger.debug(f"netsh delete rule stdout: {result.stdout.strip()}")
192+
return True
193+
else:
194+
# Sometimes netsh returns 1 when rule not found; log and return False
195+
self.logger.error(f"netsh delete rule failed: rc={result.returncode} stdout={result.stdout.strip()} stderr={result.stderr.strip()}")
196+
return False
197+
except Exception as e:
198+
self.logger.error(f"Exception when running netsh delete rule: {e}")
199+
return False
200+
181201

182202
def get_blocked_ips(self) -> Dict[str, str]:
183203
"""Get currently blocked IPs with their block times"""

src/network/interface.py

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,115 @@
1+
# src/network/interface.py
12
"""Network interface detection and management"""
23

34
import netifaces
5+
import platform
46
from typing import Optional, List
57

8+
# Use psutil on Windows to get friendly interface names
9+
try:
10+
import psutil
11+
except Exception:
12+
psutil = None
13+
614

715
class NetworkInterface:
816
"""Handles network interface detection and management"""
9-
17+
1018
def __init__(self, interface: str = None):
19+
self._platform = platform.system().lower()
1120
self.interface = interface or self._get_default_interface()
12-
21+
1322
def _get_default_interface(self) -> str:
14-
"""Get the default network interface"""
23+
"""Get the default network interface (Windows-friendly names when available)"""
1524
try:
16-
interfaces = netifaces.interfaces()
17-
18-
# Filter out loopback and virtual interfaces
19-
physical_interfaces = [
20-
iface for iface in interfaces
21-
if not iface.startswith(('lo', 'docker', 'veth', 'br-'))
22-
]
23-
24-
# Prefer ethernet interfaces, then wireless
25-
for iface in physical_interfaces:
26-
if iface.startswith(('eth', 'en')):
27-
return iface
28-
29-
for iface in physical_interfaces:
30-
if iface.startswith(('wl', 'wlan')):
31-
return iface
32-
33-
# Fallback to first available interface
34-
return physical_interfaces[0] if physical_interfaces else 'eth0'
35-
25+
# On Windows prefer psutil names (friendly names)
26+
if self._platform == 'windows' and psutil is not None:
27+
# psutil returns a mapping of friendly names
28+
names = [name for name in psutil.net_if_addrs().keys()
29+
if not name.startswith(('Loopback', 'loopback', 'vEthernet'))]
30+
# Prefer "Ethernet" or "Wi-Fi" heuristically
31+
for n in names:
32+
if n.lower().startswith('ether') or 'ethernet' in n.lower():
33+
return n
34+
for n in names:
35+
if 'wi' in n.lower() or 'wifi' in n.lower() or 'wi-fi' in n.lower():
36+
return n
37+
return names[0] if names else 'Ethernet'
38+
else:
39+
interfaces = netifaces.interfaces()
40+
41+
# Filter out loopback and virtual interfaces
42+
physical_interfaces = [
43+
iface for iface in interfaces
44+
if not iface.startswith(('lo', 'docker', 'veth', 'br-'))
45+
]
46+
47+
# Prefer ethernet interfaces, then wireless
48+
for iface in physical_interfaces:
49+
if iface.startswith(('eth', 'en')):
50+
return iface
51+
52+
for iface in physical_interfaces:
53+
if iface.startswith(('wl', 'wlan')):
54+
return iface
55+
56+
# Fallback to first available interface
57+
return physical_interfaces[0] if physical_interfaces else 'eth0'
58+
3659
except Exception:
37-
return 'eth0'
38-
60+
return ''
61+
3962
def get_interface_info(self) -> dict:
4063
"""Get information about the current interface"""
4164
try:
42-
addrs = netifaces.ifaddresses(self.interface)
65+
addrs = netifaces.ifaddresses(self.interface) if self.interface else {}
4366
info = {
4467
'name': self.interface,
4568
'ipv4': [],
4669
'ipv6': [],
4770
'mac': None
4871
}
49-
72+
5073
# Get IPv4 addresses
51-
if netifaces.AF_INET in addrs:
74+
if addrs and netifaces.AF_INET in addrs:
5275
for addr in addrs[netifaces.AF_INET]:
5376
info['ipv4'].append({
5477
'addr': addr.get('addr'),
5578
'netmask': addr.get('netmask'),
5679
'broadcast': addr.get('broadcast')
5780
})
58-
81+
5982
# Get IPv6 addresses
60-
if netifaces.AF_INET6 in addrs:
83+
if addrs and netifaces.AF_INET6 in addrs:
6184
for addr in addrs[netifaces.AF_INET6]:
6285
info['ipv6'].append({
6386
'addr': addr.get('addr'),
6487
'netmask': addr.get('netmask')
6588
})
66-
89+
6790
# Get MAC address
68-
if netifaces.AF_LINK in addrs:
91+
if addrs and netifaces.AF_LINK in addrs:
6992
info['mac'] = addrs[netifaces.AF_LINK][0].get('addr')
70-
93+
7194
return info
72-
95+
7396
except Exception as e:
7497
return {'name': self.interface, 'error': str(e)}
75-
98+
7699
def list_all_interfaces(self) -> List[str]:
77-
"""List all available network interfaces"""
100+
"""List all available network interfaces (friendly names preferred on Windows)"""
78101
try:
102+
if self._platform == 'windows' and psutil is not None:
103+
return list(psutil.net_if_addrs().keys())
79104
return netifaces.interfaces()
80105
except Exception:
81106
return []
82-
107+
83108
def is_interface_up(self) -> bool:
84109
"""Check if the interface is up and running"""
85110
try:
111+
if self._platform == 'windows' and psutil is not None:
112+
return self.interface in psutil.net_if_stats()
86113
return self.interface in netifaces.interfaces()
87114
except Exception:
88-
return False
115+
return False

0 commit comments

Comments
 (0)