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

  1. Check cclose state / treat this error as benign and stop writing
  2. Ensure only one Write per DNS exchange occurs before Close
  3. Guard late goroutines with a done channel or context cancellation
  4. 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

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

Related errors


AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02). Data as JSON: /api/errors/f38f7b22416f06e0. Report an issue: GitHub.