fatedier/frp · error
get natHoleRespMsg error: invalid message type
Error message
get natHoleRespMsg error: invalid message type
What it means
Returned by nathole.PreCheck (pkg/nathole/nathole.go) when transporter.Do returns a message that is not *msg.NatHoleResp even though the request registered msg.TypeNameNatHoleResp as the expected response. The transport is supposed to correlate replies by transaction and expected type; a different concrete type indicates a protocol or transport-layer inconsistency rather than a user config problem.
Source
Thrown at pkg/nathole/nathole.go:107
ctx context.Context, transporter transport.MessageTransporter,
proxyName string, timeout time.Duration,
) error {
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
var natHoleRespMsg *msg.NatHoleResp
transactionID := NewTransactionID()
m, err := transporter.Do(timeoutCtx, &msg.NatHoleVisitor{
TransactionID: transactionID,
ProxyName: proxyName,
PreCheck: true,
}, transactionID, msg.TypeNameNatHoleResp)
if err != nil {
return fmt.Errorf("get natHoleRespMsg error: %v", err)
}
mm, ok := m.(*msg.NatHoleResp)
if !ok {
return fmt.Errorf("get natHoleRespMsg error: invalid message type")
}
natHoleRespMsg = mm
if natHoleRespMsg.Error != "" {
return fmt.Errorf("%s", natHoleRespMsg.Error)
}
return nil
}
// Prepare is used to do some preparation work before penetration.
func Prepare(stunServers []string, opts PrepareOptions) (*PrepareResult, error) {
// discover for Nat type
addrs, localAddr, err := Discover(stunServers, "")
if err != nil {
return nil, fmt.Errorf("discover error: %v", err)
}
if len(addrs) < 2 {
return nil, fmt.Errorf("discover error: not enough addresses")View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Use the stock transport implementation provided by frp rather than a custom MessageTransporter
- Align frpc and frps versions
- In mock/test transporters, make Do return exactly *msg.NatHoleResp for NatHoleVisitor requests
- Log the concrete type returned by Do to see what actually arrived
Example fix
// before (mock transporter returning wrong type)
func (m *mockTP) Do(...) (msg.Message, error) { return &msg.Pong{}, nil }
// after
func (m *mockTP) Do(...) (msg.Message, error) { return &msg.NatHoleResp{}, nil } Defensive patterns
Strategy: type-guard
Type guard
func isNatHoleResp(m msg.Message) bool {
_, ok := m.(*msg.NatHoleResp)
return ok
} Try / catch
m, err := tp.Do(ctx, req, tid, msg.TypeNameNatHoleResp)
if err != nil {
return err
}
if !isNatHoleResp(m) {
// transport delivered an unexpected type: treat as protocol violation, reset transport
} Prevention
- Use frp's stock MessageTransporter; honor expected-type in custom ones
- Mock transporters must return *msg.NatHoleResp for NatHoleVisitor
- Keep peer versions aligned
When it happens
Trigger: nathole.PreCheck where the message dispatcher delivers an unexpected type for the transaction — e.g. a custom MessageTransporter implementation with lax type matching, a version-skewed peer sending a different response shape, or a deserialization path that decodes into the wrong struct.
Common situations: Custom transporters that do not honor the expected-response-type argument; frpc/frps version mismatch changing response encoding; tests with mock transporters returning generic messages.
Related errors
- invalid transport.protocol, optional values are %v
- %s cannot be empty
- %s is too long: length %d exceeds maximum %d
- %s must be valid UTF-8
- unexpected message type %s, want %s
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/43422bcb67ca907a.
Report an issue: GitHub.