{"record":{"id":"ff89ee006d0a407b","repo":"OpenNHP/opennhp","slug":"peer-does-not-match-its-previous-address-on-this-c","errorCode":null,"errorMessage":"peer does not match its previous address on this connection (type=%s, pubkey=%s)","messagePattern":"peer does not match its previous address on this connection \\(type=(.+?), pubkey=(.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nhp/core/responder.go","lineNumber":517,"sourceCode":"\t\t\t\t// Skip expiry/address checks for fallback peers.\n\t\t\t\tgoto peerAccepted\n\t\t\t}\n\t\t\tlog.Error(\"validatePeer: %s peer not found in peer pool, pubkey=%s\",\n\t\t\t\tpeerDeviceTypeName, peerPkBase64)\n\t\t\terr = fmt.Errorf(\"peer not found in peer pool (type=%s, pubkey=%s)\", peerDeviceTypeName, peerPkBase64)\n\t\t\treturn err\n\t\t}\n\n\t\tif peer.IsExpired() {\n\t\t\tlog.Error(\"validatePeer: %s peer expired, pubkey=%s\", peerDeviceTypeName, peerPkBase64)\n\t\t\terr = fmt.Errorf(\"peer expired (type=%s, pubkey=%s)\", peerDeviceTypeName, peerPkBase64)\n\t\t\treturn err\n\t\t}\n\n\t\tif !ppd.ConnData.CheckRecvAddress(ppd.LocalInitTime, ppd.ConnData.RemoteAddr) {\n\t\t\tlog.Error(\"validatePeer: %s peer address mismatch on connection, pubkey=%s, remoteAddr=%s\",\n\t\t\t\tpeerDeviceTypeName, peerPkBase64, ppd.ConnData.RemoteAddr)\n\t\t\terr = fmt.Errorf(\"peer does not match its previous address on this connection (type=%s, pubkey=%s)\", peerDeviceTypeName, peerPkBase64)\n\t\t\treturn err\n\t\t}\n\t\tppd.ConnData.UpdateRecvAddress(ppd.LocalInitTime, ppd.ConnData.RemoteAddr)\n\t\tpeer.UpdateRecv(ppd.LocalInitTime)\n\tpeerAccepted:\n\t}\n\n\tppd.RemotePubKey = peerPk\n\tif ppd.ConnPeerPublicKey != nil {\n\t\tcopy((*ppd.ConnPeerPublicKey)[:], peerPk)\n\t}\n\n\t// evolve chainhash ChainHash1 -> ChainHash2\n\tppd.chainHash.Write(ppd.header.StaticBytes())\n\n\t// init shared key\n\tss := ppd.deviceEcdh.SharedSecret(peerPk)\n\tif ss == nil {","sourceCodeStart":499,"sourceCodeEnd":535,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/nhp/core/responder.go#L499-L535","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Restart or re-register the peer so a fresh connection is created from its current address (re-run the knock/registration flow).","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.","Verify the peer is not multi-homed or flipping between interfaces; pin it to a single stable source address/interface.","If the peer's address legitimately changed permanently, update the peer's entry in the server/ac config (peer tables) and restart the responder."],"exampleFix":"// before: agent keeps knocking after roaming, connection still bound to old addr\n// agent.toml\n[knock]\nintervalSeconds = 0 // no keepalive, NAT rebinds silently\n\n// after\n[knock]\nintervalSeconds = 30 // keeps NAT binding alive so source addr stays stable","handlingStrategy":"retry","validationCode":"// client-side: confirm source address is stable before knocking\nconn, err := net.Dial(\"udp\", serverAddr)\nif err != nil { return err }\nlocal := conn.LocalAddr().(*net.UDPAddr)\nif local.IP.String() != lastBoundIP || local.Port != lastBoundPort {\n    // NAT rebinding detected — re-register/re-knock to create a fresh connection\n}","typeGuard":null,"tryCatchPattern":"if err := sendKnock(pkt); err != nil && strings.Contains(err.Error(), \"previous address on this connection\") {\n    // re-register peer from its current address, then retry\n    reRegisterPeer(pubkey)\n    err = sendKnock(buildFreshPacket())\n}","preventionTips":["Send periodic UDP keepalive knocks to hold NAT bindings open","Pin clients to a single network interface and avoid roaming mid-session","Keep peer tables in server/ac config updated when a peer's address changes permanently","Monitor logs for 'peer address mismatch' to catch NAT rebinding early"],"tags":["network","udp","security","peer-validation"],"backgroundTag":"invalid-state-transition","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}