OpenNHP/opennhp · error
relay source address
Error message
%s relay source address
What it means
HandleRelayForward validates the claimed real client address carried inside a relayed packet. validateRelaySourceAddr returns a reason (e.g. nil IP, disallowed private/reserved source) and the handler wraps it into this error, rejecting the forward from the relay. It prevents relays from spoofing client source addresses.
Solutions
- Check the relay is stamping the real public client IP into SourceAddr.Ip before forwarding
- If relays legitimately sit on private networks, enable the allowPrivateRelaySource option on the server
- Verify the relay software version matches the server's expected relay message format
- Inspect the warning log line 'rejecting %s from relay' for the exact reason string
Example fix
// before (relay on private net, rejected) // server: allowPrivateRelaySource = false // after cfg.AllowPrivateRelaySource.Store(true) // only if relays are trusted and on private links
Defensive patterns
Strategy: validation
Validate before calling
ip := net.ParseIP(sourceAddrIp)
if ip == nil || (ip.IsPrivate() && !allowPrivateRelaySource) {
return fmt.Errorf("relay source address %q will be rejected", sourceAddrIp)
} Type guard
func isRelaySourceAllowed(ipStr string, allowPrivate bool) bool {
ip := net.ParseIP(ipStr)
if ip == nil { return false }
if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() { return allowPrivate }
return true
} Prevention
- Run relays with correct public source-IP stamping
- Enable allowPrivateRelaySource only for trusted private-network relays
- Monitor 'rejecting %s from relay' warnings for misbehaving relays
When it happens
Trigger: A relay forwards a packet whose rlyMsg.SourceAddr.Ip fails net.ParseIP, is a private/loopback/reserved address while s.allowPrivateRelaySource is false, or has an invalid port (0).
Common situations: Relay deployed behind NAT reporting a private client IP; misconfigured relay stamping empty or malformed source IPs; operator not setting allowPrivateRelaySource when relays run on a private network.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- inner packet too large
- 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]]
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/ed1baee2a32d13f0.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/msghandler.go:805
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",
relayAddrStr, realAddr, len(innerBytes))
// Allocate a pool packet for the inner bytes.
innerPkt := s.device.AllocatePoolPacket()
if len(innerBytes) > len(innerPkt.Buf) {
s.device.ReleasePoolPacket(innerPkt)
log.Warning("server-relay[HandleRelayForward] inner packet too large (%d bytes)", len(innerBytes))
return fmt.Errorf("inner packet too large")
}
copy(innerPkt.Buf[:], innerBytes)
innerPkt.Content = innerPkt.Buf[:len(innerBytes)]
// Run standard RecvPrecheck on the inner packet.View on GitHub (pinned to 6e04ca5ff0)