mirror of
https://github.com/netbirdio/netbird.git
synced 2026-03-31 06:34:14 -04:00
[client/android] - Fix Rosenpass connectivity for Android peers (#5044)
* [client] Add WGConfigurer interface To allow Rosenpass to work both with kernel WireGuard via wgctrl (default behavior) and userspace WireGuard via IPC on Android/iOS using WGUSPConfigurer * [client] Remove Rosenpass debug logs * [client] Return simpler peer configuration in outputKey method ConfigureDevice, the method previously used in outputKey via wgClient to update the device's properties, is now defined in the WGConfigurer interface and implemented both in kernel_unix and usp configurers. PresharedKey datatype was also changed from boolean to [32]byte to compare it to the original NetBird PSK, so that Rosenpass may replace it with its own when necessary. * [client] Remove unused field * [client] Replace usage of WGConfigurer Replaced with preshared key setter interface, which only defines a method to set / update the preshared key. Logic has been migrated from rosenpass/netbird_handler to client/iface. * [client] Use same default peer keepalive value when setting preshared keys * [client] Store PresharedKeySetter iface in rosenpass manager To avoid no-op if SetInterface is called before generateConfig * [client] Add mutex usage in rosenpass netbird handler * [client] change implementation setting Rosenpass preshared key Instead of providing a method to configure a device (device/interface.go), it forwards the new parameters to the configurer (either kernel_unix.go / usp.go). This removes dependency on reading FullStats, and makes use of a common method (buildPresharedKeyConfig in configurer/common.go) to build a minimal WG config that only sets/updates the PSK. netbird_handler.go now keeps s list of initializedPeers to choose whether to set the value of "UpdateOnly" when calling iface.SetPresharedKey. * [client] Address possible race condition Between outputKey calls and peer removal; it checks again if the peer still exists in the peers map before inserting it in the initializedPeers map. * [client] Add psk Rosenpass-initialized check On client/internal/peer/conn.go, the presharedKey function would always return the current key set in wgConfig.presharedKey. This would eventually overwrite a key set by Rosenpass if the feature is active. The purpose here is to set a handler that will check if a given peer has its psk initialized by Rosenpass to skip updating the psk via updatePeer (since it calls presharedKey method in conn.go). * Add missing updateOnly flag setup for usp peers * Change common.go buildPresharedKeyConfig signature PeerKey datatype changed from string to wgTypes.Key. Callers are responsible for parsing a peer key with string datatype.
This commit is contained in:
@@ -3,8 +3,22 @@ package configurer
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
// buildPresharedKeyConfig creates a wgtypes.Config for setting a preshared key on a peer.
|
||||
// This is a shared helper used by both kernel and userspace configurers.
|
||||
func buildPresharedKeyConfig(peerKey wgtypes.Key, psk wgtypes.Key, updateOnly bool) wgtypes.Config {
|
||||
return wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: peerKey,
|
||||
PresharedKey: &psk,
|
||||
UpdateOnly: updateOnly,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func prefixesToIPNets(prefixes []netip.Prefix) []net.IPNet {
|
||||
ipNets := make([]net.IPNet, len(prefixes))
|
||||
for i, prefix := range prefixes {
|
||||
|
||||
@@ -15,8 +15,6 @@ import (
|
||||
"github.com/netbirdio/netbird/monotime"
|
||||
)
|
||||
|
||||
var zeroKey wgtypes.Key
|
||||
|
||||
type KernelConfigurer struct {
|
||||
deviceName string
|
||||
}
|
||||
@@ -48,6 +46,18 @@ func (c *KernelConfigurer) ConfigureInterface(privateKey string, port int) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPresharedKey sets the preshared key for a peer.
|
||||
// If updateOnly is true, only updates the existing peer; if false, creates or updates.
|
||||
func (c *KernelConfigurer) SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error {
|
||||
parsedPeerKey, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := buildPresharedKeyConfig(parsedPeerKey, psk, updateOnly)
|
||||
return c.configure(cfg)
|
||||
}
|
||||
|
||||
func (c *KernelConfigurer) UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
@@ -279,7 +289,7 @@ func (c *KernelConfigurer) FullStats() (*Stats, error) {
|
||||
TxBytes: p.TransmitBytes,
|
||||
RxBytes: p.ReceiveBytes,
|
||||
LastHandshake: p.LastHandshakeTime,
|
||||
PresharedKey: p.PresharedKey != zeroKey,
|
||||
PresharedKey: [32]byte(p.PresharedKey),
|
||||
}
|
||||
if p.Endpoint != nil {
|
||||
peer.Endpoint = *p.Endpoint
|
||||
|
||||
@@ -22,17 +22,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
privateKey = "private_key"
|
||||
ipcKeyLastHandshakeTimeSec = "last_handshake_time_sec"
|
||||
ipcKeyLastHandshakeTimeNsec = "last_handshake_time_nsec"
|
||||
ipcKeyTxBytes = "tx_bytes"
|
||||
ipcKeyRxBytes = "rx_bytes"
|
||||
allowedIP = "allowed_ip"
|
||||
endpoint = "endpoint"
|
||||
fwmark = "fwmark"
|
||||
listenPort = "listen_port"
|
||||
publicKey = "public_key"
|
||||
presharedKey = "preshared_key"
|
||||
privateKey = "private_key"
|
||||
ipcKeyLastHandshakeTimeSec = "last_handshake_time_sec"
|
||||
ipcKeyTxBytes = "tx_bytes"
|
||||
ipcKeyRxBytes = "rx_bytes"
|
||||
allowedIP = "allowed_ip"
|
||||
endpoint = "endpoint"
|
||||
fwmark = "fwmark"
|
||||
listenPort = "listen_port"
|
||||
publicKey = "public_key"
|
||||
presharedKey = "preshared_key"
|
||||
)
|
||||
|
||||
var ErrAllowedIPNotFound = fmt.Errorf("allowed IP not found")
|
||||
@@ -72,6 +71,18 @@ func (c *WGUSPConfigurer) ConfigureInterface(privateKey string, port int) error
|
||||
return c.device.IpcSet(toWgUserspaceString(config))
|
||||
}
|
||||
|
||||
// SetPresharedKey sets the preshared key for a peer.
|
||||
// If updateOnly is true, only updates the existing peer; if false, creates or updates.
|
||||
func (c *WGUSPConfigurer) SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error {
|
||||
parsedPeerKey, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := buildPresharedKeyConfig(parsedPeerKey, psk, updateOnly)
|
||||
return c.device.IpcSet(toWgUserspaceString(cfg))
|
||||
}
|
||||
|
||||
func (c *WGUSPConfigurer) UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
@@ -422,23 +433,19 @@ func toWgUserspaceString(wgCfg wgtypes.Config) string {
|
||||
hexKey := hex.EncodeToString(p.PublicKey[:])
|
||||
sb.WriteString(fmt.Sprintf("public_key=%s\n", hexKey))
|
||||
|
||||
if p.Remove {
|
||||
sb.WriteString("remove=true\n")
|
||||
}
|
||||
|
||||
if p.UpdateOnly {
|
||||
sb.WriteString("update_only=true\n")
|
||||
}
|
||||
|
||||
if p.PresharedKey != nil {
|
||||
preSharedHexKey := hex.EncodeToString(p.PresharedKey[:])
|
||||
sb.WriteString(fmt.Sprintf("preshared_key=%s\n", preSharedHexKey))
|
||||
}
|
||||
|
||||
if p.Remove {
|
||||
sb.WriteString("remove=true")
|
||||
}
|
||||
|
||||
if p.ReplaceAllowedIPs {
|
||||
sb.WriteString("replace_allowed_ips=true\n")
|
||||
}
|
||||
|
||||
for _, aip := range p.AllowedIPs {
|
||||
sb.WriteString(fmt.Sprintf("allowed_ip=%s\n", aip.String()))
|
||||
}
|
||||
|
||||
if p.Endpoint != nil {
|
||||
sb.WriteString(fmt.Sprintf("endpoint=%s\n", p.Endpoint.String()))
|
||||
}
|
||||
@@ -446,6 +453,14 @@ func toWgUserspaceString(wgCfg wgtypes.Config) string {
|
||||
if p.PersistentKeepaliveInterval != nil {
|
||||
sb.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", int(p.PersistentKeepaliveInterval.Seconds())))
|
||||
}
|
||||
|
||||
if p.ReplaceAllowedIPs {
|
||||
sb.WriteString("replace_allowed_ips=true\n")
|
||||
}
|
||||
|
||||
for _, aip := range p.AllowedIPs {
|
||||
sb.WriteString(fmt.Sprintf("allowed_ip=%s\n", aip.String()))
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
@@ -599,7 +614,9 @@ func parseStatus(deviceName, ipcStr string) (*Stats, error) {
|
||||
continue
|
||||
}
|
||||
if val != "" && val != "0000000000000000000000000000000000000000000000000000000000000000" {
|
||||
currentPeer.PresharedKey = true
|
||||
if pskKey, err := hexToWireguardKey(val); err == nil {
|
||||
currentPeer.PresharedKey = [32]byte(pskKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ type Peer struct {
|
||||
TxBytes int64
|
||||
RxBytes int64
|
||||
LastHandshake time.Time
|
||||
PresharedKey bool
|
||||
PresharedKey [32]byte
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
|
||||
@@ -17,6 +17,7 @@ type WGConfigurer interface {
|
||||
RemovePeer(peerKey string) error
|
||||
AddAllowedIP(peerKey string, allowedIP netip.Prefix) error
|
||||
RemoveAllowedIP(peerKey string, allowedIP netip.Prefix) error
|
||||
SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error
|
||||
Close()
|
||||
GetStats() (map[string]configurer.WGStats, error)
|
||||
FullStats() (*configurer.Stats, error)
|
||||
|
||||
@@ -297,6 +297,19 @@ func (w *WGIface) FullStats() (*configurer.Stats, error) {
|
||||
return w.configurer.FullStats()
|
||||
}
|
||||
|
||||
// SetPresharedKey sets or updates the preshared key for a peer.
|
||||
// If updateOnly is true, only updates existing peer; if false, creates or updates.
|
||||
func (w *WGIface) SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if w.configurer == nil {
|
||||
return ErrIfaceNotFound
|
||||
}
|
||||
|
||||
return w.configurer.SetPresharedKey(peerKey, psk, updateOnly)
|
||||
}
|
||||
|
||||
func (w *WGIface) waitUntilRemoved() error {
|
||||
maxWaitTime := 5 * time.Second
|
||||
timeout := time.NewTimer(maxWaitTime)
|
||||
|
||||
@@ -60,7 +60,7 @@ func (g *BundleGenerator) toWGShowFormat(s *configurer.Stats) string {
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" latest handshake: %s\n", peer.LastHandshake.Format(time.RFC1123)))
|
||||
sb.WriteString(fmt.Sprintf(" transfer: %d B received, %d B sent\n", peer.RxBytes, peer.TxBytes))
|
||||
if peer.PresharedKey {
|
||||
if peer.PresharedKey != [32]byte{} {
|
||||
sb.WriteString(" preshared key: (hidden)\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,6 +505,11 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
||||
return fmt.Errorf("up wg interface: %w", err)
|
||||
}
|
||||
|
||||
// Set the WireGuard interface for rosenpass after interface is up
|
||||
if e.rpManager != nil {
|
||||
e.rpManager.SetInterface(e.wgInterface)
|
||||
}
|
||||
|
||||
// if inbound conns are blocked there is no need to create the ACL manager
|
||||
if e.firewall != nil && !e.config.BlockInbound {
|
||||
e.acl = acl.NewDefaultManager(e.firewall)
|
||||
@@ -1512,6 +1517,7 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs []netip.Prefix, agentV
|
||||
if e.rpManager != nil {
|
||||
peerConn.SetOnConnected(e.rpManager.OnConnected)
|
||||
peerConn.SetOnDisconnected(e.rpManager.OnDisconnected)
|
||||
peerConn.SetRosenpassInitializedPresharedKeyValidator(e.rpManager.IsPresharedKeyInitialized)
|
||||
}
|
||||
|
||||
return peerConn, nil
|
||||
|
||||
@@ -214,6 +214,10 @@ func (m *MockWGIface) LastActivities() map[string]monotime.Time {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockWGIface) SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
_ = util.InitLog("debug", util.LogConsole)
|
||||
code := m.Run()
|
||||
|
||||
@@ -42,4 +42,5 @@ type wgIfaceBase interface {
|
||||
GetNet() *netstack.Net
|
||||
FullStats() (*configurer.Stats, error)
|
||||
LastActivities() map[string]monotime.Time
|
||||
SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error
|
||||
}
|
||||
|
||||
@@ -88,8 +88,9 @@ type Conn struct {
|
||||
relayManager *relayClient.Manager
|
||||
srWatcher *guard.SRWatcher
|
||||
|
||||
onConnected func(remoteWireGuardKey string, remoteRosenpassPubKey []byte, wireGuardIP string, remoteRosenpassAddr string)
|
||||
onDisconnected func(remotePeer string)
|
||||
onConnected func(remoteWireGuardKey string, remoteRosenpassPubKey []byte, wireGuardIP string, remoteRosenpassAddr string)
|
||||
onDisconnected func(remotePeer string)
|
||||
rosenpassInitializedPresharedKeyValidator func(peerKey string) bool
|
||||
|
||||
statusRelay *worker.AtomicWorkerStatus
|
||||
statusICE *worker.AtomicWorkerStatus
|
||||
@@ -289,6 +290,13 @@ func (conn *Conn) SetOnDisconnected(handler func(remotePeer string)) {
|
||||
conn.onDisconnected = handler
|
||||
}
|
||||
|
||||
// SetRosenpassInitializedPresharedKeyValidator sets a function to check if Rosenpass has taken over
|
||||
// PSK management for a peer. When this returns true, presharedKey() returns nil
|
||||
// to prevent UpdatePeer from overwriting the Rosenpass-managed PSK.
|
||||
func (conn *Conn) SetRosenpassInitializedPresharedKeyValidator(handler func(peerKey string) bool) {
|
||||
conn.rosenpassInitializedPresharedKeyValidator = handler
|
||||
}
|
||||
|
||||
func (conn *Conn) OnRemoteOffer(offer OfferAnswer) {
|
||||
conn.dumpState.RemoteOffer()
|
||||
conn.Log.Infof("OnRemoteOffer, on status ICE: %s, status Relay: %s", conn.statusICE, conn.statusRelay)
|
||||
@@ -759,10 +767,24 @@ func (conn *Conn) presharedKey(remoteRosenpassKey []byte) *wgtypes.Key {
|
||||
return conn.config.WgConfig.PreSharedKey
|
||||
}
|
||||
|
||||
// If Rosenpass has already set a PSK for this peer, return nil to prevent
|
||||
// UpdatePeer from overwriting the Rosenpass-managed key.
|
||||
if conn.rosenpassInitializedPresharedKeyValidator != nil && conn.rosenpassInitializedPresharedKeyValidator(conn.config.Key) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use NetBird PSK as the seed for Rosenpass. This same PSK is passed to
|
||||
// Rosenpass as PeerConfig.PresharedKey, ensuring the derived post-quantum
|
||||
// key is cryptographically bound to the original secret.
|
||||
if conn.config.WgConfig.PreSharedKey != nil {
|
||||
return conn.config.WgConfig.PreSharedKey
|
||||
}
|
||||
|
||||
// Fallback to deterministic key if no NetBird PSK is configured
|
||||
determKey, err := conn.rosenpassDetermKey()
|
||||
if err != nil {
|
||||
conn.Log.Errorf("failed to generate Rosenpass initial key: %v", err)
|
||||
return conn.config.WgConfig.PreSharedKey
|
||||
return nil
|
||||
}
|
||||
|
||||
return determKey
|
||||
|
||||
@@ -284,3 +284,27 @@ func TestConn_presharedKey(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_presharedKey_RosenpassManaged(t *testing.T) {
|
||||
conn := Conn{
|
||||
config: ConnConfig{
|
||||
Key: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
LocalKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
RosenpassConfig: RosenpassConfig{PubKey: []byte("dummykey")},
|
||||
},
|
||||
}
|
||||
|
||||
// When Rosenpass has already initialized the PSK for this peer,
|
||||
// presharedKey must return nil to avoid UpdatePeer overwriting it.
|
||||
conn.rosenpassInitializedPresharedKeyValidator = func(peerKey string) bool { return true }
|
||||
if k := conn.presharedKey([]byte("remote")); k != nil {
|
||||
t.Fatalf("expected nil presharedKey when Rosenpass manages PSK, got %v", k)
|
||||
}
|
||||
|
||||
// When Rosenpass hasn't taken over yet, presharedKey should provide
|
||||
// a non-nil initial key (deterministic or from NetBird PSK).
|
||||
conn.rosenpassInitializedPresharedKeyValidator = func(peerKey string) bool { return false }
|
||||
if k := conn.presharedKey([]byte("remote")); k == nil {
|
||||
t.Fatalf("expected non-nil presharedKey before Rosenpass manages PSK")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ type Manager struct {
|
||||
server *rp.Server
|
||||
lock sync.Mutex
|
||||
port int
|
||||
wgIface PresharedKeySetter
|
||||
}
|
||||
|
||||
// NewManager creates a new Rosenpass manager
|
||||
@@ -109,7 +110,13 @@ func (m *Manager) generateConfig() (rp.Config, error) {
|
||||
cfg.SecretKey = m.ssk
|
||||
|
||||
cfg.Peers = []rp.PeerConfig{}
|
||||
m.rpWgHandler, _ = NewNetbirdHandler(m.preSharedKey, m.ifaceName)
|
||||
|
||||
m.lock.Lock()
|
||||
m.rpWgHandler = NewNetbirdHandler()
|
||||
if m.wgIface != nil {
|
||||
m.rpWgHandler.SetInterface(m.wgIface)
|
||||
}
|
||||
m.lock.Unlock()
|
||||
|
||||
cfg.Handlers = []rp.Handler{m.rpWgHandler}
|
||||
|
||||
@@ -172,6 +179,20 @@ func (m *Manager) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetInterface sets the WireGuard interface for the rosenpass handler.
|
||||
// This can be called before or after Run() - the interface will be stored
|
||||
// and passed to the handler when it's created or updated immediately if
|
||||
// already running.
|
||||
func (m *Manager) SetInterface(iface PresharedKeySetter) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
m.wgIface = iface
|
||||
if m.rpWgHandler != nil {
|
||||
m.rpWgHandler.SetInterface(iface)
|
||||
}
|
||||
}
|
||||
|
||||
// OnConnected is a handler function that is triggered when a connection to a remote peer establishes
|
||||
func (m *Manager) OnConnected(remoteWireGuardKey string, remoteRosenpassPubKey []byte, wireGuardIP string, remoteRosenpassAddr string) {
|
||||
m.lock.Lock()
|
||||
@@ -192,6 +213,20 @@ func (m *Manager) OnConnected(remoteWireGuardKey string, remoteRosenpassPubKey [
|
||||
}
|
||||
}
|
||||
|
||||
// IsPresharedKeyInitialized returns true if Rosenpass has completed a handshake
|
||||
// and set a PSK for the given WireGuard peer.
|
||||
func (m *Manager) IsPresharedKeyInitialized(wireGuardPubKey string) bool {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
peerID, ok := m.rpPeerIDs[wireGuardPubKey]
|
||||
if !ok || peerID == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return m.rpWgHandler.IsPeerInitialized(*peerID)
|
||||
}
|
||||
|
||||
func findRandomAvailableUDPPort() (int, error) {
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err != nil {
|
||||
|
||||
@@ -1,46 +1,50 @@
|
||||
package rosenpass
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
rp "cunicu.li/go-rosenpass"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
// PresharedKeySetter is the interface for setting preshared keys on WireGuard peers.
|
||||
// This minimal interface allows rosenpass to update PSKs without depending on the full WGIface.
|
||||
type PresharedKeySetter interface {
|
||||
SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error
|
||||
}
|
||||
|
||||
type wireGuardPeer struct {
|
||||
Interface string
|
||||
PublicKey rp.Key
|
||||
}
|
||||
|
||||
type NetbirdHandler struct {
|
||||
ifaceName string
|
||||
client *wgctrl.Client
|
||||
peers map[rp.PeerID]wireGuardPeer
|
||||
presharedKey [32]byte
|
||||
mu sync.Mutex
|
||||
iface PresharedKeySetter
|
||||
peers map[rp.PeerID]wireGuardPeer
|
||||
initializedPeers map[rp.PeerID]bool
|
||||
}
|
||||
|
||||
func NewNetbirdHandler(preSharedKey *[32]byte, wgIfaceName string) (hdlr *NetbirdHandler, err error) {
|
||||
hdlr = &NetbirdHandler{
|
||||
ifaceName: wgIfaceName,
|
||||
peers: map[rp.PeerID]wireGuardPeer{},
|
||||
func NewNetbirdHandler() *NetbirdHandler {
|
||||
return &NetbirdHandler{
|
||||
peers: map[rp.PeerID]wireGuardPeer{},
|
||||
initializedPeers: map[rp.PeerID]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
if preSharedKey != nil {
|
||||
hdlr.presharedKey = *preSharedKey
|
||||
}
|
||||
|
||||
if hdlr.client, err = wgctrl.New(); err != nil {
|
||||
return nil, fmt.Errorf("failed to creat WireGuard client: %w", err)
|
||||
}
|
||||
|
||||
return hdlr, nil
|
||||
// SetInterface sets the WireGuard interface for the handler.
|
||||
// This must be called after the WireGuard interface is created.
|
||||
func (h *NetbirdHandler) SetInterface(iface PresharedKeySetter) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.iface = iface
|
||||
}
|
||||
|
||||
func (h *NetbirdHandler) AddPeer(pid rp.PeerID, intf string, pk rp.Key) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.peers[pid] = wireGuardPeer{
|
||||
Interface: intf,
|
||||
PublicKey: pk,
|
||||
@@ -48,79 +52,61 @@ func (h *NetbirdHandler) AddPeer(pid rp.PeerID, intf string, pk rp.Key) {
|
||||
}
|
||||
|
||||
func (h *NetbirdHandler) RemovePeer(pid rp.PeerID) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
delete(h.peers, pid)
|
||||
delete(h.initializedPeers, pid)
|
||||
}
|
||||
|
||||
// IsPeerInitialized returns true if Rosenpass has completed a handshake
|
||||
// and set a PSK for this peer.
|
||||
func (h *NetbirdHandler) IsPeerInitialized(pid rp.PeerID) bool {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
return h.initializedPeers[pid]
|
||||
}
|
||||
|
||||
func (h *NetbirdHandler) HandshakeCompleted(pid rp.PeerID, key rp.Key) {
|
||||
log.Debug("Handshake complete")
|
||||
h.outputKey(rp.KeyOutputReasonStale, pid, key)
|
||||
}
|
||||
|
||||
func (h *NetbirdHandler) HandshakeExpired(pid rp.PeerID) {
|
||||
key, _ := rp.GeneratePresharedKey()
|
||||
log.Debug("Handshake expired")
|
||||
h.outputKey(rp.KeyOutputReasonStale, pid, key)
|
||||
}
|
||||
|
||||
func (h *NetbirdHandler) outputKey(_ rp.KeyOutputReason, pid rp.PeerID, psk rp.Key) {
|
||||
h.mu.Lock()
|
||||
iface := h.iface
|
||||
wg, ok := h.peers[pid]
|
||||
isInitialized := h.initializedPeers[pid]
|
||||
h.mu.Unlock()
|
||||
|
||||
if iface == nil {
|
||||
log.Warn("rosenpass: interface not set, cannot update preshared key")
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
device, err := h.client.Device(h.ifaceName)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get WireGuard device: %v", err)
|
||||
peerKey := wgtypes.Key(wg.PublicKey).String()
|
||||
pskKey := wgtypes.Key(psk)
|
||||
|
||||
// Use updateOnly=true for later rotations (peer already has Rosenpass PSK)
|
||||
// Use updateOnly=false for first rotation (peer has original/empty PSK)
|
||||
if err := iface.SetPresharedKey(peerKey, pskKey, isInitialized); err != nil {
|
||||
log.Errorf("Failed to apply rosenpass key: %v", err)
|
||||
return
|
||||
}
|
||||
config := []wgtypes.PeerConfig{
|
||||
{
|
||||
UpdateOnly: true,
|
||||
PublicKey: wgtypes.Key(wg.PublicKey),
|
||||
PresharedKey: (*wgtypes.Key)(&psk),
|
||||
},
|
||||
}
|
||||
for _, peer := range device.Peers {
|
||||
if peer.PublicKey == wgtypes.Key(wg.PublicKey) {
|
||||
if publicKeyEmpty(peer.PresharedKey) || peer.PresharedKey == h.presharedKey {
|
||||
log.Debugf("Restart wireguard connection to peer %s", peer.PublicKey)
|
||||
config = []wgtypes.PeerConfig{
|
||||
{
|
||||
PublicKey: wgtypes.Key(wg.PublicKey),
|
||||
PresharedKey: (*wgtypes.Key)(&psk),
|
||||
Endpoint: peer.Endpoint,
|
||||
AllowedIPs: peer.AllowedIPs,
|
||||
},
|
||||
}
|
||||
err = h.client.ConfigureDevice(wg.Interface, wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{
|
||||
{
|
||||
Remove: true,
|
||||
PublicKey: wgtypes.Key(wg.PublicKey),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
slog.Debug("Failed to remove peer")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Mark peer as isInitialized after the successful first rotation
|
||||
if !isInitialized {
|
||||
h.mu.Lock()
|
||||
if _, exists := h.peers[pid]; exists {
|
||||
h.initializedPeers[pid] = true
|
||||
}
|
||||
}
|
||||
|
||||
if err = h.client.ConfigureDevice(wg.Interface, wgtypes.Config{
|
||||
Peers: config,
|
||||
}); err != nil {
|
||||
log.Errorf("Failed to apply rosenpass key: %v", err)
|
||||
h.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func publicKeyEmpty(key wgtypes.Key) bool {
|
||||
for _, b := range key {
|
||||
if b != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user