Skip to content

hysteria, hysteria2: apply desired socket buffer sizes on obfuscated server listeners - #20

Open
realyxl wants to merge 2 commits into
SagerNet:devfrom
realyxl:hysteria2-server-obfs-socket-buffer
Open

realyxl wants to merge 2 commits into
SagerNet:devfrom
realyxl:hysteria2-server-obfs-socket-buffer

Conversation

@realyxl

@realyxl realyxl commented Sep 13, 2026 •

Copy link
Copy Markdown

Problem

When an obfuscator is enabled on the server side (Hysteria2 salamander/gecko, Hysteria v1 xplus), the listening UDP socket keeps the kernel default buffers (net.core.rmem_default / wmem_default, 208 KiB on Debian) instead of the 8 MiB that quic-go is meant to configure. Under bursty traffic the receive buffer overflows in the kernel and packets are dropped before quic-go ever sees them.

Measured on sing-box 1.14.0 (Debian 13, net.core.rmem_max = 16777216), hysteria2 inbound with salamander: ss -ulnm shows rb212992 tb212992 for the listener, and the socket drop counter (d in ss -m, matching UdpRcvbufErrors) grew by 1 500–7 000 during each 10 s, ~450 Mbit/s download test.

Root cause

Service.Start wraps the raw *net.UDPConn with the obfuscator before passing it to quic-go. The wrappers only expose ReadFrom/WriteTo/Upstream, so quic-go cannot reach the file descriptor. Since sagernet/quic-go a7bffd4a (v0.61.0-sing-box-mod.2), wrapConn probes syscall.Conn only and otherwise returns basicConn without touching any socket option — by design: "the socket behind a non-syscall conn is expected to have been configured by its owner".

The owner is sing-quic. The client side already handles this: hysteria2/client.go and hysteria/client.go call qtls.SetDesiredBufferSizes(rawConn) whenever obfs is enabled, before wrapping. The server side never did.

Fix

Call qtls.SetDesiredBufferSizes on the raw listener conn before wrapping it, under the same condition as the client:

  • hysteria2.Service.Start and startWithRealm (PunchPacketConn already forwards SetReadBuffer/SetWriteBuffer)
  • hysteria.Service.Start (xplus)

Listeners without obfs are unchanged: quic-go keeps configuring them itself through the file descriptor. GSO/ECN/batch I/O remain unavailable with obfs as before; this only restores the buffer sizing.

Verification

Same host, three temporary hysteria2 listeners, ss -ulnm:

listener rb tb
sing-box 1.14.0 + salamander (current) 212992 212992
sing-box 1.14.0 built with this branch + salamander 16777216 16777216
sing-box 1.14.0, no obfs (quic-go fd path) 16777216 16777216

With the patch an obfuscated listener ends up with exactly the same socket buffers as a non-obfuscated one.

Supersedes #17, which added SetReadBuffer/SetWriteBuffer to the wrapper types; that stopped having any effect once quic-go stopped probing those methods.

nekohasekai and others added 2 commits September 4, 2026 21:53
…server listeners

The obfuscator wrappers (salamander/gecko for hysteria2, xplus for
hysteria) hide the file descriptor from quic-go, and since
sagernet/quic-go a7bffd4a quic-go no longer configures sockets it cannot
reach through syscall.Conn. The listening socket therefore keeps the
kernel default buffers (net.core.rmem_default, 208 KiB on Debian) and
drops packets under bursty load.

The client side already compensates by calling qtls.SetDesiredBufferSizes
on the raw conn before wrapping it; do the same for the hysteria2 (plain
and realm) and hysteria xplus server listeners, so they end up with the
same 8 MiB buffers as a non-obfuscated listener.
@realyxl
realyxl force-pushed the hysteria2-server-obfs-socket-buffer branch from 99ce6aa to 4fd0de4 Compare September 13, 2026 14:40
@nekohasekai
nekohasekai force-pushed the dev branch 2 times, most recently from 0c33e6f to 4f371c8 Compare September 24, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants