diff --git a/api/app.py b/api/app.py index 324a01d..06ca94a 100644 --- a/api/app.py +++ b/api/app.py @@ -832,6 +832,44 @@ def update_peer_ip(): logger.error(f"Error updating peer IP: {e}") return jsonify({"error": str(e)}), 500 +@app.route('/api/wireguard/peers/status', methods=['POST']) +def get_peer_status(): + """Get WireGuard peer status.""" + try: + data = request.get_json(silent=True) + if data is None or 'public_key' not in data: + return jsonify({"error": "Missing public key"}), 400 + + public_key = data['public_key'] + status = wireguard_manager.get_peer_status(public_key) + return jsonify(status) + except Exception as e: + logger.error(f"Error getting peer status: {e}") + return jsonify({"error": str(e)}), 500 + +@app.route('/api/wireguard/network/setup', methods=['POST']) +def setup_network(): + """Setup network configuration for internet access.""" + try: + success = wireguard_manager.setup_network_configuration() + if success: + return jsonify({"message": "Network configuration setup completed successfully"}) + else: + return jsonify({"error": "Failed to setup network configuration"}), 500 + except Exception as e: + logger.error(f"Error setting up network configuration: {e}") + return jsonify({"error": str(e)}), 500 + +@app.route('/api/wireguard/network/status', methods=['GET']) +def get_network_status(): + """Get network configuration status.""" + try: + status = wireguard_manager.get_network_status() + return jsonify(status) + except Exception as e: + logger.error(f"Error getting network status: {e}") + return jsonify({"error": str(e)}), 500 + @app.route('/api/wireguard/peers/config', methods=['POST']) def get_peer_config(): try: @@ -849,10 +887,14 @@ def get_peer_config(): # Get server configuration server_config = wireguard_manager.get_server_config() + # Check if IP already has a subnet mask, if not add /32 + peer_ip = peer.get('ip', '10.0.0.2') + peer_address = peer_ip if '/' in peer_ip else f"{peer_ip}/32" + # Generate client configuration using peer registry data config = f"""[Interface] PrivateKey = {peer.get('private_key', 'YOUR_PRIVATE_KEY_HERE')} -Address = {peer.get('ip', '10.0.0.2')}/32 +Address = {peer_address} DNS = 8.8.8.8, 1.1.1.1 [Peer] diff --git a/api/wireguard_manager.py b/api/wireguard_manager.py index a7d91f9..ba26f75 100644 --- a/api/wireguard_manager.py +++ b/api/wireguard_manager.py @@ -331,13 +331,62 @@ AllowedIPs = {allowed_ips} raise def _reload_wireguard_config(self): - """Reload WireGuard configuration""" + """Reload WireGuard configuration by updating the main config file""" try: - # This would typically involve restarting the WireGuard service - # or reloading the configuration - logger.info("WireGuard configuration reloaded") + # Read the main server configuration + server_config_path = os.path.join(self.wg_config_dir, 'wg_confs', 'wg0.conf') + if not os.path.exists(server_config_path): + logger.error("Server configuration file not found") + return False + + with open(server_config_path, 'r') as f: + server_content = f.read() + + # Find the end of the [Interface] section + lines = server_content.split('\n') + interface_end = 0 + for i, line in enumerate(lines): + if line.strip().startswith('[Peer]'): + interface_end = i + break + else: + interface_end = len(lines) + + # Keep only the [Interface] section + interface_lines = lines[:interface_end] + + # Add all peer configurations + peer_lines = [] + for filename in os.listdir(self.peers_dir): + if filename.endswith('.conf') and not filename.endswith('_keys.json'): + peer_file = os.path.join(self.peers_dir, filename) + with open(peer_file, 'r') as f: + peer_content = f.read().strip() + if peer_content: + peer_lines.append('') # Empty line before peer + peer_lines.extend(peer_content.split('\n')) + + # Combine interface and peer configurations + new_content = '\n'.join(interface_lines + peer_lines) + + # Write the updated configuration + with open(server_config_path, 'w') as f: + f.write(new_content) + + # Restart WireGuard container to apply changes + import subprocess + result = subprocess.run(['docker', 'restart', 'cell-wireguard'], + capture_output=True, text=True, timeout=30) + if result.returncode == 0: + logger.info("WireGuard configuration reloaded and container restarted") + return True + else: + logger.error(f"Failed to restart WireGuard container: {result.stderr}") + return False + except Exception as e: logger.error(f"Failed to reload WireGuard configuration: {e}") + return False def get_metrics(self) -> Dict[str, Any]: """Get WireGuard metrics""" @@ -528,9 +577,13 @@ AllowedIPs = {allowed_ips} # Get peer private key from peer data peer_private_key = peer_info.get('private_key', 'YOUR_PRIVATE_KEY_HERE') + # Check if IP already has a subnet mask, if not add /32 + peer_ip = peer_info.get('ip', '10.0.0.2') + peer_address = peer_ip if '/' in peer_ip else f"{peer_ip}/32" + config = f"""[Interface] PrivateKey = {peer_private_key} -Address = {peer_info.get('ip', '10.0.0.2')}/32 +Address = {peer_address} DNS = 8.8.8.8, 1.1.1.1 [Peer] @@ -660,4 +713,184 @@ PersistentKeepalive = {peer_info.get('persistent_keepalive', 25)}""" return { 'public_key': 'SERVER_PUBLIC_KEY_PLACEHOLDER', 'endpoint': 'YOUR_SERVER_IP:51820' - } \ No newline at end of file + } + + def get_peer_status(self, public_key: str) -> Dict[str, Any]: + """Get status for a specific peer""" + try: + # Get WireGuard interface status + result = subprocess.run(['wg', 'show'], capture_output=True, text=True, check=True) + wg_output = result.stdout + + # Parse the output to find the specific peer + lines = wg_output.strip().split('\n') + peer_info = {} + in_peer = False + + for line in lines: + if line.startswith('peer:') and public_key in line: + in_peer = True + peer_info['public_key'] = public_key + elif line.startswith('peer:') and public_key not in line: + in_peer = False + elif in_peer and line.startswith(' allowed ips:'): + peer_info['allowed_ips'] = line.split(':', 1)[1].strip() + elif in_peer and line.startswith(' latest handshake:'): + handshake_str = line.split(':', 1)[1].strip() + if handshake_str and handshake_str != '(none)': + peer_info['latest_handshake'] = handshake_str + peer_info['online'] = True + else: + peer_info['online'] = False + elif in_peer and line.startswith(' transfer:'): + transfer_str = line.split(':', 1)[1].strip() + if transfer_str and transfer_str != '(none)': + # Parse transfer data (e.g., "1.2 KiB received, 3.4 KiB sent") + parts = transfer_str.split(',') + if len(parts) >= 2: + rx_part = parts[0].strip() + tx_part = parts[1].strip() + + # Extract numbers from strings like "1.2 KiB received" + import re + rx_match = re.search(r'([\d.]+)\s+(\w+)', rx_part) + tx_match = re.search(r'([\d.]+)\s+(\w+)', tx_part) + + if rx_match and tx_match: + rx_value = float(rx_match.group(1)) + rx_unit = rx_match.group(2) + tx_value = float(tx_match.group(1)) + tx_unit = tx_match.group(2) + + # Convert to bytes + def convert_to_bytes(value, unit): + multipliers = {'B': 1, 'KiB': 1024, 'MiB': 1024**2, 'GiB': 1024**3} + return int(value * multipliers.get(unit, 1)) + + peer_info['transfer_rx'] = convert_to_bytes(rx_value, rx_unit) + peer_info['transfer_tx'] = convert_to_bytes(tx_value, tx_unit) + + # Set default values if not found + if 'online' not in peer_info: + peer_info['online'] = False + if 'transfer_rx' not in peer_info: + peer_info['transfer_rx'] = 0 + if 'transfer_tx' not in peer_info: + peer_info['transfer_tx'] = 0 + if 'latest_handshake' not in peer_info: + peer_info['latest_handshake'] = None + + return peer_info + except Exception as e: + logger.error(f"Failed to get peer status for {public_key}: {e}") + return {'online': False, 'transfer_rx': 0, 'transfer_tx': 0, 'latest_handshake': None} + + def setup_network_configuration(self) -> bool: + """Setup network configuration for internet access""" + try: + logger.info("Setting up network configuration for internet access...") + + # Enable IP forwarding + self._enable_ip_forwarding() + + # Configure NAT and routing + self._configure_nat_routing() + + logger.info("Network configuration completed successfully") + return True + except Exception as e: + logger.error(f"Failed to setup network configuration: {e}") + return False + + def _enable_ip_forwarding(self): + """Enable IP forwarding""" + try: + # Enable IP forwarding in the container + subprocess.run(['sh', '-c', 'echo 1 > /proc/sys/net/ipv4/ip_forward'], check=True) + logger.info("IP forwarding enabled") + except Exception as e: + logger.error(f"Failed to enable IP forwarding: {e}") + raise + + def _configure_nat_routing(self): + """Configure NAT and routing for internet access""" + try: + # Get the main network interface + result = subprocess.run(['ip', 'route', 'show', 'default'], capture_output=True, text=True, check=True) + main_interface = result.stdout.split()[4] # Extract interface name + + # Configure iptables rules + rules = [ + # Allow forwarding for WireGuard interface + f"iptables -A FORWARD -i wg0 -j ACCEPT", + f"iptables -A FORWARD -o wg0 -j ACCEPT", + + # NAT rule for internet access + f"iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o {main_interface} -j MASQUERADE", + + # Allow established and related connections + "iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT" + ] + + for rule in rules: + try: + subprocess.run(['sh', '-c', rule], check=True) + except subprocess.CalledProcessError as e: + logger.warning(f"Rule may already exist: {rule} - {e}") + + logger.info(f"NAT and routing configured for interface {main_interface}") + except Exception as e: + logger.error(f"Failed to configure NAT routing: {e}") + raise + + def get_network_status(self) -> Dict[str, Any]: + """Get network configuration status""" + try: + status = { + 'ip_forwarding': self._check_ip_forwarding(), + 'nat_rules': self._check_nat_rules(), + 'forwarding_rules': self._check_forwarding_rules(), + 'interface_status': self._check_interface_status(), + 'timestamp': datetime.utcnow().isoformat() + } + return status + except Exception as e: + logger.error(f"Failed to get network status: {e}") + return {'error': str(e)} + + def _check_ip_forwarding(self) -> bool: + """Check if IP forwarding is enabled""" + try: + # Check in WireGuard container + result = subprocess.run(['docker', 'exec', 'cell-wireguard', 'cat', '/proc/sys/net/ipv4/ip_forward'], capture_output=True, text=True, check=True) + return result.stdout.strip() == '1' + except: + return False + + def _check_nat_rules(self) -> bool: + """Check if NAT rules are configured""" + try: + # Check in WireGuard container + result = subprocess.run(['docker', 'exec', 'cell-wireguard', 'iptables', '-t', 'nat', '-L', 'POSTROUTING', '-n'], capture_output=True, text=True, check=True) + return 'MASQUERADE' in result.stdout + except: + return False + + def _check_forwarding_rules(self) -> bool: + """Check if forwarding rules are configured""" + try: + # Check in WireGuard container + result = subprocess.run(['docker', 'exec', 'cell-wireguard', 'iptables', '-L', 'FORWARD', '-n'], capture_output=True, text=True, check=True) + # Check for ACCEPT rules (which indicate forwarding is allowed) + return 'ACCEPT' in result.stdout and len(result.stdout.strip().split('\n')) > 2 + except: + return False + + def _check_interface_status(self) -> bool: + """Check if WireGuard interface is up""" + try: + # Check in WireGuard container + result = subprocess.run(['docker', 'exec', 'cell-wireguard', 'ip', 'link', 'show', 'wg0'], capture_output=True, text=True, check=True) + return 'UP' in result.stdout + except: + return False \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index a2df860..7891196 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,7 +57,7 @@ services: restart: unless-stopped networks: - cell-network - command: ["/bin/sh", "-c", "apk add --no-cache chrony && chronyd -d -f /etc/chrony/chrony.conf"] + command: ["/bin/sh", "-c", "apk add --no-cache chrony && exec chronyd -d -f /etc/chrony/chrony.conf -n"] # Email Server - Postfix + Dovecot mail: @@ -177,4 +177,4 @@ networks: driver: bridge ipam: config: - - subnet: 172.20.0.0/16 \ No newline at end of file + - subnet: 172.20.0.0/16 diff --git a/docs/NETWORK_CONFIGURATION.md b/docs/NETWORK_CONFIGURATION.md new file mode 100644 index 0000000..741f1db --- /dev/null +++ b/docs/NETWORK_CONFIGURATION.md @@ -0,0 +1,389 @@ +# Personal Internet Cell - Network Configuration Guide + +This guide explains how to configure networking for the Personal Internet Cell to provide internet access to WireGuard VPN clients. + +## Table of Contents + +1. [Overview](#overview) +2. [Network Architecture](#network-architecture) +3. [Quick Setup](#quick-setup) +4. [Detailed Configuration](#detailed-configuration) +5. [Troubleshooting](#troubleshooting) +6. [Advanced Configuration](#advanced-configuration) +7. [Security Considerations](#security-considerations) + +## Overview + +The Personal Internet Cell provides a complete VPN solution with internet access. This requires proper configuration of: + +- **IP Forwarding**: Allow traffic to pass through the server +- **NAT (Network Address Translation)**: Translate private IPs to public IPs +- **Routing**: Direct traffic from VPN clients to the internet +- **Firewall Rules**: Control traffic flow and security + +## Network Architecture + +``` +Internet + │ + ▼ +[Host Server] (195.178.106.244) + │ + ├── [Docker Network] (172.20.0.0/16) + │ └── [WireGuard Container] (cell-wireguard) + │ └── [WireGuard Interface] (wg0: 10.0.0.1/24) + │ + └── [VPN Clients] (10.0.0.2-10.0.0.254/24) + └── [Internet Access via NAT] +``` + +### Key Components + +- **Host Interface**: `eth0` (or main network interface) +- **WireGuard Interface**: `wg0` (10.0.0.1/24) +- **Client Network**: `10.0.0.0/24` +- **NAT Translation**: Client IPs → Host IP + +## Quick Setup + +### 1. Run the Network Configuration Script + +```bash +# Make the script executable (if not already done) +chmod +x /opt/pic/scripts/setup-network.sh + +# Run the configuration +sudo /opt/pic/scripts/setup-network.sh setup +``` + +### 2. Verify Configuration + +```bash +# Check status +sudo /opt/pic/scripts/setup-network.sh status + +# Test configuration +sudo /opt/pic/scripts/setup-network.sh test +``` + +### 3. Connect a VPN Client + +Use the generated WireGuard configuration to connect a client. The client should now have internet access. + +## Detailed Configuration + +### IP Forwarding + +IP forwarding allows the server to route packets between different network interfaces. + +**Enable on Host:** +```bash +echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf +sysctl -p +``` + +**Enable in Container:** +```bash +docker exec cell-wireguard sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward" +``` + +### NAT Configuration + +NAT (Network Address Translation) allows VPN clients to access the internet using the server's public IP. + +**Container NAT Rules:** +```bash +# Allow forwarding for WireGuard traffic +iptables -A FORWARD -i wg0 -j ACCEPT +iptables -A FORWARD -o wg0 -j ACCEPT + +# NAT rule for internet access +iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE +``` + +**Host NAT Rules:** +```bash +# Allow traffic from WireGuard network +iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE +iptables -A FORWARD -i wg0 -j ACCEPT +iptables -A FORWARD -o wg0 -j ACCEPT +``` + +### Routing Configuration + +**WireGuard Interface Setup:** +```bash +# Create WireGuard interface +ip link add dev wg0 type wireguard + +# Set private key +wg set wg0 private-key /path/to/private-key + +# Set listen port +wg set wg0 listen-port 51820 + +# Add IP address +ip addr add 10.0.0.1/24 dev wg0 + +# Bring interface up +ip link set wg0 up + +# Add peers +wg set wg0 peer allowed-ips 10.0.0.2/32 +``` + +## Troubleshooting + +### Common Issues + +#### 1. VPN Connected but No Internet + +**Symptoms:** +- WireGuard shows connected +- Can ping server (10.0.0.1) +- Cannot access internet + +**Solutions:** +```bash +# Check IP forwarding +cat /proc/sys/net/ipv4/ip_forward +# Should return 1 + +# Check NAT rules +iptables -t nat -L POSTROUTING -n +# Should show MASQUERADE rule for 10.0.0.0/24 + +# Check forwarding rules +iptables -L FORWARD -n +# Should show ACCEPT rules for wg0 + +# Restart network configuration +sudo /opt/pic/scripts/setup-network.sh reset +sudo /opt/pic/scripts/setup-network.sh setup +``` + +#### 2. Cannot Connect to VPN + +**Symptoms:** +- WireGuard client cannot connect +- No handshake in server logs + +**Solutions:** +```bash +# Check WireGuard interface +docker exec cell-wireguard wg show + +# Check if port 51820 is open +netstat -ulnp | grep 51820 + +# Check firewall rules +ufw status +iptables -L INPUT -n + +# Check Docker port mapping +docker port cell-wireguard +``` + +#### 3. DNS Issues + +**Symptoms:** +- Can ping IP addresses +- Cannot resolve domain names + +**Solutions:** +```bash +# Check DNS configuration in client config +# Should include: DNS = 8.8.8.8, 1.1.1.1 + +# Test DNS from container +docker exec cell-wireguard nslookup google.com + +# Check if DNS is being blocked +docker exec cell-wireguard iptables -L -n | grep 53 +``` + +### Diagnostic Commands + +```bash +# Check network status +sudo /opt/pic/scripts/setup-network.sh status + +# Test connectivity from container +docker exec cell-wireguard ping -c 3 8.8.8.8 + +# Check routing table +docker exec cell-wireguard ip route show + +# Check interface status +docker exec cell-wireguard ip addr show wg0 + +# Check NAT rules +docker exec cell-wireguard iptables -t nat -L -n + +# Check forwarding rules +docker exec cell-wireguard iptables -L FORWARD -n +``` + +## Advanced Configuration + +### Custom DNS Servers + +To use custom DNS servers, modify the WireGuard client configuration: + +```ini +[Interface] +PrivateKey = +Address = 10.0.0.2/32 +DNS = 1.1.1.1, 1.0.0.1, 8.8.8.8, 8.8.4.4 + +[Peer] +PublicKey = +Endpoint = 195.178.106.244:51820 +AllowedIPs = 0.0.0.0/0 +PersistentKeepalive = 25 +``` + +### Split Tunneling + +To allow only specific traffic through the VPN: + +```ini +[Peer] +AllowedIPs = 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 +# Only route private networks through VPN +``` + +### Port Forwarding + +To forward specific ports to VPN clients: + +```bash +# Forward port 8080 to client 10.0.0.2 +iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.0.2:8080 +iptables -A FORWARD -p tcp -d 10.0.0.2 --dport 8080 -j ACCEPT +``` + +### Bandwidth Limiting + +To limit bandwidth for VPN clients: + +```bash +# Install tc (traffic control) +apt-get install iproute2 + +# Limit client 10.0.0.2 to 1Mbps +tc qdisc add dev wg0 root handle 1: htb default 30 +tc class add dev wg0 parent 1: classid 1:1 htb rate 1mbit +tc class add dev wg0 parent 1:1 classid 1:10 htb rate 1mbit ceil 1mbit +tc filter add dev wg0 protocol ip parent 1:0 prio 1 u32 match ip dst 10.0.0.2 flowid 1:10 +``` + +## Security Considerations + +### Firewall Rules + +**Basic Security Rules:** +```bash +# Drop invalid packets +iptables -A INPUT -m conntrack --ctstate INVALID -j DROP + +# Allow established connections +iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT + +# Allow WireGuard traffic +iptables -A INPUT -p udp --dport 51820 -j ACCEPT + +# Allow SSH (if needed) +iptables -A INPUT -p tcp --dport 22 -j ACCEPT + +# Drop everything else +iptables -A INPUT -j DROP +``` + +### Client Isolation + +To prevent clients from communicating with each other: + +```bash +# Block inter-client communication +iptables -A FORWARD -i wg0 -o wg0 -j DROP +``` + +### Logging + +To log VPN traffic: + +```bash +# Log all WireGuard traffic +iptables -A FORWARD -i wg0 -j LOG --log-prefix "WG-FORWARD: " +iptables -A FORWARD -o wg0 -j LOG --log-prefix "WG-FORWARD: " + +# Log NAT traffic +iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j LOG --log-prefix "WG-NAT: " +``` + +## Monitoring + +### Real-time Monitoring + +```bash +# Monitor WireGuard connections +watch -n 1 "docker exec cell-wireguard wg show" + +# Monitor traffic +watch -n 1 "docker exec cell-wireguard wg show wg0 transfer" + +# Monitor NAT rules +watch -n 1 "iptables -t nat -L POSTROUTING -n -v" +``` + +### Log Analysis + +```bash +# Check system logs +journalctl -u pic-network.service -f + +# Check iptables logs +tail -f /var/log/kern.log | grep WG- + +# Check Docker logs +docker logs cell-wireguard -f +``` + +## Backup and Recovery + +### Backup Configuration + +```bash +# Backup iptables rules +iptables-save > /opt/pic/backups/iptables-backup-$(date +%Y%m%d).rules + +# Backup WireGuard configuration +cp /opt/pic/config/wireguard/wg_confs/wg0.conf /opt/pic/backups/wg0-backup-$(date +%Y%m%d).conf + +# Backup network script +cp /opt/pic/scripts/setup-network.sh /opt/pic/backups/setup-network-backup-$(date +%Y%m%d).sh +``` + +### Restore Configuration + +```bash +# Restore iptables rules +iptables-restore < /opt/pic/backups/iptables-backup-YYYYMMDD.rules + +# Restore WireGuard configuration +cp /opt/pic/backups/wg0-backup-YYYYMMDD.conf /opt/pic/config/wireguard/wg_confs/wg0.conf +docker restart cell-wireguard +``` + +## Support + +If you encounter issues: + +1. Check the troubleshooting section above +2. Run the diagnostic commands +3. Check the logs for error messages +4. Verify your network configuration +5. Test with a simple client configuration + +For additional help, check the main Personal Internet Cell documentation or create an issue in the project repository. diff --git a/scripts/setup-network.sh b/scripts/setup-network.sh new file mode 100755 index 0000000..373ebf8 --- /dev/null +++ b/scripts/setup-network.sh @@ -0,0 +1,268 @@ +#!/bin/bash + +# Personal Internet Cell - Network Configuration Script +# This script sets up proper routing and NAT for WireGuard VPN internet access + +set -e + +echo "🔧 Setting up Personal Internet Cell Network Configuration..." + +# Configuration variables +WG_INTERFACE="wg0" +WG_NETWORK="10.0.0.0/24" +WG_CONTAINER="cell-wireguard" +HOST_INTERFACE="eth0" # This will be auto-detected + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to check if running as root +check_root() { + if [[ $EUID -ne 0 ]]; then + log_error "This script must be run as root" + exit 1 + fi +} + +# Function to detect the main network interface +detect_interface() { + # Try to detect the main interface (not loopback, not docker) + HOST_INTERFACE=$(ip route | grep default | awk '{print $5}' | head -1) + if [[ -z "$HOST_INTERFACE" ]]; then + log_error "Could not detect main network interface" + exit 1 + fi + log_info "Detected main interface: $HOST_INTERFACE" +} + +# Function to enable IP forwarding +enable_ip_forwarding() { + log_info "Enabling IP forwarding..." + echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf + sysctl -p + log_success "IP forwarding enabled" +} + +# Function to configure WireGuard container networking +configure_wireguard_container() { + log_info "Configuring WireGuard container networking..." + + # Check if container is running + if ! docker ps | grep -q "$WG_CONTAINER"; then + log_error "WireGuard container is not running" + exit 1 + fi + + # Get container's main interface + CONTAINER_INTERFACE=$(docker exec $WG_CONTAINER ip route | grep default | awk '{print $5}' | head -1) + if [[ -z "$CONTAINER_INTERFACE" ]]; then + CONTAINER_INTERFACE="eth0" + fi + + log_info "Container interface: $CONTAINER_INTERFACE" + + # Configure iptables rules inside the container + docker exec $WG_CONTAINER sh -c " + # Enable IP forwarding + echo 1 > /proc/sys/net/ipv4/ip_forward + + # Add default route if missing + ip route add default via 172.20.0.1 dev $CONTAINER_INTERFACE 2>/dev/null || true + + # Clear existing rules (be careful!) + iptables -t nat -F + iptables -F FORWARD + + # Allow forwarding for WireGuard interface + iptables -A FORWARD -i $WG_INTERFACE -j ACCEPT + iptables -A FORWARD -o $WG_INTERFACE -j ACCEPT + + # NAT rule for internet access + iptables -t nat -A POSTROUTING -s $WG_NETWORK -o $CONTAINER_INTERFACE -j MASQUERADE + + # Allow established and related connections + iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT + + # Log the configuration + echo 'Network configuration applied:' + echo 'IP Forwarding:' + cat /proc/sys/net/ipv4/ip_forward + echo 'Routing Table:' + ip route show + echo 'NAT Rules:' + iptables -t nat -L POSTROUTING -n + echo 'Forwarding Rules:' + iptables -L FORWARD -n + " + + log_success "WireGuard container networking configured" +} + +# Function to configure host networking +configure_host_networking() { + log_info "Configuring host networking..." + + # Enable IP forwarding on host + echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-wireguard.conf + sysctl -p /etc/sysctl.d/99-wireguard.conf + + # Configure iptables rules on host + iptables -t nat -A POSTROUTING -s $WG_NETWORK -o $HOST_INTERFACE -j MASQUERADE + iptables -A FORWARD -i $WG_INTERFACE -j ACCEPT + iptables -A FORWARD -o $WG_INTERFACE -j ACCEPT + + # Save iptables rules + if command -v iptables-save >/dev/null 2>&1; then + mkdir -p /etc/iptables + iptables-save > /etc/iptables/rules.v4 + log_info "iptables rules saved" + fi + + log_success "Host networking configured" +} + +# Function to create persistent configuration +create_persistent_config() { + log_info "Creating persistent configuration..." + + # Create systemd service for network configuration + cat > /etc/systemd/system/pic-network.service << EOF +[Unit] +Description=Personal Internet Cell Network Configuration +After=docker.service +Requires=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/opt/pic/scripts/setup-network.sh +ExecReload=/opt/pic/scripts/setup-network.sh + +[Install] +WantedBy=multi-user.target +EOF + + # Enable the service + systemctl daemon-reload + systemctl enable pic-network.service + + log_success "Persistent configuration created" +} + +# Function to test the configuration +test_configuration() { + log_info "Testing network configuration..." + + # Check if WireGuard interface is up + if docker exec $WG_CONTAINER ip link show $WG_INTERFACE >/dev/null 2>&1; then + log_success "WireGuard interface is up" + else + log_error "WireGuard interface is not up" + return 1 + fi + + # Check NAT rules + if docker exec $WG_CONTAINER iptables -t nat -L POSTROUTING | grep -q MASQUERADE; then + log_success "NAT rules are configured" + else + log_error "NAT rules are missing" + return 1 + fi + + # Check IP forwarding + if docker exec $WG_CONTAINER cat /proc/sys/net/ipv4/ip_forward | grep -q 1; then + log_success "IP forwarding is enabled" + else + log_error "IP forwarding is not enabled" + return 1 + fi + + log_success "Network configuration test passed" +} + +# Function to show status +show_status() { + log_info "Network Configuration Status:" + echo "==================================" + + echo "WireGuard Interface:" + docker exec $WG_CONTAINER ip addr show $WG_INTERFACE 2>/dev/null || echo " Interface not found" + + echo -e "\nRouting Table:" + docker exec $WG_CONTAINER ip route show + + echo -e "\nNAT Rules:" + docker exec $WG_CONTAINER iptables -t nat -L POSTROUTING -n + + echo -e "\nForwarding Rules:" + docker exec $WG_CONTAINER iptables -L FORWARD -n + + echo -e "\nIP Forwarding Status:" + echo " Container: $(docker exec $WG_CONTAINER cat /proc/sys/net/ipv4/ip_forward)" + echo " Host: $(cat /proc/sys/net/ipv4/ip_forward)" +} + +# Main execution +main() { + log_info "Starting Personal Internet Cell Network Setup..." + + check_root + detect_interface + + case "${1:-setup}" in + "setup") + enable_ip_forwarding + configure_wireguard_container + configure_host_networking + create_persistent_config + test_configuration + log_success "Network configuration completed successfully!" + ;; + "test") + test_configuration + ;; + "status") + show_status + ;; + "reset") + log_warning "Resetting network configuration..." + docker exec $WG_CONTAINER iptables -t nat -F + docker exec $WG_CONTAINER iptables -F FORWARD + iptables -t nat -D POSTROUTING -s $WG_NETWORK -o $HOST_INTERFACE -j MASQUERADE 2>/dev/null || true + iptables -D FORWARD -i $WG_INTERFACE -j ACCEPT 2>/dev/null || true + iptables -D FORWARD -o $WG_INTERFACE -j ACCEPT 2>/dev/null || true + log_success "Network configuration reset" + ;; + *) + echo "Usage: $0 {setup|test|status|reset}" + echo " setup - Configure network (default)" + echo " test - Test configuration" + echo " status - Show current status" + echo " reset - Reset configuration" + exit 1 + ;; + esac +} + +# Run main function +main "$@" diff --git a/webui/src/pages/Peers.jsx b/webui/src/pages/Peers.jsx index c05dc2a..59265f1 100644 --- a/webui/src/pages/Peers.jsx +++ b/webui/src/pages/Peers.jsx @@ -283,9 +283,12 @@ function Peers() { const serverAllowedIPs = peer.allowed_ips || "0.0.0.0/0"; const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE'; + // Check if IP already has a subnet mask, if not add /32 + const peerAddress = peer.ip.includes('/') ? peer.ip : `${peer.ip}/32`; + return `[Interface] PrivateKey = ${privateKey} -Address = ${peer.ip}/32 +Address = ${peerAddress} DNS = 8.8.8.8, 1.1.1.1 [Peer] @@ -301,11 +304,14 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`; const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820"; const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE'; + // Check if IP already has a subnet mask, if not add /32 + const peerAddress = peer.ip.includes('/') ? peer.ip : `${peer.ip}/32`; + // Create a config that's compatible with qrencode and mobile apps // Use proper spacing and format that WireGuard apps expect return `[Interface] PrivateKey = ${privateKey} -Address = ${peer.ip}/32 +Address = ${peerAddress} DNS = 8.8.8.8, 1.1.1.1 [Peer] diff --git a/webui/src/pages/Routing.jsx b/webui/src/pages/Routing.jsx index a30f8c4..fba6885 100644 --- a/webui/src/pages/Routing.jsx +++ b/webui/src/pages/Routing.jsx @@ -27,12 +27,18 @@ function Routing() { const [showFwForm, setShowFwForm] = useState(false); const [newFwRule, setNewFwRule] = useState({ rule_type: 'INPUT', source: '', destination: '', action: 'ACCEPT', protocol: 'ALL', port_range: '' }); const [fwSubmitting, setFwSubmitting] = useState(false); + // Network Configuration state + const [networkStatus, setNetworkStatus] = useState(null); + const [networkLoading, setNetworkLoading] = useState(false); + const [networkError, setNetworkError] = useState(null); + const [isSettingUp, setIsSettingUp] = useState(false); useEffect(() => { fetchRoutingStatus(); fetchNatRules(); fetchPeerRoutes(); fetchFirewallRules(); + fetchNetworkStatus(); }, []); const fetchRoutingStatus = async () => { @@ -85,6 +91,53 @@ function Routing() { } }; + const fetchNetworkStatus = async () => { + setNetworkLoading(true); + setNetworkError(null); + try { + const response = await fetch('http://localhost:3000/api/wireguard/network/status'); + if (response.ok) { + const data = await response.json(); + setNetworkStatus(data); + } else { + throw new Error('Failed to fetch network status'); + } + } catch (error) { + console.error('Failed to fetch network status:', error); + setNetworkError('Failed to fetch network status'); + } finally { + setNetworkLoading(false); + } + }; + + const setupNetworkConfiguration = async () => { + setIsSettingUp(true); + setNetworkError(null); + try { + const response = await fetch('http://localhost:3000/api/wireguard/network/setup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + const data = await response.json(); + console.log('Network setup successful:', data); + // Refresh network status + await fetchNetworkStatus(); + } else { + const errorData = await response.json(); + throw new Error(errorData.error || 'Failed to setup network configuration'); + } + } catch (error) { + console.error('Failed to setup network configuration:', error); + setNetworkError(error.message); + } finally { + setIsSettingUp(false); + } + }; + const handleNatInputChange = (e) => { const { name, value, type, checked } = e.target; setNewNat((prev) => ({ @@ -216,6 +269,7 @@ function Routing() { const tabs = [ { id: 'overview', name: 'Overview', icon: Activity }, + { id: 'network', name: 'Network Config', icon: Wifi }, { id: 'nat', name: 'NAT Rules', icon: Shield }, { id: 'peers', name: 'Peer Routes', icon: Wifi }, { id: 'firewall', name: 'Firewall', icon: Settings }, @@ -332,6 +386,148 @@ function Routing() { )} + {activeTab === 'network' && ( +
+
+

