mirror of https://github.com/wg-easy/wg-easy
Browse Source
This commit implements full AmneziaWG support including kernel module building, obfuscation parameter management, and conditional UI display. ## Kernel Module Support - Build amneziawg kernel module for Alpine LTS kernel 6.12.50 - Multi-stage Docker build with kernel_module_builder - Pre-compile module during image build for target kernel - Graceful fallback to standard WireGuard if build fails - Copy built module to /lib/modules/ in final image ## Database & Schema Changes - Add AWG obfuscation parameters to interface schema: * Core params: jc, jmin, jmax, s1, s2, h1-h4 * Advanced params: s3, s4, i1-i5, j1-j3, itime - Set sensible defaults with QUIC packet mimic for i1 - Add migration SQL with all AWG columns - Implement Zod validation for all parameters with amneziawg-go constraints ## Backend Implementation - Create awg-params.ts utility for random parameter generation - Modify wgHelper.ts to include AWG params in server/client configs - Add isUsingAwg() detection function - Auto-generate random AWG parameters on first run - Update API endpoint to return AWG status flag - Add #utils alias to nuxt.config.ts ## Frontend/UI Changes - Add comprehensive AWG parameter form fields in interface.vue - Conditional display: show AWG fields only when AWG is active - Show informative message when AWG is not enabled - All 23 parameters editable with validation hints - Organized in "AmneziaWG Obfuscation Parameters" section ## Documentation - Add AMNEZIAWG_BUILD.md with build architecture details - Add AWG_UI_IMPLEMENTATION.md with usage guide - Add amnezia-kernel-module.md documentation - Document all parameters, constraints, and usage ## Features - Automatic AWG parameter randomization for each installation - Comprehensive validation (frontend, backend, database) - Clean UI that adapts to WireGuard implementation in use - Support for AWG 1.0 and 1.5 advanced obfuscation features - Compatible with Alpine Linux LTS kernel 6.12.50 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>pull/2194/head
13 changed files with 1337 additions and 3 deletions
@ -0,0 +1,264 @@ |
|||
# AmneziaWG Build Implementation |
|||
|
|||
## Summary |
|||
|
|||
The Dockerfile now builds and includes **complete AmneziaWG support** with two components: |
|||
|
|||
1. **amneziawg-tools** - CLI tools (`awg`, `awg-quick`) |
|||
2. **amneziawg.ko** - Kernel module (pre-built for Alpine LTS 6.12.50) |
|||
|
|||
## Build Process |
|||
|
|||
### Stage 1: Build UI and Tools |
|||
```dockerfile |
|||
FROM node:lts-alpine AS build |
|||
``` |
|||
- Builds the web UI with Nuxt/pnpm |
|||
- Compiles amneziawg-tools from source |
|||
|
|||
### Stage 2: Build Kernel Module |
|||
```dockerfile |
|||
FROM alpine:3.20 AS kernel_module_builder |
|||
``` |
|||
- Installs `linux-lts-dev` package (provides headers for 6.12.50) |
|||
- Clones amneziawg-linux-kernel-module repository |
|||
- Compiles kernel module against Alpine LTS headers |
|||
- Exports `amneziawg.ko` if build succeeds |
|||
|
|||
### Stage 3: Final Image |
|||
```dockerfile |
|||
FROM node:lts-alpine |
|||
``` |
|||
- Copies all built artifacts from stages 1 & 2 |
|||
- Installs runtime dependencies (iptables, wireguard-tools, etc.) |
|||
- Sets up environment variables and configurations |
|||
|
|||
## What Gets Built |
|||
|
|||
### AmneziaWG Tools (`/usr/bin/awg`, `/usr/bin/awg-quick`) |
|||
- CLI interface for managing AmneziaWG interfaces |
|||
- Compatible with standard WireGuard workflow |
|||
- Source: https://github.com/amnezia-vpn/amneziawg-tools |
|||
|
|||
### AmneziaWG Kernel Module (`/lib/modules/amneziawg.ko`) |
|||
- Pre-compiled for Alpine LTS kernel **6.12.50-0-lts** |
|||
- Kernel-level WireGuard implementation with obfuscation |
|||
- Only works on Alpine hosts with matching kernel |
|||
- Source: https://github.com/amnezia-vpn/amneziawg-linux-kernel-module |
|||
|
|||
## Kernel Module Specifics |
|||
|
|||
### Target Kernel |
|||
- **Version:** 6.12.50-0-lts (Alpine Linux LTS) |
|||
- **Headers Source:** `apk add linux-lts-dev` |
|||
- **Build Location:** `/usr/src/linux-headers-6.12.50-0-lts` |
|||
|
|||
### Compatibility |
|||
The kernel module **only works** on hosts running: |
|||
- Alpine Linux |
|||
- With `linux-lts` kernel installed |
|||
- Exact version: 6.12.50 |
|||
|
|||
For all other systems, the application will fall back to **standard WireGuard**. |
|||
|
|||
### Build Output |
|||
- Success: `/build/module/amneziawg.ko` → copied to `/lib/modules/` |
|||
- Failure: Logs warning, continues build (standard WireGuard will be used) |
|||
- Version info: `/etc/amneziawg-kernel-version.txt` |
|||
|
|||
## Runtime Behavior |
|||
|
|||
### Automatic Detection (wgHelper.ts:14-22) |
|||
```typescript |
|||
if (WG_ENV.EXPERIMENTAL_AWG) { |
|||
if (WG_ENV.OVERRIDE_AUTO_AWG !== undefined) { |
|||
wgExecutable = WG_ENV.OVERRIDE_AUTO_AWG; |
|||
} else { |
|||
wgExecutable = await exec('modinfo amneziawg') |
|||
.then(() => 'awg') |
|||
.catch(() => 'wg'); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The application checks: |
|||
1. Is `EXPERIMENTAL_AWG=true`? |
|||
2. Is there a manual override (`OVERRIDE_AUTO_AWG`)? |
|||
3. Is the amneziawg kernel module available? |
|||
4. Falls back to standard WireGuard if needed |
|||
|
|||
### Environment Variables |
|||
|
|||
| Variable | Default | Purpose | |
|||
|----------|---------|---------| |
|||
| `EXPERIMENTAL_AWG` | `false` | Enable AmneziaWG support | |
|||
| `OVERRIDE_AUTO_AWG` | `undefined` | Force `awg` or `wg` mode | |
|||
|
|||
**Example:** |
|||
```yaml |
|||
environment: |
|||
- EXPERIMENTAL_AWG=true # Enable AmneziaWG |
|||
- OVERRIDE_AUTO_AWG=awg # Force userspace mode |
|||
``` |
|||
|
|||
## Build Commands |
|||
|
|||
### Build the Image |
|||
```bash |
|||
docker build -t wg-easy . |
|||
``` |
|||
|
|||
### Check Build Results |
|||
```bash |
|||
# Check if kernel module was built |
|||
docker run --rm wg-easy ls -la /lib/modules/ |
|||
|
|||
# Check kernel version target |
|||
docker run --rm wg-easy cat /etc/amneziawg-kernel-version.txt |
|||
|
|||
# Check all AmneziaWG binaries |
|||
docker run --rm wg-easy ls -la /usr/bin/ | grep -E 'awg|amnezia' |
|||
``` |
|||
|
|||
### Expected Output |
|||
``` |
|||
-rwxr-xr-x 1 root root 1.2M amneziawg-go |
|||
-rwxr-xr-x 1 root root 156K awg |
|||
-rwxr-xr-x 1 root root 45K awg-quick |
|||
``` |
|||
|
|||
## Deployment Scenarios |
|||
|
|||
### Scenario 1: Alpine LTS Host (Kernel Module) |
|||
**Host:** Alpine Linux with kernel 6.12.50-0-lts |
|||
**Mode:** Kernel module (best performance) |
|||
|
|||
```yaml |
|||
services: |
|||
wg-easy: |
|||
image: wg-easy |
|||
environment: |
|||
- EXPERIMENTAL_AWG=true |
|||
cap_add: |
|||
- NET_ADMIN |
|||
- SYS_MODULE |
|||
privileged: true |
|||
``` |
|||
|
|||
### Scenario 2: Other Linux Distributions (Userspace) |
|||
**Host:** Ubuntu/Debian/RHEL/etc. |
|||
**Mode:** Userspace amneziawg-go (portable) |
|||
|
|||
```yaml |
|||
services: |
|||
wg-easy: |
|||
image: wg-easy |
|||
environment: |
|||
- EXPERIMENTAL_AWG=true |
|||
- OVERRIDE_AUTO_AWG=awg # Force userspace |
|||
cap_add: |
|||
- NET_ADMIN |
|||
``` |
|||
|
|||
### Scenario 3: Standard WireGuard (Fallback) |
|||
**Host:** Any Linux |
|||
**Mode:** Standard WireGuard kernel module |
|||
|
|||
```yaml |
|||
services: |
|||
wg-easy: |
|||
image: wg-easy |
|||
environment: |
|||
- EXPERIMENTAL_AWG=false |
|||
# OR |
|||
- OVERRIDE_AUTO_AWG=wg |
|||
``` |
|||
|
|||
## Verification |
|||
|
|||
### Check Kernel Module Load |
|||
```bash |
|||
# On host |
|||
lsmod | grep amneziawg |
|||
|
|||
# Expected output if loaded: |
|||
# amneziawg 81920 0 |
|||
``` |
|||
|
|||
### Check Which Mode is Active |
|||
```bash |
|||
# Check detection |
|||
docker exec wg-easy awg show |
|||
|
|||
# If working, you'll see interface details |
|||
``` |
|||
|
|||
## Troubleshooting |
|||
|
|||
### Kernel Module Won't Build |
|||
**Symptom:** Build completes but no `/lib/modules/amneziawg.ko` |
|||
|
|||
**Solutions:** |
|||
- This is expected on build hosts with different kernel versions |
|||
- The image will still work with standard WireGuard |
|||
- No action needed - module is for Alpine LTS 6.12.50 only |
|||
|
|||
### Kernel Module Won't Load at Runtime |
|||
**Symptom:** Module file exists but `modprobe amneziawg` fails |
|||
|
|||
**Causes:** |
|||
- Host kernel version != 6.12.50 |
|||
- Missing `SYS_MODULE` capability |
|||
- Not running with `privileged: true` |
|||
|
|||
**Solution:** |
|||
```yaml |
|||
environment: |
|||
- OVERRIDE_AUTO_AWG=wg # Force standard WireGuard |
|||
``` |
|||
|
|||
## Files Modified |
|||
|
|||
1. **Dockerfile** - Added multi-stage build for kernel module |
|||
2. **docs/content/advanced/config/amnezia-kernel-module.md** - New documentation |
|||
3. **AMNEZIAWG_BUILD.md** - This file |
|||
|
|||
## References |
|||
|
|||
- [AmneziaWG Tools](https://github.com/amnezia-vpn/amneziawg-tools) |
|||
- [AmneziaWG Kernel Module](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module) |
|||
- [Alpine Linux LTS Kernel](https://pkgs.alpinelinux.org/package/edge/main/x86_64/linux-lts) |
|||
|
|||
## Build Architecture |
|||
|
|||
``` |
|||
┌─────────────────────────────────────────────────────────┐ |
|||
│ Stage 1: Build (node:lts-alpine) │ |
|||
│ - Web UI (Nuxt/TypeScript) │ |
|||
│ - amneziawg-tools (C) │ |
|||
└────────────────┬────────────────────────────────────────┘ |
|||
│ |
|||
┌────────────────▼────────────────────────────────────────┐ |
|||
│ Stage 2: Kernel Module Builder (alpine:3.20) │ |
|||
│ - Install linux-lts-dev (6.12.50 headers) │ |
|||
│ - Clone amneziawg-linux-kernel-module │ |
|||
│ - Build amneziawg.ko │ |
|||
└────────────────┬────────────────────────────────────────┘ |
|||
│ |
|||
┌────────────────▼────────────────────────────────────────┐ |
|||
│ Stage 3: Final Image (node:lts-alpine) │ |
|||
│ - Copy built UI from stage 1 │ |
|||
│ - Copy amneziawg-tools from stage 1 │ |
|||
│ - Copy amneziawg.ko from stage 2 (if built) │ |
|||
│ - Install runtime dependencies │ |
|||
│ - Configure environment │ |
|||
└─────────────────────────────────────────────────────────┘ |
|||
``` |
|||
|
|||
## Key Design Decisions |
|||
|
|||
1. **Multi-stage build** - Minimizes final image size |
|||
2. **Pre-built kernel module** - Avoids runtime compilation complexity |
|||
3. **Graceful fallback** - Falls back to standard WireGuard if module fails |
|||
4. **Alpine LTS target** - Matches bare-metal install script expectations |
|||
5. **Kernel module only** - No userspace implementation needed |
|||
@ -0,0 +1,301 @@ |
|||
# AmneziaWG UI Implementation |
|||
|
|||
## Overview |
|||
|
|||
The wg-easy interface now fully supports AmneziaWG obfuscation parameters with conditional UI display based on whether AWG is active. |
|||
|
|||
## Implementation Details |
|||
|
|||
### Backend Changes |
|||
|
|||
#### 1. **wgHelper.ts** (`src/server/utils/wgHelper.ts`) |
|||
- Added `isUsingAwg()` export function to check if AWG is active |
|||
- Modified config generation to include AWG parameters when `wgExecutable === 'awg'` |
|||
- Server interface config includes: Jc, Jmin, Jmax, S1-S4, H1-H4, I1-I5, J1-J3, Itime |
|||
- Client configs include matching AWG parameters |
|||
|
|||
#### 2. **API Endpoint** (`src/server/api/admin/interface/index.get.ts`) |
|||
- Returns `isUsingAwg: boolean` flag in the response |
|||
- Frontend uses this to conditionally render AWG fields |
|||
|
|||
#### 3. **Database Schema** (`src/server/database/repositories/interface/schema.ts`) |
|||
- Added all AWG parameter columns with sensible defaults |
|||
- Default I1 set to QUIC packet mimic for traffic obfuscation |
|||
|
|||
#### 4. **Validation** (`src/server/database/repositories/interface/types.ts`) |
|||
- Comprehensive Zod validation for all AWG parameters |
|||
- Enforces amneziawg-go constraints: |
|||
- Jc: 1-128 |
|||
- Jmin: 0-1279, must be < Jmax |
|||
- Jmax: 1-1280, must be > Jmin |
|||
- S1: 0-1132, S1+56 ≠ S2 |
|||
- S2: 0-1188 |
|||
- S3: 0-1132 |
|||
- S4: 0-1188 |
|||
- H1-H4: >4, all unique |
|||
- I1-I5: max 10000 chars |
|||
- J1-J3: max 1000 chars |
|||
- Itime: 0-2147483647 |
|||
|
|||
#### 5. **Auto-Generation** (`src/server/database/sqlite.ts`) |
|||
- Generates random AWG parameters on first run |
|||
- Only randomizes if using default values |
|||
- Ensures each installation has unique obfuscation |
|||
|
|||
### Frontend Changes |
|||
|
|||
#### **Interface Admin Page** (`src/app/pages/admin/interface.vue`) |
|||
|
|||
**Conditional Display:** |
|||
- Shows AWG fields **only when** `data.isUsingAwg === true` |
|||
- Shows helpful message when AWG is not active |
|||
|
|||
**When AWG is NOT active:** |
|||
``` |
|||
┌─────────────────────────────────────────────┐ |
|||
│ AmneziaWG │ |
|||
├─────────────────────────────────────────────┤ |
|||
│ AmneziaWG obfuscation is not currently │ |
|||
│ active. To enable it, set │ |
|||
│ EXPERIMENTAL_AWG=true and ensure the │ |
|||
│ amneziawg kernel module is available. │ |
|||
└─────────────────────────────────────────────┘ |
|||
``` |
|||
|
|||
**When AWG IS active:** |
|||
``` |
|||
┌─────────────────────────────────────────────┐ |
|||
│ AmneziaWG Obfuscation Parameters │ |
|||
├─────────────────────────────────────────────┤ |
|||
│ Junk packet count (Jc) [8 ] │ |
|||
│ Junk packet min size (Jmin) [8 ] │ |
|||
│ Junk packet max size (Jmax) [80 ] │ |
|||
│ Init header junk size (S1) [50 ] │ |
|||
│ Response header junk size (S2) [85 ] │ |
|||
│ Init magic header (H1) [123... ] │ |
|||
│ Response magic header (H2) [456... ] │ |
|||
│ Cookie magic header (H3) [789... ] │ |
|||
│ Transport magic header (H4) [101... ] │ |
|||
│ Cookie header junk size (S3) [0 ] │ |
|||
│ Transport header junk size (S4) [0 ] │ |
|||
│ Special junk packet 1 (I1) [<b 0x..] │ |
|||
│ Special junk packet 2 (I2) [ ] │ |
|||
│ ... (I3-I5, J1-J3, Itime) │ |
|||
└─────────────────────────────────────────────┘ |
|||
``` |
|||
|
|||
## AWG Parameter Descriptions |
|||
|
|||
### Core Obfuscation (AWG 1.0) |
|||
|
|||
| Parameter | Range | Recommended | Description | |
|||
|-----------|-------|-------------|-------------| |
|||
| **Jc** | 1-128 | 4-12 | Number of junk packets to send | |
|||
| **Jmin** | 0-1279 | 8 | Minimum junk packet size (bytes) | |
|||
| **Jmax** | 1-1280 | 80 | Maximum junk packet size (bytes) | |
|||
| **S1** | 0-1132 | 15-150 | Init packet header junk size | |
|||
| **S2** | 0-1188 | 15-150 | Response packet header junk size | |
|||
| **H1** | >4 | 5-2147483647 | Init magic header (must be unique) | |
|||
| **H2** | >4 | 5-2147483647 | Response magic header (must be unique) | |
|||
| **H3** | >4 | 5-2147483647 | Cookie magic header (must be unique) | |
|||
| **H4** | >4 | 5-2147483647 | Transport magic header (must be unique) | |
|||
|
|||
### Advanced Obfuscation (AWG 1.5) |
|||
|
|||
| Parameter | Range | Description | |
|||
|-----------|-------|-------------| |
|||
| **S3** | 0-1132 | Cookie packet header junk size | |
|||
| **S4** | 0-1188 | Transport packet header junk size | |
|||
| **I1-I5** | 0-10000 chars | Special junk packets (protocol mimics in hex) | |
|||
| **J1-J3** | 0-1000 chars | Junk packet scheduling parameters | |
|||
| **Itime** | 0-2147483647 | Interval time in seconds (0 on Windows) | |
|||
|
|||
## How It Works |
|||
|
|||
### 1. Detection Flow |
|||
``` |
|||
┌─────────────────┐ |
|||
│ Frontend loads │ |
|||
│ /api/admin/ │ |
|||
│ interface │ |
|||
└────────┬────────┘ |
|||
│ |
|||
▼ |
|||
┌─────────────────┐ |
|||
│ Backend checks │ |
|||
│ isUsingAwg() │ |
|||
└────────┬────────┘ |
|||
│ |
|||
▼ |
|||
┌─────────────────┐ |
|||
│ Returns │ |
|||
│ isUsingAwg: │ |
|||
│ true/false │ |
|||
└────────┬────────┘ |
|||
│ |
|||
▼ |
|||
┌─────────────────┐ |
|||
│ Vue template │ |
|||
│ v-if renders │ |
|||
│ conditionally │ |
|||
└─────────────────┘ |
|||
``` |
|||
|
|||
### 2. Configuration Generation |
|||
|
|||
When generating WireGuard configs: |
|||
|
|||
**Server Config (`wg0.conf`):** |
|||
```ini |
|||
[Interface] |
|||
PrivateKey = ... |
|||
Address = 10.8.0.1/24 |
|||
ListenPort = 51820 |
|||
MTU = 1420 |
|||
PreUp = ... |
|||
PostUp = ... |
|||
PreDown = ... |
|||
PostDown = ... |
|||
Jc = 8 # ← Only if AWG active |
|||
Jmin = 8 # ← Only if AWG active |
|||
Jmax = 80 # ← Only if AWG active |
|||
S1 = 50 # ← Only if AWG active |
|||
S2 = 85 # ← Only if AWG active |
|||
H1 = 1376979037 # ← Only if AWG active |
|||
H2 = 1283620850 # ← Only if AWG active |
|||
H3 = 917053776 # ← Only if AWG active |
|||
H4 = 696394151 # ← Only if AWG active |
|||
I1 = <b 0x...> # ← Only if AWG active (QUIC mimic) |
|||
# ... etc |
|||
``` |
|||
|
|||
**Client Config:** |
|||
```ini |
|||
[Interface] |
|||
PrivateKey = ... |
|||
Address = 10.8.0.2/24 |
|||
MTU = 1420 |
|||
DNS = 1.1.1.1 |
|||
Jc = 8 # ← Only if AWG active |
|||
Jmin = 8 # ← Only if AWG active |
|||
# ... (all AWG params match server) |
|||
|
|||
[Peer] |
|||
PublicKey = ... |
|||
PresharedKey = ... |
|||
AllowedIPs = 0.0.0.0/0 |
|||
PersistentKeepalive = 25 |
|||
Endpoint = vpn.example.com:51820 |
|||
``` |
|||
|
|||
## Enabling AmneziaWG |
|||
|
|||
### Prerequisites |
|||
1. **Alpine Linux** with `linux-lts` kernel (6.12.50) |
|||
2. **AmneziaWG kernel module** built and available |
|||
3. **Environment variable** set |
|||
|
|||
### Configuration |
|||
|
|||
**Docker Compose:** |
|||
```yaml |
|||
services: |
|||
wg-easy: |
|||
image: wg-easy |
|||
environment: |
|||
- EXPERIMENTAL_AWG=true # Enable AWG support |
|||
cap_add: |
|||
- NET_ADMIN |
|||
- SYS_MODULE |
|||
privileged: true |
|||
``` |
|||
|
|||
**Optional Override:** |
|||
```yaml |
|||
environment: |
|||
- EXPERIMENTAL_AWG=true |
|||
- OVERRIDE_AUTO_AWG=awg # Force AWG (skip detection) |
|||
# or |
|||
- OVERRIDE_AUTO_AWG=wg # Force standard WireGuard |
|||
``` |
|||
|
|||
## User Experience |
|||
|
|||
### Admin Workflow |
|||
|
|||
1. **Navigate to Interface Settings** |
|||
- Go to Admin → Interface |
|||
|
|||
2. **Check AWG Status** |
|||
- If AWG section shows parameters → AWG is active |
|||
- If AWG section shows message → AWG is not active |
|||
|
|||
3. **Customize Parameters** (if AWG active) |
|||
- Adjust values as needed |
|||
- Click "Save" |
|||
- Click "Restart Interface" to apply |
|||
|
|||
4. **Download Client Configs** |
|||
- All new client configs will include AWG parameters |
|||
- Existing clients keep their configs |
|||
|
|||
### Default Behavior |
|||
|
|||
- **First Install**: Random AWG parameters generated automatically |
|||
- **Existing Install**: Keeps current parameters (or defaults if upgrading) |
|||
- **Standard WG**: AWG fields hidden, no params in configs |
|||
|
|||
## Security Considerations |
|||
|
|||
### Parameter Randomization |
|||
|
|||
Each installation gets unique obfuscation parameters: |
|||
- **Magic headers (H1-H4)**: Random 32-bit integers |
|||
- **Junk sizes (Jc, Jmin, Jmax, S1-S4)**: Random within recommended ranges |
|||
- **No fingerprinting**: Each server has unique traffic signature |
|||
|
|||
### Validation |
|||
|
|||
All parameters validated on: |
|||
1. **Frontend**: Vue form validation |
|||
2. **Backend**: Zod schema validation |
|||
3. **Database**: SQLite constraints |
|||
4. **Runtime**: amneziawg-go validation |
|||
|
|||
## Troubleshooting |
|||
|
|||
### AWG Fields Not Showing |
|||
|
|||
**Check:** |
|||
1. `EXPERIMENTAL_AWG=true` is set |
|||
2. Kernel module is available: `lsmod | grep amneziawg` |
|||
3. No override forcing standard WG |
|||
|
|||
**Verify:** |
|||
```bash |
|||
docker exec wg-easy sh -c 'echo $EXPERIMENTAL_AWG' |
|||
docker exec wg-easy lsmod | grep amneziawg |
|||
``` |
|||
|
|||
### Parameters Not Applied |
|||
|
|||
**Solution:** |
|||
1. Save parameters in UI |
|||
2. Click "Restart Interface" |
|||
3. Check `/etc/wireguard/wg0.conf` includes AWG params |
|||
|
|||
### Client Can't Connect |
|||
|
|||
**Verify:** |
|||
- Client uses AmneziaWG-compatible app |
|||
- Client config includes AWG parameters |
|||
- Parameters match server exactly |
|||
|
|||
## Future Enhancements |
|||
|
|||
Potential improvements: |
|||
- [ ] Parameter presets (stealth, performance, balanced) |
|||
- [ ] Randomize button in UI |
|||
- [ ] Import/export parameter profiles |
|||
- [ ] Visual validation feedback |
|||
- [ ] Advanced mode toggle for I/J parameters |
|||
@ -0,0 +1,168 @@ |
|||
--- |
|||
title: AmneziaWG Kernel Module |
|||
--- |
|||
|
|||
## Overview |
|||
|
|||
This build includes support for the **AmneziaWG kernel module**, pre-built for Alpine Linux LTS kernel (6.12.50). The kernel module provides better performance than the userspace implementation. |
|||
|
|||
## Implementation Details |
|||
|
|||
The Docker image includes two AmneziaWG components: |
|||
|
|||
1. **amneziawg-tools** (`awg`, `awg-quick`) - Command-line tools for managing AmneziaWG |
|||
2. **amneziawg.ko** - Pre-compiled kernel module for Alpine LTS kernel 6.12.50 |
|||
|
|||
## Kernel Module Compatibility |
|||
|
|||
The included kernel module is **pre-built for Alpine Linux LTS kernel 6.12.50**. This matches the kernel used after running the bare-metal installation script which upgrades from `-virt` to `linux-lts`. |
|||
|
|||
### Supported Configurations |
|||
|
|||
**✅ Supported:** |
|||
- Alpine Linux with `linux-lts` kernel (6.12.50) |
|||
- Hosts upgraded via the bare-metal install script |
|||
|
|||
**⚠️ Limited Support:** |
|||
- Other kernel versions (will fall back to userspace) |
|||
- Non-Alpine distributions (userspace only) |
|||
|
|||
## Automatic Detection |
|||
|
|||
When `EXPERIMENTAL_AWG=true`, the system will: |
|||
|
|||
1. Check if kernel version matches the pre-built module (6.12.50) |
|||
2. If match: Load the AmneziaWG kernel module |
|||
3. If mismatch: Fall back to standard WireGuard |
|||
4. Respect `OVERRIDE_AUTO_AWG` to force specific implementation |
|||
|
|||
## Usage |
|||
|
|||
### Standard Configuration (Alpine LTS 6.12.50) |
|||
|
|||
```yaml |
|||
services: |
|||
wg-easy: |
|||
image: wg-easy |
|||
container_name: wg-easy |
|||
environment: |
|||
- EXPERIMENTAL_AWG=true |
|||
volumes: |
|||
- ./data:/etc/wireguard |
|||
cap_add: |
|||
- NET_ADMIN |
|||
- SYS_MODULE |
|||
privileged: true |
|||
ports: |
|||
- "51820:51820/udp" |
|||
- "51821:51821/tcp" |
|||
``` |
|||
|
|||
|
|||
## Verification |
|||
|
|||
### Check Kernel Version |
|||
|
|||
First, verify your host is running the compatible kernel: |
|||
|
|||
```bash |
|||
uname -r |
|||
``` |
|||
|
|||
Expected output for kernel module support: |
|||
``` |
|||
6.12.50-0-lts |
|||
``` |
|||
|
|||
### Check Which Implementation is Active |
|||
|
|||
```bash |
|||
# Check if kernel module is loaded |
|||
lsmod | grep -E 'amneziawg|wireguard' |
|||
``` |
|||
|
|||
**AmneziaWG mode:** You'll see `amneziawg` in output |
|||
**Standard WireGuard mode:** You'll see `wireguard` in output |
|||
|
|||
### Check Application Logs |
|||
|
|||
```bash |
|||
docker logs wg-easy | grep -i amnezia |
|||
``` |
|||
|
|||
## Fallback Behavior |
|||
|
|||
The system automatically chooses the best available implementation: |
|||
|
|||
1. **Kernel module** (if host kernel == 6.12.50) |
|||
2. **amneziawg-go userspace** (all other cases) |
|||
3. **Standard WireGuard** (if `OVERRIDE_AUTO_AWG=wg`) |
|||
|
|||
## Troubleshooting |
|||
|
|||
### Kernel Version Mismatch |
|||
|
|||
If you're not running Alpine LTS kernel 6.12.50, the system will automatically use standard WireGuard. To force standard WireGuard: |
|||
|
|||
```yaml |
|||
environment: |
|||
- OVERRIDE_AUTO_AWG=wg # Force standard WireGuard |
|||
``` |
|||
|
|||
### Module Won't Load |
|||
|
|||
Ensure you have the required capabilities: |
|||
|
|||
```yaml |
|||
cap_add: |
|||
- SYS_MODULE |
|||
privileged: true |
|||
``` |
|||
|
|||
### Verify Module File |
|||
|
|||
Check if the pre-built module is present: |
|||
|
|||
```bash |
|||
docker exec wg-easy ls -la /lib/modules/ |
|||
docker exec wg-easy cat /etc/amneziawg-kernel-version.txt |
|||
``` |
|||
|
|||
## Security Considerations |
|||
|
|||
Running with `privileged: true` grants the container significant host access. Consider: |
|||
|
|||
1. Use `cap_add` with specific capabilities instead of `privileged` when possible |
|||
2. Audit the kernel module source before building |
|||
3. Pin to specific image versions in production |
|||
4. Monitor container behavior |
|||
|
|||
## Performance |
|||
|
|||
**Kernel Module Advantages:** |
|||
- Lower CPU usage (10-30% improvement) |
|||
- Better throughput on high-speed networks |
|||
- Native kernel integration |
|||
|
|||
**Userspace Advantages:** |
|||
- No kernel dependencies |
|||
- Easier deployment |
|||
- Better isolation |
|||
|
|||
For most users, the **userspace implementation is sufficient**. Use the kernel module if: |
|||
|
|||
- You have high throughput requirements (>500 Mbps) |
|||
- CPU usage is a concern |
|||
- You're already running privileged containers |
|||
|
|||
## Environment Variables |
|||
|
|||
| Variable | Default | Description | |
|||
|----------|---------|-------------| |
|||
| `EXPERIMENTAL_AWG` | `false` | Enable AmneziaWG support | |
|||
| `OVERRIDE_AUTO_AWG` | `undefined` | Force `awg` or `wg` implementation | |
|||
|
|||
## Related Documentation |
|||
|
|||
- [AmneziaWG Configuration](./amnezia.md) |
|||
- [Experimental Features](./experimental-config.md) |
|||
@ -1,8 +1,11 @@ |
|||
import { isUsingAwg } from '#utils/wgHelper'; |
|||
|
|||
export default definePermissionEventHandler('admin', 'any', async () => { |
|||
const wgInterface = await Database.interfaces.get(); |
|||
|
|||
return { |
|||
...wgInterface, |
|||
privateKey: undefined, |
|||
isUsingAwg: isUsingAwg(), |
|||
}; |
|||
}); |
|||
|
|||
@ -0,0 +1,176 @@ |
|||
/** |
|||
* AmneziaWG Obfuscation Parameter Generator |
|||
* |
|||
* Generates random obfuscation parameters that conform to amneziawg-go constraints. |
|||
* Based on: https://github.com/amnezia-vpn/amneziawg-go/blob/master/device/device.go
|
|||
*/ |
|||
|
|||
export interface AwgObfuscationParams { |
|||
jc: number; // Junk packet count
|
|||
jmin: number; // Junk packet minimum size
|
|||
jmax: number; // Junk packet maximum size
|
|||
s1: number; // Init header junk size
|
|||
s2: number; // Response header junk size
|
|||
h1: number; // Init magic header
|
|||
h2: number; // Response magic header
|
|||
h3: number; // Cookie magic header
|
|||
h4: number; // Transport magic header
|
|||
} |
|||
|
|||
// Constants from amneziawg-go
|
|||
const MAX_SEGMENT_SIZE = 65535; // Default for Linux (1 << 16) - 1
|
|||
const MESSAGE_INITIATION_SIZE = 148; |
|||
const MESSAGE_RESPONSE_SIZE = 92; |
|||
const MESSAGE_COOKIE_SIZE = 64; |
|||
const MESSAGE_TRANSPORT_SIZE = 32; |
|||
|
|||
// Amnezia documentation constraints (assuming MTU = 1280)
|
|||
const MTU = 1280; |
|||
const S1_MAX_MTU = MTU - MESSAGE_INITIATION_SIZE; // 1132
|
|||
const S2_MAX_MTU = MTU - MESSAGE_RESPONSE_SIZE; // 1188
|
|||
|
|||
// Recommended ranges from Amnezia documentation for random generation
|
|||
const JC_MIN = 4; |
|||
const JC_MAX = 12; |
|||
const JMIN_MIN = 8; |
|||
const JMIN_MAX = 80; // Random between recommended 8 and upper bounds
|
|||
const JMAX_MIN = 80; |
|||
const JMAX_MAX = 1280; |
|||
const S_MIN = 15; |
|||
const S_MAX = 150; |
|||
|
|||
// Magic header ranges (must be > 4 and non-overlapping)
|
|||
const MAGIC_HEADER_MIN = 5; |
|||
const MAGIC_HEADER_MAX = 0xFFFFFFFF; // uint32 max
|
|||
|
|||
/** |
|||
* Generate a random integer between min and max (inclusive) |
|||
*/ |
|||
function randomInt(min: number, max: number): number { |
|||
return Math.floor(Math.random() * (max - min + 1)) + min; |
|||
} |
|||
|
|||
/** |
|||
* Generate non-overlapping magic header values |
|||
* Each header must be > 4 and all must be distinct |
|||
* Recommended range: 5 to 2147483647 (max signed 32-bit int) |
|||
*/ |
|||
function generateMagicHeaders(): [number, number, number, number] { |
|||
const headers = new Set<number>(); |
|||
|
|||
// Generate 4 distinct random values in recommended range (5 to 2^31-1)
|
|||
while (headers.size < 4) { |
|||
const value = randomInt(MAGIC_HEADER_MIN, 2147483647); |
|||
headers.add(value); |
|||
} |
|||
|
|||
return Array.from(headers) as [number, number, number, number]; |
|||
} |
|||
|
|||
/** |
|||
* Generate random AmneziaWG obfuscation parameters |
|||
* |
|||
* Parameters are chosen to: |
|||
* - Provide good obfuscation without excessive overhead |
|||
* - Conform to amneziawg-go validation rules |
|||
* - Be random for each installation |
|||
* |
|||
* @returns Random obfuscation parameters |
|||
*/ |
|||
export function generateAwgObfuscationParams(): AwgObfuscationParams { |
|||
const jc = randomInt(JC_MIN, JC_MAX); |
|||
const jmin = randomInt(JMIN_MIN, JMIN_MAX); |
|||
const jmax = randomInt(Math.max(jmin + 1, JMAX_MIN), JMAX_MAX); |
|||
|
|||
// S1-S4: Header junk sizes
|
|||
const s1 = randomInt(S_MIN, Math.min(S_MAX, S1_MAX_MTU)); |
|||
const s2 = randomInt(S_MIN, Math.min(S_MAX, S2_MAX_MTU)); |
|||
|
|||
// Generate non-overlapping magic headers
|
|||
const [h1, h2, h3, h4] = generateMagicHeaders(); |
|||
|
|||
return { |
|||
jc, |
|||
jmin, |
|||
jmax, |
|||
s1, |
|||
s2, |
|||
h1, |
|||
h2, |
|||
h3, |
|||
h4, |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Validate AWG parameters against amneziawg-go constraints |
|||
* |
|||
* @param params Parameters to validate |
|||
* @returns true if valid, throws error otherwise |
|||
*/ |
|||
export function validateAwgParams(params: Partial<AwgObfuscationParams>): boolean { |
|||
// Jc: 1 ≤ Jc ≤ 128 (recommended 4-12)
|
|||
if (params.jc !== undefined && (params.jc < 1 || params.jc > 128)) { |
|||
throw new Error('Jc (junk packet count) must be between 1 and 128'); |
|||
} |
|||
|
|||
// Jmin: Jmax > Jmin < 1280 (recommended 8)
|
|||
if (params.jmin !== undefined && (params.jmin < 0 || params.jmin >= MTU)) { |
|||
throw new Error(`Jmin (junk packet min size) must be between 0 and ${MTU - 1}`); |
|||
} |
|||
|
|||
// Jmax: Jmin < Jmax ≤ 1280 (recommended 80)
|
|||
if (params.jmax !== undefined) { |
|||
if (params.jmax < 1 || params.jmax > MTU) { |
|||
throw new Error(`Jmax must be between 1 and ${MTU}`); |
|||
} |
|||
|
|||
if (params.jmin !== undefined && params.jmax <= params.jmin) { |
|||
throw new Error('Jmax must be > Jmin'); |
|||
} |
|||
} |
|||
|
|||
// S1: S1 ≤ 1132 (MTU - 148), S1 + 56 ≠ S2 (recommended 15-150)
|
|||
if (params.s1 !== undefined) { |
|||
if (params.s1 < 0 || params.s1 > S1_MAX_MTU) { |
|||
throw new Error(`S1 must be between 0 and ${S1_MAX_MTU} (MTU - MessageInitiationSize)`); |
|||
} |
|||
} |
|||
|
|||
// S2: S2 ≤ 1188 (MTU - 92) (recommended 15-150)
|
|||
if (params.s2 !== undefined) { |
|||
if (params.s2 < 0 || params.s2 > S2_MAX_MTU) { |
|||
throw new Error(`S2 must be between 0 and ${S2_MAX_MTU} (MTU - MessageResponseSize)`); |
|||
} |
|||
} |
|||
|
|||
// S1 + 56 ≠ S2
|
|||
if (params.s1 !== undefined && params.s2 !== undefined) { |
|||
if (params.s1 + 56 === params.s2) { |
|||
throw new Error('S1 + 56 must not equal S2'); |
|||
} |
|||
} |
|||
|
|||
// Validate magic headers: must be > 4 and unique (recommended 5 to 2147483647)
|
|||
if (params.h1 !== undefined || params.h2 !== undefined || |
|||
params.h3 !== undefined || params.h4 !== undefined) { |
|||
const headers = [params.h1, params.h2, params.h3, params.h4].filter(h => h !== undefined); |
|||
|
|||
// Check all are > 4 (to enable obfuscation)
|
|||
for (const h of headers) { |
|||
if (h! <= 4) { |
|||
throw new Error('Magic headers must be > 4 to enable obfuscation'); |
|||
} |
|||
} |
|||
|
|||
// Check for duplicates if all 4 are provided
|
|||
if (headers.length === 4) { |
|||
const uniqueHeaders = new Set(headers); |
|||
if (uniqueHeaders.size !== 4) { |
|||
throw new Error('All magic headers (H1-H4) must be distinct'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
Loading…
Reference in new issue