wip: wireguard
This commit is contained in:
+43
-1
@@ -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
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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.
|
||||||
Executable
+268
@@ -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 "$@"
|
||||||
@@ -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]
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
+323
-80
@@ -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
|
||||||
return {
|
const response = await fetch('http://localhost:3000/api/wireguard/peers/status', {
|
||||||
online: Math.random() > 0.3, // Random for demo
|
method: 'POST',
|
||||||
lastHandshake: new Date(Date.now() - Math.random() * 3600000).toISOString(),
|
headers: {
|
||||||
transferRx: Math.floor(Math.random() * 1000000),
|
'Content-Type': 'application/json',
|
||||||
transferTx: Math.floor(Math.random() * 1000000)
|
},
|
||||||
};
|
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) {
|
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>
|
||||||
@@ -199,28 +379,31 @@ function WireGuard() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Peers Table */}
|
{/* Peers Table */}
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<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 className="text-sm text-gray-500">
|
||||||
|
{peers.length} peer{peers.length !== 1 ? 's' : ''} currently connected
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
|
||||||
{peers.length} peer{peers.length !== 1 ? 's' : ''} configured
|
|
||||||
</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>
|
||||||
<button
|
<div className="space-y-2">
|
||||||
onClick={() => window.location.href = '/peers'}
|
<p className="text-sm text-gray-400">Configured peers will appear here when they connect</p>
|
||||||
className="btn btn-primary"
|
<button
|
||||||
>
|
onClick={() => window.location.href = '/peers'}
|
||||||
Add Peer
|
className="btn btn-secondary"
|
||||||
</button>
|
>
|
||||||
|
Manage Peers
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
@@ -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,39 +572,93 @@ function WireGuard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<div>
|
||||||
WireGuard Configuration
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
</label>
|
WireGuard Configuration
|
||||||
<div className="relative">
|
</label>
|
||||||
<textarea
|
<div className="relative">
|
||||||
value={peerConfig}
|
<textarea
|
||||||
readOnly
|
value={peerConfig}
|
||||||
className="w-full h-64 p-3 border border-gray-300 rounded-md font-mono text-sm bg-gray-50"
|
readOnly
|
||||||
placeholder="Loading configuration..."
|
className="w-full h-64 p-3 border border-gray-300 rounded-md font-mono text-sm bg-gray-50"
|
||||||
/>
|
placeholder="Loading configuration..."
|
||||||
<div className="absolute top-2 right-2 flex space-x-2">
|
/>
|
||||||
<button
|
<div className="absolute top-2 right-2 flex space-x-2">
|
||||||
onClick={() => copyToClipboard(peerConfig)}
|
<button
|
||||||
className="btn btn-secondary btn-sm flex items-center"
|
onClick={() => copyToClipboard(peerConfig)}
|
||||||
title="Copy to clipboard"
|
className="btn btn-secondary btn-sm flex items-center"
|
||||||
>
|
title="Copy to clipboard"
|
||||||
<Copy className="h-4 w-4 mr-1" />
|
>
|
||||||
Copy
|
<Copy className="h-4 w-4 mr-1" />
|
||||||
</button>
|
Copy
|
||||||
<button
|
</button>
|
||||||
onClick={() => downloadConfig(selectedPeer.name, peerConfig)}
|
<button
|
||||||
className="btn btn-secondary btn-sm flex items-center"
|
onClick={() => downloadConfig(selectedPeer.name, peerConfig)}
|
||||||
title="Download config file"
|
className="btn btn-secondary btn-sm flex items-center"
|
||||||
>
|
title="Download config file"
|
||||||
<Download className="h-4 w-4 mr-1" />
|
>
|
||||||
Download
|
<Download className="h-4 w-4 mr-1" />
|
||||||
</button>
|
Download
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-500 mt-2">
|
||||||
|
Use this configuration on your mobile device or computer to connect to this WireGuard network.
|
||||||
|
</p>
|
||||||
|
</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>
|
||||||
<p className="text-xs text-gray-500 mt-2">
|
|
||||||
Use this configuration on your mobile device or computer to connect to this WireGuard network.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user