{"record":{"id":"7a1024d607217201","repo":"OpenNHP/opennhp","slug":"received-replay-packet","errorCode":null,"errorMessage":"received replay packet","messagePattern":"received replay packet","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"nhp/core/responder.go","lineNumber":580,"sourceCode":"\t}\n\n\tremoteSendTime := int64(binary.BigEndian.Uint64(tsBytes[:]))\n\n\tif shouldCheckRecvAttack(ppd.device.deviceType, peerDeviceType, ppd.HeaderType) {\n\t\t// block remote if threat level is reached\n\t\tif remoteSendTime < ppd.ConnData.LastRemoteSendTime {\n\t\t\t// replay packet, drop\n\t\t\tlog.Critical(\"received replay packet from %s, drop packet\", ppd.ConnData.RemoteAddr.String())\n\t\t\t// threat plus 1\n\t\t\tthreat := atomic.AddInt32(&ppd.ConnData.RecvThreatCount, 1)\n\t\t\t// with high queue number, the device may use ConnData channels when conn is already closed\n\t\t\tif threat > ThreatCountBeforeBlock && !ppd.ConnData.IsClosed() {\n\t\t\t\t// clamp threat count to avoid overflow\n\t\t\t\tatomic.StoreInt32(&ppd.ConnData.RecvThreatCount, ThreatCountBeforeBlock)\n\t\t\t\t// block source address\n\t\t\t\tppd.ConnData.SendBlockSignal()\n\t\t\t}\n\t\t\terr = fmt.Errorf(\"received replay packet\")\n\t\t\treturn err\n\t\t}\n\t\tif remoteSendTime < ppd.ConnData.LastRemoteSendTime+MinimalRecvIntervalMs*int64(time.Millisecond) {\n\t\t\t// flood packet, drop\n\t\t\tlog.Critical(\"received flood packet from %s, drop packet\", ppd.ConnData.RemoteAddr.String())\n\t\t\t// threat plus 1\n\t\t\tthreat := atomic.AddInt32(&ppd.ConnData.RecvThreatCount, 1)\n\t\t\tif threat > ThreatCountBeforeBlock && !ppd.ConnData.IsClosed() {\n\t\t\t\t// clamp threat count to avoid overflow\n\t\t\t\tatomic.StoreInt32(&ppd.ConnData.RecvThreatCount, ThreatCountBeforeBlock)\n\t\t\t\t// block source address\n\t\t\t\tppd.ConnData.SendBlockSignal()\n\t\t\t}\n\t\t\terr = fmt.Errorf(\"received flood packet\")\n\t\t\treturn err\n\t\t}\n\t}\n\tif remoteSendTime < (ppd.LocalInitTime - 600*int64(time.Second)) {","sourceCodeStart":562,"sourceCodeEnd":598,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/nhp/core/responder.go#L562-L598","documentation":"validatePeer detects that the packet's remote send timestamp equals one already seen (the connection's replay window/last-send-time tracking), meaning this exact packet was received before. The responder rejects it as a replay attack defense: an attacker capturing and retransmitting a legitimate encrypted NHP packet must not be able to replay knock or ack messages.","triggerScenarios":"A UDP packet whose signed/encrypted remoteSendTime is identical to a previously processed packet on the same connection arrives again — either a genuine network duplicate (retransmission at UDP level), or an active replay attack, or a peer whose clock/stamp logic is broken and emits identical timestamps.","commonSituations":"Aggressive UDP retransmit settings in the client, packet duplication by network equipment, an attacker replaying captured knock packets to reopen firewall rules, or a buggy custom client reusing the same timestamp for multiple sends.","solutions":["Verify the packet source is legitimate; if replays are malicious, keep the automatic block (threat counter triggers SendBlockSignal) and investigate the capturing attacker path.","On the client, ensure each send uses a monotonically increasing timestamp and never retransmits the exact same serialized packet; regenerate a fresh packet per attempt.","Check for network duplication (bonded interfaces, misconfigured switches, VPN tunnels multiplying packets) and fix at the network layer.","If a custom integration is the source, review its packet construction so send time and nonce are updated for every transmission."],"exampleFix":"// before: client retransmits the same serialized packet on timeout\nretry(packetBytes)\n\n// after: rebuild packet with fresh timestamp/nonce each attempt\nnewPacket := buildPacket(nowMillis())\nsend(newPacket)","handlingStrategy":"retry","validationCode":"// client-side: never retransmit an identical serialized packet\nvar lastSentTs int64\nfunc beforeSend(pkt *Packet) error {\n    if pkt.SendTimeMs <= atomic.LoadInt64(&lastSentTs) {\n        return errors.New(\"timestamp must strictly increase per packet\")\n    }\n    atomic.StoreInt64(&lastSentTs, pkt.SendTimeMs)\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := send(pkt); err != nil && strings.Contains(err.Error(), \"replay packet\") {\n    // rebuild with a fresh timestamp/nonce and retry once\n    pkt = buildPacket(time.Now().UnixMilli())\n    err = send(pkt)\n}","preventionTips":["Regenerate the packet (fresh timestamp and nonce) for every send attempt","Check client retry logic never resends byte-identical payloads","Investigate network paths that duplicate UDP packets (bonds, VPNs, load balancers)","Treat repeated replay errors as a possible active attack and review packet capture exposure"],"tags":["security","replay-attack","udp","packet-validation"],"backgroundTag":"checksum-mismatch","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"}