mihomo/component/dialer/dialer.go

185 lines
3.9 KiB
Go
Raw Normal View History

2020-02-09 17:02:48 +08:00
package dialer
import (
"context"
"errors"
2020-02-09 17:02:48 +08:00
"net"
"github.com/Dreamacro/clash/component/resolver"
2020-02-09 17:02:48 +08:00
)
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
2021-11-17 16:03:47 +08:00
opt := &option{
interfaceName: DefaultInterface.Load(),
routingMark: int(DefaultRoutingMark.Load()),
2021-11-17 16:03:47 +08:00
}
for _, o := range DefaultOptions {
o(opt)
}
for _, o := range options {
o(opt)
}
switch network {
case "tcp4", "tcp6", "udp4", "udp6":
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ip net.IP
switch network {
case "tcp4", "udp4":
2021-11-17 16:03:47 +08:00
if opt.interfaceName != "" {
ip, err = resolver.ResolveIPv4WithMain(host)
} else {
ip, err = resolver.ResolveIPv4(host)
}
default:
2021-11-17 16:03:47 +08:00
if opt.interfaceName != "" {
ip, err = resolver.ResolveIPv6WithMain(host)
} else {
ip, err = resolver.ResolveIPv6(host)
}
}
if err != nil {
return nil, err
}
2021-11-17 16:03:47 +08:00
return dialContext(ctx, network, ip, port, opt)
case "tcp", "udp":
2021-11-17 16:03:47 +08:00
return dualStackDialContext(ctx, network, address, opt)
default:
return nil, errors.New("network invalid")
}
2020-02-09 17:02:48 +08:00
}
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
cfg := &option{
interfaceName: DefaultInterface.Load(),
routingMark: int(DefaultRoutingMark.Load()),
}
for _, o := range DefaultOptions {
o(cfg)
}
for _, o := range options {
o(cfg)
}
lc := &net.ListenConfig{}
if cfg.interfaceName != "" {
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
if err != nil {
return nil, err
}
address = addr
}
if cfg.addrReuse {
addrReuseToListenConfig(lc)
}
2021-11-08 16:59:48 +08:00
if cfg.routingMark != 0 {
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
}
return lc.ListenPacket(ctx, network, address)
2020-02-09 17:02:48 +08:00
}
2021-11-17 16:03:47 +08:00
func dialContext(ctx context.Context, network string, destination net.IP, port string, opt *option) (net.Conn, error) {
dialer := &net.Dialer{}
if opt.interfaceName != "" {
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err
}
}
2021-11-08 16:59:48 +08:00
if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination)
}
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
}
2021-11-17 16:03:47 +08:00
func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
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()
}
}
}()
var ip net.IP
if ipv6 {
2021-11-17 16:03:47 +08:00
if opt.interfaceName != "" {
ip, result.error = resolver.ResolveIPv6WithMain(host)
} else {
ip, result.error = resolver.ResolveIPv6(host)
}
} else {
2021-11-17 16:03:47 +08:00
if opt.interfaceName != "" {
ip, result.error = resolver.ResolveIPv4WithMain(host)
} else {
ip, result.error = resolver.ResolveIPv4(host)
}
}
if result.error != nil {
return
}
result.resolved = true
2021-11-17 16:03:47 +08:00
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
}
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-08-25 22:19:59 +08:00
if !res.ipv6 {
primary = res
} else {
fallback = res
}
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-08-25 22:19:59 +08:00
return nil, errors.New("never touched")
}