{"record":{"id":"dd7c087949139779","repo":"XTLS/Xray-core","slug":"unexpected-eof","errorCode":null,"errorMessage":"unexpected EOF","messagePattern":"unexpected EOF","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/trojan/server.go","lineNumber":281,"sourceCode":"\t\t}\n\t})\n\tdefer udpServer.RemoveRay()\n\n\tinbound := session.InboundFromContext(ctx)\n\tuser := inbound.User\n\n\tvar dest *net.Destination\n\n\trequestDone := func() error {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t\tmb, err := clientReader.ReadMultiBuffer()\n\t\t\t\tif err != nil {\n\t\t\t\t\tif errors.Cause(err) != io.EOF {\n\t\t\t\t\t\treturn errors.New(\"unexpected EOF\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tmb2, b := buf.SplitFirst(mb)\n\t\t\t\tif b == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttimer.Update()\n\t\t\t\tdestination := *b.UDP\n\n\t\t\t\tcurrentPacketCtx := ctx\n\t\t\t\tif inbound.Source.IsValid() {\n\t\t\t\t\tcurrentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\t\t\t\t\tFrom:   inbound.Source,\n\t\t\t\t\t\tTo:     destination,\n\t\t\t\t\t\tStatus: log.AccessAccepted,\n\t\t\t\t\t\tReason: \"\",","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/proxy/trojan/server.go#L263-L299","documentation":"Thrown in the trojan UDP relay path (handleUDPPayload) when reading the next UDP payload from the client fails with something other than clean io.EOF. Each trojan UDP packet carries address+length framing; a mid-stream read error (reset, deadline, truncated frame) breaks the framing and the loop aborts with this wrapper.","triggerScenarios":"clientReader.ReadMultiBuffer() returns a connection reset, an i/o timeout, or a truncated trojan-UDP frame while relaying UDP (e.g. DNS or gaming traffic) over the trojan TCP carrier.","commonSituations":"UDP-over-trojan clients that vanish mid-session (NAT timeout, app close without a clean close_notify), mobile network switches, or MTU/truncation issues corrupting the length prefix.","solutions":["Confirm it happens at session end (client gone) — then it is benign noise","If it happens mid-session, check for middleboxes/NAT truncating the long-lived trojan TCP connection and enable keepalives/heartbeat in client policy","Verify client and server run compatible trojan-UDP framing versions"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"mb, err := clientReader.ReadMultiBuffer()\nif err != nil {\n    if errors.Cause(err) == io.EOF {\n        return nil // clean end of UDP session\n    }\n    // client went away mid-framing: nothing recoverable, stop relaying\n    return errors.New(\"unexpected EOF\").Base(err)\n}","preventionTips":["Enable TCP keepalive on the trojan carrier so dead peers are detected cleanly as io.EOF","Size client-side UDP session lifetimes below NAT timeout so sessions end before the carrier breaks","Do not treat this as retriable: the trojan-UDP framing cannot resync after a partial read"],"tags":["trojan","udp","relay"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}