fatedier/frp · error
UDP packet codec %q requires wire protocol v2
Error message
UDP packet codec %q requires wire protocol v2
What it means
NewUDPPacketReadWriter refuses a non-empty udpPacketCodec when the wire protocol is v1 ("" or "v1"). The binary UDP packet codec is implemented on top of v2 message framing (2-byte type IDs), which v1 framing cannot carry, so the combination is rejected up front instead of failing per-packet later.
Source
Thrown at pkg/msg/udp_binary.go:323
return fmt.Errorf("unexpected frame type %d, want %d", frame.Type, wire.FrameTypeMessage)
}
if len(frame.Payload) < 2 {
return fmt.Errorf("message frame payload too short")
}
typeID := binary.BigEndian.Uint16(frame.Payload[:2])
if typeID == V2TypeUDPPacket {
return fmt.Errorf("received JSON UDP packet after binary codec negotiation")
}
return fmt.Errorf("unexpected message type %d, want %d", typeID, V2TypeUDPPacketBinary)
}
// NewUDPPacketReadWriter selects the negotiated packet codec without changing
// the framing or codecs used by non-UDP messages on the work connection.
func NewUDPPacketReadWriter(rw io.ReadWriter, wireProtocol, udpPacketCodec string) (ReadWriter, error) {
switch wireProtocol {
case "", wire.ProtocolV1:
if udpPacketCodec != "" {
return nil, fmt.Errorf("UDP packet codec %q requires wire protocol v2", udpPacketCodec)
}
return NewV1ReadWriter(rw), nil
case wire.ProtocolV2:
switch udpPacketCodec {
case "":
return NewV2ReadWriter(rw), nil
case wire.UDPPacketCodecBinary:
return NewV2BinaryUDPPacketReadWriter(rw), nil
default:
return nil, fmt.Errorf("unsupported UDP packet codec %q", udpPacketCodec)
}
default:
return nil, fmt.Errorf("unsupported wire protocol %q", wireProtocol)
}
}
View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Set the wire protocol to v2 wherever you set the UDP packet codec: NewUDPPacketReadWriter(rw, wire.ProtocolV2, wire.UDPPacketCodecBinary).
- In TOML config, ensure the transport/protocol selection enabling v2 matches on both frpc and frps.
- Or drop udpPacketCodec to keep the v1 JSON codec.
Example fix
// before rw, err := msg.NewUDPPacketReadWriter(conn, wire.ProtocolV1, wire.UDPPacketCodecBinary) // error // after rw, err := msg.NewUDPPacketReadWriter(conn, wire.ProtocolV2, wire.UDPPacketCodecBinary)
Defensive patterns
Strategy: validation
Validate before calling
if udpPacketCodec != "" && wireProtocol != wire.ProtocolV2 {
return fmt.Errorf("UDP packet codec %q requires wire protocol v2", udpPacketCodec)
} Prevention
- Always set wireProtocol and udpPacketCodec together from one validated config struct.
- Use the wire package constants instead of string literals.
When it happens
Trigger: Calling NewUDPPacketReadWriter(rw, "v1", "binary") or NewUDPPacketReadWriter(rw, "", wire.UDPPacketCodecBinary) — i.e. config leaves wireProtocol at default v1 while setting udpPacketCodec to binary.
Common situations: Enabling the binary UDP codec in frpc.toml without also setting the v2 wire protocol; default config templates that omit wireProtocol; upgrading configs piecemeal.
Related errors
- received JSON UDP packet after binary codec negotiation
- invalid transport.protocol, optional values are %v
- truncated zone
- unexpected frame type %d, want %d
- message frame payload too short
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/016c05560bc43f3c.
Report an issue: GitHub.