wip: wireguard

This commit is contained in:
Cloud
2025-09-14 03:31:14 -05:00
parent 5bd7443681
commit bb6ccfe023
8 changed files with 1468 additions and 91 deletions
+43 -1
View File
@@ -832,6 +832,44 @@ def update_peer_ip():
logger.error(f"Error updating peer IP: {e}") logger.error(f"Error updating peer IP: {e}")
return jsonify({"error": str(e)}), 500 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']) @app.route('/api/wireguard/peers/config', methods=['POST'])
def get_peer_config(): def get_peer_config():
try: try:
@@ -849,10 +887,14 @@ def get_peer_config():
# Get server configuration # Get server configuration
server_config = wireguard_manager.get_server_config() 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 # Generate client configuration using peer registry data
config = f"""[Interface] config = f"""[Interface]
PrivateKey = {peer.get('private_key', 'YOUR_PRIVATE_KEY_HERE')} 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 DNS = 8.8.8.8, 1.1.1.1
[Peer] [Peer]
+238 -5
View File
@@ -331,13 +331,62 @@ AllowedIPs = {allowed_ips}
raise raise
def _reload_wireguard_config(self): def _reload_wireguard_config(self):
"""Reload WireGuard configuration""" """Reload WireGuard configuration by updating the main config file"""
try: try:
# This would typically involve restarting the WireGuard service # Read the main server configuration
# or reloading the configuration server_config_path = os.path.join(self.wg_config_dir, 'wg_confs', 'wg0.conf')
logger.info("WireGuard configuration reloaded") 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: except Exception as e:
logger.error(f"Failed to reload WireGuard configuration: {e}") logger.error(f"Failed to reload WireGuard configuration: {e}")
return False
def get_metrics(self) -> Dict[str, Any]: def get_metrics(self) -> Dict[str, Any]:
"""Get WireGuard metrics""" """Get WireGuard metrics"""
@@ -528,9 +577,13 @@ AllowedIPs = {allowed_ips}
# Get peer private key from peer data # Get peer private key from peer data
peer_private_key = peer_info.get('private_key', 'YOUR_PRIVATE_KEY_HERE') 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] config = f"""[Interface]
PrivateKey = {peer_private_key} 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 DNS = 8.8.8.8, 1.1.1.1
[Peer] [Peer]
@@ -661,3 +714,183 @@ PersistentKeepalive = {peer_info.get('persistent_keepalive', 25)}"""
'public_key': 'SERVER_PUBLIC_KEY_PLACEHOLDER', 'public_key': 'SERVER_PUBLIC_KEY_PLACEHOLDER',
'endpoint': 'YOUR_SERVER_IP:51820' 'endpoint': 'YOUR_SERVER_IP:51820'
} }
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
+1 -1
View File
@@ -57,7 +57,7 @@ services:
restart: unless-stopped restart: unless-stopped
networks: networks:
- cell-network - 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 # Email Server - Postfix + Dovecot
mail: mail:
+389
View File
@@ -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 <public-key> 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 = <private-key>
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 = <server-public-key>
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.
+268
View File
@@ -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 "$@"
+8 -2
View File
@@ -283,9 +283,12 @@ function Peers() {
const serverAllowedIPs = peer.allowed_ips || "0.0.0.0/0"; const serverAllowedIPs = peer.allowed_ips || "0.0.0.0/0";
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE'; 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] return `[Interface]
PrivateKey = ${privateKey} PrivateKey = ${privateKey}
Address = ${peer.ip}/32 Address = ${peerAddress}
DNS = 8.8.8.8, 1.1.1.1 DNS = 8.8.8.8, 1.1.1.1
[Peer] [Peer]
@@ -301,11 +304,14 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820"; const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820";
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE'; 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 // Create a config that's compatible with qrencode and mobile apps
// Use proper spacing and format that WireGuard apps expect // Use proper spacing and format that WireGuard apps expect
return `[Interface] return `[Interface]
PrivateKey = ${privateKey} PrivateKey = ${privateKey}
Address = ${peer.ip}/32 Address = ${peerAddress}
DNS = 8.8.8.8, 1.1.1.1 DNS = 8.8.8.8, 1.1.1.1
[Peer] [Peer]
+196
View File
@@ -27,12 +27,18 @@ function Routing() {
const [showFwForm, setShowFwForm] = useState(false); const [showFwForm, setShowFwForm] = useState(false);
const [newFwRule, setNewFwRule] = useState({ rule_type: 'INPUT', source: '', destination: '', action: 'ACCEPT', protocol: 'ALL', port_range: '' }); const [newFwRule, setNewFwRule] = useState({ rule_type: 'INPUT', source: '', destination: '', action: 'ACCEPT', protocol: 'ALL', port_range: '' });
const [fwSubmitting, setFwSubmitting] = useState(false); 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(() => { useEffect(() => {
fetchRoutingStatus(); fetchRoutingStatus();
fetchNatRules(); fetchNatRules();
fetchPeerRoutes(); fetchPeerRoutes();
fetchFirewallRules(); fetchFirewallRules();
fetchNetworkStatus();
}, []); }, []);
const fetchRoutingStatus = async () => { 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 handleNatInputChange = (e) => {
const { name, value, type, checked } = e.target; const { name, value, type, checked } = e.target;
setNewNat((prev) => ({ setNewNat((prev) => ({
@@ -216,6 +269,7 @@ function Routing() {
const tabs = [ const tabs = [
{ id: 'overview', name: 'Overview', icon: Activity }, { id: 'overview', name: 'Overview', icon: Activity },
{ id: 'network', name: 'Network Config', icon: Wifi },
{ id: 'nat', name: 'NAT Rules', icon: Shield }, { id: 'nat', name: 'NAT Rules', icon: Shield },
{ id: 'peers', name: 'Peer Routes', icon: Wifi }, { id: 'peers', name: 'Peer Routes', icon: Wifi },
{ id: 'firewall', name: 'Firewall', icon: Settings }, { id: 'firewall', name: 'Firewall', icon: Settings },
@@ -332,6 +386,148 @@ function Routing() {
</div> </div>
)} )}
{activeTab === 'network' && (
<div>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-medium text-gray-900">Network Configuration</h3>
<button
className="btn btn-primary flex items-center"
onClick={setupNetworkConfiguration}
disabled={isSettingUp}
>
{isSettingUp ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
) : (
<Settings className="h-4 w-4 mr-2" />
)}
{isSettingUp ? 'Setting up...' : 'Setup Network'}
</button>
</div>
{networkError && (
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-800">{networkError}</p>
</div>
)}
{networkLoading ? (
<div className="flex justify-center items-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
</div>
) : networkStatus ? (
<div className="space-y-6">
{/* Network Status Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center">
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.ip_forwarding ? 'bg-green-400' : 'bg-red-400'}`}></div>
<div>
<p className="text-sm font-medium text-gray-900">IP Forwarding</p>
<p className="text-xs text-gray-500">{networkStatus.ip_forwarding ? 'Enabled' : 'Disabled'}</p>
</div>
</div>
</div>
<div className="bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center">
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.interface_status ? 'bg-green-400' : 'bg-red-400'}`}></div>
<div>
<p className="text-sm font-medium text-gray-900">WireGuard Interface</p>
<p className="text-xs text-gray-500">{networkStatus.interface_status ? 'Up' : 'Down'}</p>
</div>
</div>
</div>
<div className="bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center">
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.nat_rules ? 'bg-green-400' : 'bg-red-400'}`}></div>
<div>
<p className="text-sm font-medium text-gray-900">NAT Rules</p>
<p className="text-xs text-gray-500">{networkStatus.nat_rules ? 'Configured' : 'Missing'}</p>
</div>
</div>
</div>
<div className="bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center">
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.forwarding_rules ? 'bg-green-400' : 'bg-red-400'}`}></div>
<div>
<p className="text-sm font-medium text-gray-900">Forwarding Rules</p>
<p className="text-xs text-gray-500">{networkStatus.forwarding_rules ? 'Configured' : 'Missing'}</p>
</div>
</div>
</div>
</div>
{/* Configuration Details */}
<div className="bg-gray-50 p-4 rounded-lg">
<h4 className="text-md font-medium text-gray-900 mb-3">Configuration Details</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-600">Last Updated:</span>
<span className="text-gray-900">{new Date(networkStatus.timestamp).toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">IP Forwarding:</span>
<span className={`font-medium ${networkStatus.ip_forwarding ? 'text-green-600' : 'text-red-600'}`}>
{networkStatus.ip_forwarding ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">WireGuard Interface:</span>
<span className={`font-medium ${networkStatus.interface_status ? 'text-green-600' : 'text-red-600'}`}>
{networkStatus.interface_status ? 'Up (wg0)' : 'Down'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">NAT Translation:</span>
<span className={`font-medium ${networkStatus.nat_rules ? 'text-green-600' : 'text-red-600'}`}>
{networkStatus.nat_rules ? 'Active' : 'Not Configured'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Traffic Forwarding:</span>
<span className={`font-medium ${networkStatus.forwarding_rules ? 'text-green-600' : 'text-red-600'}`}>
{networkStatus.forwarding_rules ? 'Allowed' : 'Blocked'}
</span>
</div>
</div>
</div>
{/* Quick Actions */}
<div className="bg-blue-50 p-4 rounded-lg">
<h4 className="text-md font-medium text-blue-900 mb-2">Quick Actions</h4>
<div className="flex flex-wrap gap-2">
<button
className="btn btn-sm btn-outline"
onClick={fetchNetworkStatus}
>
Refresh Status
</button>
<button
className="btn btn-sm btn-primary"
onClick={setupNetworkConfiguration}
disabled={isSettingUp}
>
{isSettingUp ? 'Setting up...' : 'Setup Network'}
</button>
</div>
</div>
</div>
) : (
<div className="text-center py-8">
<p className="text-gray-500">Failed to load network status</p>
<button
className="btn btn-primary mt-2"
onClick={fetchNetworkStatus}
>
Retry
</button>
</div>
)}
</div>
)}
{activeTab === 'nat' && ( {activeTab === 'nat' && (
<div> <div>
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
+279 -36
View File
@@ -1,15 +1,19 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Shield, Key, Users, Activity, Wifi, Download, Copy, RefreshCw, Play, Pause, AlertCircle } from 'lucide-react'; import { Shield, Key, Users, Activity, Wifi, Download, Copy, RefreshCw, Play, Pause, AlertCircle, Eye } from 'lucide-react';
import { wireguardAPI } from '../services/api'; import { wireguardAPI, peerAPI } from '../services/api';
import QRCode from 'qrcode';
function WireGuard() { function WireGuard() {
const [status, setStatus] = useState(null); const [status, setStatus] = useState(null);
const [peers, setPeers] = useState([]); const [peers, setPeers] = useState([]);
const [totalPeers, setTotalPeers] = useState(0);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
const [selectedPeer, setSelectedPeer] = useState(null); const [selectedPeer, setSelectedPeer] = useState(null);
const [showPeerConfig, setShowPeerConfig] = useState(false); const [showPeerConfig, setShowPeerConfig] = useState(false);
const [peerConfig, setPeerConfig] = useState(''); const [peerConfig, setPeerConfig] = useState('');
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
const [peerStatuses, setPeerStatuses] = useState({});
useEffect(() => { useEffect(() => {
fetchWireGuardData(); fetchWireGuardData();
@@ -17,13 +21,69 @@ function WireGuard() {
const fetchWireGuardData = async () => { const fetchWireGuardData = async () => {
try { try {
const [statusResponse, peersResponse] = await Promise.all([ const [statusResponse, peersResponse, wireguardResponse] = await Promise.all([
wireguardAPI.getStatus(), wireguardAPI.getStatus(),
peerAPI.getPeers(),
wireguardAPI.getPeers() wireguardAPI.getPeers()
]); ]);
setStatus(statusResponse.data); 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) { } catch (error) {
console.error('Failed to fetch WireGuard data:', error); console.error('Failed to fetch WireGuard data:', error);
} finally { } finally {
@@ -40,11 +100,39 @@ function WireGuard() {
const handleViewPeerConfig = async (peer) => { const handleViewPeerConfig = async (peer) => {
setSelectedPeer(peer); setSelectedPeer(peer);
try { try {
// Try to get existing config first
const response = await wireguardAPI.getPeerConfig({ name: peer.name }); 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) { } catch (error) {
console.error('Failed to get peer config:', error); console.error('Failed to get peer config:', error);
setPeerConfig('Failed to load configuration'); setPeerConfig('Failed to load configuration');
setQrCodeDataUrl('');
} }
setShowPeerConfig(true); setShowPeerConfig(true);
}; };
@@ -71,6 +159,68 @@ function WireGuard() {
URL.revokeObjectURL(url); 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) => { const formatBytes = (bytes) => {
if (bytes === 0) return '0 B'; if (bytes === 0) return '0 B';
const k = 1024; const k = 1024;
@@ -79,15 +229,45 @@ function WireGuard() {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}; };
const getPeerStatus = (peer) => { const getPeerStatus = async (peer) => {
// This would need to be implemented based on actual peer status try {
// For now, we'll show a mock status // 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 { return {
online: Math.random() > 0.3, // Random for demo online: status.online,
lastHandshake: new Date(Date.now() - Math.random() * 3600000).toISOString(), lastHandshake: status.latest_handshake,
transferRx: Math.floor(Math.random() * 1000000), transferRx: status.transfer_rx || 0,
transferTx: Math.floor(Math.random() * 1000000) 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) { if (isLoading) {
@@ -142,7 +322,7 @@ function WireGuard() {
</div> </div>
<div className="ml-4"> <div className="ml-4">
<p className="text-sm font-medium text-gray-500">Total Peers</p> <p className="text-sm font-medium text-gray-500">Total Peers</p>
<p className="text-lg font-semibold text-gray-900">{peers.length}</p> <p className="text-lg font-semibold text-gray-900">{totalPeers}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -153,9 +333,9 @@ function WireGuard() {
<Activity className="h-6 w-6 text-green-600" /> <Activity className="h-6 w-6 text-green-600" />
</div> </div>
<div className="ml-4"> <div className="ml-4">
<p className="text-sm font-medium text-gray-500">Active Peers</p> <p className="text-sm font-medium text-gray-500">Live Connections</p>
<p className="text-lg font-semibold text-gray-900"> <p className="text-lg font-semibold text-gray-900">
{peers.filter(peer => getPeerStatus(peer).online).length} {peers.length}
</p> </p>
</div> </div>
</div> </div>
@@ -203,25 +383,28 @@ function WireGuard() {
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div className="flex items-center"> <div className="flex items-center">
<Users className="h-6 w-6 text-primary-500 mr-2" /> <Users className="h-6 w-6 text-primary-500 mr-2" />
<h3 className="text-lg font-medium text-gray-900">Connected Peers</h3> <h3 className="text-lg font-medium text-gray-900">Live Connected Peers</h3>
</div> </div>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
{peers.length} peer{peers.length !== 1 ? 's' : ''} configured {peers.length} peer{peers.length !== 1 ? 's' : ''} currently connected
</div> </div>
</div> </div>
{peers.length === 0 ? ( {peers.length === 0 ? (
<div className="text-center py-12"> <div className="text-center py-12">
<Shield className="h-12 w-12 text-gray-400 mx-auto mb-4" /> <Wifi className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No peers configured</h3> <h3 className="text-lg font-medium text-gray-900 mb-2">No active connections</h3>
<p className="text-gray-500 mb-4">Add your first peer to start using WireGuard VPN</p> <p className="text-gray-500 mb-4">No peers are currently connected to the WireGuard VPN</p>
<div className="space-y-2">
<p className="text-sm text-gray-400">Configured peers will appear here when they connect</p>
<button <button
onClick={() => window.location.href = '/peers'} onClick={() => window.location.href = '/peers'}
className="btn btn-primary" className="btn btn-secondary"
> >
Add Peer Manage Peers
</button> </button>
</div> </div>
</div>
) : ( ) : (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200"> <table className="min-w-full divide-y divide-gray-200">
@@ -249,7 +432,7 @@ function WireGuard() {
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{peers.map((peer, index) => { {peers.map((peer, index) => {
const peerStatus = getPeerStatus(peer); const peerStatus = peerStatuses[peer.name] || { online: null, lastHandshake: null, transferRx: 0, transferTx: 0 };
return ( return (
<tr key={index} className="hover:bg-gray-50"> <tr key={index} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
@@ -272,31 +455,35 @@ function WireGuard() {
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ <span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
peerStatus.online peerStatus.online === true
? 'bg-green-100 text-green-800' ? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800' : peerStatus.online === false
? 'bg-red-100 text-red-800'
: 'bg-gray-100 text-gray-800'
}`}> }`}>
<div className={`w-1.5 h-1.5 rounded-full mr-1.5 ${ <div className={`w-1.5 h-1.5 rounded-full mr-1.5 ${
peerStatus.online ? 'bg-green-400' : 'bg-red-400' peerStatus.online === true ? 'bg-green-400' :
peerStatus.online === false ? 'bg-red-400' : 'bg-gray-400'
}`} /> }`} />
{peerStatus.online ? 'Online' : 'Offline'} {peerStatus.online === true ? 'Online' :
peerStatus.online === false ? 'Offline' : 'Unknown'}
</span> </span>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{peerStatus.online {peerStatus.lastHandshake
? new Date(peerStatus.lastHandshake).toLocaleString() ? new Date(peerStatus.lastHandshake).toLocaleString()
: 'Never' : 'Unknown'
} }
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<div className="space-y-1"> <div className="space-y-1">
<div className="flex items-center text-xs"> <div className="flex items-center text-xs">
<span className="text-green-600 mr-1"></span> <span className="text-green-600 mr-1"></span>
{formatBytes(peerStatus.transferRx)} {peerStatus.transferRx > 0 ? formatBytes(peerStatus.transferRx) : 'No data'}
</div> </div>
<div className="flex items-center text-xs"> <div className="flex items-center text-xs">
<span className="text-blue-600 mr-1"></span> <span className="text-blue-600 mr-1"></span>
{formatBytes(peerStatus.transferTx)} {peerStatus.transferTx > 0 ? formatBytes(peerStatus.transferTx) : 'No data'}
</div> </div>
</div> </div>
</td> </td>
@@ -307,14 +494,16 @@ function WireGuard() {
className="text-primary-600 hover:text-primary-900" className="text-primary-600 hover:text-primary-900"
title="View Configuration" title="View Configuration"
> >
<Copy className="h-4 w-4" /> <Eye className="h-4 w-4" />
</button> </button>
<button <button
onClick={() => downloadConfig(peer.name, peerConfig)} onClick={() => handleViewPeerConfig(peer)}
className="text-primary-600 hover:text-primary-900" className="text-green-600 hover:text-green-900"
title="Download Config" title="Show QR Code"
> >
<Download className="h-4 w-4" /> <div className="h-4 w-4 border-2 border-current rounded-sm flex items-center justify-center">
<div className="w-2 h-2 bg-current rounded-sm"></div>
</div>
</button> </button>
</div> </div>
</td> </td>
@@ -383,6 +572,7 @@ function WireGuard() {
</div> </div>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
WireGuard Configuration WireGuard Configuration
@@ -417,6 +607,59 @@ function WireGuard() {
Use this configuration on your mobile device or computer to connect to this WireGuard network. Use this configuration on your mobile device or computer to connect to this WireGuard network.
</p> </p>
</div> </div>
{/* QR Code Section */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2 flex items-center">
<div className="h-5 w-5 mr-2 border-2 border-gray-400 rounded-sm flex items-center justify-center">
<div className="w-2 h-2 bg-gray-400 rounded-sm"></div>
</div>
QR Code
</label>
<div className="text-center">
{qrCodeDataUrl ? (
<div className="space-y-4">
<div className="inline-block p-4 bg-white border-2 border-gray-200 rounded-lg">
<img
src={qrCodeDataUrl}
alt="WireGuard QR Code"
className="w-48 h-48 mx-auto"
/>
</div>
<div className="space-y-2">
<p className="text-sm text-gray-600">
Scan this QR code with your WireGuard app to connect automatically
</p>
<div className="flex justify-center space-x-2">
<button
onClick={() => {
const link = document.createElement('a');
link.href = qrCodeDataUrl;
link.download = `${selectedPeer.name}-qrcode.png`;
link.click();
}}
className="btn btn-sm btn-secondary flex items-center"
title="Download QR Code"
>
<Download className="h-4 w-4 mr-1" />
Download QR
</button>
</div>
</div>
</div>
) : (
<div className="p-8 bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg">
<div className="h-12 w-12 border-2 border-gray-400 rounded-sm flex items-center justify-center mx-auto mb-2">
<div className="w-6 h-6 bg-gray-400 rounded-sm"></div>
</div>
<p className="text-sm text-gray-500">
QR Code will appear here
</p>
</div>
)}
</div>
</div>
</div>
</div> </div>
<div className="flex justify-end space-x-3 mt-6"> <div className="flex justify-end space-x-3 mt-6">