fatedier/frp · error

IPv4 zone is forbidden

Error message

IPv4 zone is forbidden

What it means

The v2 binary UDP address decoder refuses a non-zero zone length when the address family is IPv4. IPv6 addresses may carry a zone (scope) string such as eth0, but an IPv4 address with a zone is meaningless, so the codec treats it as a malformed packet. This is a strict validation that catches encoder bugs and hostile/malformed input.

Source

Thrown at pkg/msg/udp_binary.go:212

		ipLen = net.IPv6len
	default:
		return nil, offset, fmt.Errorf("unknown address family %d", family)
	}
	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()

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. When encoding IPv4 addresses, always write zone length 0 and omit the zone string.
  2. Prefer the library's own EncodeUDPPacketBinary rather than hand-rolling the encoding.
  3. If fuzzing, register this input as an expected rejection rather than a crash.
  4. Close the connection on receipt — the peer's encoder is non-conformant.

Example fix

// before (hand-rolled encoder): always writes zone
writeZone(addr.Zone) // writes "eth0" even for IPv4

// after
if addr.IP.To4() != nil && addr.Zone != "" {
	return errors.New("IPv4 address cannot carry a zone")
}
writeZone(addr.Zone) // empty for IPv4
Defensive patterns

Strategy: validation

Validate before calling

// before sending a v2 binary UDP packet, normalize the address
if ip4 := addr.IP.To4(); ip4 != nil {
	addr.Zone = "" // IPv4 must not carry a zone
}

Try / catch

if _, err := rw.ReadMsg(); err != nil {
	if strings.Contains(err.Error(), "IPv4 zone is forbidden") {
		conn.Close() // peer encoder is non-conformant
	}
}

Prevention

When it happens

Trigger: DecodeUDPPacketBinary receives a frame with family=4 and a zone length > 0. Typically from a custom or buggy encoder that always writes a zone, or from fuzzed/garbage data hitting the decoder.

Common situations: Custom client code that builds binary UDP packets and reuses the IPv6 layout for IPv4; a fuzzing corpus exercising the decoder; a protocol-level test fixture authored with a wrong zone length.

Understand the failure class

Related errors


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