ginuerzh/gost · info
broken pipe
Error message
broken pipe
What it means
dnsServerConn.Write returns this when cclose is closed, meaning the server side of the DNS exchange is gone and there is no reader left on the mr channel — analogous to writing to a broken pipe on a socket. It lets the code satisfy the net.Conn interface while signaling the DNS response can no longer be delivered.
Source
Thrown at dns.go:367
}
}
func (c *dnsServerConn) Read(b []byte) (n int, err error) {
select {
case mb := <-c.mq:
n = copy(b, mb)
case <-c.cclose:
err = errors.New("connection is closed")
}
return
}
func (c *dnsServerConn) Write(b []byte) (n int, err error) {
select {
case c.mr <- b:
n = len(b)
case <-c.cclose:
err = errors.New("broken pipe")
}
return
}
func (c *dnsServerConn) Close() error {
select {
case <-c.cclose:
default:
close(c.cclose)
}
return nil
}
func (c *dnsServerConn) LocalAddr() net.Addr {
return c.laddr
}
View on GitHub (pinned to a33fdbf4c9)
Solutions
- Check cclose state / treat this error as benign and stop writing
- Ensure only one Write per DNS exchange occurs before Close
- Guard late goroutines with a done channel or context cancellation
- Log at debug level — the DNS client simply drops the missed reply
Example fix
// before
n, err := conn.Write(resp)
if err != nil {
log.Fatal(err)
}
// after
n, err := conn.Write(resp)
if err != nil {
if err.Error() == "broken pipe" {
return // client gone; safe to ignore
}
log.Log(err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// no pre-check possible; Write fails only after cclose fires
select {
case <-connClosed():
return // skip Write, conn already closed
default:
} Type guard
func isBrokenPipe(err error) bool {
return err != nil && err.Error() == "broken pipe"
} Try / catch
_, err := conn.Write(resp)
if err != nil {
if isBrokenPipe(err) {
return // benign: peer/exchange gone, drop the reply
}
return err
} Prevention
- Write each DNS response exactly once per exchange
- Cancel writer goroutines when the exchange ends to avoid late writes
- Log broken-pipe on DNS conns at debug level, not as an error
When it happens
Trigger: Writing a DNS response after the serve() exchange has closed the virtual connection (response already delivered, timed out, or conn closed by peer abort).
Common situations: Duplicate response writes in DNS tunneling handlers, late writes from a goroutine after the request timed out, client disconnected between query and reply.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- connection is closed
- connection queue is full
- deadline not supported
- failed to perform an HTTPS request: %s
- empty chain
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/f38f7b22416f06e0.
Report an issue: GitHub.