7.6 KiB
Multi-Path WireGuard Implementation
This document describes the multi-path networking feature for WireGuard-Go, which allows sending the same packet through multiple network interfaces simultaneously.
Overview
The multi-path implementation extends WireGuard-Go to support redundant packet transmission through multiple network paths. When configured, each outbound packet is sent through ALL specified network interfaces, providing:
- Increased Reliability: If one network path fails, communication continues through other paths
- Better Performance: Multiple paths can provide better throughput and lower latency
- Redundancy: Critical for scenarios where network reliability is paramount
How It Works
Architecture
The multi-path functionality is implemented through several key components:
-
MultiPathBind (
conn/multipath_bind.go):- Implements the
conn.Bindinterface - Manages multiple underlying
Bindinstances - Sends packets through ALL configured network paths
- Receives packets through the primary path only
- Implements the
-
Multi-Path Device Creation (
device/multipath.go):- Helper functions to create WireGuard devices with multiple network interfaces
- Interface discovery and configuration utilities
-
Network Transmission Flow:
TUN Device → Peer Lookup → Packet Staging → Sequential Sender → SendBuffers → MultiPathBind.Send() → [Bind1, Bind2, Bind3, ...] → Network
Code Locations
The actual network transmission happens in these key locations:
- Primary Send Method:
device/peer.go:135-peer.device.net.bind.Send(buffers, endpoint) - Multi-Path Send:
conn/multipath_bind.go:95- Sends through all configured binds - Socket Transmission:
conn/bind_std.go:339- Individual socket transmission
Usage
Basic Usage
package main
import (
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
)
func main() {
// Create TUN device
tunDevice, err := tun.CreateTUN("wg-multipath", 1420)
if err != nil {
panic(err)
}
defer tunDevice.Close()
// Create logger
logger := device.NewLogger(device.LogLevelVerbose, "multipath: ")
// Create multi-path device using interface names
interfaceNames := []string{"eth0", "wlan0"}
wgDevice, err := device.NewMultiPathDeviceByNames(tunDevice, interfaceNames, logger)
if err != nil {
panic(err)
}
defer wgDevice.Close()
// Device is ready - configure with wg(8) tools and bring up
err = wgDevice.Up()
if err != nil {
panic(err)
}
// Now all outbound packets will be sent through both eth0 and wlan0
}
Advanced Configuration
// Using interface indexes instead of names
config := device.MultiPathConfig{
InterfaceIndexes: []uint32{2, 3, 4}, // eth0, wlan0, usb0
BindFactory: func() conn.Bind {
return conn.NewStdNetBind() // or custom bind implementation
},
}
wgDevice, err := device.NewMultiPathDevice(tunDevice, config, logger)
Command Line Example
Build and run the example program:
# Build the example
go build -o multipath-example ./examples/multipath/
# List available interfaces
sudo ./multipath-example
# Create multi-path tunnel using eth0 and wlan0
sudo ./multipath-example eth0 wlan0
Configuration
Interface Discovery
Use the helper function to discover available network interfaces:
interfaces, err := device.GetNetworkInterfaceInfo()
if err != nil {
log.Fatal(err)
}
for _, iface := range interfaces {
fmt.Printf("Interface: %s (index %d)\n", iface.Name, iface.Index)
fmt.Printf(" Addresses: %v\n", iface.Addresses)
fmt.Printf(" MTU: %d\n", iface.MTU)
}
WireGuard Configuration
After creating the multi-path device, configure it normally with wg(8):
# Generate keys
wg genkey | tee private.key | wg pubkey > public.key
# Configure the device
sudo wg set wg-multipath private-key private.key
sudo wg set wg-multipath peer <PEER_PUBLIC_KEY> \
endpoint <PEER_IP>:<PORT> \
allowed-ips 0.0.0.0/0
# Assign IP and bring up
sudo ip addr add 10.0.0.2/24 dev wg-multipath
sudo ip link set wg-multipath up
# Route traffic through the tunnel
sudo ip route add default dev wg-multipath
Technical Details
Packet Duplication
When MultiPathBind.Send() is called:
- The same packet buffers are sent through ALL configured network binds
- Each bind may be bound to a different network interface
- The method succeeds if at least one bind successfully sends the packet
- Errors from individual binds are logged but don't stop other binds
Receiving
- Only the primary bind (first in the list) is used for receiving packets
- This prevents duplicate packet reception
- All receive functions come from the primary bind
Error Handling
- Individual bind failures don't stop transmission through other binds
- At least one successful transmission is required for overall success
- Failed binds are logged for debugging
Performance Considerations
- CPU Usage: Sending through multiple interfaces increases CPU usage proportionally
- Memory: Each bind maintains its own buffers and state
- Network Bandwidth: Total bandwidth usage is multiplied by the number of interfaces
- Latency: Latency is determined by the fastest responding interface
Limitations
- Packet Duplication: Receiving peer will see duplicate packets (WireGuard's replay protection handles this)
- Bandwidth Usage: Network usage increases proportionally with number of interfaces
- Interface Binding: Requires platform support for binding sockets to specific interfaces
- Receive Path: Only receives through primary interface (no multi-path receiving)
Platform Support
The multi-path functionality works on platforms that support:
- Socket binding to specific network interfaces
- Multiple UDP sockets on the same port (with SO_REUSEPORT or similar)
Tested on:
- Linux (fully supported)
- macOS (limited support)
- Windows (limited support)
Example Scenarios
Dual-WAN Setup
Use both your main internet connection and backup cellular connection:
interfaces := []string{"eth0", "wwan0"} // Ethernet + Cellular
WiFi + Ethernet Redundancy
For laptops with both WiFi and Ethernet:
interfaces := []string{"eth0", "wlan0"} // Ethernet + WiFi
Multi-Homed Server
Server with multiple network interfaces:
interfaces := []string{"eth0", "eth1", "eth2"} // Multiple Ethernet
Troubleshooting
Interface Binding Issues
# Check interface exists and is up
ip link show eth0
# Check interface has IP address
ip addr show eth0
# Test basic connectivity
ping -I eth0 8.8.8.8
Permission Issues
Multi-path binding typically requires root privileges:
sudo ./your-wireguard-program
Debugging
Enable verbose logging to see multi-path operations:
logger := device.NewLogger(device.LogLevelVerbose, "multipath: ")
Building
Ensure you have the modified WireGuard-Go source and build normally:
go mod tidy
go build ./...
# Build example
go build -o multipath-example ./examples/multipath/
Future Enhancements
Potential improvements for the multi-path implementation:
- Load Balancing: Distribute packets across interfaces rather than duplicating
- Health Monitoring: Automatic detection and handling of failed interfaces
- Quality Metrics: Choose best interface based on latency/bandwidth measurements
- Receive Multi-Path: Receive from multiple interfaces and handle reordering
- Configuration API: Runtime configuration of interface sets