Hysteria Version: v2.x (latest)
OS/Platform: Linux / OpenWrt (Kernel 6.1+, Architecture: ARM64 / Banana Pi R4)
Network Setup: IPv4-only GRE Tunnel (ip.gre) between Client and Server.
Describe the bug
When running the Hysteria2 client inside an IPv4-only GRE tunnel, the client fails to initialize and immediately terminates with a timeout error: connect error: timeout: no recent network activity.
This happens even when the server IP is explicitly configured as a static IPv4 address (e.g., 10.12.10.1), and despite configuring resolvePreference: 4 or trying to bind the client via bind: 10.12.10.2:0.
Standard tools like curl can communicate over the GRE tunnel flawlessly, but Hysteria2 fails because the Go runtime forces the creation of an AF_INET6 socket and uses IPv4-mapped IPv6 addresses (::ffff:10.12.10.1), which the Linux kernel's GRE virtual interface cannot properly route or encapsulate, leading to silent packet drops.
The Go standard library (net package) prefers dual-stack AF_INET6 sockets by default. When sending a packet to an IPv4 address, it uses IPv4-mapped IPv6 encapsulation. While physical interfaces (eth0, wan) handle this seamlessly via the kernel, Linux virtual GRE tunnels (ip.gre) are strictly stateless IPv4 structures. They do not accept AF_INET6 socket payloads natively if net.ipv6.bindv6only or specific routing conditions are met on embedded devices (like OpenWrt routers), causing the kernel to silently drop the outbound UDP handshake.
To Reproduce
Steps to reproduce
Establish a standard Linux ip.gre tunnel between two machines (IPv4-only).
Configure the Hysteria2 client to connect to the server's tunnel IPv4 address.
Run the client. It will hang for 5 seconds and crash with a timeout.
Expected behavior
A clear and concise description of what you expected to happen.
Logs
strace -e socket,bind hysteria client -c client.yaml reveals that Hysteria2 creates an AF_INET6 socket for outbound UDP traffic and binds to ::, ignoring the fact that the destination is a strict IPv4 GRE interface:
socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 3
socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 3
bind(3, {sa_family=AF_INET6, sin6_port=htons(0), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_scope_id=0}, 28) = 0
socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 6
bind(6, {sa_family=AF_INET6, sin6_port=htons(0), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_scope_id=0}, 28) = 0
// Critical part: Outbound QUIC/UDP socket is forced into IPv6
socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET6, sin6_port=htons(0), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::", &sin6_addr), sin6_scope_id=0}, 28) = 0
FATAL failed to initialize client {"error": "connect error: timeout: no recent network activity"}
Temporary Workaround
socat UDP6-LISTEN:8443,bind=[::1],reuseaddr,fork UDP4:10.12.10.1:8443 &
and change server to
server: "[::1]:8443"
Hysteria Version: v2.x (latest)
OS/Platform: Linux / OpenWrt (Kernel 6.1+, Architecture: ARM64 / Banana Pi R4)
Network Setup: IPv4-only GRE Tunnel (ip.gre) between Client and Server.
Describe the bug
When running the Hysteria2 client inside an IPv4-only GRE tunnel, the client fails to initialize and immediately terminates with a timeout error: connect error: timeout: no recent network activity.
This happens even when the server IP is explicitly configured as a static IPv4 address (e.g., 10.12.10.1), and despite configuring resolvePreference: 4 or trying to bind the client via bind: 10.12.10.2:0.
Standard tools like curl can communicate over the GRE tunnel flawlessly, but Hysteria2 fails because the Go runtime forces the creation of an AF_INET6 socket and uses IPv4-mapped IPv6 addresses (::ffff:10.12.10.1), which the Linux kernel's GRE virtual interface cannot properly route or encapsulate, leading to silent packet drops.
The Go standard library (net package) prefers dual-stack AF_INET6 sockets by default. When sending a packet to an IPv4 address, it uses IPv4-mapped IPv6 encapsulation. While physical interfaces (eth0, wan) handle this seamlessly via the kernel, Linux virtual GRE tunnels (ip.gre) are strictly stateless IPv4 structures. They do not accept AF_INET6 socket payloads natively if net.ipv6.bindv6only or specific routing conditions are met on embedded devices (like OpenWrt routers), causing the kernel to silently drop the outbound UDP handshake.
To Reproduce
Steps to reproduce
Establish a standard Linux ip.gre tunnel between two machines (IPv4-only).
Configure the Hysteria2 client to connect to the server's tunnel IPv4 address.
Run the client. It will hang for 5 seconds and crash with a timeout.
Expected behavior
A clear and concise description of what you expected to happen.
Logs
strace -e socket,bind hysteria client -c client.yaml reveals that Hysteria2 creates an AF_INET6 socket for outbound UDP traffic and binds to ::, ignoring the fact that the destination is a strict IPv4 GRE interface:
socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 3
socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 3
bind(3, {sa_family=AF_INET6, sin6_port=htons(0), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_scope_id=0}, 28) = 0
socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_TCP) = 6
bind(6, {sa_family=AF_INET6, sin6_port=htons(0), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_scope_id=0}, 28) = 0
// Critical part: Outbound QUIC/UDP socket is forced into IPv6
socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET6, sin6_port=htons(0), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::", &sin6_addr), sin6_scope_id=0}, 28) = 0
FATAL failed to initialize client {"error": "connect error: timeout: no recent network activity"}
Temporary Workaround
socat UDP6-LISTEN:8443,bind=[::1],reuseaddr,fork UDP4:10.12.10.1:8443 &
and change server to
server: "[::1]:8443"