#3211·mihomo

[Bug] REJECT outbound silently drops UDP packets (nopPacketConn) instead of rejecting, behaving identically to REJECT-DROP

Author: 4ekuct25Created Sep 14, 2026Updated Sep 15, 2026

Verify steps

  • Ensure you are using the latest version of Mihomo or Mihomo Alpha from this repository.
  • I have searched on the issue tracker for a related issue.
  • I have read the documentation and was unable to solve the issue.
  • This is an issue of the Mihomo core per se, not to the derivatives of Mihomo, like OpenMihomo or KoolMihomo.

Mihomo version

Mihomo v1.19.27 / Alpha (all current versions)

What OS are you seeing the problem on?

Linux, macOS

Mihomo config

yaml
rules:
  - AND,((DST-PORT,443),(NETWORK,UDP)),REJECT
  # or
  - RULE-SET,quic,REJECT

Mihomo log

bash
[Rule] 192.168.1.100:54321 --> 157.240.253.174:443 match And(DstPort(443), Network(UDP)) using REJECT

Description

In Mihomo, rules matching UDP traffic with target REJECT are commonly used to block QUIC / HTTP/3 traffic so that clients (web browsers, mobile apps like Instagram, YouTube, etc.) instantly fall back to TCP (HTTP/2).

However, in adapter/outbound/reject.go:

go
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, ...) (C.PacketConn, error) {
	return &nopPacketConn{}, nil
}

type nopPacketConn struct{}

func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
	return len(b), nil
}

func (npc *nopPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
	return 0, nil, io.EOF
}

And in adapter/outbound/reject_drop.go:

go
func (r *RejectDrop) ListenPacketContext(ctx context.Context, metadata *C.Metadata, ...) (C.PacketConn, error) {
	return &nopPacketConn{}, nil
}

Both REJECT and REJECT-DROP use the exact same nopPacketConn, which accepts UDP packets and discards them silently (WriteTo returns len(b), nil).

The Problem:

  • For TCP, REJECT actively resets/closes the connection, giving the client an immediate connection reset (RST / ECONNRESET).
  • For UDP, REJECT behaves as a silent black hole (100% identical to REJECT-DROP).
  • When a client sends UDP packets (e.g. QUIC ClientHello to port 443), it receives no response, no ICMP Port Unreachable, and no error.
  • As a result, the client's network stack waits for the UDP socket timeout (typically 2 to 5 seconds per connection attempt) before falling back to TCP. In media-heavy apps (like Instagram Reels or video streaming), every new media segment initiates a QUIC attempt, causing visible 2–5 second freezes and buffering delays.

Expected Behavior:

REJECT should actively reject UDP packets rather than silently dropping them:

  • When traffic is routed to REJECT (e.g. in TUN mode or TPROXY), reply with ICMP Port Unreachable (IPv4: Type 3, Code 3 / IPv6: Type 1, Code 4) back to the client, or actively signal connection refusal.
  • This would differentiate REJECT from REJECT-DROP for UDP (matching their TCP distinction) and allow instant (<5 ms) TCP fallback when blocking QUIC.