AlexxIT/go2rtc · error

dtls: client handshake failed

Error message

dtls: client handshake failed: %w

What it means

connect performs the client-side PSK DTLS handshake on the main channel and wraps any NewDTLSClient failure with this error. It means the DTLS client handshake with the camera did not complete, so the session cannot be established.

Solutions

  1. Verify the PSK/credentials for the device are current
  2. Retry the dial — handshake failures over UDP are often transient
  3. Enable verbose DTLS logging to find the failing flight
  4. Check MTU/fragmentation issues on the path (handshake packets exceeding MTU)
Defensive patterns

Strategy: retry

Validate before calling

if psk == nil || len(psk) == 0 {
	return fmt.Errorf("missing DTLS PSK for device")
}

Try / catch

conn, err := dtls.DialDTLS(ctx, uid, psk)
if err != nil {
	if strings.Contains(err.Error(), "client handshake failed") {
		// transient UDP loss is common: re-dial with backoff
	}
	return err
}

Prevention

When it happens

Trigger: DialDTLS -> connect when NewDTLSServer/NewDTLSClient handshake fails: wrong PSK, handshake timeouts, UDP packet loss, or DTLS alert from the camera.

Common situations: PSK mismatch after a device password change; lossy WAN link dropping handshake flights; camera firmware with non-standard DTLS parameters; NAT rebinding mid-handshake.

Understand the failure class

Related errors


AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07). Data as JSON: /api/errors/852c4c5b14b530f4. Report an issue: GitHub.

Appendix: source

Thrown at pkg/tutk/dtls/conn_dtls.go:569

}

func (c *DTLSConn) discoDoneCC51() error {
	_, err := c.WriteAndWait(c.msgDiscoCC51(2, c.ticket, false), func(res []byte) bool {
		if len(res) < packetSizeCC51 || string(res[:2]) != magicCC51 {
			return false
		}
		cmd := binary.LittleEndian.Uint16(res[4:])
		dir := binary.LittleEndian.Uint16(res[8:])
		seq := binary.LittleEndian.Uint16(res[12:])
		return cmd == cmdDiscoCC51 && dir == 0xFFFF && seq == 3
	})
	return err
}

func (c *DTLSConn) connect() error {
	conn, err := NewDTLSClient(c.ctx, iotcChannelMain, c.addr, c.WriteDTLS, c.clientBuf, c.psk)
	if err != nil {
		return fmt.Errorf("dtls: client handshake failed: %w", err)
	}

	c.mu.Lock()
	c.clientConn = conn
	c.mu.Unlock()

	if c.verbose {
		fmt.Printf("[DTLS] Client handshake complete on channel %d\n", iotcChannelMain)
	}

	return nil
}

func (c *DTLSConn) worker() {
	defer c.wg.Done()

	buf := make([]byte, 2048)

View on GitHub (pinned to c245815e75)