-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
Is your feature request related to a problem? Please describe.
One of the problems with the Happy Eyeballs implementation in Hysteria is that it does send both IPv4 and IPv6 requests at the same time and thus, creates a race between them. This makes the websites see your connections fluctuating between server's IPv6 and IPv4. However, in Golang's Happy Eyeballs implementation (and I'm fairly certain even in curl's implementation), the IPv6 connections will be fired at first and if there is no response for 300ms, it will initiate another connection using IPv4. This timeout is configurable in net.Dialer
. Here is Hysteria's implementation which creates a race between IPv4 and IPv6:
hysteria/extras/outbounds/ob_direct.go
Lines 265 to 274 in 5f3c47e
func (d *directOutbound) dualStackDialTCP(ipv4, ipv6 net.IP, port uint16) (net.Conn, error) { | |
ch := make(chan dialResult, 2) | |
go func() { | |
conn, err := d.dialTCP(ipv4, port) | |
ch <- dialResult{Conn: conn, Err: err} | |
}() | |
go func() { | |
conn, err := d.dialTCP(ipv6, port) | |
ch <- dialResult{Conn: conn, Err: err} | |
}() |
Moreover, this is not a very efficient implementation as it will always create two sockets and close one of them on the server.
Describe the solution you'd like
There are two ways to improve this. The first one is to add a new mode to outbound named AsIs
(like in Xray). What it should do, is that it should use the system's resolver (overrides the config's DNS) and Dialer.Dial
in order to connect to the destination address. This will allow Golang to use their own Happy Eyeballs implementation which prefers IPv6 over IPv4.
Another way this could be achieved is to introduce a delay when dialing IPv4. For instance, in the IPv4 dialing function, inside dualStackDialTCP
, we can just create a timer and wait for it for 300ms. If IPv6 connection succeeds, it will cancel the IPv4 timer and no connection will be made to the server. If IPv6 connection fails, it should immediately fire the IPv4 connection. Or, if the timer expires, a IPv4 connection will be made and will race the IPv6 connection.
Additional context
I would be glad to draft a pull request based if needed. Both of these methods should not be very hard to implement.