{"record":{"id":"dc896acb513f37c5","repo":"ginuerzh/gost","slug":"connection-is-closed","errorCode":null,"errorMessage":"connection is closed","messagePattern":"connection is closed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"info","filePath":"dns.go","lineNumber":357,"sourceCode":"\tladdr, raddr net.Addr\n}\n\nfunc newDNSServerConn(laddr, raddr net.Addr) *dnsServerConn {\n\treturn &dnsServerConn{\n\t\tmq:     make(chan []byte, 1),\n\t\tmr:     make(chan []byte, 1),\n\t\tladdr:  laddr,\n\t\traddr:  raddr,\n\t\tcclose: make(chan struct{}),\n\t}\n}\n\nfunc (c *dnsServerConn) Read(b []byte) (n int, err error) {\n\tselect {\n\tcase mb := <-c.mq:\n\t\tn = copy(b, mb)\n\tcase <-c.cclose:\n\t\terr = errors.New(\"connection is closed\")\n\t}\n\treturn\n}\n\nfunc (c *dnsServerConn) Write(b []byte) (n int, err error) {\n\tselect {\n\tcase c.mr <- b:\n\t\tn = len(b)\n\tcase <-c.cclose:\n\t\terr = errors.New(\"broken pipe\")\n\t}\n\n\treturn\n}\n\nfunc (c *dnsServerConn) Close() error {\n\tselect {\n\tcase <-c.cclose:","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/ginuerzh/gost/blob/a33fdbf4c98034f4bfeeaea9868909822b9c526d/dns.go#L339-L375","documentation":"dnsServerConn.Read returns this when the connection's cclose channel is fired, meaning the DNS request/response exchange has completed or been aborted before a queued message could be read. It emulates the 'read on closed conn' behavior of a net.Conn over DNS.","triggerScenarios":"Calling Read on a dnsServerConn after Close() was called (server finished writing the response or timed out the exchange), so the mq channel has no producer and cclose is closed.","commonSituations":"Handler goroutine still reading after the DNS exchange ended, client query timeout causing early Close, double-response scenarios in DNS tunneling.","solutions":["Treat this as io.EOF-style termination: stop reading and exit the conn loop","Check errors like this in the read loop and return without logging as fatal","Avoid calling Read after the serve() exchange has returned; ensure single reader per conn","Add errors.Is/if string match handling to map it to io.ErrClosedPipe for cleaner code"],"exampleFix":"// before\nfor {\n    n, err := conn.Read(buf)\n    if err != nil {\n        log.Fatal(err)\n    }\n    handle(buf[:n])\n}\n// after\nfor {\n    n, err := conn.Read(buf)\n    if err != nil {\n        if err.Error() == \"connection is closed\" {\n            err = io.EOF // expected end of DNS exchange\n        }\n        break\n    }\n    handle(buf[:n])\n}","handlingStrategy":"type-guard","validationCode":"// ensure exactly one reader and that serve() has not returned before reading\nselect {\ncase <-connClosed():\n    return // conn already closed, do not Read\ndefault:\n}","typeGuard":"func isConnClosed(err error) bool {\n    return err != nil && (errors.Is(err, io.EOF) || err.Error() == \"connection is closed\")\n}","tryCatchPattern":"n, err := conn.Read(buf)\nif err != nil {\n    if isConnClosed(err) {\n        return io.EOF // expected end of DNS virtual conn\n    }\n    return err\n}","preventionTips":["Use a single reader goroutine per dnsServerConn","Treat close as terminal: never Read after Close or after the reply is written","Map this error to io.EOF in wrappers so downstream code handles it idiomatically"],"tags":["dns","go","closed-connection","net-conn"],"backgroundTag":"read-on-closed-connection","analyzedSha":"a33fdbf4c98034f4bfeeaea9868909822b9c526d","analyzedAt":"2026-09-02T22:15:54.506Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}