OpenNHP/opennhp · error

peer does not match its previous address on this connection

Error message

peer does not match its previous address on this connection (type=%s, pubkey=%s)

What it means

During validatePeer, the responder checks that the remote peer's address matches the address it originally used on this connection (CheckRecvAddress against the address recorded at connection init). If the source UDP address changes mid-connection, the packet is rejected because NHP binds a connection to one peer endpoint to prevent endpoint hijacking/spoofing. The error identifies the peer device type and public key so the operator can tell which peer moved.

Solutions

  1. Restart or re-register the peer so a fresh connection is created from its current address (re-run the knock/registration flow).
  2. Check the peer's network for NAT/UDP timeout issues and shorten the peer's re-knock interval or enable NAT keepalives so the binding does not expire and silently rebind to a new port.
  3. Verify the peer is not multi-homed or flipping between interfaces; pin it to a single stable source address/interface.
  4. If the peer's address legitimately changed permanently, update the peer's entry in the server/ac config (peer tables) and restart the responder.

Example fix

// before: agent keeps knocking after roaming, connection still bound to old addr
// agent.toml
[knock]
intervalSeconds = 0 // no keepalive, NAT rebinds silently

// after
[knock]
intervalSeconds = 30 // keeps NAT binding alive so source addr stays stable
Defensive patterns

Strategy: retry

Validate before calling

// client-side: confirm source address is stable before knocking
conn, err := net.Dial("udp", serverAddr)
if err != nil { return err }
local := conn.LocalAddr().(*net.UDPAddr)
if local.IP.String() != lastBoundIP || local.Port != lastBoundPort {
    // NAT rebinding detected — re-register/re-knock to create a fresh connection
}

Try / catch

if err := sendKnock(pkt); err != nil && strings.Contains(err.Error(), "previous address on this connection") {
    // re-register peer from its current address, then retry
    reRegisterPeer(pubkey)
    err = sendKnock(buildFreshPacket())
}

Prevention

When it happens

Trigger: A valid, correctly signed NHP packet arrives on an existing connection but from a different IP or port than the address recorded when the connection was established (ppd.LocalInitTime); e.g. an agent behind NAT rebinding, a container restart picking a new source port, or a client switching networks (Wi-Fi to LTE) while reusing the same key.

Common situations: NAT gateways remapping UDP bindings mid-session, mobile clients roaming between networks, Kubernetes pods restarted with new IPs reusing persisted private keys, load balancers routing packets through different egress IPs, or misconfigured dual-stack setups flipping between IPv4 and IPv6 source addresses.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/ff89ee006d0a407b. Report an issue: GitHub.

Appendix: source

Thrown at nhp/core/responder.go:517

				// Skip expiry/address checks for fallback peers.
				goto peerAccepted
			}
			log.Error("validatePeer: %s peer not found in peer pool, pubkey=%s",
				peerDeviceTypeName, peerPkBase64)
			err = fmt.Errorf("peer not found in peer pool (type=%s, pubkey=%s)", peerDeviceTypeName, peerPkBase64)
			return err
		}

		if peer.IsExpired() {
			log.Error("validatePeer: %s peer expired, pubkey=%s", peerDeviceTypeName, peerPkBase64)
			err = fmt.Errorf("peer expired (type=%s, pubkey=%s)", peerDeviceTypeName, peerPkBase64)
			return err
		}

		if !ppd.ConnData.CheckRecvAddress(ppd.LocalInitTime, ppd.ConnData.RemoteAddr) {
			log.Error("validatePeer: %s peer address mismatch on connection, pubkey=%s, remoteAddr=%s",
				peerDeviceTypeName, peerPkBase64, ppd.ConnData.RemoteAddr)
			err = fmt.Errorf("peer does not match its previous address on this connection (type=%s, pubkey=%s)", peerDeviceTypeName, peerPkBase64)
			return err
		}
		ppd.ConnData.UpdateRecvAddress(ppd.LocalInitTime, ppd.ConnData.RemoteAddr)
		peer.UpdateRecv(ppd.LocalInitTime)
	peerAccepted:
	}

	ppd.RemotePubKey = peerPk
	if ppd.ConnPeerPublicKey != nil {
		copy((*ppd.ConnPeerPublicKey)[:], peerPk)
	}

	// evolve chainhash ChainHash1 -> ChainHash2
	ppd.chainHash.Write(ppd.header.StaticBytes())

	// init shared key
	ss := ppd.deviceEcdh.SharedSecret(peerPk)
	if ss == nil {

View on GitHub (pinned to 6e04ca5ff0)