Network Configuration

+ +
+ + {networkError && ( +
+

{networkError}

+
+ )} + + {networkLoading ? ( +
+
+
+ ) : networkStatus ? ( +
+ {/* Network Status Cards */} +
+
+
+
+
+

IP Forwarding

+

{networkStatus.ip_forwarding ? 'Enabled' : 'Disabled'}

+
+
+
+ +
+
+
+
+

WireGuard Interface

+

{networkStatus.interface_status ? 'Up' : 'Down'}

+
+
+
+ +
+
+
+
+

NAT Rules

+

{networkStatus.nat_rules ? 'Configured' : 'Missing'}

+
+
+
+ +
+
+
+
+

Forwarding Rules

+

{networkStatus.forwarding_rules ? 'Configured' : 'Missing'}

+
+
+
+
+ + {/* Configuration Details */} +
+

Configuration Details

+
+
+ Last Updated: + {new Date(networkStatus.timestamp).toLocaleString()} +
+
+ IP Forwarding: + + {networkStatus.ip_forwarding ? 'Enabled' : 'Disabled'} + +
+
+ WireGuard Interface: + + {networkStatus.interface_status ? 'Up (wg0)' : 'Down'} + +
+
+ NAT Translation: + + {networkStatus.nat_rules ? 'Active' : 'Not Configured'} + +
+
+ Traffic Forwarding: + + {networkStatus.forwarding_rules ? 'Allowed' : 'Blocked'} + +
+
+
+ + {/* Quick Actions */} +
+

Quick Actions

+
+ + +
+
+
+ ) : ( +
+

Failed to load network status

+ +
+ )} +
+ )} + {activeTab === 'nat' && (
diff --git a/webui/src/pages/WireGuard.jsx b/webui/src/pages/WireGuard.jsx index 9457359..952c709 100644 --- a/webui/src/pages/WireGuard.jsx +++ b/webui/src/pages/WireGuard.jsx @@ -1,15 +1,19 @@ import { useState, useEffect } from 'react'; -import { Shield, Key, Users, Activity, Wifi, Download, Copy, RefreshCw, Play, Pause, AlertCircle } from 'lucide-react'; -import { wireguardAPI } from '../services/api'; +import { Shield, Key, Users, Activity, Wifi, Download, Copy, RefreshCw, Play, Pause, AlertCircle, Eye } from 'lucide-react'; +import { wireguardAPI, peerAPI } from '../services/api'; +import QRCode from 'qrcode'; function WireGuard() { const [status, setStatus] = useState(null); const [peers, setPeers] = useState([]); + const [totalPeers, setTotalPeers] = useState(0); const [isLoading, setIsLoading] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); const [selectedPeer, setSelectedPeer] = useState(null); const [showPeerConfig, setShowPeerConfig] = useState(false); const [peerConfig, setPeerConfig] = useState(''); + const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); + const [peerStatuses, setPeerStatuses] = useState({}); useEffect(() => { fetchWireGuardData(); @@ -17,13 +21,69 @@ function WireGuard() { const fetchWireGuardData = async () => { try { - const [statusResponse, peersResponse] = await Promise.all([ + const [statusResponse, peersResponse, wireguardResponse] = await Promise.all([ wireguardAPI.getStatus(), + peerAPI.getPeers(), wireguardAPI.getPeers() ]); setStatus(statusResponse.data); - setPeers(peersResponse.data || []); + + // Merge peer registry data with WireGuard data (same as Peers page) + const peersData = peersResponse.data || []; + const wireguardPeers = wireguardResponse.data || []; + + // Create a map of WireGuard peers by name for quick lookup + const wireguardMap = {}; + wireguardPeers.forEach(peer => { + wireguardMap[peer.name] = peer; + }); + + // Merge the data + const mergedPeers = peersData.map(peer => ({ + ...peer, + ...wireguardMap[peer.peer || peer.name], + name: peer.peer || peer.name, + status: 'Online', // For now, assume all peers are online + type: 'WireGuard', + // Preserve important fields that might be overwritten + private_key: peer.private_key, + server_public_key: peer.server_public_key, + server_endpoint: peer.server_endpoint, + allowed_ips: peer.allowed_ips || wireguardMap[peer.peer || peer.name]?.AllowedIPs || '0.0.0.0/0', + persistent_keepalive: peer.persistent_keepalive || wireguardMap[peer.peer || peer.name]?.PersistentKeepalive || 25 + })); + + // Load peer statuses first + const statusPromises = mergedPeers.map(async (peer) => { + if (peer.public_key) { + const status = await getPeerStatus(peer); + return { peerId: peer.name, status }; + } + return { peerId: peer.name, status: { online: null, lastHandshake: null, transferRx: 0, transferTx: 0 } }; + }); + + const statusResults = await Promise.all(statusPromises); + const statusMap = {}; + statusResults.forEach(({ peerId, status }) => { + statusMap[peerId] = status; + }); + setPeerStatuses(statusMap); + + // Set total peers count + setTotalPeers(mergedPeers.length); + + // Filter to only show live connected peers + const livePeers = mergedPeers.filter(peer => { + const peerStatus = statusMap[peer.name]; + return peerStatus && ( + peerStatus.online === true || + (peerStatus.lastHandshake && peerStatus.lastHandshake !== null) || + (peerStatus.transferRx > 0 || peerStatus.transferTx > 0) + ); + }); + + setPeers(livePeers); } catch (error) { console.error('Failed to fetch WireGuard data:', error); } finally { @@ -40,11 +100,39 @@ function WireGuard() { const handleViewPeerConfig = async (peer) => { setSelectedPeer(peer); try { + // Try to get existing config first const response = await wireguardAPI.getPeerConfig({ name: peer.name }); - setPeerConfig(response.data.config || 'Configuration not available'); + let config = response.data.config; + + // If no config exists, generate a complete one with real server config + if (!config || config === 'Configuration not available') { + // Get server configuration first + const serverConfig = await getServerConfig(); + + // Create peer with server config + const peerWithServerConfig = { + ...peer, + server_public_key: serverConfig.public_key, + server_endpoint: serverConfig.endpoint + }; + + config = generateWireGuardConfig(peerWithServerConfig); + } + + setPeerConfig(config); + + // Generate QR code for the config + try { + const qrDataUrl = await generateQRCode(config); + setQrCodeDataUrl(qrDataUrl); + } catch (qrError) { + console.error('Failed to generate QR code:', qrError); + setQrCodeDataUrl(''); + } } catch (error) { console.error('Failed to get peer config:', error); setPeerConfig('Failed to load configuration'); + setQrCodeDataUrl(''); } setShowPeerConfig(true); }; @@ -71,6 +159,68 @@ function WireGuard() { URL.revokeObjectURL(url); }; + const getServerConfig = async () => { + try { + // Try to get server configuration from API + const response = await fetch('/api/wireguard/server-config'); + if (response.ok) { + const config = await response.json(); + return { + public_key: config.public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER", + endpoint: config.endpoint || "YOUR_SERVER_IP:51820" + }; + } + } catch (error) { + console.warn('Could not get server config:', error); + } + + // Return default values + return { + public_key: "SERVER_PUBLIC_KEY_PLACEHOLDER", + endpoint: "YOUR_SERVER_IP:51820" + }; + }; + + const generateWireGuardConfig = (peer) => { + // Use real keys from the peer data + const serverPublicKey = peer.server_public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER"; + const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820"; + const serverAllowedIPs = peer.allowed_ips || "0.0.0.0/0"; + const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE'; + + // Check if IP already has a subnet mask, if not add /32 + const peerAddress = peer.ip.includes('/') ? peer.ip : `${peer.ip}/32`; + + return `[Interface] +PrivateKey = ${privateKey} +Address = ${peerAddress} +DNS = 8.8.8.8, 1.1.1.1 + +[Peer] +PublicKey = ${serverPublicKey} +Endpoint = ${serverEndpoint} +AllowedIPs = ${serverAllowedIPs} +PersistentKeepalive = ${peer.persistent_keepalive || 25}`; + }; + + const generateQRCode = async (text) => { + try { + const qrDataUrl = await QRCode.toDataURL(text, { + width: 256, + margin: 2, + color: { + dark: '#000000', + light: '#FFFFFF' + }, + errorCorrectionLevel: 'M' + }); + return qrDataUrl; + } catch (error) { + console.error('QR Code generation error:', error); + throw error; + } + }; + const formatBytes = (bytes) => { if (bytes === 0) return '0 B'; const k = 1024; @@ -79,15 +229,45 @@ function WireGuard() { return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; - const getPeerStatus = (peer) => { - // This would need to be implemented based on actual peer status - // For now, we'll show a mock status - return { - online: Math.random() > 0.3, // Random for demo - lastHandshake: new Date(Date.now() - Math.random() * 3600000).toISOString(), - transferRx: Math.floor(Math.random() * 1000000), - transferTx: Math.floor(Math.random() * 1000000) - }; + const getPeerStatus = async (peer) => { + try { + // Get real peer status from the API + const response = await fetch('http://localhost:3000/api/wireguard/peers/status', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + public_key: peer.public_key + }) + }); + + if (response.ok) { + const status = await response.json(); + return { + online: status.online, + lastHandshake: status.latest_handshake, + transferRx: status.transfer_rx || 0, + transferTx: status.transfer_tx || 0 + }; + } else { + console.error('Failed to get peer status:', response.status); + return { + online: null, + lastHandshake: null, + transferRx: 0, + transferTx: 0 + }; + } + } catch (error) { + console.error('Error getting peer status:', error); + return { + online: null, + lastHandshake: null, + transferRx: 0, + transferTx: 0 + }; + } }; if (isLoading) { @@ -142,7 +322,7 @@ function WireGuard() {

Total Peers

-

{peers.length}

+

{totalPeers}

@@ -153,9 +333,9 @@ function WireGuard() {
-

Active Peers

+

Live Connections

- {peers.filter(peer => getPeerStatus(peer).online).length} + {peers.length}

@@ -199,28 +379,31 @@ function WireGuard() { )} {/* Peers Table */} -
-
-
- -

Connected Peers

+
+
+
+ +

Live Connected Peers

+
+
+ {peers.length} peer{peers.length !== 1 ? 's' : ''} currently connected +
-
- {peers.length} peer{peers.length !== 1 ? 's' : ''} configured -
-
{peers.length === 0 ? (
- -

No peers configured

-

Add your first peer to start using WireGuard VPN

- + +

No active connections

+

No peers are currently connected to the WireGuard VPN

+
+

Configured peers will appear here when they connect

+ +
) : (
@@ -249,7 +432,7 @@ function WireGuard() { {peers.map((peer, index) => { - const peerStatus = getPeerStatus(peer); + const peerStatus = peerStatuses[peer.name] || { online: null, lastHandshake: null, transferRx: 0, transferTx: 0 }; return ( @@ -272,31 +455,35 @@ function WireGuard() {
- {peerStatus.online ? 'Online' : 'Offline'} + {peerStatus.online === true ? 'Online' : + peerStatus.online === false ? 'Offline' : 'Unknown'} - {peerStatus.online + {peerStatus.lastHandshake ? new Date(peerStatus.lastHandshake).toLocaleString() - : 'Never' + : 'Unknown' }
- {formatBytes(peerStatus.transferRx)} + {peerStatus.transferRx > 0 ? formatBytes(peerStatus.transferRx) : 'No data'}
- {formatBytes(peerStatus.transferTx)} + {peerStatus.transferTx > 0 ? formatBytes(peerStatus.transferTx) : 'No data'}
@@ -307,14 +494,16 @@ function WireGuard() { className="text-primary-600 hover:text-primary-900" title="View Configuration" > - +
@@ -383,39 +572,93 @@ function WireGuard() {
-
- -
-