netbirdio/netbird · warning

decode peerID: %w

Error message

decode peerID: %w

What it means

parseTransfers, which turns a wireguard-go IpcGet dump into per-peer stats, failed to hex-decode the value of a public_key= line with hex.DecodeString. The wireguard-go UAPI always emits public keys as 64 lowercase hex characters, so this only fires when the dump is malformed, comes from an incompatible producer, or was corrupted. The decoded bytes are re-encoded to base64 to key the stats map.

Source

Thrown at client/iface/configurer/usp.go:384

func parseTransfers(ipc string) (map[string]WGStats, error) {
	stats := make(map[string]WGStats)
	var (
		currentKey   string
		currentStats WGStats
		hasPeer      bool
	)
	lines := strings.Split(ipc, "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)

		// If we're within the details of the found peer and encounter another public key,
		// this means we're starting another peer's details. So, stop.
		if strings.HasPrefix(line, "public_key=") {
			peerID := strings.TrimPrefix(line, "public_key=")
			h, err := hex.DecodeString(peerID)
			if err != nil {
				return nil, fmt.Errorf("decode peerID: %w", err)
			}
			currentKey = base64.StdEncoding.EncodeToString(h)
			currentStats = WGStats{} // Reset stats for the new peer
			hasPeer = true
			stats[currentKey] = currentStats
			continue
		}

		if !hasPeer {
			continue
		}

		key := strings.SplitN(line, "=", 2)
		if len(key) != 2 {
			continue
		}
		switch key[0] {
		case ipcKeyLastHandshakeTimeSec:

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Validate the producer: only pass IpcGet output from the matching wireguard-go device
  2. Skip unparseable public_key lines (log at debug) rather than failing the whole stats fetch
  3. Pin the wireguard-go version and add a fixture test for the dump format
  4. Check the raw dump for truncation when this appears in logs

Example fix

// before
h, err := hex.DecodeString(peerID)
if err != nil {
	return nil, fmt.Errorf("decode peerID: %w", err)
}

// after: skip malformed keys instead of dropping all stats
h, err := hex.DecodeString(peerID)
if err != nil {
	log.Debugf("skipping malformed public_key line: %v", err)
	continue
}
Defensive patterns

Strategy: type-guard

Validate before calling

// sanity-check a UAPI public_key line before relying on it
func validPublicKeyLine(line string) bool {
	v := strings.TrimPrefix(line, "public_key=")
	return len(v) == 64
}

Type guard

func isHexPeerID(s string) bool {
	if len(s) != 64 {
		return false
	}
	_, err := hex.DecodeString(s)
	return err == nil
}

Try / catch

stats, err := uspCfg.GetStats()
if err != nil {
	if strings.Contains(err.Error(), "decode peerID") {
		// malformed/foreign IPC dump: log and return partial stats rather than failing
		log.Warnf("stats parse hit malformed peer key: %v", err)
		return map[string]configurer.WGStats{}, nil
	}
	return nil, err
}

Prevention

When it happens

Trigger: An IPC string not produced by the paired wireguard-go device (hand-crafted test input, foreign implementation); truncated or whitespace-padded public_key line; memory corruption of the dumped string.

Common situations: Unit tests feeding synthetic IPC strings with wrong key formats; a vendored wireguard-go fork emitting base64 keys; version skew after upgrading the wireguard-go dependency.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/f2eab06a472c432f. Report an issue: GitHub.