fatedier/frp · error

zone is not valid UTF-8

Error message

zone is not valid UTF-8

What it means

The v2 binary UDP address decoder validates the zone bytes with utf8.Valid before converting them into net.UDPAddr.Zone. IPv6 zone identifiers are conventionally interface names, and Go's net package expects a valid string, so invalid UTF-8 is rejected as malformed input rather than propagated. This guards downstream string handling from binary garbage.

Source

Thrown at pkg/msg/udp_binary.go:215

	}
	if len(body)-offset < ipLen+3 {
		return nil, offset, fmt.Errorf("truncated address")
	}
	ip := append(net.IP(nil), body[offset:offset+ipLen]...)
	offset += ipLen
	port := binary.BigEndian.Uint16(body[offset : offset+2])
	offset += 2
	zoneLen := int(body[offset])
	offset++
	if len(body)-offset < zoneLen {
		return nil, offset, fmt.Errorf("truncated zone")
	}
	zoneBytes := body[offset : offset+zoneLen]
	if family == 4 && zoneLen != 0 {
		return nil, offset, fmt.Errorf("IPv4 zone is forbidden")
	}
	if !utf8.Valid(zoneBytes) {
		return nil, offset, fmt.Errorf("zone is not valid UTF-8")
	}
	offset += zoneLen
	return &net.UDPAddr{IP: ip, Port: int(port), Zone: string(zoneBytes)}, offset, nil
}

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
	}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Close and re-establish the connection — the frame stream is likely desynchronized or hostile.
  2. Use the library's EncodeUDPPacketBinary for sending so zones are always valid UTF-8.
  3. Verify both peers run matching frp versions.
  4. When fuzzing, treat this as a handled rejection.
Defensive patterns

Strategy: try-catch

Try / catch

if _, err := rw.ReadMsg(); err != nil {
	if strings.Contains(err.Error(), "not valid UTF-8") {
		conn.Close() // garbage or desynchronized stream
	}
}

Prevention

When it happens

Trigger: DecodeUDPPacketBinary receives an address whose zone bytes fail utf8.Valid — fuzzed input, a desynchronized stream being decoded as an address, or a custom encoder stuffing raw bytes into the zone field.

Common situations: Stream desynchronization after an earlier truncation error (error 264) makes random bytes land in the zone field; fuzzing corpora; hostile peers sending garbage frames.

Related errors


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