fatedier/frp · error

xtcp connection of [%s] auth failed

Error message

xtcp connection of [%s] auth failed

What it means

Thrown in the nathole Controller's visitor-message handler (pkg/nathole/controller.go) when the visitor's SignKey does not match the expected auth key derived from the client's secret key and timestamp (util.GetAuthKey(clientCfg.sk, m.Timestamp), compared in constant time). The xtcp visitor must share the same secret key (sk) as the xtcp proxy's client; otherwise authentication fails and the error is returned to the visitor in the NatHoleResp.

Source

Thrown at pkg/nathole/controller.go:190

		sid:                sid,
		visitorMsg:         m,
		visitorTransporter: transporter,
		notifyCh:           make(chan struct{}, 1),
	}
	var (
		clientCfg *ClientCfg
		ok        bool
	)
	err := func() error {
		c.mu.Lock()
		defer c.mu.Unlock()

		clientCfg, ok = c.clientCfgs[m.ProxyName]
		if !ok {
			return fmt.Errorf("xtcp server for [%s] doesn't exist", m.ProxyName)
		}
		if !util.ConstantTimeEqString(m.SignKey, util.GetAuthKey(clientCfg.sk, m.Timestamp)) {
			return fmt.Errorf("xtcp connection of [%s] auth failed", m.ProxyName)
		}
		c.sessions[sid] = session
		return nil
	}()
	if err != nil {
		log.Warnf("handle visitorMsg error: %v", err)
		_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error()))
		return
	}
	log.Tracef("handle visitor message, sid [%s], server name: %s", sid, m.ProxyName)

	defer func() {
		c.mu.Lock()
		defer c.mu.Unlock()
		delete(c.sessions, sid)
	}()

	if err := errors.PanicToError(func() {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Set the same sk value in the visitor config as in the client's xtcp proxy config
  2. After rotating secrets, update and restart both client and visitor
  3. Check for trailing whitespace/newlines when pasting sk into config files
  4. Verify the timestamp is not wildly off (clock skew affects key derivation inputs)

Example fix

# before (visitor)
[[visitors]]
name = "p2p-v"
type = "xtcp"
serverName = "p2p"
sk = "old-secret"

# after
[[visitors]]
name = "p2p-v"
type = "xtcp"
serverName = "p2p"
sk = "same-secret-as-client"
Defensive patterns

Strategy: validation

Try / catch

if err := nathole.PreCheck(ctx, tp, proxyName, timeout); err != nil {
    if strings.Contains(err.Error(), "auth failed") {
        // fix sk in visitor config to match client, then retry
    }
}

Prevention

When it happens

Trigger: A NatHoleVisitor message whose ProxyName resolves to a registered client, but whose sk differs from the client's configured secret. Common with copy-pasted or rotated sk values, or when the visitor config omits sk entirely (falling back to a different default).

Common situations: sk in the visitor block does not match sk on the client's xtcp proxy; the secret was rotated on one side only; sk omitted from the visitor config so an empty/default value is signed with.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/b0a87e55134e6fbd. Report an issue: GitHub.