OpenNHP/opennhp · error
missing source address
Error message
missing source address
What it means
HandleRelayForward rejected a relayed NHP_RLY message because its JSON RelayForwardMsg carried no SourceAddr. Without the real client's IP/port the server cannot attribute or validate the forwarded inner packet, so it drops the message with "missing source address".
Solutions
- Update the relay to a version that always populates RelayForwardMsg.SourceAddr with the real client's IP/port.
- Confirm the JSON field name/tag matches between relay and server (go vet / compare common.RelayForwardMsg on both sides).
- Check that the agent-to-relay path preserves the original source address metadata.
- If a hostile relay is suspected, verify relay identity/allow-lists and drop traffic from it.
Example fix
// before: relay omits source address
fwd := common.RelayForwardMsg{InnerPacket: b64}
// after: relay always sets the real client address
fwd := common.RelayForwardMsg{SourceAddr: &common.SourceAddr{Ip: clientIP, Port: clientPort}, InnerPacket: b64} Defensive patterns
Strategy: type-guard
Validate before calling
var fwd common.RelayForwardMsg
if err := json.Unmarshal(body, &fwd); err != nil { return err }
if fwd.SourceAddr == nil || net.ParseIP(fwd.SourceAddr.Ip) == nil || fwd.SourceAddr.Port <= 0 {
return fmt.Errorf("relay forward missing/invalid source address")
} Type guard
func hasValidSourceAddr(m *common.RelayForwardMsg) bool {
return m != nil && m.SourceAddr != nil &&
net.ParseIP(m.SourceAddr.Ip) != nil &&
m.SourceAddr.Port > 0 && m.SourceAddr.Port < 65536
} Try / catch
if err := server.HandleRelayForward(ppd); err != nil && err.Error() == "missing source address" {
log.Warn("relay %s sent forward without SourceAddr — check relay version", ppd.ConnData.RemoteAddr)
// drop packet; optionally alert on the relay
} Prevention
- Keep relay and server builds version-matched so RelayForwardMsg field tags agree.
- Unit-test the relay forwarder with a fixture containing SourceAddr.
- Alert/log when a relay repeatedly sends forwards lacking SourceAddr — indicates a broken or hostile relay.
When it happens
Trigger: A relay forwards a JSON body whose `sourceAddr` field is null/omitted — either the relay's forwarder code didn't populate it, the agent's original packet lacked it, or a field-name mismatch (wrong JSON tag) caused unmarshal to leave the pointer nil.
Common situations: Mismatched relay/server versions where the relay builds RelayForwardMsg with an older field name; a misbehaving or malicious relay sending empty forward frames; agents talking through a relay that lost the original connection metadata.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- relay: privateKeyBase64 must be set in config
- relay: no upstream configured; add at least one [[Servers]]…
- relay: server # missing publicKeyBase64
- relay: server # (fingerprint ) has no [[Servers.Instances]]
- relay: server # instance # missing host
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/649dfe1a4d43bcdd.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/msghandler.go:792
// Noise pipeline. The relay's identity has already been validated by
// validatePeer as part of the standard decryption flow.
//
// The message body is a JSON-encoded RelayForwardMsg containing:
// - SourceAddr: the real client's IP/port
// - InnerPacket: base64-encoded inner NHP packet (encrypted by agent)
//
// The inner packet is injected into the standard pipeline as if the agent
// had connected directly.
func (s *UdpServer) HandleRelayForward(ppd *core.PacketParserData) error {
var rlyMsg common.RelayForwardMsg
if err := json.Unmarshal(ppd.BodyMessage, &rlyMsg); err != nil {
log.Error("server-relay[HandleRelayForward] failed to parse RelayForwardMsg: %v", err)
return err
}
if rlyMsg.SourceAddr == nil {
log.Error("server-relay[HandleRelayForward] missing source address")
return fmt.Errorf("missing source address")
}
innerBytes, err := base64.StdEncoding.DecodeString(rlyMsg.InnerPacket)
if err != nil {
log.Error("server-relay[HandleRelayForward] failed to decode inner packet: %v", err)
return err
}
realIP := net.ParseIP(rlyMsg.SourceAddr.Ip)
if reason := validateRelaySourceAddr(realIP, rlyMsg.SourceAddr.Port, s.allowPrivateRelaySource.Load()); reason != "" {
log.Warning("server-relay[HandleRelayForward] rejecting %s from relay %s: %s:%d",
reason, ppd.ConnData.RemoteAddr.String(), rlyMsg.SourceAddr.Ip, rlyMsg.SourceAddr.Port)
return fmt.Errorf("%s relay source address", reason)
}
realAddr := &net.UDPAddr{IP: realIP, Port: rlyMsg.SourceAddr.Port}
relayAddrStr := ppd.ConnData.RemoteAddr.String()
log.Info("server-relay[HandleRelayForward] from relay %s, real client %s, inner %d bytes",View on GitHub (pinned to 6e04ca5ff0)