package wireguard import ( "context" "encoding/base64" "encoding/hex" "fmt" "net" "net/netip" "sync" "golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/tun/netstack" "lol.mleku.dev/log" ) // Config holds the WireGuard server configuration. type Config struct { Port int // UDP port for WireGuard (default 51820) Endpoint string // Public IP/domain for clients to connect to PrivateKey []byte // Server's 32-byte Curve25519 private key Network string // CIDR for internal network (e.g., "10.73.0.0/16") ServerIP string // Server's internal IP (e.g., "10.73.0.1") } // Peer represents a WireGuard peer (client). type Peer struct { NostrPubkey []byte // User's Nostr pubkey (32 bytes) WGPublicKey []byte // WireGuard public key (32 bytes) AssignedIP string // Assigned internal IP } // Server manages the embedded WireGuard VPN server. type Server struct { cfg *Config device *device.Device tun *netstack.Net tunDev tun.Device publicKey []byte peers map[string]*Peer // WG pubkey (base64) -> Peer peersMu sync.RWMutex ctx context.Context cancel context.CancelFunc running bool mu sync.RWMutex } // New creates a new WireGuard server with the given configuration. func New(cfg *Config) (*Server, error) { if cfg.Endpoint == "" { return nil, ErrEndpointRequired } // Parse network CIDR to validate it _, _, err := net.ParseCIDR(cfg.Network) if err != nil { return nil, fmt.Errorf("%w: %v", ErrInvalidNetwork, err) } // Default server IP if not set if cfg.ServerIP == "" { cfg.ServerIP = "10.73.0.1" } // Derive public key from private key publicKey, err := DerivePublicKey(cfg.PrivateKey) if err != nil { return nil, fmt.Errorf("failed to derive public key: %w", err) } return &Server{ cfg: cfg, publicKey: publicKey, peers: make(map[string]*Peer), }, nil } // Start initializes and starts the WireGuard server. func (s *Server) Start() error { s.mu.Lock() defer s.mu.Unlock() if s.running { return nil } s.ctx, s.cancel = context.WithCancel(context.Background()) // Parse server IP serverAddr, err := netip.ParseAddr(s.cfg.ServerIP) if err != nil { return fmt.Errorf("invalid server IP: %w", err) } // Create netstack TUN device (userspace, no root required) s.tunDev, s.tun, err = netstack.CreateNetTUN( []netip.Addr{serverAddr}, []netip.Addr{}, // No DNS servers 1420, // MTU ) if err != nil { return fmt.Errorf("failed to create netstack TUN: %w", err) } // Create WireGuard device s.device = device.NewDevice( s.tunDev, conn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, "wg"), ) // Configure device with server private key and listen port privateKeyHex := hex.EncodeToString(s.cfg.PrivateKey) ipcConfig := fmt.Sprintf("private_key=%s\nlisten_port=%d\n", privateKeyHex, s.cfg.Port, ) if err = s.device.IpcSet(ipcConfig); err != nil { s.device.Close() return fmt.Errorf("failed to configure WireGuard device: %w", err) } // Bring up the device if err = s.device.Up(); err != nil { s.device.Close() return fmt.Errorf("failed to bring up WireGuard device: %w", err) } s.running = true log.I.F("WireGuard server started on UDP port %d", s.cfg.Port) log.I.F("WireGuard server public key: %s", base64.StdEncoding.EncodeToString(s.publicKey)) log.I.F("WireGuard internal network: %s (server: %s)", s.cfg.Network, s.cfg.ServerIP) return nil } // Stop shuts down the WireGuard server. func (s *Server) Stop() error { s.mu.Lock() defer s.mu.Unlock() if !s.running { return nil } if s.cancel != nil { s.cancel() } if s.device != nil { s.device.Close() } s.running = false log.I.F("WireGuard server stopped") return nil } // IsRunning returns whether the server is currently running. func (s *Server) IsRunning() bool { s.mu.RLock() defer s.mu.RUnlock() return s.running } // ServerPublicKey returns the server's WireGuard public key. func (s *Server) ServerPublicKey() []byte { return s.publicKey } // Endpoint returns the configured endpoint address. func (s *Server) Endpoint() string { return fmt.Sprintf("%s:%d", s.cfg.Endpoint, s.cfg.Port) } // GetNetstack returns the netstack networking interface. // This is used by the bunker to listen on the WireGuard network. func (s *Server) GetNetstack() *netstack.Net { s.mu.RLock() defer s.mu.RUnlock() return s.tun } // ServerIP returns the server's internal IP address. func (s *Server) ServerIP() string { return s.cfg.ServerIP } // AddPeer adds a new peer to the WireGuard server. func (s *Server) AddPeer(nostrPubkey, wgPublicKey []byte, assignedIP string) error { s.mu.RLock() if !s.running { s.mu.RUnlock() return ErrServerNotRunning } s.mu.RUnlock() // Encode WG public key as hex for IPC wgPubkeyHex := hex.EncodeToString(wgPublicKey) wgPubkeyBase64 := base64.StdEncoding.EncodeToString(wgPublicKey) // Configure peer in WireGuard device ipcConfig := fmt.Sprintf( "public_key=%s\nallowed_ip=%s/32\n", wgPubkeyHex, assignedIP, ) if err := s.device.IpcSet(ipcConfig); err != nil { return fmt.Errorf("failed to add peer: %w", err) } // Track peer s.peersMu.Lock() s.peers[wgPubkeyBase64] = &Peer{ NostrPubkey: nostrPubkey, WGPublicKey: wgPublicKey, AssignedIP: assignedIP, } s.peersMu.Unlock() log.I.F("WireGuard peer added: %s -> %s", wgPubkeyBase64[:16]+"...", assignedIP) return nil } // RemovePeer removes a peer from the WireGuard server. func (s *Server) RemovePeer(wgPublicKey []byte) error { s.mu.RLock() if !s.running { s.mu.RUnlock() return ErrServerNotRunning } s.mu.RUnlock() wgPubkeyHex := hex.EncodeToString(wgPublicKey) wgPubkeyBase64 := base64.StdEncoding.EncodeToString(wgPublicKey) // Remove peer from WireGuard device ipcConfig := fmt.Sprintf( "public_key=%s\nremove=true\n", wgPubkeyHex, ) if err := s.device.IpcSet(ipcConfig); err != nil { return fmt.Errorf("failed to remove peer: %w", err) } // Remove from tracking s.peersMu.Lock() delete(s.peers, wgPubkeyBase64) s.peersMu.Unlock() log.I.F("WireGuard peer removed: %s", wgPubkeyBase64[:16]+"...") return nil } // GetPeer returns a peer by their WireGuard public key. func (s *Server) GetPeer(wgPublicKey []byte) (*Peer, bool) { wgPubkeyBase64 := base64.StdEncoding.EncodeToString(wgPublicKey) s.peersMu.RLock() defer s.peersMu.RUnlock() peer, ok := s.peers[wgPubkeyBase64] return peer, ok } // PeerCount returns the number of active peers. func (s *Server) PeerCount() int { s.peersMu.RLock() defer s.peersMu.RUnlock() return len(s.peers) }