XTLS/Xray-core · critical

hopIntervalMax < hopIntervalMin

Error message

hopIntervalMax < hopIntervalMin

What it means

NewPacketConn for hysteria UDP port-hopping panics because hopIntervalMax < hopIntervalMin after defaults are applied. Both intervals default when zero and must each be >= 5s; if a caller passes e.g. min=30s max=10s, the max<min invariant trips and the constructor panics with this literal message.

Source

Thrown at transport/internet/hysteria/udphop/conn.go:66

func NewUDPHopPacketConn(addrs []net.Addr, hopIntervalMin time.Duration, hopIntervalMax time.Duration, listenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error), currentConn net.PacketConn, addrIndex int) net.PacketConn {
	if len(addrs) == 0 {
		panic("len(addrs) == 0")
	}
	if hopIntervalMin == 0 {
		hopIntervalMin = defaultHopInterval
	}
	if hopIntervalMax == 0 {
		hopIntervalMax = defaultHopInterval
	}
	if hopIntervalMin < 5*time.Second {
		panic("hopIntervalMin < 5*time.Second")
	}
	if hopIntervalMax < 5*time.Second {
		panic("hopIntervalMax < 5*time.Second")
	}
	if hopIntervalMax < hopIntervalMin {
		panic("hopIntervalMax < hopIntervalMin")
	}
	if listenUDPFunc == nil {
		panic("listenUDPFunc is nil")
	}
	hConn := &UdpHopPacketConn{
		Addrs:          addrs,
		HopIntervalMin: hopIntervalMin,
		HopIntervalMax: hopIntervalMax,
		ListenUDPFunc:  listenUDPFunc,
		prevConn:       nil,
		currentConn:    currentConn,
		addrIndex:      addrIndex,
		recvQueue:      make(chan *udpPacket, packetQueueSize),
		closeChan:      make(chan struct{}),
		bufPool: sync.Pool{
			New: func() interface{} {
				return make([]byte, udpBufferSize)
			},

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Set hopIntervalMax >= hopIntervalMin (both >= 5s), e.g. min 30s / max 60s.
  2. If you want a single fixed interval, pass the same value for both or leave both zero for defaults.
  3. Swap-check any config template: min comes first in the parameter list and in the config schema.

Example fix

// before
conn, err := udphop.NewPacketConn(addrs, 60*time.Second /*min*/, 30*time.Second /*max*/, ...)
// after
conn, err := udphop.NewPacketConn(addrs, 30*time.Second /*min*/, 60*time.Second /*max*/, ...)
Defensive patterns

Strategy: validation

Validate before calling

if hopIntervalMax < hopIntervalMin {
    return nil, fmt.Errorf("hop intervals invalid: max %v < min %v (both must be >= 5s)", hopIntervalMax, hopIntervalMin)
}
if hopIntervalMin < 5*time.Second || hopIntervalMax < 5*time.Second {
    return nil, errors.New("hop intervals must be >= 5s")
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        return nil, fmt.Errorf("udphop conn rejected (min=%v max=%v): %v", hopIntervalMin, hopIntervalMax, r)
    }
}()

Prevention

When it happens

Trigger: Constructing udphop.NewPacketConn with hopIntervalMin > hopIntervalMax (directly, or min set high with max left at a value below it); config parsed as {"hopInterval": ...} where min/max got swapped or one defaulted while the other was raised above 5–30s defaults.

Common situations: YAML/JSON fields hop_interval_min/max transcribed in the wrong order; raising min for stability while forgetting max defaults; unit tests constructing the conn directly with arbitrary intervals.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/496f64473a392729. Report an issue: GitHub.