Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 136 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,115 @@ This program uses `netsh` to manage firewall rules, which requires administrativ
# Example:
python main.py -i "Ethernet"
```

## 🍎 Running on macOS

This program uses `pfctl` (Packet Filter Control) to manage firewall rules, which requires administrative privileges.

### Prerequisites

- **macOS 10.5+**
- **Administrator privileges**
- **Python 3.6+**

### Setup Instructions

1. **Open Terminal:** Open the Terminal application (found in Applications > Utilities)

2. **Navigate to Project Directory:**
```bash
cd path/to/simple_firewall
```

3. **(Optional) Create Virtual Environment:**
```bash
python3 -m venv venv
source venv/bin/activate
```

4. **Install Requirements:**
```bash
pip install -r requirements.txt
```

5. **Create Configuration File:**
```bash
python3 main.py --create-config
```

6. **Run the Firewall:**
```bash
sudo python3 main.py
```

Or specify an interface:
```bash
sudo python3 main.py -i en0
```

### macOS Firewall Management

The firewall uses `pfctl` to manage blocking rules:

- **Automatic Setup:** On first run, the firewall automatically:
- Creates a `blocked_ips` table in pfctl
- Enables pfctl if not already enabled
- Loads blocking rules from a temporary configuration file

- **Blocking Mechanism:** IPs are added to a pfctl table, and rules block all traffic from/to those IPs

- **Automatic Cleanup:** When the firewall stops, all blocked IPs are automatically removed

### Managing pfctl Manually

If you need to manually manage the firewall:

```bash
# View currently blocked IPs
sudo pfctl -t blocked_ips -T show

# Remove a specific IP manually
sudo pfctl -t blocked_ips -T delete <IP_ADDRESS>

# Flush all blocked IPs
sudo pfctl -t blocked_ips -T flush

# Check pfctl status
sudo pfctl -s info

# View current rules
sudo pfctl -s rules
```

### Troubleshooting macOS

**Issue: "pfctl: command not found"**
- This shouldn't happen on macOS, but if it does, ensure you're running a supported macOS version

**Issue: "pfctl: Permission denied"**
- Make sure you're running with `sudo`
- Some macOS versions may require additional permissions

**Issue: "pfctl: cannot enable"**
- macOS System Preferences may have the built-in firewall enabled
- You may need to disable it in System Preferences > Security & Privacy > Firewall
- Or allow pfctl to run by granting Full Disk Access in System Preferences

**Issue: Interface not found**
- List available interfaces: `ifconfig` or `networksetup -listallhardwareports`
- Common macOS interfaces: `en0` (Ethernet), `en1` (Wi-Fi), `en2` (USB Ethernet)

**Issue: Blocks not working**
- Check if pfctl is enabled: `sudo pfctl -s info`
- Verify rules are loaded: `sudo pfctl -s rules`
- Check if IPs are in the table: `sudo pfctl -t blocked_ips -T show`

### macOS-Specific Notes

- **System Firewall:** macOS has a built-in firewall in System Preferences. The Simple Firewall works alongside it but uses pfctl directly
- **Interface Names:** macOS uses names like `en0`, `en1`, etc. (not `eth0` like Linux)
- **Root Access:** Always run with `sudo` for firewall operations
- **Temporary Rules:** The firewall creates temporary pf.conf files that are automatically managed

## Usage

Expand Down Expand Up @@ -306,8 +415,11 @@ src/
## Requirements

- **Python 3.6+**
- **Root privileges** (required for iptables access)
- **Linux system** (uses iptables for blocking)
- **Root/Administrator privileges** (required for firewall access)
- **Supported Platforms:**
- **Linux:** Uses iptables for blocking
- **macOS:** Uses pfctl (Packet Filter) for blocking
- **Windows:** Uses netsh (Windows Firewall) for blocking

### Python Packages:
- `scapy` - Packet capture and analysis
Expand Down Expand Up @@ -389,6 +501,27 @@ sudo iptables -D INPUT -s [IP_ADDRESS] -j DROP
sudo iptables -F INPUT
```

**macOS (pfctl):**
```bash
# View blocked IPs in table
sudo pfctl -t blocked_ips -T show

# Remove specific IP
sudo pfctl -t blocked_ips -T delete [IP_ADDRESS]

# Flush all blocked IPs
sudo pfctl -t blocked_ips -T flush
```

**Windows (netsh):**
```powershell
# List firewall rules
netsh advfirewall firewall show rule name=all

# Delete specific rule (replace IP_ADDRESS with actual IP)
netsh advfirewall firewall delete rule name="SimpleFirewall_Block_[IP_ADDRESS]"
```

## Current Limitations & Known Issues

⚠️ **Platform Limitations:**
Expand Down Expand Up @@ -448,6 +581,7 @@ This is an **educational open-source project** designed to help people learn net
- **Performance:** Optimize packet processing and memory usage
- **Testing:** Add unit tests and integration tests
- **Documentation:** Improve code comments and examples
- **IPv6 Support:** Extend blocking to IPv6 addresses

### 📋 **Current Architecture Issues to Fix:**
- Replace hardcoded magic numbers with named constants
Expand Down
132 changes: 120 additions & 12 deletions src/firewall/blocking.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import subprocess
import threading
import platform
import tempfile
import shutil
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, Set, List
from colorama import Fore, Style
from utils.logger import get_logger
Expand All @@ -21,6 +24,10 @@ def __init__(self, block_duration: int, whitelist: Set[str]):
self.logger = get_logger(__name__)
self.platform = platform.system().lower()
self.firewall_cmd = get_platform_firewall_command()

