add
This commit is contained in:
+268
@@ -0,0 +1,268 @@
|
|||||||
|
# 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:
|
||||||
|
|
||||||
|
1. **MultiPathBind** (`conn/multipath_bind.go`):
|
||||||
|
- Implements the `conn.Bind` interface
|
||||||
|
- Manages multiple underlying `Bind` instances
|
||||||
|
- Sends packets through ALL configured network paths
|
||||||
|
- Receives packets through the primary path only
|
||||||
|
|
||||||
|
2. **Multi-Path Device Creation** (`device/multipath.go`):
|
||||||
|
- Helper functions to create WireGuard devices with multiple network interfaces
|
||||||
|
- Interface discovery and configuration utilities
|
||||||
|
|
||||||
|
3. **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
|
||||||
|
|
||||||
|
```go
|
||||||
|
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
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```go
|
||||||
|
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)`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
1. The same packet buffers are sent through ALL configured network binds
|
||||||
|
2. Each bind may be bound to a different network interface
|
||||||
|
3. The method succeeds if at least one bind successfully sends the packet
|
||||||
|
4. 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
|
||||||
|
|
||||||
|
1. **Packet Duplication**: Receiving peer will see duplicate packets (WireGuard's replay protection handles this)
|
||||||
|
2. **Bandwidth Usage**: Network usage increases proportionally with number of interfaces
|
||||||
|
3. **Interface Binding**: Requires platform support for binding sockets to specific interfaces
|
||||||
|
4. **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:
|
||||||
|
```go
|
||||||
|
interfaces := []string{"eth0", "wwan0"} // Ethernet + Cellular
|
||||||
|
```
|
||||||
|
|
||||||
|
### WiFi + Ethernet Redundancy
|
||||||
|
For laptops with both WiFi and Ethernet:
|
||||||
|
```go
|
||||||
|
interfaces := []string{"eth0", "wlan0"} // Ethernet + WiFi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Homed Server
|
||||||
|
Server with multiple network interfaces:
|
||||||
|
```go
|
||||||
|
interfaces := []string{"eth0", "eth1", "eth2"} // Multiple Ethernet
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Interface Binding Issues
|
||||||
|
```bash
|
||||||
|
# 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:
|
||||||
|
```bash
|
||||||
|
sudo ./your-wireguard-program
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
Enable verbose logging to see multi-path operations:
|
||||||
|
```go
|
||||||
|
logger := device.NewLogger(device.LogLevelVerbose, "multipath: ")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Ensure you have the modified WireGuard-Go source and build normally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go mod tidy
|
||||||
|
go build ./...
|
||||||
|
|
||||||
|
# Build example
|
||||||
|
go build -o multipath-example ./examples/multipath/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential improvements for the multi-path implementation:
|
||||||
|
|
||||||
|
1. **Load Balancing**: Distribute packets across interfaces rather than duplicating
|
||||||
|
2. **Health Monitoring**: Automatic detection and handling of failed interfaces
|
||||||
|
3. **Quality Metrics**: Choose best interface based on latency/bandwidth measurements
|
||||||
|
4. **Receive Multi-Path**: Receive from multiple interfaces and handle reordering
|
||||||
|
5. **Configuration API**: Runtime configuration of interface sets
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package conn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MultiPathBind implements Bind interface but sends packets through multiple network paths
|
||||||
|
type MultiPathBind struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
binds []Bind
|
||||||
|
// Store the primary bind for receive operations (only one bind receives)
|
||||||
|
primaryBind Bind
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiPathBind creates a new multi-path bind with multiple underlying binds
|
||||||
|
func NewMultiPathBind(binds []Bind) *MultiPathBind {
|
||||||
|
if len(binds) == 0 {
|
||||||
|
panic("MultiPathBind requires at least one bind")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MultiPathBind{
|
||||||
|
binds: binds,
|
||||||
|
primaryBind: binds[0], // Use first bind as primary for receiving
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open puts all binds into listening state
|
||||||
|
func (mpb *MultiPathBind) Open(port uint16) (fns []ReceiveFunc, actualPort uint16, err error) {
|
||||||
|
mpb.mu.Lock()
|
||||||
|
defer mpb.mu.Unlock()
|
||||||
|
|
||||||
|
// Open primary bind first to get the actual port and receive functions
|
||||||
|
fns, actualPort, err = mpb.primaryBind.Open(port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to open primary bind: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open additional binds on the same port
|
||||||
|
for i, bind := range mpb.binds[1:] {
|
||||||
|
_, bindPort, bindErr := bind.Open(actualPort)
|
||||||
|
if bindErr != nil {
|
||||||
|
// If any bind fails, close already opened binds
|
||||||
|
mpb.primaryBind.Close()
|
||||||
|
for j := 0; j < i; j++ {
|
||||||
|
mpb.binds[j+1].Close()
|
||||||
|
}
|
||||||
|
return nil, 0, fmt.Errorf("failed to open bind %d: %w", i+1, bindErr)
|
||||||
|
}
|
||||||
|
// Verify all binds use the same port
|
||||||
|
if bindPort != actualPort {
|
||||||
|
mpb.primaryBind.Close()
|
||||||
|
for j := 0; j <= i; j++ {
|
||||||
|
mpb.binds[j+1].Close()
|
||||||
|
}
|
||||||
|
return nil, 0, fmt.Errorf("bind %d opened on different port %d vs %d", i+1, bindPort, actualPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fns, actualPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes all underlying binds
|
||||||
|
func (mpb *MultiPathBind) Close() error {
|
||||||
|
mpb.mu.Lock()
|
||||||
|
defer mpb.mu.Unlock()
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
for i, bind := range mpb.binds {
|
||||||
|
if err := bind.Close(); err != nil && firstErr == nil {
|
||||||
|
firstErr = fmt.Errorf("failed to close bind %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMark sets the mark for all underlying binds
|
||||||
|
func (mpb *MultiPathBind) SetMark(mark uint32) error {
|
||||||
|
mpb.mu.RLock()
|
||||||
|
defer mpb.mu.RUnlock()
|
||||||
|
|
||||||
|
for i, bind := range mpb.binds {
|
||||||
|
if err := bind.SetMark(mark); err != nil {
|
||||||
|
return fmt.Errorf("failed to set mark on bind %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends the same packets through ALL configured network paths
|
||||||
|
func (mpb *MultiPathBind) Send(bufs [][]byte, ep Endpoint) error {
|
||||||
|
mpb.mu.RLock()
|
||||||
|
defer mpb.mu.RUnlock()
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
successCount := 0
|
||||||
|
|
||||||
|
// Send through all binds
|
||||||
|
for i, bind := range mpb.binds {
|
||||||
|
if err := bind.Send(bufs, ep); err != nil {
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = fmt.Errorf("bind %d failed: %w", i, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
successCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consider it successful if at least one path succeeded
|
||||||
|
if successCount > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEndpoint uses the primary bind to parse endpoints
|
||||||
|
func (mpb *MultiPathBind) ParseEndpoint(s string) (Endpoint, error) {
|
||||||
|
mpb.mu.RLock()
|
||||||
|
defer mpb.mu.RUnlock()
|
||||||
|
return mpb.primaryBind.ParseEndpoint(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchSize returns the minimum batch size among all binds
|
||||||
|
func (mpb *MultiPathBind) BatchSize() int {
|
||||||
|
mpb.mu.RLock()
|
||||||
|
defer mpb.mu.RUnlock()
|
||||||
|
|
||||||
|
if len(mpb.binds) == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
minBatchSize := mpb.binds[0].BatchSize()
|
||||||
|
for _, bind := range mpb.binds[1:] {
|
||||||
|
if size := bind.BatchSize(); size < minBatchSize {
|
||||||
|
minBatchSize = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minBatchSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindToInterface binds specific binds to specific interfaces
|
||||||
|
// This is a helper method for configuring each bind to use different interfaces
|
||||||
|
func (mpb *MultiPathBind) BindToInterface(bindIndex int, interfaceIndex uint32, blackhole bool) error {
|
||||||
|
mpb.mu.RLock()
|
||||||
|
defer mpb.mu.RUnlock()
|
||||||
|
|
||||||
|
if bindIndex >= len(mpb.binds) {
|
||||||
|
return fmt.Errorf("bind index %d out of range (have %d binds)", bindIndex, len(mpb.binds))
|
||||||
|
}
|
||||||
|
|
||||||
|
bind := mpb.binds[bindIndex]
|
||||||
|
if binder, ok := bind.(BindSocketToInterface); ok {
|
||||||
|
// Try IPv4 first
|
||||||
|
if err := binder.BindSocketToInterface4(interfaceIndex, blackhole); err != nil {
|
||||||
|
// If IPv4 fails, try IPv6
|
||||||
|
if err := binder.BindSocketToInterface6(interfaceIndex, blackhole); err != nil {
|
||||||
|
return fmt.Errorf("failed to bind to interface %d: %w", interfaceIndex, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("bind %d does not support interface binding", bindIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBindCount returns the number of configured network paths
|
||||||
|
func (mpb *MultiPathBind) GetBindCount() int {
|
||||||
|
mpb.mu.RLock()
|
||||||
|
defer mpb.mu.RUnlock()
|
||||||
|
return len(mpb.binds)
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MultiPathConfig represents configuration for multi-path networking
|
||||||
|
type MultiPathConfig struct {
|
||||||
|
// InterfaceIndexes are the network interface indexes to bind to
|
||||||
|
// If empty, uses default interface selection
|
||||||
|
InterfaceIndexes []uint32
|
||||||
|
// BindFactory creates new Bind instances. If nil, uses conn.NewStdNetBind()
|
||||||
|
BindFactory func() conn.Bind
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiPathDevice creates a new WireGuard device with multi-path networking
|
||||||
|
// It creates separate bind instances for each specified network interface
|
||||||
|
func NewMultiPathDevice(tunDevice tun.Device, config MultiPathConfig, logger *Logger) (*Device, error) {
|
||||||
|
if len(config.InterfaceIndexes) == 0 {
|
||||||
|
return nil, fmt.Errorf("MultiPathConfig must specify at least one interface index")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use default bind factory if none specified
|
||||||
|
bindFactory := config.BindFactory
|
||||||
|
if bindFactory == nil {
|
||||||
|
bindFactory = func() conn.Bind {
|
||||||
|
return conn.NewStdNetBind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a bind for each interface
|
||||||
|
binds := make([]conn.Bind, len(config.InterfaceIndexes))
|
||||||
|
for i := range config.InterfaceIndexes {
|
||||||
|
binds[i] = bindFactory()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create multi-path bind
|
||||||
|
multiPathBind := conn.NewMultiPathBind(binds)
|
||||||
|
|
||||||
|
// Configure each bind to use its specific interface
|
||||||
|
for i, interfaceIndex := range config.InterfaceIndexes {
|
||||||
|
err := multiPathBind.BindToInterface(i, interfaceIndex, false)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to bind to interface %d: %v", interfaceIndex, err)
|
||||||
|
// Continue with other interfaces rather than failing completely
|
||||||
|
} else {
|
||||||
|
logger.Verbosef("Bound network path %d to interface index %d", i, interfaceIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create device with multi-path bind
|
||||||
|
device := NewDevice(tunDevice, multiPathBind, logger)
|
||||||
|
|
||||||
|
logger.Verbosef("Created multi-path WireGuard device with %d network paths", len(config.InterfaceIndexes))
|
||||||
|
return device, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiPathDeviceByNames creates a multi-path device using interface names instead of indexes
|
||||||
|
func NewMultiPathDeviceByNames(tunDevice tun.Device, interfaceNames []string, logger *Logger) (*Device, error) {
|
||||||
|
if len(interfaceNames) == 0 {
|
||||||
|
return nil, fmt.Errorf("must specify at least one interface name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert interface names to indexes
|
||||||
|
interfaceIndexes := make([]uint32, len(interfaceNames))
|
||||||
|
for i, name := range interfaceNames {
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find interface %s: %w", name, err)
|
||||||
|
}
|
||||||
|
interfaceIndexes[i] = uint32(iface.Index)
|
||||||
|
logger.Verbosef("Interface %s has index %d", name, iface.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := MultiPathConfig{
|
||||||
|
InterfaceIndexes: interfaceIndexes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewMultiPathDevice(tunDevice, config, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkInterfaceInfo returns information about available network interfaces
|
||||||
|
func GetNetworkInterfaceInfo() ([]NetworkInterfaceInfo, error) {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list network interfaces: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]NetworkInterfaceInfo, 0, len(interfaces))
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
// Skip loopback and down interfaces for multi-path networking
|
||||||
|
if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, _ := iface.Addrs()
|
||||||
|
addrStrings := make([]string, len(addrs))
|
||||||
|
for i, addr := range addrs {
|
||||||
|
addrStrings[i] = addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, NetworkInterfaceInfo{
|
||||||
|
Index: uint32(iface.Index),
|
||||||
|
Name: iface.Name,
|
||||||
|
Addresses: addrStrings,
|
||||||
|
MTU: iface.MTU,
|
||||||
|
Flags: iface.Flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkInterfaceInfo represents information about a network interface
|
||||||
|
type NetworkInterfaceInfo struct {
|
||||||
|
Index uint32
|
||||||
|
Name string
|
||||||
|
Addresses []string
|
||||||
|
MTU int
|
||||||
|
Flags net.Flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a human-readable description of the interface
|
||||||
|
func (nii NetworkInterfaceInfo) String() string {
|
||||||
|
return fmt.Sprintf("Interface %s (index %d): MTU=%d, Addresses=%v, Flags=%v",
|
||||||
|
nii.Name, nii.Index, nii.MTU, nii.Addresses, nii.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example usage function
|
||||||
|
func ExampleMultiPathUsage(logger *Logger) {
|
||||||
|
// Print available interfaces
|
||||||
|
interfaces, err := GetNetworkInterfaceInfo()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to get interface info: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Verbosef("Available network interfaces:")
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
logger.Verbosef(" %s", iface.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Create multi-path device using specific interface names
|
||||||
|
// This would send each packet through both eth0 and wlan0
|
||||||
|
interfaceNames := []string{"eth0", "wlan0"}
|
||||||
|
|
||||||
|
// Note: You would need to create/configure your TUN device
|
||||||
|
// tunDevice, err := tun.CreateTUN("wg0", 1420)
|
||||||
|
// if err != nil {
|
||||||
|
// logger.Errorf("Failed to create TUN: %v", err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// device, err := NewMultiPathDeviceByNames(tunDevice, interfaceNames, logger)
|
||||||
|
// if err != nil {
|
||||||
|
// logger.Errorf("Failed to create multi-path device: %v", err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// logger.Verbosef("Multi-path WireGuard device created successfully")
|
||||||
|
}
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Build script for multi-path WireGuard example
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Building multi-path WireGuard example..."
|
||||||
|
|
||||||
|
# Ensure we're in the right directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Build the example
|
||||||
|
go build -o multipath-example main.go
|
||||||
|
|
||||||
|
echo "Build complete! Executable: ./multipath-example"
|
||||||
|
echo ""
|
||||||
|
echo "Usage examples:"
|
||||||
|
echo " # List available interfaces:"
|
||||||
|
echo " sudo ./multipath-example"
|
||||||
|
echo ""
|
||||||
|
echo " # Create multi-path tunnel:"
|
||||||
|
echo " sudo ./multipath-example eth0 wlan0"
|
||||||
|
echo ""
|
||||||
|
echo "Note: This example requires root privileges to create TUN devices and bind to interfaces."
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Multi-path WireGuard Example
|
||||||
|
*
|
||||||
|
* This example demonstrates how to create a WireGuard device that sends
|
||||||
|
* packets through multiple network interfaces simultaneously.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fmt.Printf("Usage: %s <interface1> [interface2] [interface3] ...\n", os.Args[0])
|
||||||
|
fmt.Println("Example: sudo ./multipath eth0 wlan0")
|
||||||
|
fmt.Println("\nThis will create a WireGuard tunnel that sends packets through both eth0 and wlan0")
|
||||||
|
fmt.Println("Available interfaces:")
|
||||||
|
|
||||||
|
interfaces, err := device.GetNetworkInterfaceInfo()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to get interface info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
fmt.Printf(" %s\n", iface.String())
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get interface names from command line
|
||||||
|
interfaceNames := os.Args[1:]
|
||||||
|
|
||||||
|
fmt.Printf("Creating multi-path WireGuard device using interfaces: %v\n", interfaceNames)
|
||||||
|
|
||||||
|
// Create logger
|
||||||
|
logger := device.NewLogger(device.LogLevelVerbose, "multipath-example: ")
|
||||||
|
|
||||||
|
// Create TUN device
|
||||||
|
tunDevice, err := tun.CreateTUN("wg-multipath", 1420)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create TUN device: %v", err)
|
||||||
|
}
|
||||||
|
defer tunDevice.Close()
|
||||||
|
|
||||||
|
fmt.Printf("Created TUN device: %s\n", tunDevice.Name())
|
||||||
|
|
||||||
|
// Create multi-path WireGuard device
|
||||||
|
wgDevice, err := device.NewMultiPathDeviceByNames(tunDevice, interfaceNames, logger)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create multi-path WireGuard device: %v", err)
|
||||||
|
}
|
||||||
|
defer wgDevice.Close()
|
||||||
|
|
||||||
|
fmt.Printf("Multi-path WireGuard device created successfully!\n")
|
||||||
|
fmt.Printf("Each outbound packet will be sent through ALL %d specified interfaces\n", len(interfaceNames))
|
||||||
|
|
||||||
|
// Configure WireGuard (you would normally load this from a config file)
|
||||||
|
// This is just a basic example configuration
|
||||||
|
logger.Verbosef("Device ready. You can now configure it using wg(8) commands:")
|
||||||
|
logger.Verbosef(" sudo wg set %s private-key <private-key-file>", tunDevice.Name())
|
||||||
|
logger.Verbosef(" sudo wg set %s peer <peer-public-key> endpoint <peer-endpoint> allowed-ips <allowed-ips>", tunDevice.Name())
|
||||||
|
logger.Verbosef(" sudo ip addr add <your-vpn-ip>/24 dev %s", tunDevice.Name())
|
||||||
|
logger.Verbosef(" sudo ip link set %s up", tunDevice.Name())
|
||||||
|
|
||||||
|
// Bring device up
|
||||||
|
err = wgDevice.Up()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to bring device up: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("WireGuard device is up and running!")
|
||||||
|
fmt.Println("Press Ctrl+C to stop...")
|
||||||
|
|
||||||
|
// Wait for interrupt signal
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-c
|
||||||
|
|
||||||
|
fmt.Println("\nShutting down...")
|
||||||
|
wgDevice.Down()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user