OpenNHP/opennhp · error

relay internal error

Error message

relay internal error

What it means

handleRelay responds with HTTP 502 'relay internal error' when json.Marshal of the RelayForwardMsg fails. Because the message contains only a base64 string and plain fields, this virtually always indicates an internal invariant violation rather than caller input — but it is surfaced as a 502 since the forward cannot be built.

Solutions

  1. Inspect the logged error ('failed to marshal RelayForwardMsg') to find the offending field
  2. Fix the struct so all fields are JSON-serializable (base64/encode binary data into strings)
  3. Add a unit test marshaling RelayForwardMsg with representative data to catch regressions
  4. Retry the request only after a relay fix; callers cannot remedy this themselves

Example fix

// before: new field of unsupported type breaks Marshal
type RelayForwardMsg struct {
    InnerPacket string
    OnDone chan struct{} // not JSON-serializable
}
// after: keep struct JSON-safe
type RelayForwardMsg struct {
    InnerPacket string
    Done bool
}
Defensive patterns

Strategy: try-catch

Try / catch

if resp.StatusCode == http.StatusBadGateway {
    b, _ := io.ReadAll(resp.Body)
    if strings.Contains(string(b), "relay internal error") {
        return fmt.Errorf("relay failed to build forward message; report with relay logs (%s)", relayVersion)
    }
}

Prevention

When it happens

Trigger: json.Marshal returning an error for the RelayForwardMsg struct — in practice only if a field added to the struct is unsupported by encoding/json (e.g. a channel, func, or cyclic value introduced by a code change).

Common situations: Post-refactor regressions where a new RelayForwardMsg field is of an unmarshalable type; custom MarshalJSON methods returning errors; not triggered by normal client input.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/a7a7c1de6c6831e0. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/relay/relay.go:1075

			if len(waiters) == 0 {
				delete(inst.pendingRequests, innerCounter)
			}
		}
		inst.pendingMu.Unlock()
	}()

	// Construct RelayForwardMsg (standard JSON body).
	rlyMsg := &common.RelayForwardMsg{
		SourceAddr: &common.NetAddress{
			Ip:   realAddr.IP.String(),
			Port: realAddr.Port,
		},
		InnerPacket: base64.StdEncoding.EncodeToString(innerPacket),
	}
	msgBytes, err := json.Marshal(rlyMsg)
	if err != nil {
		log.Error("[Relay] failed to marshal RelayForwardMsg: %v", err)
		http.Error(w, "relay internal error", http.StatusBadGateway)
		return
	}

	// Send the NHP_RLY envelope to the chosen instance.
	trxId := rs.device.NextCounterIndex()
	md := &core.MsgData{
		RemoteAddr:    inst.addr,
		HeaderType:    core.NHP_RLY,
		CipherScheme:  rs.config.CipherScheme,
		TransactionId: trxId,
		Message:       msgBytes,
		PeerPk:        cr.pubKey,
	}

	udpTimeout := rs.config.UDPTimeoutMs
	if udpTimeout <= 0 {
		udpTimeout = defaultUDPTimeoutMs
	}

View on GitHub (pinned to 6e04ca5ff0)