WIP
This commit is contained in:
177
peer/wgdev.go
Normal file
177
peer/wgdev.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
func createWGDevice(name string, privKey wgtypes.Key, listenPort int, vpnIP netip.Addr, network []byte) (*wgctrl.Client, error) {
|
||||
if len(network) != 4 {
|
||||
return nil, fmt.Errorf("expected 4-byte network, got %d", len(network))
|
||||
}
|
||||
|
||||
la := netlink.NewLinkAttrs()
|
||||
la.Name = name
|
||||
if err := netlink.LinkAdd(&netlink.GenericLink{LinkAttrs: la, LinkType: "wireguard"}); err != nil {
|
||||
return nil, fmt.Errorf("add wireguard link: %w", err)
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("get wireguard link: %w", err)
|
||||
}
|
||||
|
||||
a4 := vpnIP.As4()
|
||||
if err := netlink.AddrAdd(link, &netlink.Addr{
|
||||
IPNet: &net.IPNet{
|
||||
IP: net.IP(a4[:]),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
}); err != nil {
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("add VPN address: %w", err)
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(link); err != nil {
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("set link up: %w", err)
|
||||
}
|
||||
|
||||
client, err := wgctrl.New()
|
||||
if err != nil {
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("new wgctrl client: %w", err)
|
||||
}
|
||||
|
||||
cfg := wgtypes.Config{
|
||||
PrivateKey: &privKey,
|
||||
ListenPort: &listenPort,
|
||||
}
|
||||
if err := client.ConfigureDevice(name, cfg); err != nil {
|
||||
client.Close()
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("configure wireguard: %w", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func destroyWGDevice(name string) error {
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get link %q: %w", name, err)
|
||||
}
|
||||
return netlink.LinkDel(link)
|
||||
}
|
||||
|
||||
// applyBaseConfig adds the relay peer with /24 AllowedIPs, making it the
|
||||
// fallback route for all VPN traffic.
|
||||
func applyBaseConfig(client *wgctrl.Client, devName string, relayPubKey wgtypes.Key, relayEndpoint netip.AddrPort, network []byte) error {
|
||||
if len(network) != 4 {
|
||||
return fmt.Errorf("expected 4-byte network, got %d", len(network))
|
||||
}
|
||||
keepalive := 25 * time.Second
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: relayPubKey,
|
||||
Endpoint: net.UDPAddrFromAddrPort(relayEndpoint),
|
||||
AllowedIPs: []net.IPNet{{
|
||||
IP: net.IP{network[0], network[1], network[2], 0},
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
}},
|
||||
ReplaceAllowedIPs: true,
|
||||
PersistentKeepaliveInterval: &keepalive,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
// addProbeEntry adds a peer with no AllowedIPs and a 5s keepalive so WireGuard
|
||||
// attempts handshakes without routing any traffic through it yet.
|
||||
func addProbeEntry(client *wgctrl.Client, devName string, pubKey wgtypes.Key, endpoint netip.AddrPort) error {
|
||||
keepalive := 5 * time.Second
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Endpoint: net.UDPAddrFromAddrPort(endpoint),
|
||||
AllowedIPs: []net.IPNet{},
|
||||
ReplaceAllowedIPs: true,
|
||||
PersistentKeepaliveInterval: &keepalive,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
// addDirectPeer adds a peer with a known endpoint and /32 AllowedIPs in one
|
||||
// step, for use when the hub reports a peer with a stable public endpoint.
|
||||
func addDirectPeer(client *wgctrl.Client, devName string, pubKey wgtypes.Key, endpoint netip.AddrPort, vpnIP netip.Addr) error {
|
||||
a4 := vpnIP.As4()
|
||||
cfg := 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,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
// promoteToDirect upgrades a probe entry to a /32 AllowedIPs entry, causing
|
||||
// WireGuard to prefer the direct path over the relay's /24 route.
|
||||
func promoteToDirect(client *wgctrl.Client, devName string, pubKey wgtypes.Key, vpnIP netip.Addr) error {
|
||||
a4 := vpnIP.As4()
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
AllowedIPs: []net.IPNet{{
|
||||
IP: net.IP(a4[:]),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}},
|
||||
ReplaceAllowedIPs: true,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
func removePeerEntry(client *wgctrl.Client, devName string, pubKey wgtypes.Key) error {
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Remove: true,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
func enableForwarding(ifaceName string) error {
|
||||
path := fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/forwarding", ifaceName)
|
||||
return os.WriteFile(path, []byte("1\n"), 0644)
|
||||
}
|
||||
|
||||
func getPeerEndpoint(client *wgctrl.Client, devName string, pubKey wgtypes.Key) (netip.AddrPort, time.Time, error) {
|
||||
dev, err := client.Device(devName)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, time.Time{}, fmt.Errorf("get device: %w", err)
|
||||
}
|
||||
for _, p := range dev.Peers {
|
||||
if p.PublicKey == pubKey {
|
||||
if p.Endpoint == nil {
|
||||
return netip.AddrPort{}, p.LastHandshakeTime, nil
|
||||
}
|
||||
return p.Endpoint.AddrPort(), p.LastHandshakeTime, nil
|
||||
}
|
||||
}
|
||||
return netip.AddrPort{}, time.Time{}, fmt.Errorf("peer %v not found in device %s", pubKey, devName)
|
||||
}
|
||||
Reference in New Issue
Block a user