Refactor - now wireguard based. (#7)
This commit is contained in:
184
peer/wginterface/manage.go
Normal file
184
peer/wginterface/manage.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package wginterface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
const (
|
||||
// RekeyTimeout is the WireGuard session lifetime before a new handshake
|
||||
// is initiated. Sessions older than this but younger than SessionTimeout
|
||||
// remain valid.
|
||||
RekeyTimeout = 120 * time.Second
|
||||
|
||||
// SessionTimeout is the WireGuard session lifetime after which sessions
|
||||
// are rejected. A peer with LastHandshakeTime older than this is
|
||||
// effectively disconnected.
|
||||
SessionTimeout = 180 * time.Second
|
||||
)
|
||||
|
||||
const ProbeKeepalive = 8 * time.Second
|
||||
|
||||
var zeroKeepalive = time.Duration(0)
|
||||
|
||||
// Device wraps a wgctrl client bound to a named WireGuard interface.
|
||||
type Device struct {
|
||||
client *wgctrl.Client
|
||||
name string
|
||||
}
|
||||
|
||||
// Open attaches to an existing WireGuard interface.
|
||||
func Open(name string) (*Device, error) {
|
||||
client, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wgctrl: %w", err)
|
||||
}
|
||||
return &Device{client: client, name: name}, nil
|
||||
}
|
||||
|
||||
// Close releases the underlying wgctrl client.
|
||||
func (d *Device) Close() error {
|
||||
return d.client.Close()
|
||||
}
|
||||
|
||||
// Name returns the interface name.
|
||||
func (d *Device) Name() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
// Configure sets the device's private key and UDP listen port.
|
||||
func (d *Device) Configure(privKey wgtypes.Key, listenPort int) error {
|
||||
return d.client.ConfigureDevice(d.name, wgtypes.Config{
|
||||
PrivateKey: &privKey,
|
||||
ListenPort: &listenPort,
|
||||
})
|
||||
}
|
||||
|
||||
// Peers returns the current state of all peers on the device.
|
||||
func (d *Device) Peers() ([]wgtypes.Peer, error) {
|
||||
dev, err := d.client.Device(d.name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get device %q: %w", d.name, err)
|
||||
}
|
||||
return dev.Peers, nil
|
||||
}
|
||||
|
||||
// Peer returns the current state of a single peer by public key.
|
||||
func (d *Device) Peer(pubKey wgtypes.Key) (wgtypes.Peer, error) {
|
||||
peers, err := d.Peers()
|
||||
if err != nil {
|
||||
return wgtypes.Peer{}, err
|
||||
}
|
||||
for _, p := range peers {
|
||||
if p.PublicKey == pubKey {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return wgtypes.Peer{}, fmt.Errorf("peer %v not found in %q", pubKey, d.name)
|
||||
}
|
||||
|
||||
// AddPeer registers a peer with no AllowedIPs and no endpoint. WireGuard will
|
||||
// accept handshakes from this peer but route no traffic to it yet.
|
||||
func (d *Device) AddPeer(pubKey wgtypes.Key) error {
|
||||
return d.client.ConfigureDevice(d.name, wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
ReplaceAllowedIPs: true,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// SetRelay configures the relay peer with AllowedIPs covering the entire VPN
|
||||
// network prefix. This is the fallback route for all VPN traffic.
|
||||
func (d *Device) SetRelay(pubKey wgtypes.Key, endpoint netip.AddrPort, network netip.Prefix) error {
|
||||
masked := network.Masked()
|
||||
a4 := masked.Addr().As4()
|
||||
return d.client.ConfigureDevice(d.name, wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Endpoint: net.UDPAddrFromAddrPort(endpoint),
|
||||
AllowedIPs: []net.IPNet{{
|
||||
IP: net.IP(a4[:]),
|
||||
Mask: net.CIDRMask(masked.Bits(), 32),
|
||||
}},
|
||||
ReplaceAllowedIPs: true,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// AddProbe adds a peer with no AllowedIPs and a 5s keepalive. WireGuard will
|
||||
// attempt handshakes without routing any traffic through this peer yet.
|
||||
func (d *Device) AddProbe(pubKey wgtypes.Key, endpoint netip.AddrPort) error {
|
||||
keepalive := ProbeKeepalive
|
||||
return d.client.ConfigureDevice(d.name, wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Endpoint: net.UDPAddrFromAddrPort(endpoint),
|
||||
AllowedIPs: []net.IPNet{},
|
||||
ReplaceAllowedIPs: true,
|
||||
PersistentKeepaliveInterval: &keepalive,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// Promote upgrades a probe entry to a /32 AllowedIPs and removes the probe
|
||||
// keepalive, causing WireGuard to prefer this peer's direct path over the
|
||||
// relay's wider route.
|
||||
func (d *Device) Promote(pubKey wgtypes.Key, vpnIP netip.Addr) error {
|
||||
a4 := vpnIP.As4()
|
||||
return d.client.ConfigureDevice(d.name, wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
AllowedIPs: []net.IPNet{{
|
||||
IP: net.IP(a4[:]),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}},
|
||||
ReplaceAllowedIPs: true,
|
||||
PersistentKeepaliveInterval: &zeroKeepalive,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// AddDirect adds a peer with a known endpoint and /32 AllowedIPs in one step,
|
||||
// for peers with a stable public endpoint reported by the hub.
|
||||
func (d *Device) AddDirect(pubKey wgtypes.Key, endpoint netip.AddrPort, vpnIP netip.Addr) error {
|
||||
a4 := vpnIP.As4()
|
||||
return d.client.ConfigureDevice(d.name, wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Endpoint: net.UDPAddrFromAddrPort(endpoint),
|
||||
AllowedIPs: []net.IPNet{{
|
||||
IP: net.IP(a4[:]),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}},
|
||||
ReplaceAllowedIPs: true,
|
||||
PersistentKeepaliveInterval: &zeroKeepalive,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// RemovePeer removes a peer from the device.
|
||||
func (d *Device) RemovePeer(pubKey wgtypes.Key) error {
|
||||
return d.client.ConfigureDevice(d.name, wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Remove: true,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
// EnableForwarding enables IPv4 forwarding globally and on the interface,
|
||||
// required for relay peers that forward traffic between VPN peers.
|
||||
func (d *Device) EnableForwarding() error {
|
||||
if err := os.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
path := fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/forwarding", d.name)
|
||||
return os.WriteFile(path, []byte("1\n"), 0644)
|
||||
}
|
||||
Reference in New Issue
Block a user