{"record":{"id":"deba34776e5707b6","repo":"louislam/uptime-kuma","slug":"invalid-ntp-response-expected-48-bytes-got-ms","errorCode":null,"errorMessage":"Invalid NTP response: expected 48+ bytes, got ${msg.length}","messagePattern":"Invalid NTP response: expected 48\\+ bytes, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/monitor-types/ntp.js","lineNumber":147,"sourceCode":"     * @returns {Buffer} NTP request packet\n     */\n    createNTPPacket() {\n        const packet = Buffer.alloc(48);\n        packet[0] = 0x1b;\n        return packet;\n    }\n\n    /**\n     * Parse an NTP response packet and calculate offset/delay\n     * @param {Buffer} msg NTP response packet (48+ bytes)\n     * @param {number} t1 Client originate timestamp in ms since NTP epoch (1900)\n     * @param {number} t4 Client receive timestamp in ms since NTP epoch (1900)\n     * @returns {object} Parsed NTP data including stratum, offset, refid, rootDispersion, roundTripDelay\n     * @throws {Error} If the packet is shorter than 48 bytes\n     */\n    parseNTPResponse(msg, t1, t4) {\n        if (msg.length < 48) {\n            throw new Error(`Invalid NTP response: expected 48+ bytes, got ${msg.length}`);\n        }\n\n        const leapIndicator = (msg[0] >> 6) & 0x03;\n        const stratum = msg[1];\n\n        // Root dispersion: 32-bit unsigned fixed-point at offset 8, unit = seconds\n        const rootDispersionRaw = msg.readUInt32BE(8);\n        const rootDispersion = (rootDispersionRaw / 65536) * 1000;\n\n        // Reference ID: ASCII for stratum 0-1, IPv4 address for stratum 2+\n        let refid;\n        if (stratum <= 1) {\n            refid = msg.toString(\"ascii\", 12, 16).replace(/\\0/g, \"\").trim();\n        } else {\n            refid = `${msg[12]}.${msg[13]}.${msg[14]}.${msg[15]}`;\n        }\n\n        // Server receive timestamp (T2) at offset 32","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/monitor-types/ntp.js#L129-L165","documentation":"Thrown by NTPMonitorType.parseNTPResponse when the UDP datagram is shorter than 48 bytes. A conformant NTPv3/v4 packet is exactly 48 bytes (plus optional extensions); anything shorter cannot contain the required headers, so the response is treated as malformed. This is raised inside the 'message' handler and surfaces via queryNTP's reject path.","triggerScenarios":"An ICMP port-unreachable or NAT/firewall rewrite returns a short UDP payload that is delivered to the socket, or a non-NTP service responds on UDP 123 with a truncated packet, or a spoofed/buggy device emits a partial NTP reply. msg.length < 48 then trips the guard.","commonSituations":"Firewall/NAT translating ICMP unreachable into a short datagram; querying a host that runs a different UDP service on 123; a middlebox truncating packets; a misbehaving embedded device; DNS resolved to an IP that is not actually an NTP server.","solutions":["Confirm the target truly runs NTP on UDP 123 (e.g. ntpdate -q <host> or ntpdig from another machine).","Check firewalls/NAT between the monitor and target for packet rewriting/truncation.","Try an IP address directly to rule out DNS pointing at the wrong host.","Use a well-known public source (pool.ntp.org) to validate the monitor path, then reintroduce the target."],"exampleFix":"# before: monitor.hostname = 'ntp.internal'   (resolves to a non-NTP load balancer)\n# isolate:\nntpdate -q ntp.internal    # observe reply size / stratum\n# after: monitor.hostname = '10.0.0.53'   (the real NTP appliance)","handlingStrategy":"try-catch","validationCode":"// Sanity-check the target before relying on the monitor:\n// run: ntpdate -q <host>   (a healthy server replies with a 48-byte packet and a real stratum)\n// Also confirm DNS resolves to a real NTP server, not a load-balanced non-NTP service.","typeGuard":"function isPlausibleNtpPacket(buf) { return Buffer.isBuffer(buf) && buf.length >= 48; }","tryCatchPattern":"try { await ntpMonitor.check(monitor, heartbeat, server); }\ncatch (e) { if (/expected 48\\+ bytes/.test(e.message)) { heartbeat.status = DOWN; heartbeat.msg = 'Malformed NTP response (target may not be an NTP server)'; } else throw e; }","preventionTips":["Confirm the target actually serves NTP on UDP 123 (use ntpdate/chronyc from another host).","Rule out NAT/firewalls that rewrite or truncate UDP; try the IP directly to bypass DNS.","Validate against pool.ntp.org first to confirm the monitor path works."],"tags":["ntp","udp","malformed-packet","uptime-kuma"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}