2020-02-09 17:02:48 +08:00
|
|
|
package dialer
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-02-15 21:42:46 +08:00
|
|
|
"errors"
|
2020-02-09 17:02:48 +08:00
|
|
|
"net"
|
2020-02-15 21:42:46 +08:00
|
|
|
|
|
|
|
"github.com/Dreamacro/clash/component/resolver"
|
2020-02-09 17:02:48 +08:00
|
|
|
)
|
|
|
|
|
2020-04-24 23:48:55 +08:00
|
|
|
func Dialer() (*net.Dialer, error) {
|
2020-02-09 17:02:48 +08:00
|
|
|
dialer := &net.Dialer{}
|
|
|
|
if DialerHook != nil {
|
2020-04-24 23:48:55 +08:00
|
|
|
if err := DialerHook(dialer); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-09 17:02:48 +08:00
|
|
|
}
|
|
|
|
|
2020-04-24 23:48:55 +08:00
|
|
|
return dialer, nil
|
2020-02-09 17:02:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func Dial(network, address string) (net.Conn, error) {
|
|
|
|
return DialContext(context.Background(), network, address)
|
|
|
|
}
|
|
|
|
|
|
|
|
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
2020-02-15 21:42:46 +08:00
|
|
|
switch network {
|
|
|
|
case "tcp4", "tcp6", "udp4", "udp6":
|
|
|
|
host, port, err := net.SplitHostPort(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-04-24 23:48:55 +08:00
|
|
|
dialer, err := Dialer()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-15 21:42:46 +08:00
|
|
|
|
|
|
|
var ip net.IP
|
|
|
|
switch network {
|
|
|
|
case "tcp4", "udp4":
|
|
|
|
ip, err = resolver.ResolveIPv4(host)
|
|
|
|
default:
|
|
|
|
ip, err = resolver.ResolveIPv6(host)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if DialHook != nil {
|
2020-04-24 23:48:55 +08:00
|
|
|
if err := DialHook(dialer, network, ip); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-15 21:42:46 +08:00
|
|
|
}
|
|
|
|
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
|
|
|
|
case "tcp", "udp":
|
2020-06-28 10:59:04 +08:00
|
|
|
return dualStackDialContext(ctx, network, address)
|
2020-02-15 21:42:46 +08:00
|
|
|
default:
|
|
|
|
return nil, errors.New("network invalid")
|
|
|
|
}
|
2020-02-09 17:02:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func ListenPacket(network, address string) (net.PacketConn, error) {
|
2020-10-22 00:11:49 +08:00
|
|
|
cfg := &net.ListenConfig{}
|
|
|
|
if ListenPacketHook != nil {
|
|
|
|
var err error
|
|
|
|
address, err = ListenPacketHook(cfg, address)
|
2020-04-24 23:48:55 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-02-15 21:42:46 +08:00
|
|
|
}
|
|
|
|
}
|
2020-10-22 00:11:49 +08:00
|
|
|
|
|
|
|
return cfg.ListenPacket(context.Background(), network, address)
|
2020-02-09 17:02:48 +08:00
|
|
|
}
|
2020-02-15 21:42:46 +08:00
|
|
|
|
2020-06-28 10:59:04 +08:00
|
|
|
func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
2020-02-15 21:42:46 +08:00
|
|
|
host, port, err := net.SplitHostPort(address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
returned := make(chan struct{})
|
|
|
|
defer close(returned)
|
|
|
|
|
|
|
|
type dialResult struct {
|
|
|
|
net.Conn
|
|
|
|
error
|
|
|
|
resolved bool
|
|
|
|
ipv6 bool
|
|
|
|
done bool
|
|
|
|
}
|
|
|
|
results := make(chan dialResult)
|
|
|
|
var primary, fallback dialResult
|
|
|
|
|
|
|
|
startRacer := func(ctx context.Context, network, host string, ipv6 bool) {
|
|
|
|
result := dialResult{ipv6: ipv6, done: true}
|
|
|
|
defer func() {
|
|
|
|
select {
|
|
|
|
case results <- result:
|
|
|
|
case <-returned:
|
|
|
|
if result.Conn != nil {
|
|
|
|
result.Conn.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-04-24 23:48:55 +08:00
|
|
|
dialer, err := Dialer()
|
|
|
|
if err != nil {
|
|
|
|
result.error = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:42:46 +08:00
|
|
|
var ip net.IP
|
|
|
|
if ipv6 {
|
|
|
|
ip, result.error = resolver.ResolveIPv6(host)
|
|
|
|
} else {
|
|
|
|
ip, result.error = resolver.ResolveIPv4(host)
|
|
|
|
}
|
|
|
|
if result.error != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
result.resolved = true
|
|
|
|
|
|
|
|
if DialHook != nil {
|
2020-04-24 23:48:55 +08:00
|
|
|
if result.error = DialHook(dialer, network, ip); result.error != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-02-15 21:42:46 +08:00
|
|
|
}
|
|
|
|
result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
|
|
|
|
}
|
|
|
|
|
|
|
|
go startRacer(ctx, network+"4", host, false)
|
|
|
|
go startRacer(ctx, network+"6", host, true)
|
|
|
|
|
2020-08-25 22:19:59 +08:00
|
|
|
for res := range results {
|
|
|
|
if res.error == nil {
|
|
|
|
return res.Conn, nil
|
|
|
|
}
|
2020-02-15 21:42:46 +08:00
|
|
|
|
2020-08-25 22:19:59 +08:00
|
|
|
if !res.ipv6 {
|
|
|
|
primary = res
|
|
|
|
} else {
|
|
|
|
fallback = res
|
|
|
|
}
|
2020-02-15 21:42:46 +08:00
|
|
|
|
2020-08-25 22:19:59 +08:00
|
|
|
if primary.done && fallback.done {
|
|
|
|
if primary.resolved {
|
|
|
|
return nil, primary.error
|
|
|
|
} else if fallback.resolved {
|
|
|
|
return nil, fallback.error
|
|
|
|
} else {
|
|
|
|
return nil, primary.error
|
2020-02-15 21:42:46 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-25 22:19:59 +08:00
|
|
|
|
|
|
|
return nil, errors.New("never touched")
|
2020-02-15 21:42:46 +08:00
|
|
|
}
|