fatedier/frp · error

received JSON UDP packet after binary codec negotiation

Error message

received JSON UDP packet after binary codec negotiation

What it means

V2BinaryUDPPacketReadWriter.ReadMsg received a JSON-encoded UDP packet (message type 13, V2TypeUDPPacket) after both sides had already negotiated the binary UDP packet codec. With the binary codec active, all UDP packets must arrive as type 19 (V2TypeUDPPacketBinary); a type-13 packet means the peer ignored or missed the codec negotiation. The connection is left in an inconsistent state, so the error should be treated as fatal.

Source

Thrown at pkg/msg/udp_binary.go:238

type V2BinaryUDPPacketReadWriter struct {
	conn *wire.Conn
}

func NewV2BinaryUDPPacketReadWriter(rw io.ReadWriter) *V2BinaryUDPPacketReadWriter {
	return &V2BinaryUDPPacketReadWriter{conn: wire.NewConn(rw)}
}

func (rw *V2BinaryUDPPacketReadWriter) ReadMsg() (Message, error) {
	frame, err := rw.conn.ReadFrame()
	if err != nil {
		return nil, err
	}
	if isV2MessageType(frame, V2TypeUDPPacketBinary) {
		return decodeV2BinaryUDPPacketFrame(frame)
	}
	if isV2MessageType(frame, V2TypeUDPPacket) {
		return nil, fmt.Errorf("received JSON UDP packet after binary codec negotiation")
	}
	return DecodeV2MessageFrame(frame)
}

func (rw *V2BinaryUDPPacketReadWriter) ReadMsgInto(out Message) error {
	frame, err := rw.conn.ReadFrame()
	if err != nil {
		return err
	}
	if packetOut, ok := out.(*UDPPacket); ok {
		if !isV2MessageType(frame, V2TypeUDPPacketBinary) {
			return unexpectedV2UDPPacketType(frame)
		}
		packet, err := decodeV2BinaryUDPPacketFrame(frame)
		if err != nil {
			return err
		}
		*packetOut = *packet

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Align versions: run the same frp release on both frpc and frps.
  2. Check that udpPacketCodec = "binary" (or equivalent) is consistently configured on both ends of the tunnel, including visitor configs.
  3. If mixed versions are unavoidable, unset the binary UDP packet codec so both sides use the JSON codec.
  4. Restart the work connection after fixing config; the codec is chosen at connection setup.

Example fix

# before
# frpc.toml sets binary UDP codec, frps is an older release

# after
# upgrade frps to the matching release, or on frpc.toml remove:
# udpPacketCodec = "binary"
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure both ends agree before establishing the work connection
if udpPacketCodec != "" && wireProtocol != wire.ProtocolV2 {
	return errors.New("binary UDP codec requires v2 wire protocol on both ends")
}

Try / catch

if _, err := rw.ReadMsg(); err != nil {
	if strings.Contains(err.Error(), "after binary codec negotiation") {
		// peer did not adopt the binary codec: fix version/config on the peer, then reconnect
		conn.Close()
	}
}

Prevention

When it happens

Trigger: ReadMsg() on a work connection where frps and frpc disagreed on udpPacketCodec — e.g. the client sent binary packets while the server (older version, or config not applying) still emits JSON UDPPacket frames.

Common situations: frps upgraded but frpc not (binary codec exists only in newer versions); udpPacketCodec setting not propagated to the visitor side of an frp tunnel; a proxy between the peers strips or rewrites the negotiation.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/b1056f78d7ef6a5d. Report an issue: GitHub.