Skip to content

Commit

Permalink
Feature: add default-nameserver and outbound interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Dreamacro committed Feb 15, 2020
1 parent f69f635 commit d75cb06
Show file tree
Hide file tree
Showing 28 changed files with 578 additions and 347 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ external-controller: 127.0.0.1:9090
# experimental feature
experimental:
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
# interface-name: en0 # outbound interface name

# authentication of local SOCKS5/HTTP(S) server
# authentication:
Expand All @@ -130,6 +131,9 @@ experimental:
# enable: true # set true to enable dns (default is false)
# ipv6: false # default is false
# listen: 0.0.0.0:53
# # default-nameserver: # resolve dns nameserver host, should fill pure IP
# # - 114.114.114.114
# # - 8.8.8.8
# enhanced-mode: redir-host # or fake-ip
# # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it
# fake-ip-filter: # fake ip white domain list
Expand Down
2 changes: 1 addition & 1 deletion adapters/outbound/direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
}

c, err := dialContext(ctx, "tcp", address)
c, err := dialer.DialContext(ctx, "tcp", address)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion adapters/outbound/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"net/url"
"strconv"

"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)

Expand All @@ -35,7 +36,7 @@ type HttpOption struct {
}

func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", h.addr)
c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err == nil && h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig)
err = cc.Handshake()
Expand Down
2 changes: 1 addition & 1 deletion adapters/outbound/shadowsocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type v2rayObfsOption struct {
}

func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", ss.server)
c, err := dialer.DialContext(ctx, "tcp", ss.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
}
Expand Down
3 changes: 2 additions & 1 deletion adapters/outbound/snell.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strconv"

"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/snell"
C "github.com/Dreamacro/clash/constant"
Expand All @@ -28,7 +29,7 @@ type SnellOption struct {
}

func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", s.server)
c, err := dialer.DialContext(ctx, "tcp", s.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.server, err)
}
Expand Down
4 changes: 2 additions & 2 deletions adapters/outbound/socks5.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type Socks5Option struct {
}

func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", ss.addr)
c, err := dialer.DialContext(ctx, "tcp", ss.addr)

if err == nil && ss.tls {
cc := tls.Client(c, ss.tlsConfig)
Expand Down Expand Up @@ -64,7 +64,7 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialContext(ctx, "tcp", ss.addr)
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
return
Expand Down
85 changes: 2 additions & 83 deletions adapters/outbound/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package outbound

import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
Expand All @@ -11,10 +10,9 @@ import (
"sync"
"time"

"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
)

const (
Expand Down Expand Up @@ -88,92 +86,13 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
return bytes.Join(buf, nil)
}

func dialContext(ctx context.Context, network, address string) (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, host string, ipv6 bool) {
dialer := dialer.Dialer()
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 {
ip, result.error = dns.ResolveIPv6(host)
} else {
ip, result.error = dns.ResolveIPv4(host)
}
if result.error != nil {
return
}
result.resolved = true

if ipv6 {
result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port))
} else {
result.Conn, result.error = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
}
}

go startRacer(ctx, host, false)
go startRacer(ctx, host, true)

for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, nil
}

if !res.ipv6 {
primary = res
} else {
fallback = res
}

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
}
}
}
}
}

func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}

ip, err := dns.ResolveIP(host)
ip, err := resolver.ResolveIP(host)
if err != nil {
return nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions adapters/outbound/vmess.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strconv"
"strings"

"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant"
)
Expand All @@ -33,7 +34,7 @@ type VmessOption struct {
}

func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", v.server)
c, err := dialer.DialContext(ctx, "tcp", v.server)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.server)
}
Expand All @@ -45,7 +46,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialContext(ctx, "tcp", v.server)
c, err := dialer.DialContext(ctx, "tcp", v.server)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.server)
}
Expand Down
120 changes: 118 additions & 2 deletions component/dialer/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package dialer

import (
"context"
"errors"
"net"

"github.com/Dreamacro/clash/component/resolver"
)

func Dialer() *net.Dialer {
Expand All @@ -28,11 +31,124 @@ func Dial(network, address string) (net.Conn, error) {
}

func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
dailer := Dialer()
return dailer.DialContext(ctx, network, address)
switch network {
case "tcp4", "tcp6", "udp4", "udp6":
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}

dialer := Dialer()

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 {
DialHook(dialer, network, ip)
}
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
case "tcp", "udp":
return dualStackDailContext(ctx, network, address)
default:
return nil, errors.New("network invalid")
}
}

func ListenPacket(network, address string) (net.PacketConn, error) {
lc := ListenConfig()

if ListenPacketHook != nil && address == "" {
ip := ListenPacketHook()
if ip != nil {
address = net.JoinHostPort(ip.String(), "0")
}
}
return lc.ListenPacket(context.Background(), network, address)
}

func dualStackDailContext(ctx context.Context, network, address string) (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) {
dialer := Dialer()
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 {
ip, result.error = resolver.ResolveIPv6(host)
} else {
ip, result.error = resolver.ResolveIPv4(host)
}
if result.error != nil {
return
}
result.resolved = true

if DialHook != nil {
DialHook(dialer, network, ip)
}
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)

for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, nil
}

if !res.ipv6 {
primary = res
} else {
fallback = res
}

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
}
}
}
}
}
Loading

0 comments on commit d75cb06

Please sign in to comment.