//go:build ignore 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) }