# macOS-specific: pfctl configuration
if self.platform == 'darwin':
self._init_macos_firewall()

def block_ip(self, ip: str, reason: str) -> bool:
"""Block an IP address using the appropriate firewall system"""
Expand Down Expand Up @@ -138,23 +145,114 @@ def _unblock_ip_linux(self, ip: str) -> bool:
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode == 0

def _init_macos_firewall(self):
"""Initialize macOS firewall (pfctl) - create table and rules"""
try:
# Check if pfctl is available
pfctl_path = shutil.which('pfctl')
if not pfctl_path:
self.logger.warning("pfctl not found.")
return

cmd = ['sudo', 'pfctl', '-t', 'blocked_ips', '-T', 'show']
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
# if doesn't exist, create dummy
subprocess.run(['sudo', 'pfctl', '-t', 'blocked_ips', '-T', 'add', '127.0.0.1'],
capture_output=True, text=True)
subprocess.run(['sudo', 'pfctl', '-t', 'blocked_ips', '-T', 'delete', '127.0.0.1'],
capture_output=True, text=True)

cmd = ['sudo', 'pfctl', '-s', 'info']
result = subprocess.run(cmd, capture_output=True, text=True)
if 'Status: Enabled' not in result.stdout:
# Try to enable pfctl (may require user interaction)
self.logger.info("Attempting to enable pfctl...")
subprocess.run(['sudo', 'pfctl', '-e'], capture_output=True, text=True)

self._reload_macos_rules()

self.logger.info("macOS firewall initialized successfully")
except Exception as e:
self.logger.error(f"Failed to initialize macOS firewall: {e}")

def _reload_macos_rules(self):
"""Reload pfctl rules to ensure blocking rule is active"""
try:

pf_conf_content = """# Simple Firewall - Dynamic IP Blocking Rules
# This file is managed by Simple Firewall
# DO NOT EDIT MANUALLY

# Table for blocked IPs
table <blocked_ips> persist

# Block all traffic from IPs in the blocked_ips table
block drop in quick from <blocked_ips> to any
block drop out quick from any to <blocked_ips>

# Allow all other traffic (pass through)
pass in all
pass out all
"""
temp_dir = Path(tempfile.gettempdir())
pf_conf_path = temp_dir / 'simple_firewall_pf.conf'

with open(pf_conf_path, 'w') as f:
f.write(pf_conf_content)

cmd = ['sudo', 'pfctl', '-f', str(pf_conf_path)]
result = subprocess.run(cmd, capture_output=True, text=True)

if result.returncode == 0:
self.logger.debug("pfctl rules loaded successfully")
else:
self.logger.warning(f"pfctl rule loading returned: {result.returncode}")
self.logger.debug(f"pfctl stderr: {result.stderr}")

except Exception as e:
self.logger.error(f"Failed to reload macOS firewall rules: {e}")

def _block_ip_macos(self, ip: str) -> bool:
"""Block IP using pfctl on macOS"""
# First, add IP to a table
cmd1 = ['sudo', 'pfctl', '-t', 'blocked_ips', '-T', 'add', ip]
result1 = subprocess.run(cmd1, capture_output=True, text=True)

# Then enable the blocking rule (this might need to be done once)
cmd2 = ['sudo', 'pfctl', '-e']
result2 = subprocess.run(cmd2, capture_output=True, text=True)

return result1.returncode == 0
try:
# First, add IP to a table
cmd = ['sudo', 'pfctl', '-t', 'blocked_ips', '-T', 'add', ip]
result = subprocess.run(cmd, capture_output=True, text=True)

if result.returncode == 0:
self.logger.debug(f"Successfully added {ip} to blocked_ips table")
return True
else:

if 'already' in result.stderr.lower() or 'duplicate' in result.stderr.lower():
self.logger.debug(f"IP {ip} already in blocked_ips table")
return True
else:
self.logger.error(f"Failed to add {ip} to blocked_ips table: {result.stderr}")
return False
except Exception as e:
self.logger.error(f"Exception while blocking IP {ip} on macOS: {e}")
return False

def _unblock_ip_macos(self, ip: str) -> bool:
"""Unblock IP using pfctl on macOS"""
cmd = ['sudo', 'pfctl', '-t', 'blocked_ips', '-T', 'delete', ip]
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode == 0
try:
cmd = ['sudo', 'pfctl', '-t', 'blocked_ips', '-T', 'delete', ip]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
self.logger.debug(f"Successfully removed {ip} from blocked_ips table")
return True
else:
if 'not found' in result.stderr.lower() or 'does not exist' in result.stderr.lower():
self.logger.debug(f"IP {ip} not found in blocked_ips table (may have been already removed)")
return True
else:
self.logger.warning(f"Failed to remove {ip} from blocked_ips table: {result.stderr}")
return False
except Exception as e:
self.logger.error(f"Exception while unblocking IP {ip} on macOS: {e}")
return False

def _block_ip_windows(self, ip: str) -> bool:
"""Block IP using Windows Firewall (netsh)"""
Expand Down Expand Up @@ -216,6 +314,16 @@ def cleanup_all_blocks(self) -> List[str]:
if self.unblock_ip(ip):
cleaned_ips.append(ip)

# macOS-specific: Clean up the entire table on shutdown
if self.platform == 'darwin' and cleaned_ips:
try:
cmd = ['sudo', 'pfctl', '-t', 'blocked_ips', '-T', 'flush']
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
self.logger.info("Cleaned up all blocked IPs from pfctl table")
except Exception as e:
self.logger.warning(f"Failed to flush pfctl table: {e}")

return cleaned_ips

def get_stats(self) -> Dict[str, int]:
Expand Down
Loading